From c4bc858d94753bac391cb9ae249c17c3add85f6b Mon Sep 17 00:00:00 2001 From: pxp928 Date: Wed, 17 Aug 2022 14:09:40 -0400 Subject: [PATCH] implemented fixes and added new cli flag Signed-off-by: pxp928 --- cmd/rekor-cli/app/pflag_groups.go | 20 ++++++ cmd/rekor-cli/app/pflags.go | 63 +++++++++++++++---- cmd/rekor-cli/app/pflags_test.go | 14 +++++ pkg/generated/models/intoto_v002_schema.go | 18 +++++- pkg/generated/restapi/embedded_spec.go | 9 ++- pkg/types/intoto/v0.0.2/entry.go | 48 ++++++++------ pkg/types/intoto/v0.0.2/entry_test.go | 10 ++- .../intoto/v0.0.2/intoto_v0_0_2_schema.json | 4 +- tests/e2e_test.go | 2 +- tests/intoto_multi_dsse.json | 6 ++ 10 files changed, 149 insertions(+), 45 deletions(-) create mode 100644 tests/intoto_multi_dsse.json diff --git a/cmd/rekor-cli/app/pflag_groups.go b/cmd/rekor-cli/app/pflag_groups.go index aeabc4aec..46728a1f6 100644 --- a/cmd/rekor-cli/app/pflag_groups.go +++ b/cmd/rekor-cli/app/pflag_groups.go @@ -73,6 +73,11 @@ func addArtifactPFlags(cmd *cobra.Command) error { "path or URL to public key file", false, }, + "multi-public-key": { + multiFileOrURLFlag, + "path or URL to public key files", + false, + }, "artifact": { fileOrURLFlag, "path or URL to artifact file", @@ -157,6 +162,21 @@ func CreatePropsFromPflags() *types.ArtifactProperties { } } + multiPublicKeyString := viper.GetString("multi-public-key") + splitPubKeyString := strings.Split(multiPublicKeyString, ",") + if len(splitPubKeyString) > 0 { + collectedKeys := []*url.URL{} + for _, key := range splitPubKeyString { + if isURL(key) { + keyPath, _ := url.Parse(key) + collectedKeys = append(collectedKeys, keyPath) + } else { + collectedKeys = append(collectedKeys, &url.URL{Path: key}) + } + } + props.MultiPublicKeyPaths = collectedKeys + } + props.PKIFormat = viper.GetString("pki-format") b64aad := viper.GetString("aad") if b64aad != "" { diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index 5a83883c3..697a53982 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -35,19 +35,20 @@ import ( type FlagType string const ( - uuidFlag FlagType = "uuid" - shaFlag FlagType = "sha" - emailFlag FlagType = "email" - logIndexFlag FlagType = "logIndex" - pkiFormatFlag FlagType = "pkiFormat" - typeFlag FlagType = "type" - fileFlag FlagType = "file" - urlFlag FlagType = "url" - fileOrURLFlag FlagType = "fileOrURL" - oidFlag FlagType = "oid" - formatFlag FlagType = "format" - timeoutFlag FlagType = "timeout" - base64Flag FlagType = "base64" + uuidFlag FlagType = "uuid" + shaFlag FlagType = "sha" + emailFlag FlagType = "email" + logIndexFlag FlagType = "logIndex" + pkiFormatFlag FlagType = "pkiFormat" + typeFlag FlagType = "type" + fileFlag FlagType = "file" + urlFlag FlagType = "url" + fileOrURLFlag FlagType = "fileOrURL" + multiFileOrURLFlag FlagType = "multiFileOrURL" + oidFlag FlagType = "oid" + formatFlag FlagType = "format" + timeoutFlag FlagType = "timeout" + base64Flag FlagType = "base64" ) type newPFlagValueFunc func() pflag.Value @@ -95,6 +96,10 @@ func initializePFlagMap() { // applies logic of fileFlag OR urlFlag validators from above return valueFactory(fileOrURLFlag, validateFileOrURL, "") }, + multiFileOrURLFlag: func() pflag.Value { + // applies logic of fileFlag OR urlFlag validators from above for multi file and URL + return multiValueFactory(multiFileOrURLFlag, validateFileOrURL, []string{}) + }, oidFlag: func() pflag.Value { // this validates for an OID, which is a sequence of positive integers separated by periods return valueFactory(oidFlag, validateOID, "") @@ -137,6 +142,38 @@ func valueFactory(flagType FlagType, v validationFunc, defaultVal string) pflag. } } +func multiValueFactory(flagType FlagType, v validationFunc, defaultVal []string) pflag.Value { + return &multiBaseValue{ + flagType: flagType, + validationFunc: v, + value: defaultVal, + } +} + +// multiBaseValue implements pflag.Value +type multiBaseValue struct { + flagType FlagType + value []string + validationFunc validationFunc +} + +func (b *multiBaseValue) String() string { + return strings.Join(b.value, ",") +} + +// Type returns the type of this Value +func (b multiBaseValue) Type() string { + return string(b.flagType) +} + +func (b *multiBaseValue) Set(value string) error { + if err := b.validationFunc(value); err != nil { + return err + } + b.value = append(b.value, value) + return nil +} + // baseValue implements pflag.Value type baseValue struct { flagType FlagType diff --git a/cmd/rekor-cli/app/pflags_test.go b/cmd/rekor-cli/app/pflags_test.go index 6b1faa07b..0708f8859 100644 --- a/cmd/rekor-cli/app/pflags_test.go +++ b/cmd/rekor-cli/app/pflags_test.go @@ -37,6 +37,7 @@ func TestArtifactPFlags(t *testing.T) { artifact string signature string publicKey string + multiPublicKey []string uuid string aad string uuidRequired bool @@ -373,6 +374,14 @@ func TestArtifactPFlags(t *testing.T) { expectParseSuccess: true, expectValidateSuccess: false, }, + { + caseDesc: "valid intoto - multi keys", + typeStr: "intoto", + artifact: "../../../tests/intoto_multi_dsse.json", + multiPublicKey: []string{"../../../tests/intoto_dsse.pem", "../../../tests/intoto_dsse.pem"}, + expectParseSuccess: true, + expectValidateSuccess: true, + }, } for _, tc := range tests { @@ -405,6 +414,11 @@ func TestArtifactPFlags(t *testing.T) { if tc.publicKey != "" { args = append(args, "--public-key", tc.publicKey) } + if len(tc.multiPublicKey) > 0 { + for _, key := range tc.multiPublicKey { + args = append(args, "--multi-public-key", key) + } + } if tc.uuid != "" { args = append(args, "--uuid", tc.uuid) } diff --git a/pkg/generated/models/intoto_v002_schema.go b/pkg/generated/models/intoto_v002_schema.go index 06ddfd59f..d8d8bcfd9 100644 --- a/pkg/generated/models/intoto_v002_schema.go +++ b/pkg/generated/models/intoto_v002_schema.go @@ -314,7 +314,8 @@ func (m *IntotoV002SchemaContent) UnmarshalBinary(b []byte) error { type IntotoV002SchemaContentEnvelope struct { // payload of the envelope - Payload string `json:"payload,omitempty"` + // Required: true + Payload *string `json:"payload"` // type describing the payload // Required: true @@ -330,6 +331,10 @@ type IntotoV002SchemaContentEnvelope struct { func (m *IntotoV002SchemaContentEnvelope) Validate(formats strfmt.Registry) error { var res []error + if err := m.validatePayload(formats); err != nil { + res = append(res, err) + } + if err := m.validatePayloadType(formats); err != nil { res = append(res, err) } @@ -344,6 +349,15 @@ func (m *IntotoV002SchemaContentEnvelope) Validate(formats strfmt.Registry) erro return nil } +func (m *IntotoV002SchemaContentEnvelope) validatePayload(formats strfmt.Registry) error { + + if err := validate.Required("content"+"."+"envelope"+"."+"payload", "body", m.Payload); err != nil { + return err + } + + return nil +} + func (m *IntotoV002SchemaContentEnvelope) validatePayloadType(formats strfmt.Registry) error { if err := validate.Required("content"+"."+"envelope"+"."+"payloadType", "body", m.PayloadType); err != nil { @@ -611,7 +625,7 @@ func (m *IntotoV002SchemaContentHash) UnmarshalBinary(b []byte) error { return nil } -// IntotoV002SchemaContentPayloadHash Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded +// IntotoV002SchemaContentPayloadHash Specifies the hash algorithm and value covering the payload within the DSSE envelope // // swagger:model IntotoV002SchemaContentPayloadHash type IntotoV002SchemaContentPayloadHash struct { diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index df050f5f3..6e98b2924 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -1857,6 +1857,7 @@ func init() { "description": "dsse envelope", "type": "object", "required": [ + "payload", "payloadType", "signatures" ], @@ -1904,7 +1905,7 @@ func init() { "readOnly": true }, "payloadHash": { - "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", "type": "object", "required": [ "algorithm", @@ -1931,6 +1932,7 @@ func init() { "description": "dsse envelope", "type": "object", "required": [ + "payload", "payloadType", "signatures" ], @@ -1998,7 +2000,7 @@ func init() { "readOnly": true }, "IntotoV002SchemaContentPayloadHash": { - "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", "type": "object", "required": [ "algorithm", @@ -3143,6 +3145,7 @@ func init() { "description": "dsse envelope", "type": "object", "required": [ + "payload", "payloadType", "signatures" ], @@ -3190,7 +3193,7 @@ func init() { "readOnly": true }, "payloadHash": { - "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", "type": "object", "required": [ "algorithm", diff --git a/pkg/types/intoto/v0.0.2/entry.go b/pkg/types/intoto/v0.0.2/entry.go index 85f2e0b51..252e181e4 100644 --- a/pkg/types/intoto/v0.0.2/entry.go +++ b/pkg/types/intoto/v0.0.2/entry.go @@ -1,5 +1,5 @@ // -// Copyright 2021 The Sigstore Authors. +// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -86,7 +86,7 @@ func (v V002Entry) IndexKeys() ([]string, error) { return nil, err } - result = append(result, keyObj.EmailAddresses()...) + result = append(result, keyObj.Subjects()...) } payloadKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.PayloadHash.Algorithm, *v.IntotoObj.Content.PayloadHash.Value)) @@ -98,11 +98,11 @@ func (v V002Entry) IndexKeys() ([]string, error) { hashkey := strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.Hash.Algorithm, *v.IntotoObj.Content.Hash.Value)) result = append(result, hashkey) - if v.IntotoObj.Content.Envelope.Payload == "" { + if *v.IntotoObj.Content.Envelope.Payload == "" { log.Logger.Info("IntotoObj DSSE payload is empty") return result, nil } - decodedPayload, err := base64.StdEncoding.DecodeString(string(v.IntotoObj.Content.Envelope.Payload)) + decodedPayload, err := base64.StdEncoding.DecodeString(string(*v.IntotoObj.Content.Envelope.Payload)) if err != nil { return result, fmt.Errorf("could not decode envelope payload: %w", err) } @@ -152,7 +152,7 @@ func parseSlsaPredicate(p []byte) (*in_toto.ProvenanceStatement, error) { func (v *V002Entry) Unmarshal(pe models.ProposedEntry) error { it, ok := pe.(*models.Intoto) if !ok { - return errors.New("cannot unmarshal non Intoto v0.0.1 type") + return errors.New("cannot unmarshal non Intoto v0.0.2 type") } var err error @@ -165,12 +165,12 @@ func (v *V002Entry) Unmarshal(pe models.ProposedEntry) error { return err } - if string(v.IntotoObj.Content.Envelope.Payload) == "" { - return nil + if string(*v.IntotoObj.Content.Envelope.Payload) == "" { + return errors.New("DSSE envelope does not contain a payload") } env := &dsse.Envelope{ - Payload: string(v.IntotoObj.Content.Envelope.Payload), + Payload: string(*v.IntotoObj.Content.Envelope.Payload), PayloadType: *v.IntotoObj.Content.Envelope.PayloadType, } @@ -190,13 +190,12 @@ func (v *V002Entry) Unmarshal(pe models.ProposedEntry) error { v.env = *env - decodedPayload, err := base64.StdEncoding.DecodeString(string(v.IntotoObj.Content.Envelope.Payload)) + decodedPayload, err := base64.StdEncoding.DecodeString(string(*v.IntotoObj.Content.Envelope.Payload)) if err != nil { return fmt.Errorf("could not decode envelope payload: %w", err) } - paeEncodedPayload := dsse.PAE(*v.IntotoObj.Content.Envelope.PayloadType, decodedPayload) - h := sha256.Sum256(paeEncodedPayload) + h := sha256.Sum256(decodedPayload) v.IntotoObj.Content.PayloadHash = &models.IntotoV002SchemaContentPayloadHash{ Algorithm: swag.String(models.IntotoV002SchemaContentPayloadHashAlgorithmSha256), Value: swag.String(hex.EncodeToString(h[:])), @@ -209,12 +208,17 @@ func (v *V002Entry) Canonicalize(ctx context.Context) ([]byte, error) { canonicalEntry := models.IntotoV002Schema{ Content: &models.IntotoV002SchemaContent{ - Envelope: v.IntotoObj.Content.Envelope, - Hash: v.IntotoObj.Content.Hash, - PayloadHash: v.IntotoObj.Content.PayloadHash, + Envelope: &models.IntotoV002SchemaContentEnvelope{}, + Hash: &models.IntotoV002SchemaContentHash{}, + PayloadHash: &models.IntotoV002SchemaContentPayloadHash{}, }, } + canonicalEntry.Content.Envelope.PayloadType = v.IntotoObj.Content.Envelope.PayloadType + canonicalEntry.Content.Envelope.Signatures = v.IntotoObj.Content.Envelope.Signatures + canonicalEntry.Content.Hash = v.IntotoObj.Content.Hash + canonicalEntry.Content.PayloadHash = v.IntotoObj.Content.PayloadHash + itObj := models.Intoto{} itObj.APIVersion = swag.String(APIVERSION) itObj.Spec = &canonicalEntry @@ -237,7 +241,11 @@ func (v *V002Entry) AttestationKeyValue() (string, []byte) { log.Logger.Infof("Skipping attestation storage, size %d is greater than max %d", storageSize, viper.GetInt("max_attestation_size")) return "", nil } - attBytes, _ := base64.StdEncoding.DecodeString(v.env.Payload) + attBytes, err := base64.StdEncoding.DecodeString(v.env.Payload) + if err != nil { + log.Logger.Infof("could not decode envelope payload: %w", err) + return "", nil + } return v.AttestationKey(), attBytes } @@ -279,8 +287,12 @@ func (v *verifier) Verify(data, sig []byte) error { func (v V002Entry) CreateFromArtifactProperties(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { returnVal := models.Intoto{} - re := V002Entry{} - + re := V002Entry{ + IntotoObj: models.IntotoV002Schema{ + Content: &models.IntotoV002SchemaContent{ + Envelope: &models.IntotoV002SchemaContentEnvelope{}, + }, + }} var err error artifactBytes := props.ArtifactBytes if artifactBytes == nil { @@ -334,7 +346,7 @@ func (v V002Entry) CreateFromArtifactProperties(_ context.Context, props types.A return nil, err } - re.IntotoObj.Content.Envelope.Payload = env.Payload + re.IntotoObj.Content.Envelope.Payload = swag.String(env.Payload) re.IntotoObj.Content.Envelope.PayloadType = &env.PayloadType for _, sig := range env.Signatures { diff --git a/pkg/types/intoto/v0.0.2/entry_test.go b/pkg/types/intoto/v0.0.2/entry_test.go index 2ed4073dc..a9c8a1414 100644 --- a/pkg/types/intoto/v0.0.2/entry_test.go +++ b/pkg/types/intoto/v0.0.2/entry_test.go @@ -1,5 +1,5 @@ // -// Copyright 2021 The Sigstore Authors. +// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -103,7 +103,7 @@ func multiSignEnvelope(t *testing.T, k []*ecdsa.PrivateKey, payload []byte) *dss func createRekorEnvelope(dsseEnv *dsse.Envelope, pub [][]byte) *models.IntotoV002SchemaContentEnvelope { env := &models.IntotoV002SchemaContentEnvelope{ - Payload: dsseEnv.Payload, + Payload: swag.String(dsseEnv.Payload), PayloadType: &dsseEnv.PayloadType, } @@ -282,8 +282,7 @@ func TestV002Entry_Unmarshal(t *testing.T) { if err != nil { return fmt.Errorf("could not decode envelope payload: %w", err) } - paeEncodedPayload := dsse.PAE(tt.env.PayloadType, decodedPayload) - h := sha256.Sum256(paeEncodedPayload) + h := sha256.Sum256(decodedPayload) want = append(want, "sha256:"+hex.EncodeToString(h[:])) if !reflect.DeepEqual(v.AttestationKey(), "sha256:"+hex.EncodeToString(h[:])) { @@ -398,8 +397,7 @@ func TestV002Entry_IndexKeys(t *testing.T) { if err != nil { t.Fatal(err) } - paeEncodedPayload := dsse.PAE(in_toto.PayloadType, b) - payloadHash := sha256.Sum256(paeEncodedPayload) + payloadHash := sha256.Sum256(b) v := V002Entry{ IntotoObj: models.IntotoV002Schema{ Content: &models.IntotoV002SchemaContent{ 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 66415c765..0f79c1547 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 @@ -47,7 +47,7 @@ } } }, - "required": ["payloadType", "signatures"], + "required": ["payload", "payloadType", "signatures"], "writeOnly": true }, "hash": { @@ -73,7 +73,7 @@ "readOnly": true }, "payloadHash": { - "description": "Specifies the hash algorithm and value covering the envelope's payload after being PAE encoded", + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope", "type": "object", "properties": { "algorithm": { diff --git a/tests/e2e_test.go b/tests/e2e_test.go index b9e7a9de5..f8a66e19f 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -576,7 +576,7 @@ func TestIntotoV002(t *testing.T) { t.Errorf("diff: %s", diff) } - attHash := sha256.Sum256(dsse.PAE("application/vnd.in-toto+json", []byte(g.Attestation))) + attHash := sha256.Sum256([]byte(g.Attestation)) intotoV002Model := &models.IntotoV002Schema{} if err := types.DecodeEntry(g.Body.(map[string]interface{})["IntotoObj"], intotoV002Model); err != nil { diff --git a/tests/intoto_multi_dsse.json b/tests/intoto_multi_dsse.json new file mode 100644 index 000000000..10240b769 --- /dev/null +++ b/tests/intoto_multi_dsse.json @@ -0,0 +1,6 @@ +{"payloadType":"application/vnd.in-toto+json", +"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJmb29iYXIiLCJkaWdlc3QiOnsiZm9vIjoiYmFyIn19XSwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJmb29ISzFiZ2Y1WC8xckNxZz09In0sImJ1aWxkVHlwZSI6IiIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7fX19fQ==", +"signatures":[{ + "keyid":"first","sig":"MEQCIAIlnxHC3eU4jmUsqJExxfzqyy8bk+61btgnRiGcRDxgAiBwmdnJ/GX1yCYhYAvwAtkuYN0yFlVPQVAx9R6JpUUBiA=="}, + {"keyid":"second","sig":"MEQCIAIlnxHC3eU4jmUsqJExxfzqyy8bk+61btgnRiGcRDxgAiBwmdnJ/GX1yCYhYAvwAtkuYN0yFlVPQVAx9R6JpUUBiA=="}] +} \ No newline at end of file