From f28ea3a6c71836b5a22f0c3967e6c21b1bc74814 Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Tue, 16 Jan 2024 14:02:06 +0100 Subject: [PATCH 1/3] Added support for sha384/sha512 hash algorithms in hashedrekords Includes changes provided by @bobcallaway Signed-off-by: Riccardo Schirone --- .../models/hashedrekord_v001_schema.go | 12 +- pkg/generated/restapi/embedded_spec.go | 18 ++- pkg/types/hashedrekord/v0.0.1/entry.go | 30 ++++- pkg/types/hashedrekord/v0.0.1/entry_test.go | 121 +++++++++++++++--- .../v0.0.1/hashedrekord_v0_0_1_schema.json | 4 +- pkg/util/sha.go | 38 ++++++ pkg/util/sha_test.go | 67 ++++++++++ 7 files changed, 259 insertions(+), 31 deletions(-) diff --git a/pkg/generated/models/hashedrekord_v001_schema.go b/pkg/generated/models/hashedrekord_v001_schema.go index f8bf233ed..3b906ae29 100644 --- a/pkg/generated/models/hashedrekord_v001_schema.go +++ b/pkg/generated/models/hashedrekord_v001_schema.go @@ -277,10 +277,10 @@ type HashedrekordV001SchemaDataHash struct { // The hashing function used to compute the hash value // Required: true - // Enum: [sha256] + // Enum: [sha256 sha384 sha512] Algorithm *string `json:"algorithm"` - // The hash value for the content + // The hash value for the content, as represented by a lower case hexadecimal string // Required: true Value *string `json:"value"` } @@ -307,7 +307,7 @@ var hashedrekordV001SchemaDataHashTypeAlgorithmPropEnum []interface{} func init() { var res []string - if err := json.Unmarshal([]byte(`["sha256"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["sha256","sha384","sha512"]`), &res); err != nil { panic(err) } for _, v := range res { @@ -319,6 +319,12 @@ const ( // HashedrekordV001SchemaDataHashAlgorithmSha256 captures enum value "sha256" HashedrekordV001SchemaDataHashAlgorithmSha256 string = "sha256" + + // HashedrekordV001SchemaDataHashAlgorithmSha384 captures enum value "sha384" + HashedrekordV001SchemaDataHashAlgorithmSha384 string = "sha384" + + // HashedrekordV001SchemaDataHashAlgorithmSha512 captures enum value "sha512" + HashedrekordV001SchemaDataHashAlgorithmSha512 string = "sha512" ) // prop value enum diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index 3e353768c..234f9122c 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -1659,11 +1659,13 @@ func init() { "description": "The hashing function used to compute the hash value", "type": "string", "enum": [ - "sha256" + "sha256", + "sha384", + "sha512" ] }, "value": { - "description": "The hash value for the content", + "description": "The hash value for the content, as represented by a lower case hexadecimal string", "type": "string" } } @@ -1682,11 +1684,13 @@ func init() { "description": "The hashing function used to compute the hash value", "type": "string", "enum": [ - "sha256" + "sha256", + "sha384", + "sha512" ] }, "value": { - "description": "The hash value for the content", + "description": "The hash value for the content, as represented by a lower case hexadecimal string", "type": "string" } } @@ -3261,11 +3265,13 @@ func init() { "description": "The hashing function used to compute the hash value", "type": "string", "enum": [ - "sha256" + "sha256", + "sha384", + "sha512" ] }, "value": { - "description": "The hash value for the content", + "description": "The hash value for the content, as represented by a lower case hexadecimal string", "type": "string" } } diff --git a/pkg/types/hashedrekord/v0.0.1/entry.go b/pkg/types/hashedrekord/v0.0.1/entry.go index 5f0cb138a..4fa6ebac0 100644 --- a/pkg/types/hashedrekord/v0.0.1/entry.go +++ b/pkg/types/hashedrekord/v0.0.1/entry.go @@ -18,6 +18,7 @@ package hashedrekord import ( "bytes" "context" + "crypto" "crypto/ed25519" "crypto/sha256" "encoding/hex" @@ -38,6 +39,7 @@ import ( "github.com/sigstore/rekor/pkg/pki/x509" "github.com/sigstore/rekor/pkg/types" hashedrekord "github.com/sigstore/rekor/pkg/types/hashedrekord" + "github.com/sigstore/rekor/pkg/util" "github.com/sigstore/sigstore/pkg/signature/options" ) @@ -178,17 +180,38 @@ func (v *V001Entry) validate() (pki.Signature, pki.PublicKey, error) { return nil, nil, types.ValidationError(errors.New("invalid value for hash")) } + var alg crypto.Hash + switch swag.StringValue(hash.Algorithm) { + case models.HashedrekordV001SchemaDataHashAlgorithmSha384: + alg = crypto.SHA384 + case models.HashedrekordV001SchemaDataHashAlgorithmSha512: + alg = crypto.SHA512 + default: + alg = crypto.SHA256 + } + decoded, err := hex.DecodeString(*hash.Value) if err != nil { return nil, nil, err } - if err := sigObj.Verify(nil, keyObj, options.WithDigest(decoded)); err != nil { + if err := sigObj.Verify(nil, keyObj, options.WithDigest(decoded), options.WithCryptoSignerOpts(alg)); err != nil { return nil, nil, types.ValidationError(fmt.Errorf("verifying signature: %w", err)) } return sigObj, keyObj, nil } +func getDataHashAlgorithm(hashAlgorithm crypto.Hash) string { + switch hashAlgorithm { + case crypto.SHA384: + return models.HashedrekordV001SchemaDataHashAlgorithmSha384 + case crypto.SHA512: + return models.HashedrekordV001SchemaDataHashAlgorithmSha512 + default: + return models.HashedrekordV001SchemaDataHashAlgorithmSha256 + } +} + func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) { returnVal := models.Hashedrekord{} re := V001Entry{} @@ -230,10 +253,11 @@ func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.A return nil, errors.New("only one public key must be provided") } + hashAlgorithm, hashValue := util.UnprefixSHA(props.ArtifactHash) re.HashedRekordObj.Signature.PublicKey.Content = strfmt.Base64(publicKeyBytes[0]) re.HashedRekordObj.Data.Hash = &models.HashedrekordV001SchemaDataHash{ - Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), - Value: swag.String(props.ArtifactHash), + Algorithm: swag.String(getDataHashAlgorithm(hashAlgorithm)), + Value: swag.String(hashValue), } if _, _, err := re.validate(); err != nil { diff --git a/pkg/types/hashedrekord/v0.0.1/entry_test.go b/pkg/types/hashedrekord/v0.0.1/entry_test.go index 343712dc8..efb096d5b 100644 --- a/pkg/types/hashedrekord/v0.0.1/entry_test.go +++ b/pkg/types/hashedrekord/v0.0.1/entry_test.go @@ -24,6 +24,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/sha256" + "crypto/sha512" "crypto/x509" "crypto/x509/pkix" "encoding/hex" @@ -57,6 +58,7 @@ func TestCrossFieldValidation(t *testing.T) { type TestCase struct { caseDesc string entry V001Entry + expectedHashValue string expectUnmarshalSuccess bool expectCanonicalizeSuccess bool expectedVerifierSuccess bool @@ -90,11 +92,19 @@ func TestCrossFieldValidation(t *testing.T) { }) dataBytes := []byte("sign me!") - h := sha256.Sum256(dataBytes) - dataSHA := hex.EncodeToString(h[:]) - - signer, _ := signature.LoadSigner(key, crypto.SHA256) - sigBytes, _ := signer.SignMessage(bytes.NewReader(dataBytes)) + sha256Sum := sha256.Sum256(dataBytes) + sha384Sum := sha512.Sum384(dataBytes) + sha512Sum := sha512.Sum512(dataBytes) + dataSHA256 := hex.EncodeToString(sha256Sum[:]) + dataSHA384 := hex.EncodeToString(sha384Sum[:]) + dataSHA512 := hex.EncodeToString(sha512Sum[:]) + + sha256Signer, _ := signature.LoadSigner(key, crypto.SHA256) + sha256SigBytes, _ := sha256Signer.SignMessage(bytes.NewReader(dataBytes)) + sha384Signer, _ := signature.LoadSigner(key, crypto.SHA384) + sha384SigBytes, _ := sha384Signer.SignMessage(bytes.NewReader(dataBytes)) + sha512Signer, _ := signature.LoadSigner(key, crypto.SHA512) + sha512SigBytes, _ := sha512Signer.SignMessage(bytes.NewReader(dataBytes)) incorrectLengthHash := sha256.Sum224(dataBytes) incorrectLengthSHA := hex.EncodeToString(incorrectLengthHash[:]) @@ -124,10 +134,11 @@ func TestCrossFieldValidation(t *testing.T) { entry: V001Entry{ HashedRekordObj: models.HashedrekordV001Schema{ Signature: &models.HashedrekordV001SchemaSignature{ - Content: sigBytes, + Content: sha256SigBytes, }, }, }, + expectedHashValue: "sha256:" + dataSHA256, expectUnmarshalSuccess: false, expectedVerifierSuccess: false, }, @@ -136,11 +147,12 @@ func TestCrossFieldValidation(t *testing.T) { entry: V001Entry{ HashedRekordObj: models.HashedrekordV001Schema{ Signature: &models.HashedrekordV001SchemaSignature{ - Content: sigBytes, + Content: sha256SigBytes, PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{}, }, }, }, + expectedHashValue: "sha256:" + dataSHA256, expectUnmarshalSuccess: false, expectedVerifierSuccess: false, }, @@ -149,13 +161,14 @@ func TestCrossFieldValidation(t *testing.T) { entry: V001Entry{ HashedRekordObj: models.HashedrekordV001Schema{ Signature: &models.HashedrekordV001SchemaSignature{ - Content: sigBytes, + Content: sha256SigBytes, PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ Content: invalidKeyBytes, }, }, }, }, + expectedHashValue: "sha256:" + dataSHA256, expectUnmarshalSuccess: false, // successful even if unmarshalling fails, because the ed25519 key is valid expectedVerifierSuccess: true, @@ -165,13 +178,14 @@ func TestCrossFieldValidation(t *testing.T) { entry: V001Entry{ HashedRekordObj: models.HashedrekordV001Schema{ Signature: &models.HashedrekordV001SchemaSignature{ - Content: sigBytes, + Content: sha256SigBytes, PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ Content: keyBytes, }, }, }, }, + expectedHashValue: "sha256:" + dataSHA256, expectUnmarshalSuccess: false, expectedVerifierSuccess: true, }, @@ -180,7 +194,7 @@ func TestCrossFieldValidation(t *testing.T) { entry: V001Entry{ HashedRekordObj: models.HashedrekordV001Schema{ Signature: &models.HashedrekordV001SchemaSignature{ - Content: sigBytes, + Content: sha256SigBytes, PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ Content: keyBytes, }, @@ -188,27 +202,75 @@ func TestCrossFieldValidation(t *testing.T) { Data: &models.HashedrekordV001SchemaData{}, }, }, + expectedHashValue: "sha256:" + dataSHA256, expectUnmarshalSuccess: false, expectedVerifierSuccess: true, }, { - caseDesc: "signature with hash", + caseDesc: "signature with sha256 hash", entry: V001Entry{ HashedRekordObj: models.HashedrekordV001Schema{ Signature: &models.HashedrekordV001SchemaSignature{ - Content: sigBytes, + Content: sha256SigBytes, PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ Content: keyBytes, }, }, Data: &models.HashedrekordV001SchemaData{ Hash: &models.HashedrekordV001SchemaDataHash{ - Value: swag.String(dataSHA), + Value: swag.String(dataSHA256), Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), }, }, }, }, + expectedHashValue: "sha256:" + dataSHA256, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: true, + expectedVerifierSuccess: true, + }, + { + caseDesc: "signature with sha384 hash", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Signature: &models.HashedrekordV001SchemaSignature{ + Content: sha384SigBytes, + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ + Content: keyBytes, + }, + }, + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Value: swag.String(dataSHA384), + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha384), + }, + }, + }, + }, + expectedHashValue: "sha384:" + dataSHA384, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: true, + expectedVerifierSuccess: true, + }, + { + caseDesc: "signature with sha512 hash", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Signature: &models.HashedrekordV001SchemaSignature{ + Content: sha512SigBytes, + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ + Content: keyBytes, + }, + }, + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Value: swag.String(dataSHA512), + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha512), + }, + }, + }, + }, + expectedHashValue: "sha512:" + dataSHA512, expectUnmarshalSuccess: true, expectCanonicalizeSuccess: true, expectedVerifierSuccess: true, @@ -218,7 +280,7 @@ func TestCrossFieldValidation(t *testing.T) { entry: V001Entry{ HashedRekordObj: models.HashedrekordV001Schema{ Signature: &models.HashedrekordV001SchemaSignature{ - Content: sigBytes, + Content: sha256SigBytes, PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ Content: keyBytes, }, @@ -231,6 +293,7 @@ func TestCrossFieldValidation(t *testing.T) { }, }, }, + expectedHashValue: "sha256:" + dataSHA256, expectUnmarshalSuccess: false, expectCanonicalizeSuccess: false, expectedVerifierSuccess: true, @@ -240,7 +303,7 @@ func TestCrossFieldValidation(t *testing.T) { entry: V001Entry{ HashedRekordObj: models.HashedrekordV001Schema{ Signature: &models.HashedrekordV001SchemaSignature{ - Content: sigBytes, + Content: sha256SigBytes, PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ Content: keyBytes, }, @@ -253,6 +316,30 @@ func TestCrossFieldValidation(t *testing.T) { }, }, }, + expectedHashValue: "sha256:" + dataSHA256, + expectUnmarshalSuccess: false, + expectCanonicalizeSuccess: false, + expectedVerifierSuccess: true, + }, + { + caseDesc: "signature with mismatched hash & invalid signature", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Signature: &models.HashedrekordV001SchemaSignature{ + Content: sha512SigBytes, + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ + Content: keyBytes, + }, + }, + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Value: swag.String(dataSHA256), + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), + }, + }, + }, + }, + expectedHashValue: "sha256:" + dataSHA256, expectUnmarshalSuccess: false, expectCanonicalizeSuccess: false, expectedVerifierSuccess: true, @@ -293,7 +380,7 @@ func TestCrossFieldValidation(t *testing.T) { hash, err := v.ArtifactHash() if err != nil { t.Errorf("unexpected failure with ArtifactHash: %v", err) - } else if hash != "sha256:"+dataSHA { + } else if hash != tc.expectedHashValue { t.Errorf("unexpected match with ArtifactHash: %s", hash) } } @@ -323,7 +410,7 @@ func TestCrossFieldValidation(t *testing.T) { hash, err := ei.ArtifactHash() if err != nil { t.Errorf("unexpected failure with ArtifactHash: %v", err) - } else if hash != "sha256:"+dataSHA { + } else if hash != tc.expectedHashValue { t.Errorf("unexpected match with ArtifactHash: %s", hash) } } diff --git a/pkg/types/hashedrekord/v0.0.1/hashedrekord_v0_0_1_schema.json b/pkg/types/hashedrekord/v0.0.1/hashedrekord_v0_0_1_schema.json index 8752ae60f..576071ed8 100644 --- a/pkg/types/hashedrekord/v0.0.1/hashedrekord_v0_0_1_schema.json +++ b/pkg/types/hashedrekord/v0.0.1/hashedrekord_v0_0_1_schema.json @@ -38,10 +38,10 @@ "algorithm": { "description": "The hashing function used to compute the hash value", "type": "string", - "enum": [ "sha256" ] + "enum": [ "sha256", "sha384", "sha512" ] }, "value": { - "description": "The hash value for the content", + "description": "The hash value for the content, as represented by a lower case hexadecimal string", "type": "string" } }, diff --git a/pkg/util/sha.go b/pkg/util/sha.go index 8509a5585..07b8fb1b5 100644 --- a/pkg/util/sha.go +++ b/pkg/util/sha.go @@ -15,6 +15,7 @@ package util import ( + "crypto" "fmt" "strings" ) @@ -33,9 +34,46 @@ func PrefixSHA(sha string) string { prefix = "sha1:" case 64: prefix = "sha256:" + case 96: + prefix = "sha384:" case 128: prefix = "sha512:" } return fmt.Sprintf("%v%v", prefix, sha) } + +func UnprefixSHA(sha string) (crypto.Hash, string) { + components := strings.Split(sha, ":") + + if len(components) == 2 { + prefix := components[0] + sha = components[1] + + switch prefix { + case "sha1": + return crypto.SHA1, sha + case "sha256": + return crypto.SHA256, sha + case "sha384": + return crypto.SHA384, sha + case "sha512": + return crypto.SHA512, sha + default: + return crypto.Hash(0), "" + } + } + + switch len(sha) { + case 40: + return crypto.SHA1, sha + case 64: + return crypto.SHA256, sha + case 96: + return crypto.SHA384, sha + case 128: + return crypto.SHA512, sha + } + + return crypto.Hash(0), "" +} diff --git a/pkg/util/sha_test.go b/pkg/util/sha_test.go index 2b2e518b0..29c4b7322 100644 --- a/pkg/util/sha_test.go +++ b/pkg/util/sha_test.go @@ -15,6 +15,7 @@ package util import ( + "crypto" "testing" ) @@ -43,6 +44,10 @@ func TestPrefixSHA(t *testing.T) { input: "cfd356237e261871e8f92ae6710a75a65a925ae121d94d28533f008bd3e00b5472d261b5d0e1ab4082e3078dd1ad2af57876ed3c1c797c4097dbed870f458408", want: "sha512:cfd356237e261871e8f92ae6710a75a65a925ae121d94d28533f008bd3e00b5472d261b5d0e1ab4082e3078dd1ad2af57876ed3c1c797c4097dbed870f458408", }, + { + input: "78674b244bc9cba8ecb6dcb660b059728236e36b2f30fbcd6e17b1b64255f3ac596fbe5c84d1cc9d2a0979513260de09", + want: "sha384:78674b244bc9cba8ecb6dcb660b059728236e36b2f30fbcd6e17b1b64255f3ac596fbe5c84d1cc9d2a0979513260de09", + }, } for _, tr := range testCases { @@ -52,3 +57,65 @@ func TestPrefixSHA(t *testing.T) { } } } + +func TestUnprefixSHA(t *testing.T) { + type prefixedSHA struct { + crypto.Hash + string + } + var testCases = []struct { + input string + want prefixedSHA + }{ + { + input: "87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7", + want: prefixedSHA{ + crypto.SHA256, + "87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7", + }, + }, + { + input: "sha512:162b0b32f02482d5aca0a7c93dd03ceac3acd7e410a5f18f3fb990fc958ae0df6f32233b91831eaf99ca581a8c4ddf9c8ba315ac482db6d4ea01cc7884a635be", + want: prefixedSHA{ + crypto.SHA512, + "162b0b32f02482d5aca0a7c93dd03ceac3acd7e410a5f18f3fb990fc958ae0df6f32233b91831eaf99ca581a8c4ddf9c8ba315ac482db6d4ea01cc7884a635be", + }, + }, + { + input: "09b80428c53912d4174162fd5b7c7d485bdcc3ab", + want: prefixedSHA{ + crypto.SHA1, + "09b80428c53912d4174162fd5b7c7d485bdcc3ab", + }, + }, + { + input: "cfd356237e261871e8f92ae6710a75a65a925ae121d94d28533f008bd3e00b5472d261b5d0e1ab4082e3078dd1ad2af57876ed3c1c797c4097dbed870f458408", + want: prefixedSHA{ + crypto.SHA512, + "cfd356237e261871e8f92ae6710a75a65a925ae121d94d28533f008bd3e00b5472d261b5d0e1ab4082e3078dd1ad2af57876ed3c1c797c4097dbed870f458408", + }, + }, + { + input: "78674b244bc9cba8ecb6dcb660b059728236e36b2f30fbcd6e17b1b64255f3ac596fbe5c84d1cc9d2a0979513260de09", + want: prefixedSHA{ + crypto.SHA384, + "78674b244bc9cba8ecb6dcb660b059728236e36b2f30fbcd6e17b1b64255f3ac596fbe5c84d1cc9d2a0979513260de09", + }, + }, + { + input: "sha384:78674b244bc9cba8ecb6dcb660b059728236e36b2f30fbcd6e17b1b64255f3ac596fbe5c84d1cc9d2a0979513260de09", + want: prefixedSHA{ + crypto.SHA384, + "78674b244bc9cba8ecb6dcb660b059728236e36b2f30fbcd6e17b1b64255f3ac596fbe5c84d1cc9d2a0979513260de09", + }, + }, + } + + for _, tr := range testCases { + algo, value := UnprefixSHA(tr.input) + got := prefixedSHA{algo, value} + if got != tr.want { + t.Errorf("Got '%v' expected '%v' (input %s)", got, tr.want, tr.input) + } + } +} From 6903f5f774fb0fed31211bee18193d17cf6c11ab Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Fri, 19 Jan 2024 12:32:34 +0100 Subject: [PATCH 2/3] Added e2e test for hashedrekord type Make sure Rekor accepts SHA256 digest but not SHA1 Signed-off-by: Riccardo Schirone --- pkg/types/hashedrekord/v0.0.1/e2e_test.go | 165 ++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 pkg/types/hashedrekord/v0.0.1/e2e_test.go diff --git a/pkg/types/hashedrekord/v0.0.1/e2e_test.go b/pkg/types/hashedrekord/v0.0.1/e2e_test.go new file mode 100644 index 000000000..56f4f180b --- /dev/null +++ b/pkg/types/hashedrekord/v0.0.1/e2e_test.go @@ -0,0 +1,165 @@ +// +// Copyright 2024 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build e2e + +package hashedrekord + +import ( + "bytes" + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "os" + "strings" + "testing" + "time" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/sigstore/rekor/pkg/client" + "github.com/sigstore/rekor/pkg/generated/client/entries" + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/types" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" +) + +func rekorServer() string { + if s := os.Getenv("REKOR_SERVER"); s != "" { + return s + } + return "http://localhost:3000" +} + +// TestSHA256HashedRekordEntry tests sending a valid HashedRekord proposed entry. +func TestSHA256HashedRekordEntry(t *testing.T) { + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("error generating key: %v", err) + } + pubBytes, err := cryptoutils.MarshalPublicKeyToPEM(privKey.Public()) + if err != nil { + t.Fatalf("error marshaling public key: %v", err) + } + + data := []byte("data") + signer, err := signature.LoadSigner(privKey, crypto.SHA256) + if err != nil { + t.Fatalf("error loading verifier: %v", err) + } + signature, err := signer.SignMessage(bytes.NewReader(data)) + if err != nil { + t.Fatalf("error signing message: %v", err) + } + + ap := types.ArtifactProperties{ + ArtifactBytes: []byte("data"), + ArtifactHash: "sha256:3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7", + PublicKeyBytes: [][]byte{pubBytes}, + PKIFormat: "x509", + SignatureBytes: signature, + } + + ei := NewEntry() + + entry, err := ei.CreateFromArtifactProperties(context.Background(), ap) + if err != nil { + t.Fatalf("error creating entry: %v", err) + } + + rc, err := client.GetRekorClient(rekorServer()) + if err != nil { + t.Errorf("error getting client: %v", err) + } + + params := &entries.CreateLogEntryParams{} + params.SetProposedEntry(entry) + params.SetContext(context.Background()) + params.SetTimeout(5 * time.Second) + + if _, err = rc.Entries.CreateLogEntry(params); err != nil { + t.Fatalf("expected no errors when submitting hashedrekord entry with sha256 to rekor %s", err) + } +} + +// TestSHA1HashedRekordEntry tests sending a proposed hashedrekord entry with +// sha1 digests that should not be accepted by Rekor as SHA1 is considered +// insecure. +func TestSHA1HashedRekordEntry(t *testing.T) { + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("error generating key: %v", err) + } + pubBytes, err := cryptoutils.MarshalPublicKeyToPEM(privKey.Public()) + if err != nil { + t.Fatalf("error marshaling public key: %v", err) + } + + data := []byte("data") + signer, err := signature.LoadSigner(privKey, crypto.SHA256) + if err != nil { + t.Fatalf("error loading verifier: %v", err) + } + signature, err := signer.SignMessage(bytes.NewReader(data)) + if err != nil { + t.Fatalf("error signing message: %v", err) + } + + re := V001Entry{} + + // we will need artifact, public-key, signature + re.HashedRekordObj.Data = &models.HashedrekordV001SchemaData{} + + re.HashedRekordObj.Signature = &models.HashedrekordV001SchemaSignature{} + re.HashedRekordObj.Signature.Content = strfmt.Base64(signature) + + re.HashedRekordObj.Signature.PublicKey = &models.HashedrekordV001SchemaSignaturePublicKey{} + publicKeyBytes := [][]byte{pubBytes} + + re.HashedRekordObj.Signature.PublicKey.Content = strfmt.Base64(publicKeyBytes[0]) + re.HashedRekordObj.Data.Hash = &models.HashedrekordV001SchemaDataHash{ + Algorithm: swag.String("sha1"), + Value: swag.String("a17c9aaa61e80a1bf71d0d850af4e5baa9800bbd"), + } + + hr := models.Hashedrekord{} + hr.APIVersion = swag.String("0.0.1") + hr.Spec = re.HashedRekordObj + + rc, err := client.GetRekorClient(rekorServer()) + if err != nil { + t.Errorf("error getting client: %v", err) + } + + params := &entries.CreateLogEntryParams{} + params.SetProposedEntry(&hr) + params.SetContext(context.Background()) + params.SetTimeout(5 * time.Second) + + if _, err = rc.Entries.CreateLogEntry(params); err == nil { + t.Fatalf("expected a failure when trying to add a hashedrekord with sha1") + } + + e, ok := err.(*entries.CreateLogEntryBadRequest) + if !ok { + t.Errorf("unexpected error returned from rekor: %v", err.Error()) + } + if !strings.Contains(e.Payload.Message, "validation failure") { + t.Errorf("expected error message to include 'validation failure': %v", e.Payload.Message) + } +} From 0f1f8e32f5500b5d54b02e64628691f88251392c Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 19 Jan 2024 18:18:47 -0500 Subject: [PATCH 3/3] hashedrekord: add a SHA1 backstop test for CreateFromArtifactProperties Signed-off-by: William Woodruff --- pkg/types/hashedrekord/v0.0.1/e2e_test.go | 71 --------------------- pkg/types/hashedrekord/v0.0.1/entry_test.go | 36 +++++++++++ 2 files changed, 36 insertions(+), 71 deletions(-) diff --git a/pkg/types/hashedrekord/v0.0.1/e2e_test.go b/pkg/types/hashedrekord/v0.0.1/e2e_test.go index 56f4f180b..04fd77876 100644 --- a/pkg/types/hashedrekord/v0.0.1/e2e_test.go +++ b/pkg/types/hashedrekord/v0.0.1/e2e_test.go @@ -25,15 +25,11 @@ import ( "crypto/elliptic" "crypto/rand" "os" - "strings" "testing" "time" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" "github.com/sigstore/rekor/pkg/client" "github.com/sigstore/rekor/pkg/generated/client/entries" - "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" @@ -96,70 +92,3 @@ func TestSHA256HashedRekordEntry(t *testing.T) { t.Fatalf("expected no errors when submitting hashedrekord entry with sha256 to rekor %s", err) } } - -// TestSHA1HashedRekordEntry tests sending a proposed hashedrekord entry with -// sha1 digests that should not be accepted by Rekor as SHA1 is considered -// insecure. -func TestSHA1HashedRekordEntry(t *testing.T) { - privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Fatalf("error generating key: %v", err) - } - pubBytes, err := cryptoutils.MarshalPublicKeyToPEM(privKey.Public()) - if err != nil { - t.Fatalf("error marshaling public key: %v", err) - } - - data := []byte("data") - signer, err := signature.LoadSigner(privKey, crypto.SHA256) - if err != nil { - t.Fatalf("error loading verifier: %v", err) - } - signature, err := signer.SignMessage(bytes.NewReader(data)) - if err != nil { - t.Fatalf("error signing message: %v", err) - } - - re := V001Entry{} - - // we will need artifact, public-key, signature - re.HashedRekordObj.Data = &models.HashedrekordV001SchemaData{} - - re.HashedRekordObj.Signature = &models.HashedrekordV001SchemaSignature{} - re.HashedRekordObj.Signature.Content = strfmt.Base64(signature) - - re.HashedRekordObj.Signature.PublicKey = &models.HashedrekordV001SchemaSignaturePublicKey{} - publicKeyBytes := [][]byte{pubBytes} - - re.HashedRekordObj.Signature.PublicKey.Content = strfmt.Base64(publicKeyBytes[0]) - re.HashedRekordObj.Data.Hash = &models.HashedrekordV001SchemaDataHash{ - Algorithm: swag.String("sha1"), - Value: swag.String("a17c9aaa61e80a1bf71d0d850af4e5baa9800bbd"), - } - - hr := models.Hashedrekord{} - hr.APIVersion = swag.String("0.0.1") - hr.Spec = re.HashedRekordObj - - rc, err := client.GetRekorClient(rekorServer()) - if err != nil { - t.Errorf("error getting client: %v", err) - } - - params := &entries.CreateLogEntryParams{} - params.SetProposedEntry(&hr) - params.SetContext(context.Background()) - params.SetTimeout(5 * time.Second) - - if _, err = rc.Entries.CreateLogEntry(params); err == nil { - t.Fatalf("expected a failure when trying to add a hashedrekord with sha1") - } - - e, ok := err.(*entries.CreateLogEntryBadRequest) - if !ok { - t.Errorf("unexpected error returned from rekor: %v", err.Error()) - } - if !strings.Contains(e.Payload.Message, "validation failure") { - t.Errorf("expected error message to include 'validation failure': %v", e.Payload.Message) - } -} diff --git a/pkg/types/hashedrekord/v0.0.1/entry_test.go b/pkg/types/hashedrekord/v0.0.1/entry_test.go index efb096d5b..2330adeb3 100644 --- a/pkg/types/hashedrekord/v0.0.1/entry_test.go +++ b/pkg/types/hashedrekord/v0.0.1/entry_test.go @@ -39,6 +39,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" x509r "github.com/sigstore/rekor/pkg/pki/x509" "github.com/sigstore/rekor/pkg/types" + "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "go.uber.org/goleak" ) @@ -54,6 +55,41 @@ func TestNewEntryReturnType(t *testing.T) { } } +func TestRejectsSHA1(t *testing.T) { + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("error generating key: %v", err) + } + pubBytes, err := cryptoutils.MarshalPublicKeyToPEM(privKey.Public()) + if err != nil { + t.Fatalf("error marshaling public key: %v", err) + } + + data := []byte("data") + signer, err := signature.LoadSigner(privKey, crypto.SHA256) + if err != nil { + t.Fatalf("error loading verifier: %v", err) + } + signature, err := signer.SignMessage(bytes.NewReader(data)) + if err != nil { + t.Fatalf("error signing message: %v", err) + } + + ap := types.ArtifactProperties{ + ArtifactBytes: data, + ArtifactHash: "sha1:a17c9aaa61e80a1bf71d0d850af4e5baa9800bbd", + PublicKeyBytes: [][]byte{pubBytes}, + PKIFormat: "x509", + SignatureBytes: signature, + } + + ei := NewEntry() + _, err = ei.CreateFromArtifactProperties(context.Background(), ap) + if err == nil { + t.Fatalf("expected error creating entry") + } +} + func TestCrossFieldValidation(t *testing.T) { type TestCase struct { caseDesc string