diff --git a/pkg/generated/models/intoto_v002_schema.go b/pkg/generated/models/intoto_v002_schema.go index 3297e5a91..b630f156a 100644 --- a/pkg/generated/models/intoto_v002_schema.go +++ b/pkg/generated/models/intoto_v002_schema.go @@ -450,7 +450,6 @@ type IntotoV002SchemaContentEnvelopeSignaturesItems0 struct { Keyid string `json:"keyid,omitempty"` // public key that corresponds to this signature - // Read Only: true // Format: byte PublicKey strfmt.Base64 `json:"publicKey,omitempty"` @@ -464,26 +463,8 @@ func (m *IntotoV002SchemaContentEnvelopeSignaturesItems0) Validate(formats strfm return nil } -// ContextValidate validate this intoto v002 schema content envelope signatures items0 based on the context it is used +// ContextValidate validates this intoto v002 schema content envelope signatures items0 based on context it is used func (m *IntotoV002SchemaContentEnvelopeSignaturesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - var res []error - - if err := m.contextValidatePublicKey(ctx, formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *IntotoV002SchemaContentEnvelopeSignaturesItems0) contextValidatePublicKey(ctx context.Context, formats strfmt.Registry) error { - - if err := validate.ReadOnly(ctx, "publicKey", "body", strfmt.Base64(m.PublicKey)); err != nil { - return err - } - return nil } diff --git a/pkg/generated/models/tuf_v001_schema.go b/pkg/generated/models/tuf_v001_schema.go index f8bf4b020..db5d8a3a9 100644 --- a/pkg/generated/models/tuf_v001_schema.go +++ b/pkg/generated/models/tuf_v001_schema.go @@ -195,11 +195,30 @@ func (m *TUFV001Schema) UnmarshalBinary(b []byte) error { type TUFV001SchemaMetadata struct { // Specifies the metadata inline within the document - Content interface{} `json:"content,omitempty"` + // Required: true + Content interface{} `json:"content"` } // Validate validates this TUF v001 schema metadata func (m *TUFV001SchemaMetadata) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateContent(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *TUFV001SchemaMetadata) validateContent(formats strfmt.Registry) error { + + if m.Content == nil { + return errors.Required("metadata"+"."+"content", "body", nil) + } + return nil } diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index 9466b1ffd..02b719ba8 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -2042,8 +2042,7 @@ func init() { "publicKey": { "description": "public key that corresponds to this signature", "type": "string", - "format": "byte", - "readOnly": true + "format": "byte" }, "sig": { "description": "signature of the payload", @@ -2665,7 +2664,7 @@ func init() { "description": "TUF metadata", "type": "object", "required": [ - "metadata" + "content" ], "properties": { "content": { @@ -3852,7 +3851,7 @@ func init() { "description": "TUF metadata", "type": "object", "required": [ - "metadata" + "content" ], "properties": { "content": { diff --git a/pkg/types/alpine/alpine_test.go b/pkg/types/alpine/alpine_test.go index 9ab119815..a961968ed 100644 --- a/pkg/types/alpine/alpine_test.go +++ b/pkg/types/alpine/alpine_test.go @@ -47,6 +47,10 @@ func (u UnmarshalFailsTester) Verifier() (pki.PublicKey, error) { return nil, nil } +func (u UnmarshalFailsTester) Insertable() (bool, error) { + return false, nil +} + func TestAlpineType(t *testing.T) { // empty to start if VersionMap.Count() != 0 { diff --git a/pkg/types/alpine/v0.0.1/entry.go b/pkg/types/alpine/v0.0.1/entry.go index 827dd76f8..98d31dabf 100644 --- a/pkg/types/alpine/v0.0.1/entry.go +++ b/pkg/types/alpine/v0.0.1/entry.go @@ -357,3 +357,19 @@ func (v V001Entry) Verifier() (pki.PublicKey, error) { } return x509.NewPublicKey(bytes.NewReader(*v.AlpineModel.PublicKey.Content)) } + +func (v V001Entry) Insertable() (bool, error) { + if v.AlpineModel.Package == nil { + return false, fmt.Errorf("missing package entry") + } + if len(v.AlpineModel.Package.Content) == 0 { + return false, fmt.Errorf("missing package content") + } + if v.AlpineModel.PublicKey == nil { + return false, fmt.Errorf("missing public key") + } + if v.AlpineModel.PublicKey.Content == nil || len(*v.AlpineModel.PublicKey.Content) == 0 { + return false, fmt.Errorf("missing public key content") + } + return true, nil +} diff --git a/pkg/types/alpine/v0.0.1/entry_test.go b/pkg/types/alpine/v0.0.1/entry_test.go index abd6a2570..f6c6cc801 100644 --- a/pkg/types/alpine/v0.0.1/entry_test.go +++ b/pkg/types/alpine/v0.0.1/entry_test.go @@ -151,6 +151,12 @@ func TestCrossFieldValidation(t *testing.T) { t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) } + if tc.expectUnmarshalSuccess { + if ok, err := v.Insertable(); !ok || err != nil { + t.Errorf("unexpected result in calling Insertable on valid proposed entry: %v", err) + } + } + b, err := v.Canonicalize(context.TODO()) if (err == nil) != tc.expectCanonicalizeSuccess { t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err) @@ -164,9 +170,13 @@ func TestCrossFieldValidation(t *testing.T) { if err != nil { t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tc.caseDesc, err) } - if _, err := types.UnmarshalEntry(pe); err != nil { + ei, err := types.UnmarshalEntry(pe) + if err != nil { t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tc.caseDesc, err) } + if ok, err := ei.Insertable(); ok || err == nil { + t.Errorf("unexpected success calling Insertable on entry created from canonicalized content") + } } verifier, err := v.Verifier() @@ -187,3 +197,114 @@ func TestCrossFieldValidation(t *testing.T) { } } } + +func TestInsertable(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + expectSuccess bool + } + + pub := strfmt.Base64([]byte("pub")) + + testCases := []TestCase{ + { + caseDesc: "valid entry", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + Package: &models.AlpineV001SchemaPackage{ + Content: strfmt.Base64("package"), + }, + PublicKey: &models.AlpineV001SchemaPublicKey{ + Content: &pub, + }, + }, + }, + expectSuccess: true, + }, + { + caseDesc: "missing key content", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + Package: &models.AlpineV001SchemaPackage{ + Content: strfmt.Base64("package"), + }, + PublicKey: &models.AlpineV001SchemaPublicKey{ + //Content: &pub, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing public key", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + Package: &models.AlpineV001SchemaPackage{ + Content: strfmt.Base64("package"), + }, + /* + PublicKey: &models.AlpineV001SchemaPublicKey{ + Content: &pub, + }, + */ + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing package content", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + Package: &models.AlpineV001SchemaPackage{ + //Content: strfmt.Base64("package"), + }, + PublicKey: &models.AlpineV001SchemaPublicKey{ + Content: &pub, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing package", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + /* + Package: &models.AlpineV001SchemaPackage{ + Content: strfmt.Base64("package"), + }, + */ + PublicKey: &models.AlpineV001SchemaPublicKey{ + Content: &pub, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "empty model", + entry: V001Entry{ + AlpineModel: models.AlpineV001Schema{ + /* + Package: &models.AlpineV001SchemaPackage{ + Content: strfmt.Base64("package"), + }, + PublicKey: &models.AlpineV001SchemaPublicKey{ + Content: &pub, + }, + */ + }, + }, + expectSuccess: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseDesc, func(t *testing.T) { + if ok, err := tc.entry.Insertable(); ok != tc.expectSuccess { + t.Errorf("unexpected result calling Insertable: %v", err) + } + }) + } +} diff --git a/pkg/types/cose/cose_test.go b/pkg/types/cose/cose_test.go index 0371ea47f..080253a2a 100644 --- a/pkg/types/cose/cose_test.go +++ b/pkg/types/cose/cose_test.go @@ -48,6 +48,10 @@ func (u UnmarshalFailsTester) Verifier() (pki.PublicKey, error) { return nil, nil } +func (u UnmarshalFailsTester) Insertable() (bool, error) { + return false, nil +} + func TestCOSEType(t *testing.T) { // empty to start if VersionMap.Count() != 0 { diff --git a/pkg/types/cose/v0.0.1/entry.go b/pkg/types/cose/v0.0.1/entry.go index 685aacc4d..b7c12a907 100644 --- a/pkg/types/cose/v0.0.1/entry.go +++ b/pkg/types/cose/v0.0.1/entry.go @@ -354,3 +354,26 @@ func (v V001Entry) Verifier() (pki.PublicKey, error) { } return x509.NewPublicKey(bytes.NewReader(*v.CoseObj.PublicKey)) } + +func (v V001Entry) Insertable() (bool, error) { + if len(v.CoseObj.Message) == 0 { + return false, errors.New("missing COSE Sign1 message") + } + if v.CoseObj.PublicKey == nil || len(*v.CoseObj.PublicKey) == 0 { + return false, errors.New("missing public key") + } + if v.CoseObj.Data == nil { + return false, errors.New("missing COSE data property") + } + if len(v.envelopeHash) == 0 { + return false, errors.New("envelope hash has not been computed") + } + if v.keyObj == nil { + return false, errors.New("public key has not been parsed") + } + if v.sign1Msg == nil { + return false, errors.New("signature has not been validated") + } + + return true, nil +} diff --git a/pkg/types/cose/v0.0.1/entry_test.go b/pkg/types/cose/v0.0.1/entry_test.go index 582e42377..34af5cfe4 100644 --- a/pkg/types/cose/v0.0.1/entry_test.go +++ b/pkg/types/cose/v0.0.1/entry_test.go @@ -17,6 +17,7 @@ package cose import ( "bytes" + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -30,6 +31,7 @@ import ( "reflect" "testing" + "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" "github.com/sigstore/rekor/pkg/generated/models" sigx509 "github.com/sigstore/rekor/pkg/pki/x509" @@ -270,12 +272,39 @@ func TestV001Entry_Unmarshal(t *testing.T) { t.Errorf("V001Entry.Unmarshal() error = %v, wantErr %v", err, tt.wantErr) } + if !tt.wantErr { + if ok, err := v.Insertable(); !ok || err != nil { + t.Errorf("unexpected error calling Insertable on valid proposed entry: %v", err) + } + } + verifier, err := v.Verifier() if !tt.wantVerifierErr { if err != nil { s, _ := verifier.CanonicalValue() t.Errorf("%v: unexpected error for %v, got %v", tt.name, string(s), err) } + + if !tt.wantErr { + b, err := v.Canonicalize(context.Background()) + if err != nil { + t.Errorf("unexpected error canonicalizing %v", tt.name) + } + if len(b) != 0 { + pe, err := models.UnmarshalProposedEntry(bytes.NewReader(b), runtime.JSONConsumer()) + if err != nil { + t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tt.name, err) + } + ei, err := types.UnmarshalEntry(pe) + if err != nil { + t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tt.name, err) + } + if ok, err := ei.Insertable(); ok || err == nil { + t.Errorf("entry created from canonicalized entry should not also be insertable") + } + } + } + pubV, _ := verifier.CanonicalValue() if !reflect.DeepEqual(pubV, pub) && !reflect.DeepEqual(pubV, pemBytes) { t.Errorf("verifier and public keys do not match: %v, %v", string(pubV), string(pub)) @@ -690,3 +719,202 @@ func mustContain(t *testing.T, want string, l []string) { } t.Fatalf("list %v does not contain %s", l, want) } + +func TestInsertable(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + expectSuccess bool + } + + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + der, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + if err != nil { + t.Fatal(err) + } + pub := pem.EncodeToMemory(&pem.Block{ + Bytes: der, + Type: "PUBLIC KEY", + }) + keyObj, err := sigx509.NewPublicKey(bytes.NewReader(pub)) + if err != nil { + t.Fatal(err) + } + pubKey := strfmt.Base64(pub) + + testCases := []TestCase{ + { + caseDesc: "valid entry", + entry: V001Entry{ + CoseObj: models.CoseV001Schema{ + Data: &models.CoseV001SchemaData{ + Aad: strfmt.Base64([]byte("aad")), + }, + Message: strfmt.Base64([]byte("message")), + PublicKey: &pubKey, + }, + keyObj: keyObj, + sign1Msg: &gocose.Sign1Message{}, + envelopeHash: []byte("hash"), + }, + expectSuccess: true, + }, + { + caseDesc: "no aad", + entry: V001Entry{ + CoseObj: models.CoseV001Schema{ + Data: &models.CoseV001SchemaData{}, + Message: strfmt.Base64([]byte("message")), + PublicKey: &pubKey, + }, + keyObj: keyObj, + sign1Msg: &gocose.Sign1Message{}, + envelopeHash: []byte("hash"), + }, + expectSuccess: true, + }, + { + caseDesc: "missing hash", + entry: V001Entry{ + CoseObj: models.CoseV001Schema{ + Data: &models.CoseV001SchemaData{}, + Message: strfmt.Base64([]byte("message")), + PublicKey: &pubKey, + }, + keyObj: keyObj, + sign1Msg: &gocose.Sign1Message{}, + //envelopeHash: []byte("hash"), + }, + expectSuccess: false, + }, + { + caseDesc: "unparsed message", + entry: V001Entry{ + CoseObj: models.CoseV001Schema{ + Data: &models.CoseV001SchemaData{ + Aad: strfmt.Base64([]byte("aad")), + }, + Message: strfmt.Base64([]byte("message")), + PublicKey: &pubKey, + }, + keyObj: keyObj, + //sign1Msg: &gocose.Sign1Message{}, + envelopeHash: []byte("hash"), + }, + expectSuccess: false, + }, + { + caseDesc: "unparsed public key", + entry: V001Entry{ + CoseObj: models.CoseV001Schema{ + Data: &models.CoseV001SchemaData{ + Aad: strfmt.Base64([]byte("aad")), + }, + Message: strfmt.Base64([]byte("message")), + PublicKey: &pubKey, + }, + //keyObj: keyObj, + sign1Msg: &gocose.Sign1Message{}, + envelopeHash: []byte("hash"), + }, + expectSuccess: false, + }, + { + caseDesc: "missing public key", + entry: V001Entry{ + CoseObj: models.CoseV001Schema{ + Data: &models.CoseV001SchemaData{ + Aad: strfmt.Base64([]byte("aad")), + }, + Message: strfmt.Base64([]byte("message")), + //PublicKey: &pubKey, + }, + keyObj: keyObj, + sign1Msg: &gocose.Sign1Message{}, + envelopeHash: []byte("hash"), + }, + expectSuccess: false, + }, + { + caseDesc: "missing unparsed message", + entry: V001Entry{ + CoseObj: models.CoseV001Schema{ + Data: &models.CoseV001SchemaData{ + Aad: strfmt.Base64([]byte("aad")), + }, + //Message: strfmt.Base64([]byte("message")), + PublicKey: &pubKey, + }, + keyObj: keyObj, + sign1Msg: &gocose.Sign1Message{}, + envelopeHash: []byte("hash"), + }, + expectSuccess: false, + }, + { + caseDesc: "missing data", + entry: V001Entry{ + CoseObj: models.CoseV001Schema{ + /* + Data: &models.CoseV001SchemaData{ + Aad: strfmt.Base64([]byte("aad")), + }, + */ + Message: strfmt.Base64([]byte("message")), + PublicKey: &pubKey, + }, + keyObj: keyObj, + sign1Msg: &gocose.Sign1Message{}, + envelopeHash: []byte("hash"), + }, + expectSuccess: false, + }, + { + caseDesc: "missing cose obj", + entry: V001Entry{ + /* + CoseObj: models.CoseV001Schema{ + Data: &models.CoseV001SchemaData{ + Aad: strfmt.Base64([]byte("aad")), + }, + Message: strfmt.Base64([]byte("message")), + PublicKey: &pubKey, + }, + */ + keyObj: keyObj, + sign1Msg: &gocose.Sign1Message{}, + envelopeHash: []byte("hash"), + }, + expectSuccess: false, + }, + { + caseDesc: "empty obj", + entry: V001Entry{ + /* + CoseObj: models.CoseV001Schema{ + Data: &models.CoseV001SchemaData{ + Aad: strfmt.Base64([]byte("aad")), + }, + Message: strfmt.Base64([]byte("message")), + PublicKey: &pubKey, + }, + keyObj: keyObj, + sign1Msg: &gocose.Sign1Message{}, + envelopeHash: []byte("hash"), + */ + }, + expectSuccess: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseDesc, func(t *testing.T) { + if ok, err := tc.entry.Insertable(); ok != tc.expectSuccess { + t.Errorf("unexpected result calling Insertable: %v", err) + } + }) + } +} diff --git a/pkg/types/entries.go b/pkg/types/entries.go index 760f0d887..9309b4c33 100644 --- a/pkg/types/entries.go +++ b/pkg/types/entries.go @@ -38,6 +38,7 @@ type EntryImpl interface { Unmarshal(e models.ProposedEntry) error // unmarshal the abstract entry into the specific struct for this versioned type CreateFromArtifactProperties(context.Context, ArtifactProperties) (models.ProposedEntry, error) Verifier() (pki.PublicKey, error) + Insertable() (bool, error) // denotes whether the entry that was unmarshalled has the writeOnly fields required to validate and insert into the log } // EntryWithAttestationImpl specifies the behavior of a versioned type that also stores attestations @@ -82,6 +83,12 @@ func CreateVersionedEntry(pe models.ProposedEntry) (EntryImpl, error) { if !tf.(func() TypeImpl)().IsSupportedVersion(ei.APIVersion()) { return nil, fmt.Errorf("entry kind '%v' does not support inserting entries of version '%v'", kind, ei.APIVersion()) } + } else { + return nil, fmt.Errorf("unknown kind '%v' specified", kind) + } + + if ok, err := ei.Insertable(); !ok { + return nil, fmt.Errorf("entry not insertable into log: %w", err) } return ei, nil diff --git a/pkg/types/hashedrekord/v0.0.1/entry.go b/pkg/types/hashedrekord/v0.0.1/entry.go index 36d042dff..82f54fe1a 100644 --- a/pkg/types/hashedrekord/v0.0.1/entry.go +++ b/pkg/types/hashedrekord/v0.0.1/entry.go @@ -252,3 +252,31 @@ func (v V001Entry) Verifier() (pki.PublicKey, error) { } return x509.NewPublicKey(bytes.NewReader(v.HashedRekordObj.Signature.PublicKey.Content)) } + +func (v V001Entry) Insertable() (bool, error) { + if v.HashedRekordObj.Signature == nil { + return false, errors.New("missing signature property") + } + if len(v.HashedRekordObj.Signature.Content) == 0 { + return false, errors.New("missing signature content") + } + if v.HashedRekordObj.Signature.PublicKey == nil { + return false, errors.New("missing publicKey property") + } + if len(v.HashedRekordObj.Signature.PublicKey.Content) == 0 { + return false, errors.New("missing publicKey content") + } + if v.HashedRekordObj.Data == nil { + return false, errors.New("missing data property") + } + if v.HashedRekordObj.Data.Hash == nil { + return false, errors.New("missing hash property") + } + if v.HashedRekordObj.Data.Hash.Algorithm == nil { + return false, errors.New("missing hash algorithm") + } + if v.HashedRekordObj.Data.Hash.Value == nil { + return false, errors.New("missing hash value") + } + return true, nil +} diff --git a/pkg/types/hashedrekord/v0.0.1/entry_test.go b/pkg/types/hashedrekord/v0.0.1/entry_test.go index e78ae2139..ba9a528d1 100644 --- a/pkg/types/hashedrekord/v0.0.1/entry_test.go +++ b/pkg/types/hashedrekord/v0.0.1/entry_test.go @@ -284,6 +284,13 @@ func TestCrossFieldValidation(t *testing.T) { t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) } + if tc.expectUnmarshalSuccess { + ok, err := v.Insertable() + if !ok || err != nil { + t.Errorf("unexpected failure in testing insertable on valid entry: %v", err) + } + } + b, err := v.Canonicalize(context.TODO()) if (err == nil) != tc.expectCanonicalizeSuccess { t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err) @@ -297,9 +304,15 @@ func TestCrossFieldValidation(t *testing.T) { if err != nil { t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tc.caseDesc, err) } - if _, err := types.UnmarshalEntry(pe); err != nil { + ei, err := types.UnmarshalEntry(pe) + if err != nil { t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tc.caseDesc, err) } + // hashedrekord is one of two types (rfc3161, hashedrekord) in that what is persisted is also insertable + ok, err := ei.Insertable() + if !ok || err != nil { + t.Errorf("unexpected failure in testing insertable on entry created from canonicalized content: %v", err) + } } verifier, err := v.Verifier() @@ -443,3 +456,203 @@ func testKeyAndCert(t *testing.T) ([]byte, []byte, *ecdsa.PrivateKey) { return pub, certPem, priv } + +func TestInsertable(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + expectSuccess bool + } + + testCases := []TestCase{ + { + caseDesc: "valid entry", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), + Value: swag.String("deadbeef"), + }, + }, + Signature: &models.HashedrekordV001SchemaSignature{ + Content: strfmt.Base64("sig"), + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ + Content: strfmt.Base64("key"), + }, + }, + }, + }, + expectSuccess: true, + }, + { + caseDesc: "missing key", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), + Value: swag.String("deadbeef"), + }, + }, + Signature: &models.HashedrekordV001SchemaSignature{ + Content: strfmt.Base64("sig"), + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing key content", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), + Value: swag.String("deadbeef"), + }, + }, + Signature: &models.HashedrekordV001SchemaSignature{ + Content: strfmt.Base64("sig"), + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{}, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing key content", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), + Value: swag.String("deadbeef"), + }, + }, + Signature: &models.HashedrekordV001SchemaSignature{ + Content: strfmt.Base64("sig"), + PublicKey: nil, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing sig content", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), + Value: swag.String("deadbeef"), + }, + }, + Signature: &models.HashedrekordV001SchemaSignature{ + Content: nil, + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ + Content: strfmt.Base64("key"), + }, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing hash value", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), + }, + }, + Signature: &models.HashedrekordV001SchemaSignature{ + Content: strfmt.Base64("sig"), + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ + Content: strfmt.Base64("key"), + }, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing hash algorithm", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Value: swag.String("deadbeef"), + }, + }, + Signature: &models.HashedrekordV001SchemaSignature{ + Content: strfmt.Base64("sig"), + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ + Content: strfmt.Base64("key"), + }, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing hash object", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Data: &models.HashedrekordV001SchemaData{}, + Signature: &models.HashedrekordV001SchemaSignature{ + Content: strfmt.Base64("sig"), + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ + Content: strfmt.Base64("key"), + }, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing data object", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Signature: &models.HashedrekordV001SchemaSignature{ + Content: strfmt.Base64("sig"), + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ + Content: strfmt.Base64("key"), + }, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing sig object", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), + Value: swag.String("deadbeef"), + }, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "empty object", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{}, + }, + expectSuccess: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseDesc, func(t *testing.T) { + if ok, err := tc.entry.Insertable(); ok != tc.expectSuccess { + t.Errorf("unexpected result calling Insertable: %v", err) + } + }) + } +} diff --git a/pkg/types/helm/v0.0.1/entry.go b/pkg/types/helm/v0.0.1/entry.go index e76e752b2..dfb05c255 100644 --- a/pkg/types/helm/v0.0.1/entry.go +++ b/pkg/types/helm/v0.0.1/entry.go @@ -355,3 +355,23 @@ func (v V001Entry) Verifier() (pki.PublicKey, error) { } return pgp.NewPublicKey(bytes.NewReader(*v.HelmObj.PublicKey.Content)) } + +func (v V001Entry) Insertable() (bool, error) { + if v.HelmObj.PublicKey == nil { + return false, errors.New("missing public key property") + } + if v.HelmObj.PublicKey.Content == nil || len(*v.HelmObj.PublicKey.Content) == 0 { + return false, errors.New("missing public key content") + } + + if v.HelmObj.Chart == nil { + return false, errors.New("missing chart property") + } + if v.HelmObj.Chart.Provenance == nil { + return false, errors.New("missing provenance property") + } + if len(v.HelmObj.Chart.Provenance.Content) == 0 { + return false, errors.New("missing provenance content") + } + return true, nil +} diff --git a/pkg/types/helm/v0.0.1/entry_test.go b/pkg/types/helm/v0.0.1/entry_test.go index eabde47dc..36a4cf306 100644 --- a/pkg/types/helm/v0.0.1/entry_test.go +++ b/pkg/types/helm/v0.0.1/entry_test.go @@ -182,6 +182,13 @@ func TestCrossFieldValidation(t *testing.T) { t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) } + if tc.expectUnmarshalSuccess { + ok, err := v.Insertable() + if !ok || err != nil { + t.Errorf("unexpected error calling Insertable on valid proposed entry: %v", err) + } + } + b, err := v.Canonicalize(context.TODO()) if (err == nil) != tc.expectCanonicalizeSuccess { t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err) @@ -195,9 +202,13 @@ func TestCrossFieldValidation(t *testing.T) { if err != nil { t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tc.caseDesc, err) } - if _, err := types.UnmarshalEntry(pe); err != nil { + ei, err := types.UnmarshalEntry(pe) + if err != nil { t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tc.caseDesc, err) } + if ok, err := ei.Insertable(); ok || err == nil { + t.Errorf("unexpected success calling Insertable on entry created from canonicalized content") + } } verifier, err := v.Verifier() @@ -220,3 +231,131 @@ func TestCrossFieldValidation(t *testing.T) { }) } } + +func TestInsertable(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + expectSuccess bool + } + + pubKey := strfmt.Base64([]byte("pubKey")) + + testCases := []TestCase{ + { + caseDesc: "valid entry", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{ + Content: strfmt.Base64([]byte("content")), + }, + }, + PublicKey: &models.HelmV001SchemaPublicKey{ + Content: &pubKey, + }, + }, + }, + expectSuccess: true, + }, + { + caseDesc: "missing key content", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{ + Content: strfmt.Base64([]byte("content")), + }, + }, + PublicKey: &models.HelmV001SchemaPublicKey{ + //Content: &pubKey, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing key", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{ + Content: strfmt.Base64([]byte("content")), + }, + }, + /* + PublicKey: &models.HelmV001SchemaPublicKey{ + Content: &pubKey, + }, + */ + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing provenance content", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{ + //Content: strfmt.Base64([]byte("content")), + }, + }, + PublicKey: &models.HelmV001SchemaPublicKey{ + Content: &pubKey, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing provenance obj", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + Chart: &models.HelmV001SchemaChart{ + /* + Provenance: &models.HelmV001SchemaChartProvenance{ + Content: strfmt.Base64([]byte("content")), + }, + */ + }, + PublicKey: &models.HelmV001SchemaPublicKey{ + Content: &pubKey, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing chart obj", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + /* + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{ + Content: strfmt.Base64([]byte("content")), + }, + }, + */ + PublicKey: &models.HelmV001SchemaPublicKey{ + Content: &pubKey, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "empty obj", + entry: V001Entry{}, + expectSuccess: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseDesc, func(t *testing.T) { + if ok, err := tc.entry.Insertable(); ok != tc.expectSuccess { + t.Errorf("unexpected result calling Insertable: %v", err) + } + }) + } +} diff --git a/pkg/types/intoto/v0.0.1/entry.go b/pkg/types/intoto/v0.0.1/entry.go index 1e2c0e8ce..66af807d3 100644 --- a/pkg/types/intoto/v0.0.1/entry.go +++ b/pkg/types/intoto/v0.0.1/entry.go @@ -356,3 +356,24 @@ func (v V001Entry) Verifier() (pki.PublicKey, error) { } return x509.NewPublicKey(bytes.NewReader(*v.IntotoObj.PublicKey)) } + +func (v V001Entry) Insertable() (bool, error) { + if v.IntotoObj.Content == nil { + return false, errors.New("missing content property") + } + if len(v.IntotoObj.Content.Envelope) == 0 { + return false, errors.New("missing envelope content") + } + + if v.IntotoObj.PublicKey == nil || len(*v.IntotoObj.PublicKey) == 0 { + return false, errors.New("missing publicKey content") + } + + if v.keyObj == nil { + return false, errors.New("failed to parse public key") + } + if v.env.Payload == "" || v.env.PayloadType == "" || len(v.env.Signatures) == 0 { + return false, errors.New("invalid DSSE envelope") + } + return true, nil +} diff --git a/pkg/types/intoto/v0.0.1/entry_test.go b/pkg/types/intoto/v0.0.1/entry_test.go index d63c0336b..d48b2b4c4 100644 --- a/pkg/types/intoto/v0.0.1/entry_test.go +++ b/pkg/types/intoto/v0.0.1/entry_test.go @@ -46,6 +46,7 @@ import ( slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/rekor/pkg/generated/models" + pkix509 "github.com/sigstore/rekor/pkg/pki/x509" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/sigstore/pkg/signature" dsse_signer "github.com/sigstore/sigstore/pkg/signature/dsse" @@ -277,6 +278,13 @@ func TestV001Entry_Unmarshal(t *testing.T) { if err := v.Unmarshal(it); err != nil { return err } + + if !tt.wantErr { + if ok, err := v.Insertable(); !ok || err != nil { + t.Errorf("unexpected error calling Insertable on valid proposed entry: %v", err) + } + } + if v.IntotoObj.Content.Hash == nil || v.IntotoObj.Content.Hash.Algorithm != tt.it.Content.Hash.Algorithm || v.IntotoObj.Content.Hash.Value != tt.it.Content.Hash.Value { return errors.New("missing envelope hash in validated object") } @@ -310,6 +318,11 @@ func TestV001Entry_Unmarshal(t *testing.T) { if err != nil { t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tt.name, err) } + + if ok, err := canonicalEntry.Insertable(); ok || err == nil { + t.Errorf("unexpected success calling Insertable on entry created from canonicalized content") + } + canonicalV001 := canonicalEntry.(*V001Entry) fmt.Printf("%v", canonicalV001.IntotoObj.Content) if *canonicalV001.IntotoObj.Content.Hash.Value != *tt.it.Content.Hash.Value { @@ -438,3 +451,136 @@ func TestV001Entry_IndexKeys(t *testing.T) { }) } } + +func TestInsertable(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + expectSuccess bool + } + + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + der, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + if err != nil { + t.Fatal(err) + } + pub := pem.EncodeToMemory(&pem.Block{ + Bytes: der, + Type: "PUBLIC KEY", + }) + keyObj, err := pkix509.NewPublicKey(bytes.NewReader(pub)) + if err != nil { + t.Fatal(err) + } + + envStr := envelope(t, key, "payload", "payloadType") + env := dsse.Envelope{} + + if err := json.Unmarshal([]byte(envStr), &env); err != nil { + t.Fatal(err) + } + + testCases := []TestCase{ + { + caseDesc: "valid entry", + entry: V001Entry{ + IntotoObj: models.IntotoV001Schema{ + Content: &models.IntotoV001SchemaContent{ + Envelope: "envelope", + }, + PublicKey: p(pub), + }, + keyObj: keyObj, + env: env, + }, + expectSuccess: true, + }, + { + caseDesc: "missing parsed keyObj", + entry: V001Entry{ + IntotoObj: models.IntotoV001Schema{ + Content: &models.IntotoV001SchemaContent{ + Envelope: "envelope", + }, + PublicKey: p(pub), + }, + env: env, + }, + expectSuccess: false, + }, + { + caseDesc: "missing parsed DSSE envelope", + entry: V001Entry{ + IntotoObj: models.IntotoV001Schema{ + Content: &models.IntotoV001SchemaContent{ + Envelope: "envelope", + }, + PublicKey: p(pub), + }, + keyObj: keyObj, + }, + expectSuccess: false, + }, + { + caseDesc: "missing content", + entry: V001Entry{ + IntotoObj: models.IntotoV001Schema{ + PublicKey: p(pub), + }, + keyObj: keyObj, + env: env, + }, + expectSuccess: false, + }, + { + caseDesc: "missing envelope string", + entry: V001Entry{ + IntotoObj: models.IntotoV001Schema{ + Content: &models.IntotoV001SchemaContent{}, + PublicKey: p(pub), + }, + keyObj: keyObj, + env: env, + }, + expectSuccess: false, + }, + { + caseDesc: "missing unparsed public key", + entry: V001Entry{ + IntotoObj: models.IntotoV001Schema{ + Content: &models.IntotoV001SchemaContent{ + Envelope: "envelope", + }, + }, + keyObj: keyObj, + env: env, + }, + expectSuccess: false, + }, + { + caseDesc: "empty parsed DSSE envelope", + entry: V001Entry{ + IntotoObj: models.IntotoV001Schema{ + Content: &models.IntotoV001SchemaContent{ + Envelope: "envelope", + }, + PublicKey: p(pub), + }, + keyObj: keyObj, + env: dsse.Envelope{}, + }, + expectSuccess: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseDesc, func(t *testing.T) { + if ok, err := tc.entry.Insertable(); ok != tc.expectSuccess { + t.Errorf("unexpected result calling Insertable: %v", err) + } + }) + } +} diff --git a/pkg/types/intoto/v0.0.2/entry.go b/pkg/types/intoto/v0.0.2/entry.go index 93080267f..513812dfd 100644 --- a/pkg/types/intoto/v0.0.2/entry.go +++ b/pkg/types/intoto/v0.0.2/entry.go @@ -460,3 +460,37 @@ func (v V002Entry) Verifier() (pki.PublicKey, error) { return x509.NewPublicKey(bytes.NewReader(v.IntotoObj.Content.Envelope.Signatures[0].PublicKey)) } + +func (v V002Entry) Insertable() (bool, error) { + if v.IntotoObj.Content == nil { + return false, errors.New("missing content property") + } + if v.IntotoObj.Content.Envelope == nil { + return false, errors.New("missing envelope property") + } + if len(v.IntotoObj.Content.Envelope.Payload) == 0 { + return false, errors.New("missing envelope content") + } + + if v.IntotoObj.Content.Envelope.PayloadType == nil || len(*v.IntotoObj.Content.Envelope.PayloadType) == 0 { + return false, errors.New("missing payloadType content") + } + + if len(v.IntotoObj.Content.Envelope.Signatures) == 0 { + return false, errors.New("missing signatures content") + } + for _, sig := range v.IntotoObj.Content.Envelope.Signatures { + if len(sig.Sig) == 0 { + return false, errors.New("missing signature content") + } + if len(sig.PublicKey) == 0 { + return false, errors.New("missing publicKey content") + } + } + + if v.env.Payload == "" || v.env.PayloadType == "" || len(v.env.Signatures) == 0 { + return false, errors.New("invalid DSSE envelope") + } + + return true, nil +} diff --git a/pkg/types/intoto/v0.0.2/entry_test.go b/pkg/types/intoto/v0.0.2/entry_test.go index 556339e69..9098d3a52 100644 --- a/pkg/types/intoto/v0.0.2/entry_test.go +++ b/pkg/types/intoto/v0.0.2/entry_test.go @@ -286,6 +286,13 @@ func TestV002Entry_Unmarshal(t *testing.T) { if err := v.Unmarshal(it); err != nil { return err } + + if !tt.wantErr { + if ok, err := v.Insertable(); !ok || err != nil { + t.Errorf("unexpected error calling insertable on valid proposed entry: %v", err) + } + } + want := []string{} for _, sig := range v.IntotoObj.Content.Envelope.Signatures { keyHash := sha256.Sum256(sig.PublicKey) @@ -325,6 +332,9 @@ func TestV002Entry_Unmarshal(t *testing.T) { if err != nil { t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tt.name, err) } + if ok, err := canonicalEntry.Insertable(); ok || err == nil { + t.Errorf("unexpected success calling Insertable on entry created from canonicalized content") + } canonicalV002 := canonicalEntry.(*V002Entry) fmt.Printf("%v", canonicalV002.IntotoObj.Content) if *canonicalV002.IntotoObj.Content.Hash.Value != *tt.it.Content.Hash.Value { @@ -489,3 +499,225 @@ func TestV002Entry_IndexKeys(t *testing.T) { }) } } + +func TestInsertable(t *testing.T) { + type TestCase struct { + caseDesc string + entry V002Entry + expectSuccess bool + } + + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + + env := envelope(t, key, []byte("payload")) + + testCases := []TestCase{ + { + caseDesc: "valid entry", + entry: V002Entry{ + IntotoObj: models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: &models.IntotoV002SchemaContentEnvelope{ + Payload: strfmt.Base64("payload"), + PayloadType: swag.String("payloadType"), + Signatures: []*models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ + { + PublicKey: strfmt.Base64([]byte("key")), + Sig: strfmt.Base64([]byte("sig")), + }, + }, + }, + }, + }, + env: *env, + }, + expectSuccess: true, + }, + { + caseDesc: "valid entry but hasn't been parsed", + entry: V002Entry{ + IntotoObj: models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: &models.IntotoV002SchemaContentEnvelope{ + Payload: strfmt.Base64("payload"), + PayloadType: swag.String("payloadType"), + Signatures: []*models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ + { + PublicKey: strfmt.Base64([]byte("key")), + Sig: strfmt.Base64([]byte("sig")), + }, + }, + }, + }, + }, + env: dsse.Envelope{}, + }, + expectSuccess: false, + }, + { + caseDesc: "missing sig", + entry: V002Entry{ + IntotoObj: models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: &models.IntotoV002SchemaContentEnvelope{ + Payload: strfmt.Base64("payload"), + PayloadType: swag.String("payloadType"), + Signatures: []*models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ + { + PublicKey: strfmt.Base64([]byte("key")), + //Sig: strfmt.Base64([]byte("sig")), + }, + }, + }, + }, + }, + env: *env, + }, + expectSuccess: false, + }, + { + caseDesc: "missing key", + entry: V002Entry{ + IntotoObj: models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: &models.IntotoV002SchemaContentEnvelope{ + Payload: strfmt.Base64("payload"), + PayloadType: swag.String("payloadType"), + Signatures: []*models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ + { + //PublicKey: strfmt.Base64([]byte("key")), + Sig: strfmt.Base64([]byte("sig")), + }, + }, + }, + }, + }, + env: *env, + }, + expectSuccess: false, + }, + { + caseDesc: "empty signatures", + entry: V002Entry{ + IntotoObj: models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: &models.IntotoV002SchemaContentEnvelope{ + Payload: strfmt.Base64("payload"), + PayloadType: swag.String("payloadType"), + Signatures: []*models.IntotoV002SchemaContentEnvelopeSignaturesItems0{}, + /* + Signatures: []*models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ + { + PublicKey: strfmt.Base64([]byte("key")), + Sig: strfmt.Base64([]byte("sig")), + }, + }, + */ + }, + }, + }, + env: *env, + }, + expectSuccess: false, + }, + { + caseDesc: "missing payloadType", + entry: V002Entry{ + IntotoObj: models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: &models.IntotoV002SchemaContentEnvelope{ + Payload: strfmt.Base64("payload"), + //PayloadType: swag.String("payloadType"), + Signatures: []*models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ + { + PublicKey: strfmt.Base64([]byte("key")), + Sig: strfmt.Base64([]byte("sig")), + }, + }, + }, + }, + }, + env: *env, + }, + expectSuccess: false, + }, + { + caseDesc: "missing payload", + entry: V002Entry{ + IntotoObj: models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: &models.IntotoV002SchemaContentEnvelope{ + //Payload: strfmt.Base64("payload"), + PayloadType: swag.String("payloadType"), + Signatures: []*models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ + { + PublicKey: strfmt.Base64([]byte("key")), + Sig: strfmt.Base64([]byte("sig")), + }, + }, + }, + }, + }, + env: *env, + }, + expectSuccess: false, + }, + { + caseDesc: "missing envelope", + entry: V002Entry{ + IntotoObj: models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + /* + Envelope: &models.IntotoV002SchemaContentEnvelope{ + Payload: strfmt.Base64("payload"), + PayloadType: swag.String("payloadType"), + Signatures: []*models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ + { + PublicKey: strfmt.Base64([]byte("key")), + Sig: strfmt.Base64([]byte("sig")), + }, + }, + }, + */ + }, + }, + env: *env, + }, + expectSuccess: false, + }, + { + caseDesc: "missing content", + entry: V002Entry{ + IntotoObj: models.IntotoV002Schema{ + /* + Content: &models.IntotoV002SchemaContent{ + Envelope: &models.IntotoV002SchemaContentEnvelope{ + Payload: strfmt.Base64("payload"), + PayloadType: swag.String("payloadType"), + Signatures: []*models.IntotoV002SchemaContentEnvelopeSignaturesItems0{ + { + PublicKey: strfmt.Base64([]byte("key")), + Sig: strfmt.Base64([]byte("sig")), + }, + }, + }, + }, + */ + }, + env: *env, + }, + expectSuccess: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseDesc, func(t *testing.T) { + if ok, err := tc.entry.Insertable(); ok != tc.expectSuccess { + t.Errorf("unexpected result calling Insertable: %v", err) + } + }) + } +} diff --git a/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json index f614ccf28..9153b0a27 100644 --- a/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json +++ b/pkg/types/intoto/v0.0.2/intoto_v0_0_2_schema.json @@ -42,8 +42,7 @@ "publicKey": { "description": "public key that corresponds to this signature", "type": "string", - "format": "byte", - "readOnly": true + "format": "byte" } } } diff --git a/pkg/types/jar/v0.0.1/entry.go b/pkg/types/jar/v0.0.1/entry.go index f93a810a0..60faa25dd 100644 --- a/pkg/types/jar/v0.0.1/entry.go +++ b/pkg/types/jar/v0.0.1/entry.go @@ -344,3 +344,14 @@ func (v V001Entry) Verifier() (pki.PublicKey, error) { } return x509.NewPublicKey(bytes.NewReader(*v.JARModel.Signature.PublicKey.Content)) } + +func (v V001Entry) Insertable() (bool, error) { + if v.JARModel.Archive == nil { + return false, errors.New("missing archive property") + } + if len(v.JARModel.Archive.Content) == 0 { + return false, errors.New("missing archive content") + } + + return true, nil +} diff --git a/pkg/types/jar/v0.0.1/entry_test.go b/pkg/types/jar/v0.0.1/entry_test.go index a4b093372..bb5b54e5c 100644 --- a/pkg/types/jar/v0.0.1/entry_test.go +++ b/pkg/types/jar/v0.0.1/entry_test.go @@ -116,6 +116,10 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== continue } + if ok, err := v.Insertable(); !ok || err != nil { + t.Errorf("unexpected error calling Insertable on valid proposed entry: %v", err) + } + b, err := v.Canonicalize(context.TODO()) if (err == nil) != tc.expectCanonicalizeSuccess { t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err) @@ -129,9 +133,13 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== if err != nil { t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tc.caseDesc, err) } - if _, err := types.UnmarshalEntry(pe); err != nil { + ei, err := types.UnmarshalEntry(pe) + if err != nil { t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tc.caseDesc, err) } + if ok, err := ei.Insertable(); ok || err == nil { + t.Errorf("unexpected err from calling Insertable on entry created from canonicalized content") + } } verifier, err := v.Verifier() @@ -184,3 +192,56 @@ func TestJarMetadataSize(t *testing.T) { t.Fatalf("unexpected error %v", err) } } + +func TestInsertable(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + expectSuccess bool + } + testCases := []TestCase{ + { + caseDesc: "valid entry", + entry: V001Entry{ + JARModel: models.JarV001Schema{ + Archive: &models.JarV001SchemaArchive{ + Content: strfmt.Base64([]byte("content")), + }, + }, + }, + expectSuccess: true, + }, + { + caseDesc: "missing archive content", + entry: V001Entry{ + JARModel: models.JarV001Schema{ + Archive: &models.JarV001SchemaArchive{ + //Content: strfmt.Base64([]byte("content")), + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing archive obj", + entry: V001Entry{ + JARModel: models.JarV001Schema{ + /* + Archive: &models.JarV001SchemaArchive{ + Content: strfmt.Base64([]byte("content")), + }, + */ + }, + }, + expectSuccess: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseDesc, func(t *testing.T) { + if ok, err := tc.entry.Insertable(); ok != tc.expectSuccess { + t.Errorf("unexpected result calling Insertable: %v", err) + } + }) + } +} diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index b2853c89f..dd124e1ad 100644 --- a/pkg/types/rekord/v0.0.1/entry.go +++ b/pkg/types/rekord/v0.0.1/entry.go @@ -443,3 +443,30 @@ func (v V001Entry) Verifier() (pki.PublicKey, error) { return nil, fmt.Errorf("unexpected format of public key: %s", f) } } + +func (v V001Entry) Insertable() (bool, error) { + if v.RekordObj.Signature == nil { + return false, errors.New("missing signature property") + } + if v.RekordObj.Signature.Content == nil || len(*v.RekordObj.Signature.Content) == 0 { + return false, errors.New("missing signature content") + } + if v.RekordObj.Signature.PublicKey == nil { + return false, errors.New("missing publicKey property") + } + if v.RekordObj.Signature.PublicKey.Content == nil || len(*v.RekordObj.Signature.PublicKey.Content) == 0 { + return false, errors.New("missing publicKey content") + } + if v.RekordObj.Signature.Format == nil || len(*v.RekordObj.Signature.Format) == 0 { + return false, errors.New("missing signature format") + } + + if v.RekordObj.Data == nil { + return false, errors.New("missing data property") + } + if len(v.RekordObj.Data.Content) == 0 { + return false, errors.New("missing data content") + } + + return true, nil +} diff --git a/pkg/types/rekord/v0.0.1/entry_test.go b/pkg/types/rekord/v0.0.1/entry_test.go index 55e1f9bc9..ecca28942 100644 --- a/pkg/types/rekord/v0.0.1/entry_test.go +++ b/pkg/types/rekord/v0.0.1/entry_test.go @@ -232,6 +232,10 @@ func TestCrossFieldValidation(t *testing.T) { continue } + if ok, err := v.Insertable(); !ok || err != nil { + t.Errorf("unexpected error calling Insertable on valid proposed entry: %v", err) + } + b, err := v.Canonicalize(context.TODO()) if (err == nil) != tc.expectCanonicalizeSuccess { t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err) @@ -245,9 +249,13 @@ func TestCrossFieldValidation(t *testing.T) { if err != nil { t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tc.caseDesc, err) } - if _, err := types.UnmarshalEntry(pe); err != nil { + ei, err := types.UnmarshalEntry(pe) + if err != nil { t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tc.caseDesc, err) } + if ok, err := ei.Insertable(); ok || err == nil { + t.Errorf("unexpected success calling Insertable on entry created from canonicalized content") + } } verifier, err := v.Verifier() @@ -288,3 +296,194 @@ func TestUnspecifiedPKIFormat(t *testing.T) { t.Errorf("invalid pki format should not create a valid entry") } } + +func TestInsertable(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + expectSuccess bool + } + + sig := strfmt.Base64([]byte("sig")) + pub := strfmt.Base64([]byte("pub")) + testCases := []TestCase{ + { + caseDesc: "valid entry", + entry: V001Entry{ + RekordObj: models.RekordV001Schema{ + Data: &models.RekordV001SchemaData{ + Content: strfmt.Base64([]byte("content")), + }, + Signature: &models.RekordV001SchemaSignature{ + Content: &sig, + Format: swag.String("format"), + PublicKey: &models.RekordV001SchemaSignaturePublicKey{ + Content: &pub, + }, + }, + }, + }, + expectSuccess: true, + }, + { + caseDesc: "missing public key content", + entry: V001Entry{ + RekordObj: models.RekordV001Schema{ + Data: &models.RekordV001SchemaData{ + Content: strfmt.Base64([]byte("content")), + }, + Signature: &models.RekordV001SchemaSignature{ + Content: &sig, + Format: swag.String("format"), + PublicKey: &models.RekordV001SchemaSignaturePublicKey{ + //Content: &pub, + }, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing public key obj", + entry: V001Entry{ + RekordObj: models.RekordV001Schema{ + Data: &models.RekordV001SchemaData{ + Content: strfmt.Base64([]byte("content")), + }, + Signature: &models.RekordV001SchemaSignature{ + Content: &sig, + Format: swag.String("format"), + /* + PublicKey: &models.RekordV001SchemaSignaturePublicKey{ + Content: &pub, + }, + */ + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing format string", + entry: V001Entry{ + RekordObj: models.RekordV001Schema{ + Data: &models.RekordV001SchemaData{ + Content: strfmt.Base64([]byte("content")), + }, + Signature: &models.RekordV001SchemaSignature{ + Content: &sig, + //Format: swag.String("format"), + PublicKey: &models.RekordV001SchemaSignaturePublicKey{ + Content: &pub, + }, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing signature content", + entry: V001Entry{ + RekordObj: models.RekordV001Schema{ + Data: &models.RekordV001SchemaData{ + Content: strfmt.Base64([]byte("content")), + }, + Signature: &models.RekordV001SchemaSignature{ + //Content: &sig, + Format: swag.String("format"), + PublicKey: &models.RekordV001SchemaSignaturePublicKey{ + Content: &pub, + }, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing signature obj", + entry: V001Entry{ + RekordObj: models.RekordV001Schema{ + Data: &models.RekordV001SchemaData{ + Content: strfmt.Base64([]byte("content")), + }, + /* + Signature: &models.RekordV001SchemaSignature{ + Content: &sig, + Format: swag.String("format"), + PublicKey: &models.RekordV001SchemaSignaturePublicKey{ + Content: &pub, + }, + }, + */ + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing data content", + entry: V001Entry{ + RekordObj: models.RekordV001Schema{ + Data: &models.RekordV001SchemaData{ + //Content: strfmt.Base64([]byte("content")), + }, + Signature: &models.RekordV001SchemaSignature{ + Content: &sig, + Format: swag.String("format"), + PublicKey: &models.RekordV001SchemaSignaturePublicKey{ + Content: &pub, + }, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing data obj", + entry: V001Entry{ + RekordObj: models.RekordV001Schema{ + /* + Data: &models.RekordV001SchemaData{ + Content: strfmt.Base64([]byte("content")), + }, + */ + Signature: &models.RekordV001SchemaSignature{ + Content: &sig, + Format: swag.String("format"), + PublicKey: &models.RekordV001SchemaSignaturePublicKey{ + Content: &pub, + }, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "empty obj", + entry: V001Entry{ + RekordObj: models.RekordV001Schema{ + /* + Data: &models.RekordV001SchemaData{ + Content: strfmt.Base64([]byte("content")), + }, + Signature: &models.RekordV001SchemaSignature{ + Content: &sig, + Format: swag.String("format"), + PublicKey: &models.RekordV001SchemaSignaturePublicKey{ + Content: &pub, + }, + }, + */ + }, + }, + expectSuccess: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseDesc, func(t *testing.T) { + if ok, err := tc.entry.Insertable(); ok != tc.expectSuccess { + t.Errorf("unexpected result calling Insertable: %v", err) + } + }) + } +} diff --git a/pkg/types/rfc3161/v0.0.1/entry.go b/pkg/types/rfc3161/v0.0.1/entry.go index 6033d14c3..35a21e05f 100644 --- a/pkg/types/rfc3161/v0.0.1/entry.go +++ b/pkg/types/rfc3161/v0.0.1/entry.go @@ -210,3 +210,19 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A func (v V001Entry) Verifier() (pki.PublicKey, error) { return nil, errors.New("Verifier() does not support rfc3161 entry type") } + +func (v V001Entry) Insertable() (bool, error) { + if v.Rfc3161Obj.Tsr == nil { + return false, errors.New("missing tsr property") + } + + if v.Rfc3161Obj.Tsr.Content == nil || len(*v.Rfc3161Obj.Tsr.Content) == 0 { + return false, errors.New("missing tsr content") + } + + if v.tsrContent == nil || len(*v.tsrContent) == 0 { + return false, errors.New("timestamp response has not been parsed") + } + + return true, nil +} diff --git a/pkg/types/rfc3161/v0.0.1/entry_test.go b/pkg/types/rfc3161/v0.0.1/entry_test.go index 63b7a9bd1..6c5457455 100644 --- a/pkg/types/rfc3161/v0.0.1/entry_test.go +++ b/pkg/types/rfc3161/v0.0.1/entry_test.go @@ -170,6 +170,12 @@ func TestCrossFieldValidation(t *testing.T) { } } + if tc.expectUnmarshalSuccess { + if ok, err := v.Insertable(); !ok || err != nil { + t.Errorf("unexpected error calling Insertable on valid proposed entry: %v", err) + } + } + b, err := v.Canonicalize(context.TODO()) if (err == nil) != tc.expectCanonicalizeSuccess { t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err) @@ -183,9 +189,14 @@ func TestCrossFieldValidation(t *testing.T) { if err != nil { t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tc.caseDesc, err) } - if _, err := types.UnmarshalEntry(pe); err != nil { + ei, err := types.UnmarshalEntry(pe) + if err != nil { t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tc.caseDesc, err) } + // rfc3161 is one of two types that Insertable() should return true from canonicalized entries + if ok, err := ei.Insertable(); !ok || err != nil { + t.Errorf("unexpected error calling insertable on entry created from canonicalized content") + } } } } @@ -244,3 +255,93 @@ func p(b []byte) *strfmt.Base64 { b64 := strfmt.Base64(b) return &b64 } + +func TestInsertable(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + expectSuccess bool + } + + tsr := strfmt.Base64([]byte("tsr")) + + testCases := []TestCase{ + { + caseDesc: "valid entry", + entry: V001Entry{ + Rfc3161Obj: models.Rfc3161V001Schema{ + Tsr: &models.Rfc3161V001SchemaTsr{ + Content: &tsr, + }, + }, + tsrContent: &tsr, + }, + expectSuccess: true, + }, + { + caseDesc: "unparsed tsr", + entry: V001Entry{ + Rfc3161Obj: models.Rfc3161V001Schema{ + Tsr: &models.Rfc3161V001SchemaTsr{ + Content: &tsr, + }, + }, + //tsrContent: &tsr, + }, + expectSuccess: false, + }, + { + caseDesc: "missing tsr content", + entry: V001Entry{ + Rfc3161Obj: models.Rfc3161V001Schema{ + Tsr: &models.Rfc3161V001SchemaTsr{ + //Content: &tsr, + }, + }, + tsrContent: &tsr, + }, + expectSuccess: false, + }, + { + caseDesc: "missing tsr obj", + entry: V001Entry{ + Rfc3161Obj: models.Rfc3161V001Schema{ + /* + Tsr: &models.Rfc3161V001SchemaTsr{ + Content: &tsr, + }, + */ + }, + tsrContent: &tsr, + }, + expectSuccess: false, + }, + { + caseDesc: "missing Rfc3161 obj", + entry: V001Entry{ + /* + Rfc3161Obj: models.Rfc3161V001Schema{ + Tsr: &models.Rfc3161V001SchemaTsr{ + Content: &tsr, + }, + }, + */ + tsrContent: &tsr, + }, + expectSuccess: false, + }, + { + caseDesc: "empty obj", + entry: V001Entry{}, + expectSuccess: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseDesc, func(t *testing.T) { + if ok, err := tc.entry.Insertable(); ok != tc.expectSuccess { + t.Errorf("unexpected result calling Insertable: %v", err) + } + }) + } +} diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index 79e5e3a2d..a10b60c81 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -377,3 +377,20 @@ func (v V001Entry) Verifier() (pki.PublicKey, error) { } return pgp.NewPublicKey(bytes.NewReader(*v.RPMModel.PublicKey.Content)) } + +func (v V001Entry) Insertable() (bool, error) { + if v.RPMModel.PublicKey == nil { + return false, errors.New("missing publicKey property") + } + if v.RPMModel.PublicKey.Content == nil || len(*v.RPMModel.PublicKey.Content) == 0 { + return false, errors.New("missing publicKey content") + } + + if v.RPMModel.Package == nil { + return false, errors.New("missing package property") + } + if len(v.RPMModel.Package.Content) == 0 { + return false, errors.New("missing package content") + } + return true, nil +} diff --git a/pkg/types/rpm/v0.0.1/entry_test.go b/pkg/types/rpm/v0.0.1/entry_test.go index a994f1854..941f852b2 100644 --- a/pkg/types/rpm/v0.0.1/entry_test.go +++ b/pkg/types/rpm/v0.0.1/entry_test.go @@ -167,6 +167,12 @@ func TestCrossFieldValidation(t *testing.T) { t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) } + if tc.expectUnmarshalSuccess { + if ok, err := v.Insertable(); !ok || err != nil { + t.Errorf("unexpected error calling Insertable on valid proposed entry: %v", err) + } + } + b, err := v.Canonicalize(context.TODO()) if (err == nil) != tc.expectCanonicalizeSuccess { t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err) @@ -181,9 +187,13 @@ func TestCrossFieldValidation(t *testing.T) { if err != nil { t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tc.caseDesc, err) } - if _, err := types.UnmarshalEntry(pe); err != nil { + ei, err := types.UnmarshalEntry(pe) + if err != nil { t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tc.caseDesc, err) } + if ok, err := ei.Insertable(); ok || err == nil { + t.Errorf("unexpected success calling Insertable on entry created from canonicalized content") + } } verifier, err := v.Verifier() @@ -205,3 +215,103 @@ func TestCrossFieldValidation(t *testing.T) { } } } + +func TestInsertable(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + expectSuccess bool + } + + pub := strfmt.Base64([]byte("pub")) + + testCases := []TestCase{ + { + caseDesc: "valid entry", + entry: V001Entry{ + RPMModel: models.RpmV001Schema{ + Package: &models.RpmV001SchemaPackage{ + Content: strfmt.Base64([]byte("content")), + }, + PublicKey: &models.RpmV001SchemaPublicKey{ + Content: &pub, + }, + }, + }, + expectSuccess: true, + }, + { + caseDesc: "missing public key content", + entry: V001Entry{ + RPMModel: models.RpmV001Schema{ + Package: &models.RpmV001SchemaPackage{ + Content: strfmt.Base64([]byte("content")), + }, + PublicKey: &models.RpmV001SchemaPublicKey{ + //Content: &pub, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing public key obj", + entry: V001Entry{ + RPMModel: models.RpmV001Schema{ + Package: &models.RpmV001SchemaPackage{ + Content: strfmt.Base64([]byte("content")), + }, + /* + PublicKey: &models.RpmV001SchemaPublicKey{ + Content: &pub, + }, + */ + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing package content", + entry: V001Entry{ + RPMModel: models.RpmV001Schema{ + Package: &models.RpmV001SchemaPackage{ + //Content: strfmt.Base64([]byte("content")), + }, + PublicKey: &models.RpmV001SchemaPublicKey{ + Content: &pub, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing package obj", + entry: V001Entry{ + RPMModel: models.RpmV001Schema{ + /* + Package: &models.RpmV001SchemaPackage{ + Content: strfmt.Base64([]byte("content")), + }, + */ + PublicKey: &models.RpmV001SchemaPublicKey{ + Content: &pub, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "empty obj", + entry: V001Entry{}, + expectSuccess: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseDesc, func(t *testing.T) { + if ok, err := tc.entry.Insertable(); ok != tc.expectSuccess { + t.Errorf("unexpected result calling Insertable: %v", err) + } + }) + } +} diff --git a/pkg/types/test_util.go b/pkg/types/test_util.go index ee8d57022..03b5b9c0b 100644 --- a/pkg/types/test_util.go +++ b/pkg/types/test_util.go @@ -67,6 +67,10 @@ func (u BaseUnmarshalTester) CreateFromArtifactProperties(_ context.Context, _ A return nil, nil } +func (u BaseUnmarshalTester) Insertable() (bool, error) { + return false, nil +} + type BaseProposedEntryTester struct{} func (b BaseProposedEntryTester) Kind() string { diff --git a/pkg/types/tuf/v0.0.1/entry.go b/pkg/types/tuf/v0.0.1/entry.go index 7e4e5f98b..ec471c94b 100644 --- a/pkg/types/tuf/v0.0.1/entry.go +++ b/pkg/types/tuf/v0.0.1/entry.go @@ -379,3 +379,20 @@ func (v V001Entry) Verifier() (pki.PublicKey, error) { } return ptuf.NewPublicKey(bytes.NewReader(keyBytes)) } + +func (v V001Entry) Insertable() (bool, error) { + if v.TufObj.Metadata == nil { + return false, errors.New("missing metadata property") + } + if v.TufObj.Metadata.Content == nil { + return false, errors.New("missing metadata content") + } + + if v.TufObj.Root == nil { + return false, errors.New("missing root property") + } + if v.TufObj.Root.Content == nil { + return false, errors.New("missing root content") + } + return true, nil +} diff --git a/pkg/types/tuf/v0.0.1/entry_test.go b/pkg/types/tuf/v0.0.1/entry_test.go index 13ab2fe1e..4193c4e52 100644 --- a/pkg/types/tuf/v0.0.1/entry_test.go +++ b/pkg/types/tuf/v0.0.1/entry_test.go @@ -177,68 +177,175 @@ func TestCrossFieldValidation(t *testing.T) { } for _, tc := range testCases { - if err := tc.entry.Validate(); (err == nil) != tc.expectUnmarshalSuccess { - t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) - } - // No need to continue here if we failed at unmarshal - if !tc.expectUnmarshalSuccess { - continue - } - - v := &V001Entry{} - r := models.TUF{ - APIVersion: swag.String(tc.entry.APIVersion()), - Spec: tc.entry.TufObj, - } - - unmarshalAndValidate := func() error { - if err := v.Unmarshal(&r); err != nil { - return err + t.Run(tc.caseDesc, func(t *testing.T) { + if err := tc.entry.Validate(); (err == nil) != tc.expectUnmarshalSuccess { + t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) } - return v.Validate() - } - if err := unmarshalAndValidate(); (err == nil) != tc.expectUnmarshalSuccess { - t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) - } - - b, err := v.Canonicalize(context.TODO()) - if (err == nil) != tc.expectCanonicalizeSuccess { - t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err) - } else if err != nil { - if _, ok := err.(types.ValidationError); !ok { - t.Errorf("canonicalize returned an unexpected error that isn't of type types.ValidationError: %v", err) + // No need to continue here if we failed at unmarshal + if !tc.expectUnmarshalSuccess { + return } - } - if b != nil { - pe, err := models.UnmarshalProposedEntry(bytes.NewReader(b), runtime.JSONConsumer()) - if err != nil { - t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tc.caseDesc, err) + v := &V001Entry{} + r := models.TUF{ + APIVersion: swag.String(tc.entry.APIVersion()), + Spec: tc.entry.TufObj, } - if _, err := types.UnmarshalEntry(pe); err != nil { - t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tc.caseDesc, err) + + unmarshalAndValidate := func() error { + if err := v.Unmarshal(&r); err != nil { + return err + } + return v.Validate() + } + if err := unmarshalAndValidate(); (err == nil) != tc.expectUnmarshalSuccess { + t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) } - } - verifier, err := v.Verifier() - if tc.expectVerifierSuccess { - if err != nil { - t.Errorf("%v: unexpected error, got %v", tc.caseDesc, err) - } else { - pub, _ := verifier.CanonicalValue() - rootBytes := new(bytes.Buffer) - if err := json.Compact(rootBytes, keyBytes); err != nil { - t.Fatal(err) + if tc.expectUnmarshalSuccess { + if ok, err := v.Insertable(); !ok || err != nil { + t.Errorf("unexpected error calling Insertable on valid proposed entry: %v", err) + } + } + + b, err := v.Canonicalize(context.TODO()) + if (err == nil) != tc.expectCanonicalizeSuccess { + t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err) + } else if err != nil { + if _, ok := err.(types.ValidationError); !ok { + t.Errorf("canonicalize returned an unexpected error that isn't of type types.ValidationError: %v", err) + } + } + + if b != nil { + pe, err := models.UnmarshalProposedEntry(bytes.NewReader(b), runtime.JSONConsumer()) + if err != nil { + t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tc.caseDesc, err) } - if !reflect.DeepEqual(pub, rootBytes.Bytes()) { - t.Errorf("%v: verifier and public keys do not match: %v, %v", tc.caseDesc, string(pub), rootBytes) + if _, err := types.UnmarshalEntry(pe); err != nil { + t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tc.caseDesc, err) } + // Insertable on canonicalized content is variable so we skip testing it here } - } else { - if err == nil { - s, _ := verifier.CanonicalValue() - t.Errorf("%v: expected error for %v, got %v", tc.caseDesc, string(s), err) + + verifier, err := v.Verifier() + if tc.expectVerifierSuccess { + if err != nil { + t.Errorf("%v: unexpected error, got %v", tc.caseDesc, err) + } else { + pub, _ := verifier.CanonicalValue() + rootBytes := new(bytes.Buffer) + if err := json.Compact(rootBytes, keyBytes); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(pub, rootBytes.Bytes()) { + t.Errorf("%v: verifier and public keys do not match: %v, %v", tc.caseDesc, string(pub), rootBytes) + } + } + } else { + if err == nil { + s, _ := verifier.CanonicalValue() + t.Errorf("%v: expected error for %v, got %v", tc.caseDesc, string(s), err) + } + } + }) + } +} + +func TestInsertable(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + expectSuccess bool + } + + testCases := []TestCase{ + { + caseDesc: "valid entry", + entry: V001Entry{ + TufObj: models.TUFV001Schema{ + Metadata: &models.TUFV001SchemaMetadata{ + Content: struct{}{}, + }, + Root: &models.TUFV001SchemaRoot{ + Content: struct{}{}, + }, + }, + }, + expectSuccess: true, + }, + { + caseDesc: "missing root content", + entry: V001Entry{ + TufObj: models.TUFV001Schema{ + Metadata: &models.TUFV001SchemaMetadata{ + Content: struct{}{}, + }, + Root: &models.TUFV001SchemaRoot{ + //Content: struct{}{}, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing root obj", + entry: V001Entry{ + TufObj: models.TUFV001Schema{ + Metadata: &models.TUFV001SchemaMetadata{ + Content: struct{}{}, + }, + /* + Root: &models.TUFV001SchemaRoot{ + Content: struct{}{}, + }, + */ + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing metadata content", + entry: V001Entry{ + TufObj: models.TUFV001Schema{ + Metadata: &models.TUFV001SchemaMetadata{ + //Content: struct{}{}, + }, + Root: &models.TUFV001SchemaRoot{ + Content: struct{}{}, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "missing metadata content", + entry: V001Entry{ + TufObj: models.TUFV001Schema{ + /* + Metadata: &models.TUFV001SchemaMetadata{ + Content: struct{}{}, + }, + */ + Root: &models.TUFV001SchemaRoot{ + Content: struct{}{}, + }, + }, + }, + expectSuccess: false, + }, + { + caseDesc: "empty obj", + entry: V001Entry{}, + expectSuccess: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseDesc, func(t *testing.T) { + if ok, err := tc.entry.Insertable(); ok != tc.expectSuccess { + t.Errorf("unexpected result calling Insertable: %v", err) } - } + }) } } diff --git a/pkg/types/tuf/v0.0.1/tuf_v0_0_1_schema.json b/pkg/types/tuf/v0.0.1/tuf_v0_0_1_schema.json index f1a6cd3aa..5d2dc210d 100644 --- a/pkg/types/tuf/v0.0.1/tuf_v0_0_1_schema.json +++ b/pkg/types/tuf/v0.0.1/tuf_v0_0_1_schema.json @@ -20,7 +20,7 @@ "additionalProperties": true } }, - "required": [ "metadata" ] + "required": [ "content" ] }, "root" : { "description": "root metadata containing about the public keys used to sign the manifest",