diff --git a/metadata/metadata_api_test.go b/metadata/metadata_api_test.go new file mode 100644 index 00000000..15544926 --- /dev/null +++ b/metadata/metadata_api_test.go @@ -0,0 +1,406 @@ +// Copyright 2023 VMware, Inc. +// +// This product is licensed to you under the BSD-2 license (the "License"). +// You may not use this product except in compliance with the BSD-2 License. +// This product may include a number of subcomponents with separate copyright +// notices and license terms. Your use of these subcomponents is subject to +// the terms and conditions of the subcomponent's license, as noted in the +// LICENSE file. +// +// SPDX-License-Identifier: BSD-2-Clause + +package metadata + +import ( + "bytes" + "crypto" + "fmt" + "io/fs" + "os" + "testing" + + testutils "github.com/rdimitrov/go-tuf-metadata/testutils/testutils" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "golang.org/x/sys/unix" +) + +func TestMain(m *testing.M) { + + err := testutils.SetupTestDirs() + defer testutils.Cleanup() + + if err != nil { + log.Fatalf("failed to setup test dirs: %v", err) + } + m.Run() +} + +func TestGenericRead(t *testing.T) { + // Assert that it chokes correctly on an unknown metadata type + badMetadata := "{\"signed\": {\"_type\": \"bad-metadata\"}}" + _, err := Root().FromBytes([]byte(badMetadata)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type root, got - bad-metadata"}) + _, err = Snapshot().FromBytes([]byte(badMetadata)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type snapshot, got - bad-metadata"}) + _, err = Targets().FromBytes([]byte(badMetadata)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type targets, got - bad-metadata"}) + _, err = Timestamp().FromBytes([]byte(badMetadata)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type timestamp, got - bad-metadata"}) + + badMetadataPath := fmt.Sprintf("%s/bad-metadata.json", testutils.RepoDir) + err = os.WriteFile(badMetadataPath, []byte(badMetadata), 0644) + assert.NoError(t, err) + assert.FileExists(t, badMetadataPath) + + _, err = Root().FromFile(badMetadataPath) + assert.ErrorIs(t, err, ErrValue{"expected metadata type root, got - bad-metadata"}) + _, err = Snapshot().FromFile(badMetadataPath) + assert.ErrorIs(t, err, ErrValue{"expected metadata type snapshot, got - bad-metadata"}) + _, err = Targets().FromFile(badMetadataPath) + assert.ErrorIs(t, err, ErrValue{"expected metadata type targets, got - bad-metadata"}) + _, err = Timestamp().FromFile(badMetadataPath) + assert.ErrorIs(t, err, ErrValue{"expected metadata type timestamp, got - bad-metadata"}) + + err = os.RemoveAll(badMetadataPath) + assert.NoError(t, err) + assert.NoFileExists(t, badMetadataPath) +} + +func TestGenericReadFromMismatchingRoles(t *testing.T) { + // Test failing to load other roles from root metadata + _, err := Snapshot().FromFile(fmt.Sprintf("%s/root.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type snapshot, got - root"}) + _, err = Timestamp().FromFile(fmt.Sprintf("%s/root.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type timestamp, got - root"}) + _, err = Targets().FromFile(fmt.Sprintf("%s/root.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type targets, got - root"}) + + // Test failing to load other roles from targets metadata + _, err = Snapshot().FromFile(fmt.Sprintf("%s/targets.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type snapshot, got - targets"}) + _, err = Timestamp().FromFile(fmt.Sprintf("%s/targets.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type timestamp, got - targets"}) + _, err = Root().FromFile(fmt.Sprintf("%s/targets.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type root, got - targets"}) + + // Test failing to load other roles from timestamp metadata + _, err = Snapshot().FromFile(fmt.Sprintf("%s/timestamp.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type snapshot, got - timestamp"}) + _, err = Targets().FromFile(fmt.Sprintf("%s/timestamp.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type targets, got - timestamp"}) + _, err = Root().FromFile(fmt.Sprintf("%s/timestamp.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type root, got - timestamp"}) + + // Test failing to load other roles from snapshot metadata + _, err = Targets().FromFile(fmt.Sprintf("%s/snapshot.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type targets, got - snapshot"}) + _, err = Timestamp().FromFile(fmt.Sprintf("%s/snapshot.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type timestamp, got - snapshot"}) + _, err = Root().FromFile(fmt.Sprintf("%s/snapshot.json", testutils.RepoDir)) + assert.ErrorIs(t, err, ErrValue{"expected metadata type root, got - snapshot"}) +} + +func TestMDReadWriteFileExceptions(t *testing.T) { + // Test writing to a file with bad filename + badMetadataPath := fmt.Sprintf("%s/bad-metadata.json", testutils.RepoDir) + _, err := Root().FromFile(badMetadataPath) + expectedErr := fs.PathError{ + Op: "open", + Path: badMetadataPath, + Err: unix.ENOENT, + } + assert.ErrorIs(t, err, expectedErr.Err) + + // Test serializing to a file with bad filename + root := Root(fixedExpire) + err = root.ToFile("", false) + expectedErr = fs.PathError{ + Op: "open", + Path: "", + Err: unix.ENOENT, + } + assert.ErrorIs(t, err, expectedErr.Err) +} + +func TestCompareFromBytesFromFileToBytes(t *testing.T) { + rootBytesWant, err := os.ReadFile(fmt.Sprintf("%s/root.json", testutils.RepoDir)) + assert.NoError(t, err) + root, err := Root().FromFile(fmt.Sprintf("%s/root.json", testutils.RepoDir)) + assert.NoError(t, err) + rootBytesActual, err := root.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, rootBytesWant, rootBytesActual) + + targetsBytesWant, err := os.ReadFile(fmt.Sprintf("%s/targets.json", testutils.RepoDir)) + assert.NoError(t, err) + targets, err := Targets().FromFile(fmt.Sprintf("%s/targets.json", testutils.RepoDir)) + assert.NoError(t, err) + targetsBytesActual, err := targets.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, targetsBytesWant, targetsBytesActual) + + snapshotBytesWant, err := os.ReadFile(fmt.Sprintf("%s/snapshot.json", testutils.RepoDir)) + assert.NoError(t, err) + snapshot, err := Snapshot().FromFile(fmt.Sprintf("%s/snapshot.json", testutils.RepoDir)) + assert.NoError(t, err) + snapshotBytesActual, err := snapshot.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, snapshotBytesWant, snapshotBytesActual) + + timestampBytesWant, err := os.ReadFile(fmt.Sprintf("%s/timestamp.json", testutils.RepoDir)) + assert.NoError(t, err) + timestamp, err := Timestamp().FromFile(fmt.Sprintf("%s/timestamp.json", testutils.RepoDir)) + assert.NoError(t, err) + timestampBytesActual, err := timestamp.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, timestampBytesWant, timestampBytesActual) +} + +func TestRootReadWriteReadCompare(t *testing.T) { + src := testutils.RepoDir + "/root.json" + srcRoot, err := Root().FromFile(src) + assert.NoError(t, err) + + dst := src + ".tmp" + err = srcRoot.ToFile(dst, false) + assert.NoError(t, err) + + dstRoot, err := Root().FromFile(dst) + assert.NoError(t, err) + + srcBytes, err := srcRoot.ToBytes(false) + assert.NoError(t, err) + dstBytes, err := dstRoot.ToBytes(false) + assert.NoError(t, err) + assert.Equal(t, srcBytes, dstBytes) + + err = os.RemoveAll(dst) + assert.NoError(t, err) +} + +func TestSnapshotReadWriteReadCompare(t *testing.T) { + path1 := testutils.RepoDir + "/snapshot.json" + snaphot1, err := Snapshot().FromFile(path1) + assert.NoError(t, err) + + path2 := path1 + ".tmp" + err = snaphot1.ToFile(path2, false) + assert.NoError(t, err) + + snapshot2, err := Snapshot().FromFile(path2) + assert.NoError(t, err) + + bytes1, err := snaphot1.ToBytes(false) + assert.NoError(t, err) + bytes2, err := snapshot2.ToBytes(false) + assert.NoError(t, err) + assert.Equal(t, bytes1, bytes2) + + err = os.RemoveAll(path2) + assert.NoError(t, err) +} + +func TestTargetsReadWriteReadCompare(t *testing.T) { + path1 := testutils.RepoDir + "/targets.json" + targets1, err := Targets().FromFile(path1) + assert.NoError(t, err) + + path2 := path1 + ".tmp" + err = targets1.ToFile(path2, false) + assert.NoError(t, err) + + targets2, err := Targets().FromFile(path2) + assert.NoError(t, err) + + bytes1, err := targets1.ToBytes(false) + assert.NoError(t, err) + bytes2, err := targets2.ToBytes(false) + assert.NoError(t, err) + assert.Equal(t, bytes1, bytes2) + + err = os.RemoveAll(path2) + assert.NoError(t, err) +} + +func TestTimestampReadWriteReadCompare(t *testing.T) { + path1 := testutils.RepoDir + "/timestamp.json" + timestamp1, err := Timestamp().FromFile(path1) + assert.NoError(t, err) + + path2 := path1 + ".tmp" + err = timestamp1.ToFile(path2, false) + assert.NoError(t, err) + + timestamp2, err := Timestamp().FromFile(path2) + assert.NoError(t, err) + + bytes1, err := timestamp1.ToBytes(false) + assert.NoError(t, err) + bytes2, err := timestamp2.ToBytes(false) + assert.NoError(t, err) + assert.Equal(t, bytes1, bytes2) + + err = os.RemoveAll(path2) + assert.NoError(t, err) +} + +func TestToFromBytes(t *testing.T) { + // ROOT + data, err := os.ReadFile(testutils.RepoDir + "/root.json") + assert.NoError(t, err) + root, err := Root().FromBytes(data) + assert.NoError(t, err) + + // Comparate that from_bytes/to_bytes doesn't change the content + // for two cases for the serializer: noncompact and compact. + + // Case 1: test noncompact by overriding the default serializer. + rootBytesWant, err := root.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, data, rootBytesWant) + + // Case 2: test compact by using the default serializer. + root2, err := Root().FromBytes(rootBytesWant) + assert.NoError(t, err) + rootBytesActual, err := root2.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, rootBytesWant, rootBytesActual) + + // SNAPSHOT + data, err = os.ReadFile(testutils.RepoDir + "/snapshot.json") + assert.NoError(t, err) + snapshot, err := Snapshot().FromBytes(data) + assert.NoError(t, err) + + // Case 1: test noncompact by overriding the default serializer. + snapshotBytesWant, err := snapshot.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, string(data), string(snapshotBytesWant)) + + // Case 2: test compact by using the default serializer. + snapshot2, err := Snapshot().FromBytes(snapshotBytesWant) + assert.NoError(t, err) + snapshotBytesActual, err := snapshot2.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, string(snapshotBytesWant), string(snapshotBytesActual)) + + // TARGETS + data, err = os.ReadFile(testutils.RepoDir + "/targets.json") + assert.NoError(t, err) + targets, err := Targets().FromBytes(data) + assert.NoError(t, err) + + // Case 1: test noncompact by overriding the default serializer. + targetsBytesWant, err := targets.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, string(data), string(targetsBytesWant)) + + // Case 2: test compact by using the default serializer. + targets2, err := Targets().FromBytes(targetsBytesWant) + assert.NoError(t, err) + targetsBytesActual, err := targets2.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, string(targetsBytesWant), string(targetsBytesActual)) + + // TIMESTAMP + data, err = os.ReadFile(testutils.RepoDir + "/timestamp.json") + assert.NoError(t, err) + timestamp, err := Timestamp().FromBytes(data) + assert.NoError(t, err) + + // Case 1: test noncompact by overriding the default serializer. + timestampBytesWant, err := timestamp.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, string(data), string(timestampBytesWant)) + + // Case 2: test compact by using the default serializer. + timestamp2, err := Timestamp().FromBytes(timestampBytesWant) + assert.NoError(t, err) + timestampBytesActual, err := timestamp2.ToBytes(true) + assert.NoError(t, err) + assert.Equal(t, string(timestampBytesWant), string(timestampBytesActual)) + +} + +func TestSignVerify(t *testing.T) { + root, err := Root().FromFile(testutils.RepoDir + "/root.json") + assert.NoError(t, err) + + // Locate the public keys we need from root + assert.NotEmpty(t, root.Signed.Roles[TARGETS].KeyIDs) + targetsKeyID := root.Signed.Roles[TARGETS].KeyIDs[0] + assert.NotEmpty(t, root.Signed.Roles[SNAPSHOT].KeyIDs) + snapshotKeyID := root.Signed.Roles[SNAPSHOT].KeyIDs[0] + assert.NotEmpty(t, root.Signed.Roles[TIMESTAMP].KeyIDs) + timestampKeyID := root.Signed.Roles[TIMESTAMP].KeyIDs[0] + + // Load sample metadata (targets) and assert ... + targets, err := Targets().FromFile(testutils.RepoDir + "/targets.json") + assert.NoError(t, err) + sig := getSignatureByKeyID(targets.Signatures, targetsKeyID) + data, err := targets.Signed.MarshalJSON() + assert.NoError(t, err) + + // ... it has a single existing signature, + assert.Equal(t, 1, len(targets.Signatures)) + + // ... which is valid for the correct key. + targetsKey := root.Signed.Keys[targetsKeyID] + targetsPublicKey, err := targetsKey.ToPublicKey() + assert.NoError(t, err) + targetsHash := crypto.SHA256 + targetsVerifier, err := signature.LoadVerifier(targetsPublicKey, targetsHash) + assert.NoError(t, err) + err = targetsVerifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(data)) + assert.NoError(t, err) + + // ... and invalid for an unrelated key + snapshotKey := root.Signed.Keys[snapshotKeyID] + snapshotPublicKey, err := snapshotKey.ToPublicKey() + assert.NoError(t, err) + snapshotHash := crypto.SHA256 + snapshotVerifier, err := signature.LoadVerifier(snapshotPublicKey, snapshotHash) + assert.NoError(t, err) + err = snapshotVerifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(data)) + assert.ErrorContains(t, err, "crypto/rsa: verification error") + + // Append a new signature with the unrelated key and assert that ... + signer, err := signature.LoadSignerFromPEMFile(testutils.KeystoreDir+"/snapshot_key", crypto.SHA256, cryptoutils.SkipPassword) + assert.NoError(t, err) + snapshotSig, err := targets.Sign(signer) + assert.NoError(t, err) + // ... there are now two signatures, and + assert.Equal(t, 2, len(targets.Signatures)) + // ... both are valid for the corresponding keys. + err = targetsVerifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(data)) + assert.NoError(t, err) + err = snapshotVerifier.VerifySignature(bytes.NewReader(snapshotSig.Signature), bytes.NewReader(data)) + assert.NoError(t, err) + // ... the returned (appended) signature is for snapshot key + assert.Equal(t, snapshotSig.KeyID, snapshotKeyID) + + // Clear all signatures and add a new signature with the unrelated key and assert that ... + signer, err = signature.LoadSignerFromPEMFile(testutils.KeystoreDir+"/timestamp_key", crypto.SHA256, cryptoutils.SkipPassword) + assert.NoError(t, err) + targets.ClearSignatures() + assert.Equal(t, 0, len(targets.Signatures)) + timestampSig, err := targets.Sign(signer) + assert.NoError(t, err) + // ... there now is only one signature, + assert.Equal(t, 1, len(targets.Signatures)) + // ... valid for that key. + timestampKey := root.Signed.Keys[timestampKeyID] + timestampPublicKey, err := timestampKey.ToPublicKey() + assert.NoError(t, err) + timestampHash := crypto.SHA256 + timestampVerifier, err := signature.LoadVerifier(timestampPublicKey, timestampHash) + assert.NoError(t, err) + + err = timestampVerifier.VerifySignature(bytes.NewReader(timestampSig.Signature), bytes.NewReader(data)) + assert.NoError(t, err) + err = targetsVerifier.VerifySignature(bytes.NewReader(timestampSig.Signature), bytes.NewReader(data)) + assert.ErrorContains(t, err, "crypto/rsa: verification error") +} diff --git a/metadata/metadata_test.go b/metadata/metadata_test.go index 890a18c2..a92ac611 100644 --- a/metadata/metadata_test.go +++ b/metadata/metadata_test.go @@ -12,13 +12,32 @@ package metadata import ( + "crypto/ed25519" + "crypto/sha256" "encoding/json" + "fmt" + "os" "testing" "time" "github.com/stretchr/testify/assert" ) +var testRootBytes = []byte("{\"signatures\":[{\"keyid\":\"roothash\",\"sig\":\"1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee\"}],\"signed\":{\"_type\":\"root\",\"consistent_snapshot\":true,\"expires\":\"2030-08-15T14:30:45.0000001Z\",\"keys\":{\"roothash\":{\"keytype\":\"ed25519\",\"keyval\":{\"public\":\"pubrootval\"},\"scheme\":\"ed25519\"},\"snapshothash\":{\"keytype\":\"ed25519\",\"keyval\":{\"public\":\"pubsval\"},\"scheme\":\"ed25519\"},\"targetshash\":{\"keytype\":\"ed25519\",\"keyval\":{\"public\":\"pubtrval\"},\"scheme\":\"ed25519\"},\"timestamphash\":{\"keytype\":\"ed25519\",\"keyval\":{\"public\":\"pubtmval\"},\"scheme\":\"ed25519\"}},\"roles\":{\"root\":{\"keyids\":[\"roothash\"],\"threshold\":1},\"snapshot\":{\"keyids\":[\"snapshothash\"],\"threshold\":1},\"targets\":{\"keyids\":[\"targetshash\"],\"threshold\":1},\"timestamp\":{\"keyids\":[\"timestamphash\"],\"threshold\":1}},\"spec_version\":\"1.0.31\",\"version\":1}}") + +const TEST_REPOSITORY_DATA = "../testutils/repository_data/repository/metadata" + +var fixedExpire = time.Date(2030, 8, 15, 14, 30, 45, 100, time.UTC) + +func getSignatureByKeyID(signatures []Signature, keyID string) HexBytes { + for _, sig := range signatures { + if sig.KeyID == keyID { + return sig.Signature + } + } + return []byte{} +} + func TestDefaultValuesRoot(t *testing.T) { // without setting expiration meta := Root() @@ -335,32 +354,29 @@ func TestIsExpiredTargets(t *testing.T) { } func TestUnrecognizedFieldRolesSigned(t *testing.T) { - // fixed expire - expire := time.Date(2030, 8, 15, 14, 30, 45, 100, time.UTC) - // unrecognized field to test // added to the Signed portion of each role type testUnrecognizedField := map[string]any{"test": "true"} - root := Root(expire) + root := Root(fixedExpire) root.Signed.UnrecognizedFields = testUnrecognizedField rootJSON, err := root.ToBytes(false) assert.NoError(t, err) assert.Equal(t, []byte("{\"signatures\":[],\"signed\":{\"_type\":\"root\",\"consistent_snapshot\":true,\"expires\":\"2030-08-15T14:30:45.0000001Z\",\"keys\":{},\"roles\":{\"root\":{\"keyids\":[],\"threshold\":1},\"snapshot\":{\"keyids\":[],\"threshold\":1},\"targets\":{\"keyids\":[],\"threshold\":1},\"timestamp\":{\"keyids\":[],\"threshold\":1}},\"spec_version\":\"1.0.31\",\"test\":\"true\",\"version\":1}}"), rootJSON) - targets := Targets(expire) + targets := Targets(fixedExpire) targets.Signed.UnrecognizedFields = testUnrecognizedField targetsJSON, err := targets.ToBytes(false) assert.NoError(t, err) assert.Equal(t, []byte("{\"signatures\":[],\"signed\":{\"_type\":\"targets\",\"expires\":\"2030-08-15T14:30:45.0000001Z\",\"spec_version\":\"1.0.31\",\"targets\":{},\"test\":\"true\",\"version\":1}}"), targetsJSON) - snapshot := Snapshot(expire) + snapshot := Snapshot(fixedExpire) snapshot.Signed.UnrecognizedFields = testUnrecognizedField snapshotJSON, err := snapshot.ToBytes(false) assert.NoError(t, err) assert.Equal(t, []byte("{\"signatures\":[],\"signed\":{\"_type\":\"snapshot\",\"expires\":\"2030-08-15T14:30:45.0000001Z\",\"meta\":{\"targets.json\":{\"version\":1}},\"spec_version\":\"1.0.31\",\"test\":\"true\",\"version\":1}}"), snapshotJSON) - timestamp := Timestamp(expire) + timestamp := Timestamp(fixedExpire) timestamp.Signed.UnrecognizedFields = testUnrecognizedField timestampJSON, err := timestamp.ToBytes(false) assert.NoError(t, err) @@ -381,14 +397,11 @@ func TestUnrecognizedFieldGenericMetadata(t *testing.T) { assert.Equal(t, []byte("{\"signatures\":[],\"signed\":{\"_type\":\"root\",\"consistent_snapshot\":true,\"expires\":\"2030-08-15T14:30:45.0000001Z\",\"keys\":{},\"roles\":{\"root\":{\"keyids\":[],\"threshold\":1},\"snapshot\":{\"keyids\":[],\"threshold\":1},\"targets\":{\"keyids\":[],\"threshold\":1},\"timestamp\":{\"keyids\":[],\"threshold\":1}},\"spec_version\":\"1.0.31\",\"version\":1},\"test\":\"true\"}"), rootJSON) } func TestTargetFilesCustomField(t *testing.T) { - // fixed expire - expire := time.Date(2030, 8, 15, 14, 30, 45, 100, time.UTC) - // custom JSON to test testCustomJSON := json.RawMessage([]byte(`{"test":true}`)) // create a targets metadata - targets := Targets(expire) + targets := Targets(fixedExpire) assert.NotNil(t, targets) // create a targetfile with the custom JSON @@ -401,3 +414,259 @@ func TestTargetFilesCustomField(t *testing.T) { assert.NoError(t, err) assert.Equal(t, []byte("{\"signatures\":[],\"signed\":{\"_type\":\"targets\",\"expires\":\"2030-08-15T14:30:45.0000001Z\",\"spec_version\":\"1.0.31\",\"targets\":{\"testTarget\":{\"custom\":{\"test\":true},\"hashes\":{},\"length\":0}},\"version\":1}}"), targetsJSON) } + +func TestFromBytes(t *testing.T) { + root := Root(fixedExpire) + assert.Equal(t, fixedExpire, root.Signed.Expires) + + _, err := root.FromBytes(testRootBytes) + assert.NoError(t, err) + + assert.Equal(t, fixedExpire, root.Signed.Expires) + assert.Equal(t, fixedExpire, root.Signed.Expires) + assert.Equal(t, ROOT, root.Signed.Type) + assert.True(t, root.Signed.ConsistentSnapshot) + + assert.Equal(t, 4, len(root.Signed.Keys)) + assert.Contains(t, root.Signed.Roles, ROOT) + assert.Equal(t, 1, root.Signed.Roles[ROOT].Threshold) + assert.NotEmpty(t, root.Signed.Roles[ROOT].KeyIDs) + assert.Contains(t, root.Signed.Keys, root.Signed.Roles[ROOT].KeyIDs[0]) + assert.Equal(t, "roothash", root.Signed.Roles[ROOT].KeyIDs[0]) + + assert.Contains(t, root.Signed.Roles, SNAPSHOT) + assert.Equal(t, 1, root.Signed.Roles[SNAPSHOT].Threshold) + assert.NotEmpty(t, root.Signed.Roles[SNAPSHOT].KeyIDs) + assert.Contains(t, root.Signed.Keys, root.Signed.Roles[SNAPSHOT].KeyIDs[0]) + assert.Equal(t, "snapshothash", root.Signed.Roles[SNAPSHOT].KeyIDs[0]) + + assert.Contains(t, root.Signed.Roles, TARGETS) + assert.Equal(t, 1, root.Signed.Roles[TARGETS].Threshold) + assert.NotEmpty(t, root.Signed.Roles[TARGETS].KeyIDs) + assert.Contains(t, root.Signed.Keys, root.Signed.Roles[TARGETS].KeyIDs[0]) + assert.Equal(t, "targetshash", root.Signed.Roles[TARGETS].KeyIDs[0]) + + assert.Contains(t, root.Signed.Roles, TIMESTAMP) + assert.Equal(t, 1, root.Signed.Roles[TIMESTAMP].Threshold) + assert.NotEmpty(t, root.Signed.Roles[TIMESTAMP].KeyIDs) + assert.Contains(t, root.Signed.Keys, root.Signed.Roles[TIMESTAMP].KeyIDs[0]) + assert.Equal(t, "timestamphash", root.Signed.Roles[TIMESTAMP].KeyIDs[0]) + + assert.Equal(t, int64(1), root.Signed.Version) + assert.NotEmpty(t, root.Signatures) + assert.Equal(t, "roothash", root.Signatures[0].KeyID) + data := []byte("some data") + h32 := sha256.Sum256(data) + h := h32[:] + assert.Equal(t, HexBytes(h), root.Signatures[0].Signature) +} + +func TestToByte(t *testing.T) { + rootBytesExpireStr := "2030-08-15T14:30:45.0000001Z" + rootBytesExpire, err := time.Parse(time.RFC3339, rootBytesExpireStr) + assert.NoError(t, err) + + root := Root(rootBytesExpire) + root.Signed.Keys["roothash"] = &Key{Type: "ed25519", Value: KeyVal{PublicKey: "pubrootval"}, Scheme: "ed25519"} + root.Signed.Keys["snapshothash"] = &Key{Type: "ed25519", Value: KeyVal{PublicKey: "pubsval"}, Scheme: "ed25519"} + root.Signed.Keys["targetshash"] = &Key{Type: "ed25519", Value: KeyVal{PublicKey: "pubtrval"}, Scheme: "ed25519"} + root.Signed.Keys["timestamphash"] = &Key{Type: "ed25519", Value: KeyVal{PublicKey: "pubtmval"}, Scheme: "ed25519"} + root.Signed.Roles[ROOT] = &Role{ + Threshold: 1, + KeyIDs: []string{"roothash"}, + } + root.Signed.Roles[SNAPSHOT] = &Role{ + Threshold: 1, + KeyIDs: []string{"snapshothash"}, + } + root.Signed.Roles[TARGETS] = &Role{ + Threshold: 1, + KeyIDs: []string{"targetshash"}, + } + root.Signed.Roles[TIMESTAMP] = &Role{ + Threshold: 1, + KeyIDs: []string{"timestamphash"}, + } + + data := []byte("some data") + h32 := sha256.Sum256(data) + h := h32[:] + hash := map[string]HexBytes{"ed25519": h} + root.Signatures = append(root.Signatures, Signature{KeyID: "roothash", Signature: hash["ed25519"]}) + rootBytes, err := root.ToBytes(false) + assert.NoError(t, err) + assert.Equal(t, string(testRootBytes), string(rootBytes)) +} + +func TestFromFile(t *testing.T) { + root := Root(fixedExpire) + _, err := root.FromFile(fmt.Sprintf("%s/1.root.json", TEST_REPOSITORY_DATA)) + assert.NoError(t, err) + + assert.Equal(t, fixedExpire, root.Signed.Expires) + assert.Equal(t, fixedExpire, root.Signed.Expires) + assert.Equal(t, ROOT, root.Signed.Type) + assert.True(t, root.Signed.ConsistentSnapshot) + assert.Equal(t, 4, len(root.Signed.Keys)) + + assert.Contains(t, root.Signed.Roles, ROOT) + assert.Equal(t, 1, root.Signed.Roles[ROOT].Threshold) + assert.NotEmpty(t, root.Signed.Roles[ROOT].KeyIDs) + assert.Contains(t, root.Signed.Keys, root.Signed.Roles[ROOT].KeyIDs[0]) + assert.Equal(t, "d5fa855fce82db75ec64283e828cc90517df5edf5cdc57e7958a890d6556f5b7", root.Signed.Roles[ROOT].KeyIDs[0]) + + assert.Contains(t, root.Signed.Roles, SNAPSHOT) + assert.Equal(t, 1, root.Signed.Roles[SNAPSHOT].Threshold) + assert.NotEmpty(t, root.Signed.Roles[SNAPSHOT].KeyIDs) + assert.Contains(t, root.Signed.Keys, root.Signed.Roles[SNAPSHOT].KeyIDs[0]) + assert.Equal(t, "700464ea12f4cb5f06a7512c75b73c0b6eeb2cd42854b085eed5b3c993607cba", root.Signed.Roles[SNAPSHOT].KeyIDs[0]) + + assert.Contains(t, root.Signed.Roles, TARGETS) + assert.Equal(t, 1, root.Signed.Roles[TARGETS].Threshold) + assert.NotEmpty(t, root.Signed.Roles[TARGETS].KeyIDs) + assert.Contains(t, root.Signed.Keys, root.Signed.Roles[TARGETS].KeyIDs[0]) + assert.Equal(t, "409fb816e403e0c00646665eac21cb8adfab8e318272ca7589b2d1fc0bccb255", root.Signed.Roles[TARGETS].KeyIDs[0]) + + assert.Contains(t, root.Signed.Roles, TIMESTAMP) + assert.Equal(t, 1, root.Signed.Roles[TIMESTAMP].Threshold) + assert.NotEmpty(t, root.Signed.Roles[TIMESTAMP].KeyIDs) + assert.Contains(t, root.Signed.Keys, root.Signed.Roles[TIMESTAMP].KeyIDs[0]) + assert.Equal(t, "0a5842e65e9c8c428354f40708435de6793ac379a275effe40d6358be2de835c", root.Signed.Roles[TIMESTAMP].KeyIDs[0]) + + assert.Equal(t, SPECIFICATION_VERSION, root.Signed.SpecVersion) + assert.Contains(t, root.Signed.UnrecognizedFields, "test") + assert.Equal(t, "true", root.Signed.UnrecognizedFields["test"]) + + assert.Equal(t, int64(1), root.Signed.Version) + assert.NotEmpty(t, root.Signatures) + assert.Equal(t, "d5fa855fce82db75ec64283e828cc90517df5edf5cdc57e7958a890d6556f5b7", root.Signatures[0].KeyID) + +} + +func TestToFile(t *testing.T) { + tmp := os.TempDir() + tmpDir, err := os.MkdirTemp(tmp, "0750") + assert.NoError(t, err) + + fileName := fmt.Sprintf("%s/1.root.json", tmpDir) + assert.NoFileExists(t, fileName) + root, err := Root().FromBytes(testRootBytes) + assert.NoError(t, err) + + err = root.ToFile(fileName, false) + assert.NoError(t, err) + + assert.FileExists(t, fileName) + data, err := os.ReadFile(fileName) + assert.NoError(t, err) + assert.Equal(t, string(testRootBytes), string(data)) + + err = os.RemoveAll(tmpDir) + assert.NoError(t, err) + assert.NoFileExists(t, fileName) + +} + +func TestVerifyDelegate(t *testing.T) { + root := Root(fixedExpire) + err := root.VerifyDelegate("test", root) + assert.EqualError(t, err, "value error: no delegation found for test") + + targets := Targets(fixedExpire) + err = targets.VerifyDelegate("test", targets) + assert.EqualError(t, err, "value error: no delegations found") + + key, _, err := ed25519.GenerateKey(nil) + assert.NoError(t, err) + + delegateeKey, _ := KeyFromPublicKey(key) + delegations := &Delegations{ + Keys: map[string]*Key{ + delegateeKey.ID(): delegateeKey, + }, + Roles: []DelegatedRole{ + { + Name: "test", + KeyIDs: []string{delegateeKey.ID()}, + }, + }, + } + targets.Signed.Delegations = delegations + err = targets.VerifyDelegate("test", root) + assert.NoError(t, err) + err = targets.VerifyDelegate("test", targets) + assert.NoError(t, err) + + err = targets.VerifyDelegate("non-existing", root) + assert.EqualError(t, err, "value error: no delegation found for non-existing") + err = targets.VerifyDelegate("non-existing", targets) + assert.EqualError(t, err, "value error: no delegation found for non-existing") + + targets.Signed.Delegations.Roles[0].Threshold = 1 + err = targets.VerifyDelegate("test", targets) + assert.Errorf(t, err, "Verifying test failed, not enough signatures, got %d, want %d", 0, 1) + + delegations.Keys["incorrectkey"] = delegations.Keys[delegateeKey.ID()] + delete(delegations.Keys, delegateeKey.ID()) + err = targets.VerifyDelegate("test", root) + assert.Errorf(t, err, "key with ID %s not found in test keyids", delegateeKey.ID()) + + timestamp := Timestamp(fixedExpire) + err = timestamp.VerifyDelegate("test", timestamp) + assert.EqualError(t, err, "type error: call is valid only on delegator metadata (should be either root or targets)") + + snapshot := Snapshot(fixedExpire) + err = snapshot.VerifyDelegate("test", snapshot) + assert.EqualError(t, err, "type error: call is valid only on delegator metadata (should be either root or targets)") +} + +func TestVerifyLengthHashesTargetFiles(t *testing.T) { + targetFiles := TargetFile() + targetFiles.Hashes = map[string]HexBytes{} + + data := []byte{} + err := targetFiles.VerifyLengthHashes(data) + assert.NoError(t, err) + + data = []byte("some data") + err = targetFiles.VerifyLengthHashes(data) + assert.Error(t, err, "length/hash verification error: length verification failed - expected 0, got 9") + + h32 := sha256.Sum256(data) + h := h32[:] + targetFiles.Hashes["sha256"] = h + targetFiles.Length = int64(len(data)) + err = targetFiles.VerifyLengthHashes(data) + assert.NoError(t, err) + + targetFiles.Hashes = map[string]HexBytes{"unknownAlg": data} + err = targetFiles.VerifyLengthHashes(data) + assert.Error(t, err, "length/hash verification error: hash verification failed - unknown hashing algorithm - unknownArg") + + targetFiles.Hashes = map[string]HexBytes{"sha256": data} + err = targetFiles.VerifyLengthHashes(data) + assert.Error(t, err, "length/hash verification error: hash verification failed - mismatch for algorithm sha256") +} + +func TestVerifyLengthHashesMetaFiles(t *testing.T) { + version := int64(0) + metaFile := MetaFile(version) + data := []byte("some data") + metaFile.Hashes = map[string]HexBytes{"unknownAlg": data} + err := metaFile.VerifyLengthHashes(data) + assert.Error(t, err, "length/hash verification error: hash verification failed - unknown hashing algorithm - unknownArg") + + metaFile.Hashes = map[string]HexBytes{"sha256": data} + err = metaFile.VerifyLengthHashes(data) + assert.Error(t, err, "length/hash verification error: hash verification failed - mismatch for algorithm sha256") + + h32 := sha256.Sum256(data) + h := h32[:] + metaFile.Hashes = map[string]HexBytes{"sha256": h} + err = metaFile.VerifyLengthHashes(data) + assert.NoError(t, err) + + incorrectData := []byte("another data") + err = metaFile.VerifyLengthHashes(incorrectData) + assert.Error(t, err, "length/hash verification error: length verification failed - expected 0, got 9") +} diff --git a/testutils/repository_data/keystore/delegation_key b/testutils/repository_data/keystore/delegation_key new file mode 100644 index 00000000..461169d6 --- /dev/null +++ b/testutils/repository_data/keystore/delegation_key @@ -0,0 +1 @@ +68593a508472ad3007915379e6b1f3c0@@@@100000@@@@615986af4d1ba89aeadc2f489f89b0e8d46da133a6f75c7b162b8f99f63f86ed@@@@8319255f9856c4f40f9d71bc10e79e5d@@@@1dc7b20f1c668a1f544dc39c7a9fcb3c4a4dd34d1cc8c9d8f779bab026cf0b8e0f46e53bc5ed20bf0e5048b94a5d2ea176e79c12bcc7daa65cd55bf810deebeec5bc903ce9e5316d7dbba88f1a2b51d3f9bc782f8fa9b21dff91609ad0260e21a2039223f816d0fe97ace2e204d0025d327b38d27aa6cd87e85aa8883bfcb6d12f93155d72ffd3c7717a0570cf9811eb6d6a340baa0f27433315d83322c685fec02053ff8c173c4ebf91a258e83402f39546821e3352baa7b246e33b2a573a8ff7b289682407abbcb9184249d4304db68d3bf8e124e94377fd62dde5c4f3b7617d483776345154d047d139b1e559351577da315f54e16153c510159e1908231574bcf49c4f96cafe6530e86a09e9eee47bcff78f2fed2984754c895733938999ff085f9e3532d7174fd76dc09921506dd2137e16ec4926998f5d9df8a8ffb3e6649c71bc32571b2e24357739fa1a56be \ No newline at end of file diff --git a/testutils/repository_data/keystore/delegation_key.pub b/testutils/repository_data/keystore/delegation_key.pub new file mode 100644 index 00000000..d600bffb --- /dev/null +++ b/testutils/repository_data/keystore/delegation_key.pub @@ -0,0 +1 @@ +{"keyval": {"public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} \ No newline at end of file diff --git a/testutils/repository_data/keystore/root_key b/testutils/repository_data/keystore/root_key new file mode 100644 index 00000000..53e754d2 --- /dev/null +++ b/testutils/repository_data/keystore/root_key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDydf/VEpxBOCDoxpM6IVhq9i67P9BiVv2zwZSUO/M0RTToAvFv +NgDKXwtnp8LyjVk++wMA1aceMa+pS7vYrKvPIJa7WIT+mwy86/fIdnllJDMw5tmL +r2mE3oBMxOhpEiD2tO+liGacklFNk6nHHorX9S91iqpdRVa3zJw5ALvLdwIDAQAB +AoGBAJlhwoUVb9nmWxNGw86LV7bapDd6qCX96CL2PDsGLdWMTmrTqc5zuE5NkBZz +z2THvISWIJE/l6gHQJv1uBDbMxfquhK40k+GfE/fApVODN8KeBLLRUzYyHNz7KwW +aNF3jY8AbO4HzWpdaFYce5r+YqlWZoaVPR9i6LCW3sZXALyRAkEA/lSVaT0azp55 +2GI4Gn+EQQFqFJWEbNwJ8i3FZ4aG+/gnw2WmxJr+2nQcUlLb2cpQCCcMyWxvCfLK ++DapvvgZXwJBAPQNd+liOrKKd1gPR3S6y+D4h1ewj8ii1MHzRtAsCKCRG/e+v+hC +xp77Rc/qtZXKvVTGrccnKqCVAvG7F15rzOkCQQDCswgKn6+0+5c1ssNWbcZWaXnH +NktBdxXaI3Ya8d7GaEwwhtIrcqilnfvMfgg2a23nP9XHIU7EI+2EJXy/aHkrAkBH +wH30u9COFW+pEDTt+M1gQzFncp2TW2w56ZB0O739lywl1osNejRzIWURD+x7MbQg +bJlC6Bz8QVMwRtVECWWhAkAflD6eIJeceDhVHClHB/QwmF8mwR1o63RN7ZFlgel1 +kwMt6bPZZ1cyrRoj6Cdi4pyqBssDBuQmbBLWyYuijIwz +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/testutils/repository_data/keystore/root_key.pub b/testutils/repository_data/keystore/root_key.pub new file mode 100644 index 00000000..095c0663 --- /dev/null +++ b/testutils/repository_data/keystore/root_key.pub @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDydf/VEpxBOCDoxpM6IVhq9i67 +P9BiVv2zwZSUO/M0RTToAvFvNgDKXwtnp8LyjVk++wMA1aceMa+pS7vYrKvPIJa7 +WIT+mwy86/fIdnllJDMw5tmLr2mE3oBMxOhpEiD2tO+liGacklFNk6nHHorX9S91 +iqpdRVa3zJw5ALvLdwIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/testutils/repository_data/keystore/snapshot_key b/testutils/repository_data/keystore/snapshot_key new file mode 100644 index 00000000..cac57dd6 --- /dev/null +++ b/testutils/repository_data/keystore/snapshot_key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCPQoHresXRRRGoinN3bNn+BI23KolXdXLGqYkTvr9AjemUQJxb +qmvZXHboQMAYw8OuBrRNt5Fz20wjsrJwOBEU5U3nHSJI4zYPGckYci0/0Eo2Kjws +5BmIj38qgIfhsH4zyZ4FZZ+GLRn+W3i3wl6SfRMC/HCg0DDwi75faC0vGQIDAQAB +AoGAbPFYt2hf8qqhqRfQgysmA4QW+QnB895+8BCRC5DtA/xnerQ/s33AEkW8rxY+ +fxawQjEbAFbup7pHBoaoJ6qbYbKDBSGgZFSEbh40nriX1V0oYb9E+BCAFHE+42Rj +WYYNxXRp7LGoUQqisTsfoR1bvmrLC+9I/tDArHuMudm1slkCQQDOVn9AKTcaBGuQ +Y+JQqoRmi9eMN6XztKIAKQ+P/57BofwlKJDFnwttsvMxRud6rvN1FCnCDM638HNb +I0JDY0JXAkEAsb10uNV+SaWsHJOxfHzwK+uZJV1SkYzpBMizUREHuIyKT4MfpYNw +kn00KpyCvhIp6buwNyYo76TssejYN86UDwJAGi3ZSU+xYQisiQ5TOX7Y+5XEjFLH +KGuDnleXVOLOxqyBrElATQKH1aw9tMPVPLiTxQgA4FD1rVrBmA+aKaifUwJALBp8 +yhh/u7qWWIj1c5R07BEL8U+U23UBpSRACo+VQN/uuggpZCKXXmIe/avUbWGIcO0X +rreTVNOxv/utGzvxVQJBAL7Kpqt9d50SL1ndLr2EdqGw8ZB/B2dKMlZf7AWwbk0k +HHdvWfSDYhtvGo3ilLibHLesE/Tq1fm/2aEOds95/Eo= +-----END RSA PRIVATE KEY----- diff --git a/testutils/repository_data/keystore/snapshot_key.pub b/testutils/repository_data/keystore/snapshot_key.pub new file mode 100644 index 00000000..a0df8cf1 --- /dev/null +++ b/testutils/repository_data/keystore/snapshot_key.pub @@ -0,0 +1,2 @@ +{"keyval": {"public": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCG5DWxCcw4FW2G21RwTmuR7gdkv+ZrjZVOx0KsvJc/51QBxo/Y9xPVeoFF7YrhE8EV6A6b0qsLufIo1E63sQ6kjLOPfIMjag6dYPlmEyGcbxNDokv2elxZk7jS98iBQLxEmJLicrdERmxC2t2OOEQ6ELi5dt+C13QvNJFg4+OaTwIDAQAB"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} + diff --git a/testutils/repository_data/keystore/targets_key b/testutils/repository_data/keystore/targets_key new file mode 100644 index 00000000..800dae9a --- /dev/null +++ b/testutils/repository_data/keystore/targets_key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQCjm6HPktvTGsygQ8Gvmu+zydTNe1zqoxLxV7mVRbmsCI4kn7JT +Hc4fmWZwvo7f/Wbto6Xj5HqGJFSlYIGZuTwZqPg3w8wqv8cuPxbmsFSxMoHfzBBI +uJe0FlwXFysojbdhrSUqNL84tlwTFXEhePYrpTNMDn+9T55B0WJYT/VPxwIDAQAB +AoGANYaYRLHWS1WMNq6UMmBtJZPVlDhU6MrbSqwZojWCjj7qSh8ZF0o8AmiMdDxT +wAJGZ17PyiQY1cQTEVvmaqWIfJKvipAcTvkiXFrAxeIf/HYIVfCP9UB8RqhJufsc +XzDQyvZTmJdatHfKe2JV+q42GrsN4VN61wFEed3NuF8NGjECQQDSA5b+N1wMn5X4 +G5fxPYjhlwQmK3tlBHIPIVcVAsGOxU9Ry55xLQ8LpfKwJZIt2+LvgBIXf4DZY2u6 +GEnyR7epAkEAx267l7XX+9Dh8bHPluQSgH/tDrCp1hUNmyV4XzZCwavI/FaucANa +h8ChpUOSZTq5mR76YaUL7O3Sx8N7L/2x7wJAZDvgYf6sCT5VhnAtCa+T2A+KpGkW +YLVJdt0zwcxp8ylK3UAwo9Wcm7Oda+LSrN6IpkRa3io1pguki9Ix4NfH2QJATsXA +NxZOb1p8RFk1Y6ZGYJcm7Wx+SN8b9rIAL6thBtpxkqoyUHAirAg8UOi1xGJDuOVx +hGwKn9T4MotV9wi/5QJAB+1/2TaUMKjyL5Ca8Fh5SMigrwHp8SnX2vl7HV4hiBXi +0FaVxMPGH94tuFqHQ+q53tiTT1cp6YwcMMgpezTRRA== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/testutils/repository_data/keystore/targets_key.pub b/testutils/repository_data/keystore/targets_key.pub new file mode 100644 index 00000000..21fae67c --- /dev/null +++ b/testutils/repository_data/keystore/targets_key.pub @@ -0,0 +1 @@ +{"keyval": {"public": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjm6HPktvTGsygQ8Gvmu+zydTNe1zqoxLxV7mVRbmsCI4kn7JTHc4fmWZwvo7f/Wbto6Xj5HqGJFSlYIGZuTwZqPg3w8wqv8cuPxbmsFSxMoHfzBBIuJe0FlwXFysojbdhrSUqNL84tlwTFXEhePYrpTNMDn+9T55B0WJYT/VPxwIDAQAB"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} \ No newline at end of file diff --git a/testutils/repository_data/keystore/timestamp_key b/testutils/repository_data/keystore/timestamp_key new file mode 100644 index 00000000..4d5bbddb --- /dev/null +++ b/testutils/repository_data/keystore/timestamp_key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWgIBAAKBgHXjYnWGuCIOh5T3XGmgG/RsXWHPTbyu7OImP6O+uHg8hui8C1nY +/mcJdFdxqgl1vKEco/Nwebh2T8L6XbNfcgV9VVstWpeCalZYWi55lZSLe9KixQIA +yg15rNdhN9pcD3OuLmFvslgTx+dTbZ3ZoYMbcb4C5yqvqzcOoCTQMeWbAgMBAAEC +gYAMlDvAUKS7NZOwCIj62FPDTADW2/juhjfOlcg6n7ItWkAG+3G2n5ndwruATSeY +pNCA3H5+DmVeknlGU9LFvgx7dhJMw3WSkq7rImOGbwLN1jCVfwKP0AEEqb7GrtCU +a9lvm2ZFvKj+2VVFS2yifeluDG1Xm10ygq+RDd2lL2g6eQJBAMZrMTUwxWT/Cc0j +Yi7CFPl9V8GkYzLCKRQGR3x4QiNuXpNtQ3D+ivxHieBMEtw6M244PMDC+GpLxAfc +DtiGEl8CQQCYGXeycwkgn2YfH3w1/Mw6TWsdv4rVLPOieiQPrhZbVsBc6NT24MYW +b3c7osW5ypf7lo+xU8E6ylFUyeeVSk5FAkADTAqwSJQvHnHKP9lEz6LLloKbzCB9 +2m4WUBhmABWRQyc9Keah/QjQMlwfJwR1Nl5eaX7Q8Sxxj7q9KrHwdSHfAkAS1yTC +kAlTZytJM6c5MMVDe4+HMdDKszTCrYqF/rR6P/a4C4dFxXYEFW6ZjoIbj4LgAThv +aMaIt8L3U8NB9OBZAkA3ke4kilnVnjEyB9ibJ/SbDiUgh7e7M/XDbNQuXwSipFft +keBYEwL4Njms9uwMT4Gl59HyQls7BE2XEoiFjsY1 +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/testutils/repository_data/keystore/timestamp_key.pub b/testutils/repository_data/keystore/timestamp_key.pub new file mode 100644 index 00000000..6edc5c95 --- /dev/null +++ b/testutils/repository_data/keystore/timestamp_key.pub @@ -0,0 +1 @@ +{"keyval": {"public": "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHXjYnWGuCIOh5T3XGmgG/RsXWHPTbyu7OImP6O+uHg8hui8C1nY/mcJdFdxqgl1vKEco/Nwebh2T8L6XbNfcgV9VVstWpeCalZYWi55lZSLe9KixQIAyg15rNdhN9pcD3OuLmFvslgTx+dTbZ3ZoYMbcb4C5yqvqzcOoCTQMeWbAgMBAAE="}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} \ No newline at end of file diff --git a/testutils/repository_data/repository/metadata/1.root.json b/testutils/repository_data/repository/metadata/1.root.json new file mode 100644 index 00000000..21cb5877 --- /dev/null +++ b/testutils/repository_data/repository/metadata/1.root.json @@ -0,0 +1,72 @@ +{ + "signatures": [ + { + "keyid": "d5fa855fce82db75ec64283e828cc90517df5edf5cdc57e7958a890d6556f5b7", + "sig": "1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": true, + "expires": "2030-08-15T14:30:45.0000001Z", + "keys": { + "0a5842e65e9c8c428354f40708435de6793ac379a275effe40d6358be2de835c": { + "keytype": "ed25519", + "keyval": { + "public": "4e10fe156f07e6f6e1f6fb1579105b7d3e62790b6a62dbf7727b91f82d2bc9db" + }, + "scheme": "ed25519" + }, + "409fb816e403e0c00646665eac21cb8adfab8e318272ca7589b2d1fc0bccb255": { + "keytype": "ed25519", + "keyval": { + "public": "23e5dc4eb18d5c116e76a92b02e44a7d7279622574457050b85fb8fd9260422c" + }, + "scheme": "ed25519" + }, + "700464ea12f4cb5f06a7512c75b73c0b6eeb2cd42854b085eed5b3c993607cba": { + "keytype": "ed25519", + "keyval": { + "public": "1603f99998ca46c35c238a2c1a2a015e0f32b38771e4fa5401348ce0a677d63f" + }, + "scheme": "ed25519" + }, + "d5fa855fce82db75ec64283e828cc90517df5edf5cdc57e7958a890d6556f5b7": { + "keytype": "ed25519", + "keyval": { + "public": "17454b5e7a6594e7f00ceadda10d0267b94d0118b82f541f4f69f0d327c5a41a" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "d5fa855fce82db75ec64283e828cc90517df5edf5cdc57e7958a890d6556f5b7" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "700464ea12f4cb5f06a7512c75b73c0b6eeb2cd42854b085eed5b3c993607cba" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "409fb816e403e0c00646665eac21cb8adfab8e318272ca7589b2d1fc0bccb255" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "0a5842e65e9c8c428354f40708435de6793ac379a275effe40d6358be2de835c" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.31", + "version": 1, + "test": "true" + } +} \ No newline at end of file diff --git a/testutils/repository_data/repository/metadata/role1.json b/testutils/repository_data/repository/metadata/role1.json new file mode 100644 index 00000000..0ac4687e --- /dev/null +++ b/testutils/repository_data/repository/metadata/role1.json @@ -0,0 +1,49 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "9408b46569e622a46f1d35d9fa3c10e17a9285631ced4f2c9c2bba2c2842413fcb796db4e81d6f988fc056c21c407fdc3c10441592cf1e837e088f2e2dfd5403" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role2", + "paths": [], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file3.txt": { + "hashes": { + "sha256": "141f740f53781d1ca54b8a50af22cbf74e44c21a998fa2a8a05aaac2c002886b", + "sha512": "ef5beafa16041bcdd2937140afebd485296cd54f7348ecd5a4d035c09759608de467a7ac0eb58753d0242df873c305e8bffad2454aa48f44480f15efae1cacd0" + }, + "length": 28 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/testutils/repository_data/repository/metadata/role2.json b/testutils/repository_data/repository/metadata/role2.json new file mode 100644 index 00000000..9c49e165 --- /dev/null +++ b/testutils/repository_data/repository/metadata/role2.json @@ -0,0 +1,15 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "75b196a224fd200e46e738b1216b3316c5384f61083872f8d14b8b0a378b2344e64b1a6f1a89a711206a66a0b199d65ac0e30fe15ddbc4de89fa8ff645f99403" + } + ], + "signed": { + "_type": "targets", + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/testutils/repository_data/repository/metadata/root.json b/testutils/repository_data/repository/metadata/root.json new file mode 100644 index 00000000..584a3ec9 --- /dev/null +++ b/testutils/repository_data/repository/metadata/root.json @@ -0,0 +1,71 @@ +{ + "signatures": [ + { + "keyid": "74b58be26a6ff00ab2eec9b14da29038591a69c212223033f4efdf24489913f2", + "sig": "d0283ac0653e324ce132e47a518f8a1539b59430efe5cdec58ec53f824bec28628b57dd5fb2452bde83fc8f5d11ab0b7350a9bbcbefc7acc6c447785545fa1e36f1352c9e20dd1ebcc3ab16a2a7ff702e32e481ceba88e0f348dc2cddd26ca577445d00c7194e8656d901fd2382c479555af93a64eef48cf79cdff6ecdcd7cb7" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": true, + "expires": "2030-08-15T14:30:45.0000001Z", + "keys": { + "142919f8e933d7045abff3be450070057814da36331d7a22ccade8b35a9e3946": { + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHXjYnWGuCIOh5T3XGmgG/RsXWHP\nTbyu7OImP6O+uHg8hui8C1nY/mcJdFdxqgl1vKEco/Nwebh2T8L6XbNfcgV9VVst\nWpeCalZYWi55lZSLe9KixQIAyg15rNdhN9pcD3OuLmFvslgTx+dTbZ3ZoYMbcb4C\n5yqvqzcOoCTQMeWbAgMBAAE=\n-----END PUBLIC KEY-----\n" + }, + "scheme": "rsassa-pss-sha256" + }, + "282612f348dcd7fe3f19e0f890e89fad48d45335deeb91deef92873934e6fe6d": { + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjm6HPktvTGsygQ8Gvmu+zydTN\ne1zqoxLxV7mVRbmsCI4kn7JTHc4fmWZwvo7f/Wbto6Xj5HqGJFSlYIGZuTwZqPg3\nw8wqv8cuPxbmsFSxMoHfzBBIuJe0FlwXFysojbdhrSUqNL84tlwTFXEhePYrpTNM\nDn+9T55B0WJYT/VPxwIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "scheme": "rsassa-pss-sha256" + }, + "74b58be26a6ff00ab2eec9b14da29038591a69c212223033f4efdf24489913f2": { + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDydf/VEpxBOCDoxpM6IVhq9i67\nP9BiVv2zwZSUO/M0RTToAvFvNgDKXwtnp8LyjVk++wMA1aceMa+pS7vYrKvPIJa7\nWIT+mwy86/fIdnllJDMw5tmLr2mE3oBMxOhpEiD2tO+liGacklFNk6nHHorX9S91\niqpdRVa3zJw5ALvLdwIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "scheme": "rsassa-pss-sha256" + }, + "8a14f637b21578cc292a67899df0e46cc160d7fd56e9beae898adb666f4fd9d6": { + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCPQoHresXRRRGoinN3bNn+BI23\nKolXdXLGqYkTvr9AjemUQJxbqmvZXHboQMAYw8OuBrRNt5Fz20wjsrJwOBEU5U3n\nHSJI4zYPGckYci0/0Eo2Kjws5BmIj38qgIfhsH4zyZ4FZZ+GLRn+W3i3wl6SfRMC\n/HCg0DDwi75faC0vGQIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "scheme": "rsassa-pss-sha256" + } + }, + "roles": { + "root": { + "keyids": [ + "74b58be26a6ff00ab2eec9b14da29038591a69c212223033f4efdf24489913f2" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "8a14f637b21578cc292a67899df0e46cc160d7fd56e9beae898adb666f4fd9d6" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "282612f348dcd7fe3f19e0f890e89fad48d45335deeb91deef92873934e6fe6d" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "142919f8e933d7045abff3be450070057814da36331d7a22ccade8b35a9e3946" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.31", + "version": 1 + } +} \ No newline at end of file diff --git a/testutils/repository_data/repository/metadata/snapshot.json b/testutils/repository_data/repository/metadata/snapshot.json new file mode 100644 index 00000000..28429701 --- /dev/null +++ b/testutils/repository_data/repository/metadata/snapshot.json @@ -0,0 +1,19 @@ +{ + "signatures": [ + { + "keyid": "8a14f637b21578cc292a67899df0e46cc160d7fd56e9beae898adb666f4fd9d6", + "sig": "50d814131d3aace838af726f13a6b4431f78c086b58817f3208d73fe174fe00a4729ad796888bab86ee0cd0cc5746fd8379327e65cd317c4a5f769bf29028bcae1a5bc95a21b15f86a54ba05c3443503037c2c90062825b8a193896814acb67ee000f5816c70f23867686604820efd48ee9d643d1354897c654075076b27ee27" + } + ], + "signed": { + "_type": "snapshot", + "expires": "2030-08-15T14:30:45.0000001Z", + "meta": { + "targets.json": { + "version": 1 + } + }, + "spec_version": "1.0.31", + "version": 1 + } +} \ No newline at end of file diff --git a/testutils/repository_data/repository/metadata/targets.json b/testutils/repository_data/repository/metadata/targets.json new file mode 100644 index 00000000..f1e50bee --- /dev/null +++ b/testutils/repository_data/repository/metadata/targets.json @@ -0,0 +1,22 @@ +{ + "signatures": [ + { + "keyid": "282612f348dcd7fe3f19e0f890e89fad48d45335deeb91deef92873934e6fe6d", + "sig": "6cffed26499edcb7ee99e885675c3d9c9570d93b78ecba23f71c791dc8cc171db925e2d2105fbb4167e52b947ed08f1441ce536dccd2535957200be63735a2286784d1005d55216b4b1aa6fd0af767d1f003545c1e44ac5208ff592699e8bb36f9c74c8f90cea1893c94f975f78d058f00c97f6fef7b5aed66402cd6e71853d6" + } + ], + "signed": { + "_type": "targets", + "expires": "2030-08-15T14:30:45.0000001Z", + "spec_version": "1.0.31", + "targets": { + "metadata_api_test.go": { + "hashes": { + "sha256": "232ba4f8db1c6e83472f5457a81d9ea03f2db8686dc36161a42e6c809a0a449a" + }, + "length": 19564 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/testutils/repository_data/repository/metadata/timestamp.json b/testutils/repository_data/repository/metadata/timestamp.json new file mode 100644 index 00000000..aa11c742 --- /dev/null +++ b/testutils/repository_data/repository/metadata/timestamp.json @@ -0,0 +1,19 @@ +{ + "signatures": [ + { + "keyid": "142919f8e933d7045abff3be450070057814da36331d7a22ccade8b35a9e3946", + "sig": "639c9ce3dbb705265b5e9ad6d67fea2b38780c48ff7917e372adace8e50a7a2f054383d5960457a113059be521b8ce7e6d8a5787c600c4850b8c0ed1ae17a931a6bfe794476e7824c6f53df5232561e0a2e146b11dde7889b397c6f8136e2105bbb21b4b59b5addc032a0e755d97e531255f3b458d474184168541e542626e81" + } + ], + "signed": { + "_type": "timestamp", + "expires": "2030-08-15T14:30:45.0000001Z", + "meta": { + "snapshot.json": { + "version": 1 + } + }, + "spec_version": "1.0.31", + "version": 1 + } +} \ No newline at end of file diff --git a/testutils/testutils/setup.go b/testutils/testutils/setup.go new file mode 100644 index 00000000..ea51d499 --- /dev/null +++ b/testutils/testutils/setup.go @@ -0,0 +1,96 @@ +// Copyright 2023 VMware, Inc. +// +// This product is licensed to you under the BSD-2 license (the "License"). +// You may not use this product except in compliance with the BSD-2 License. +// This product may include a number of subcomponents with separate copyright +// notices and license terms. Your use of these subcomponents is subject to +// the terms and conditions of the subcomponent's license, as noted in the +// LICENSE file. +// +// SPDX-License-Identifier: BSD-2-Clause + +package testutils + +import ( + "fmt" + "os" + "path/filepath" + + log "github.com/sirupsen/logrus" +) + +var ( + TempDir string + RepoDir string + KeystoreDir string +) + +func SetupTestDirs() error { + tmp := os.TempDir() + var err error + TempDir, err = os.MkdirTemp(tmp, "0750") + if err != nil { + log.Fatal("failed to create temporary directory: ", err) + return err + } + + RepoDir = fmt.Sprintf("%s/repository_data/repository", TempDir) + absPath, err := filepath.Abs("../testutils/repository_data/repository/metadata") + if err != nil { + log.Debugf("failed to get absolute path: %v", err) + } + err = Copy(absPath, RepoDir) + if err != nil { + log.Debugf("failed to copy metadata to %s: %v", RepoDir, err) + return err + } + + KeystoreDir = fmt.Sprintf("%s/keystore", TempDir) + err = os.Mkdir(KeystoreDir, 0750) + if err != nil { + log.Debugf("failed to create keystore dir %s: %v", KeystoreDir, err) + } + absPath, err = filepath.Abs("../testutils/repository_data/keystore") + if err != nil { + log.Debugf("failed to get absolute path: %v", err) + } + err = Copy(absPath, KeystoreDir) + if err != nil { + log.Debugf("failed to copy keystore to %s: %v", KeystoreDir, err) + return err + } + + return nil +} + +func Copy(fromPath string, toPath string) error { + err := os.MkdirAll(toPath, 0750) + if err != nil { + log.Debugf("failed to create directory %s: %v", toPath, err) + } + files, err := os.ReadDir(fromPath) + if err != nil { + log.Debugf("failed to read path %s: %v", fromPath, err) + return err + } + for _, file := range files { + data, err := os.ReadFile(fmt.Sprintf("%s/%s", fromPath, file.Name())) + if err != nil { + log.Debugf("failed to read file %s: %v", file.Name(), err) + } + filePath := fmt.Sprintf("%s/%s", toPath, file.Name()) + err = os.WriteFile(filePath, data, 0750) + if err != nil { + log.Debugf("failed to write file %s: %v", filePath, err) + } + } + return nil +} + +func Cleanup() { + log.Printf("cleaning temporary directory: %s\n", TempDir) + err := os.RemoveAll(TempDir) + if err != nil { + log.Fatalf("failed to cleanup test directories: %v", err) + } +}