Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions internal/cubecos/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cubecos

import (
"bufio"
"fmt"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -362,13 +361,25 @@ func checkLicenseErr(err error) error {
}

func parseLicenseDat(file string) (*licenses.License, error) {
err := unzipLicense(file)
work, err := os.MkdirTemp(filepath.Dir(file), "members")
if err != nil {
log.Errorf("licenses: failed to create work dir(%v)", err)
return nil, err
}

dir, name := getLicenseDirAndName(file)
dat, err := os.Open(filepath.Join(dir, fmt.Sprintf("%s.dat", name)))
defer os.RemoveAll(work)
err = unzipLicense(file, work)
if err != nil {
return nil, err
}

stem, err := licenseMemberStem(work)
if err != nil {
log.Errorf("licenses: %v(%s)", err, filepath.Base(file))
return nil, err
}

dat, err := os.Open(stem + ".dat")
if err != nil {
return nil, err
}
Expand All @@ -378,14 +389,13 @@ func parseLicenseDat(file string) (*licenses.License, error) {
setLicenseDat(dat, license)
setLicenseDatStatus(
license,
checkImportLicense(file, *license),
checkImportLicense(stem, *license),
)

return license, nil
}

func unzipLicense(license string) error {
dir, _ := getLicenseDirAndName(license)
func unzipLicense(license string, dir string) error {
err := zip.DecompressFromTo(license, dir)
if err != nil {
log.Errorf("licenses: failed to unzip license(%v)", err)
Expand All @@ -395,14 +405,33 @@ func unzipLicense(license string) error {
return nil
}

func checkImportLicense(file string, license licenses.License) error {
dir, file := getLicenseDirAndName(file)
// licenseMemberStem returns the shared path prefix of the archive's .dat/.sig
// pair. The member names need not match the uploaded filename.
func licenseMemberStem(dir string) (string, error) {
dats, err := filepath.Glob(filepath.Join(dir, "*.dat"))
if err != nil {
return "", err
}

for _, dat := range dats {
stem := strings.TrimSuffix(dat, ".dat")
_, err := os.Stat(stem + ".sig")
if err == nil {
return stem, nil
}
}

return "", errors.ErrLicenseMalformedArchive
}

// stem is the member path prefix; hex_config license_check appends .dat/.sig.
func checkImportLicense(stem string, license licenses.License) error {
product := "def"
if license.Product.Name == licenses.CubeCMP {
product = "cmp"
}

_, err := exec.Command("hex_config", "license_check", product, filepath.Join(dir, file)).Output()
_, err := exec.Command("hex_config", "license_check", product, stem).Output()
return checkLicenseErr(err)
}

Expand Down
121 changes: 121 additions & 0 deletions internal/cubecos/license_verify_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package cubecos

import (
"archive/zip"
"os"
"path/filepath"
"testing"
)

// writeLicense builds a .license archive at dir/<filename> whose members carry
// memberStem, which need not match filename.
func writeLicense(t *testing.T, dir, filename, memberStem, dat string) string {
t.Helper()

path := filepath.Join(dir, filename)
f, err := os.Create(path)
if err != nil {
t.Fatal(err)
}

defer f.Close()
zw := zip.NewWriter(f)
for ext, body := range map[string]string{"dat": dat, "sig": "signature-bytes"} {
w, err := zw.Create(memberStem + "." + ext)
if err != nil {
t.Fatal(err)
}
if _, err := w.Write([]byte(body)); err != nil {
t.Fatal(err)
}
}
if err := zw.Close(); err != nil {
t.Fatal(err)
}

return path
}

const testDat = "license.name=NYCU ADFP3.0\n" +
"license.type=enterprise\n" +
"issue.hardware=GRRYSF4,DVRYSF4\n" +
"product=CubeCOS\n"

// Member names need not match the uploaded filename.
func TestParseLicenseDatIgnoresUploadedFilename(t *testing.T) {
dir := t.TempDir()
path := writeLicense(t, dir, "nycu-adfp3-0 (1).license", "nycu-adfp3-0", testDat)

license, err := parseLicenseDat(path)
if err != nil {
t.Fatalf("parseLicenseDat: %v", err)
}
if license.Name != "NYCU ADFP3.0" {
t.Errorf("Name = %q, want %q", license.Name, "NYCU ADFP3.0")
}
if license.Issue.Hardware != "GRRYSF4,DVRYSF4" {
t.Errorf("Hardware = %q", license.Issue.Hardware)
}
}

// A stale pair beside the upload must not be picked up.
func TestParseLicenseDatIgnoresStaleMembers(t *testing.T) {
dir := t.TempDir()
for _, ext := range []string{"dat", "sig"} {
stale := filepath.Join(dir, "someone-elses."+ext)
if err := os.WriteFile(stale, []byte("license.name=someone else\n"), 0o600); err != nil {
t.Fatal(err)
}
}

path := writeLicense(t, dir, "mine.license", "mine", testDat)
license, err := parseLicenseDat(path)
if err != nil {
t.Fatalf("parseLicenseDat: %v", err)
}
if license.Name != "NYCU ADFP3.0" {
t.Errorf("Name = %q, want the uploaded license, not the stale pair", license.Name)
}
}

// An archive without a .dat/.sig pair is genuinely malformed.
func TestParseLicenseDatRejectsArchiveWithoutPair(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "broken.license")
f, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
zw := zip.NewWriter(f)
w, err := zw.Create("readme.txt")
if err != nil {
t.Fatal(err)
}
w.Write([]byte("not a license"))
zw.Close()
f.Close()

if _, err := parseLicenseDat(path); err == nil {
t.Fatal("parseLicenseDat accepted an archive with no .dat/.sig pair")
}
}

// parseLicenseDat must not leave extracted members behind.
func TestParseLicenseDatCleansUpMembers(t *testing.T) {
dir := t.TempDir()
path := writeLicense(t, dir, "tidy.license", "tidy", testDat)

if _, err := parseLicenseDat(path); err != nil {
t.Fatal(err)
}

entries, err := os.ReadDir(dir)
if err != nil {
t.Fatal(err)
}
for _, e := range entries {
if e.Name() != "tidy.license" {
t.Errorf("left behind %q in the verify dir", e.Name())
}
}
}
1 change: 1 addition & 0 deletions internal/definition/v1/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
ErrLicenseInvalidHardware = errors.New("license's hardware serial is not matched with the current system")
ErrLicenseInvalidSignature = errors.New("license's signature is invalid")
ErrLicenseSystemCompromised = errors.New("license system is compromised")
ErrLicenseMalformedArchive = errors.New("license archive holds no .dat/.sig pair")
ErrSdkExecutionFailure = errors.New("sdk execution error")
ErrUnknownSettingType = errors.New("unknown setting type")
ErrInvalidListenAddress = errors.New("invalid listen address")
Expand Down
Loading