From 92742be653d72f61e0ab09b3d3dca2649b58ecec Mon Sep 17 00:00:00 2001 From: Riccardo Schirone <562321+ret2libc@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:40:26 +0100 Subject: [PATCH] Add support for ed25519ph user keys in hashedrekord (#1945) * Update sigstore/sigstore and use LoadOptions Signed-off-by: Riccardo Schirone * Added support for Ed25519ph in HashedRekord entries - Made X509 Signatures configurable with LoadOptions - Removed existing check that limited the use of Ed25519 keys in HashedRekord entries - Used Ed25519ph Signer/Verifier for HashedRekord entries Signed-off-by: Riccardo Schirone * Fix entry tests to ensure ed25519 is now accepted Signed-off-by: Riccardo Schirone --------- Signed-off-by: Riccardo Schirone --- pkg/pki/x509/x509.go | 12 +++-- pkg/types/hashedrekord/v0.0.1/entry.go | 8 +--- pkg/types/hashedrekord/v0.0.1/entry_test.go | 49 ++++++++++++++++----- pkg/util/checkpoint_test.go | 14 +++--- 4 files changed, 55 insertions(+), 28 deletions(-) diff --git a/pkg/pki/x509/x509.go b/pkg/pki/x509/x509.go index 84e79fc5e..2cfaf8160 100644 --- a/pkg/pki/x509/x509.go +++ b/pkg/pki/x509/x509.go @@ -38,17 +38,23 @@ import ( var EmailAddressOID asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 9, 1} type Signature struct { - signature []byte + signature []byte + verifierLoadOpts []sigsig.LoadOption } // NewSignature creates and validates an x509 signature object func NewSignature(r io.Reader) (*Signature, error) { + return NewSignatureWithOpts(r) +} + +func NewSignatureWithOpts(r io.Reader, opts ...sigsig.LoadOption) (*Signature, error) { b, err := io.ReadAll(r) if err != nil { return nil, err } return &Signature{ - signature: b, + signature: b, + verifierLoadOpts: opts, }, nil } @@ -84,7 +90,7 @@ func (s Signature) Verify(r io.Reader, k interface{}, opts ...sigsig.VerifyOptio } } - verifier, err := sigsig.LoadVerifier(p, crypto.SHA256) + verifier, err := sigsig.LoadVerifierWithOpts(p, s.verifierLoadOpts...) if err != nil { return err } diff --git a/pkg/types/hashedrekord/v0.0.1/entry.go b/pkg/types/hashedrekord/v0.0.1/entry.go index 4fa6ebac0..74b268ef0 100644 --- a/pkg/types/hashedrekord/v0.0.1/entry.go +++ b/pkg/types/hashedrekord/v0.0.1/entry.go @@ -19,7 +19,6 @@ import ( "bytes" "context" "crypto" - "crypto/ed25519" "crypto/sha256" "encoding/hex" "encoding/json" @@ -148,7 +147,7 @@ func (v *V001Entry) validate() (pki.Signature, pki.PublicKey, error) { return nil, nil, types.ValidationError(errors.New("missing signature")) } // Hashed rekord type only works for x509 signature types - sigObj, err := x509.NewSignature(bytes.NewReader(sig.Content)) + sigObj, err := x509.NewSignatureWithOpts(bytes.NewReader(sig.Content), options.WithED25519ph()) if err != nil { return nil, nil, types.ValidationError(err) } @@ -162,11 +161,6 @@ func (v *V001Entry) validate() (pki.Signature, pki.PublicKey, error) { return nil, nil, types.ValidationError(err) } - _, isEd25519 := keyObj.CryptoPubKey().(ed25519.PublicKey) - if isEd25519 { - return nil, nil, types.ValidationError(errors.New("ed25519 unsupported for hashedrekord")) - } - data := v.HashedRekordObj.Data if data == nil { return nil, nil, types.ValidationError(errors.New("missing data")) diff --git a/pkg/types/hashedrekord/v0.0.1/entry_test.go b/pkg/types/hashedrekord/v0.0.1/entry_test.go index 2330adeb3..b0535f69a 100644 --- a/pkg/types/hashedrekord/v0.0.1/entry_test.go +++ b/pkg/types/hashedrekord/v0.0.1/entry_test.go @@ -41,6 +41,7 @@ import ( "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" "go.uber.org/goleak" ) @@ -113,17 +114,17 @@ func TestCrossFieldValidation(t *testing.T) { Type: "PUBLIC KEY", }) - // testing lack of support for ed25519 - invalidEdPubKey, _, err := ed25519.GenerateKey(rand.Reader) + // testing support ed25519 + edPubKey, edPrivKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } - invalidDer, err := x509.MarshalPKIXPublicKey(invalidEdPubKey) + edDer, err := x509.MarshalPKIXPublicKey(edPubKey) if err != nil { t.Fatal(err) } - invalidKeyBytes := pem.EncodeToMemory(&pem.Block{ - Bytes: invalidDer, + edPubKeyBytes := pem.EncodeToMemory(&pem.Block{ + Bytes: edDer, Type: "PUBLIC KEY", }) @@ -142,6 +143,9 @@ func TestCrossFieldValidation(t *testing.T) { sha512Signer, _ := signature.LoadSigner(key, crypto.SHA512) sha512SigBytes, _ := sha512Signer.SignMessage(bytes.NewReader(dataBytes)) + edsha512Signer, _ := signature.LoadSignerWithOpts(edPrivKey, options.WithHash(crypto.SHA512), options.WithED25519ph()) + edsha512SigBytes, _ := edsha512Signer.SignMessage(bytes.NewReader(dataBytes)) + incorrectLengthHash := sha256.Sum224(dataBytes) incorrectLengthSHA := hex.EncodeToString(incorrectLengthHash[:]) @@ -197,16 +201,15 @@ func TestCrossFieldValidation(t *testing.T) { entry: V001Entry{ HashedRekordObj: models.HashedrekordV001Schema{ Signature: &models.HashedrekordV001SchemaSignature{ - Content: sha256SigBytes, + Content: edsha512SigBytes, PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ - Content: invalidKeyBytes, + Content: edPubKeyBytes, }, }, }, }, - expectedHashValue: "sha256:" + dataSHA256, - expectUnmarshalSuccess: false, - // successful even if unmarshalling fails, because the ed25519 key is valid + expectedHashValue: "sha512:" + dataSHA512, + expectUnmarshalSuccess: false, expectedVerifierSuccess: true, }, { @@ -242,6 +245,29 @@ func TestCrossFieldValidation(t *testing.T) { expectUnmarshalSuccess: false, expectedVerifierSuccess: true, }, + { + caseDesc: "signature with ed25519 public key (with data)", + entry: V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Signature: &models.HashedrekordV001SchemaSignature{ + Content: edsha512SigBytes, + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ + Content: edPubKeyBytes, + }, + }, + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha512), + Value: swag.String(dataSHA512), + }, + }, + }, + }, + expectedHashValue: "sha512:" + dataSHA512, + expectUnmarshalSuccess: true, + expectCanonicalizeSuccess: true, + expectedVerifierSuccess: true, + }, { caseDesc: "signature with sha256 hash", entry: V001Entry{ @@ -457,8 +483,7 @@ func TestCrossFieldValidation(t *testing.T) { t.Errorf("%v: unexpected error, got %v", tc.caseDesc, err) } else { pub, _ := verifiers[0].CanonicalValue() - // invalidKeyBytes is a valid ed25519 key - if !reflect.DeepEqual(pub, keyBytes) && !reflect.DeepEqual(pub, invalidKeyBytes) { + if !reflect.DeepEqual(pub, keyBytes) && !reflect.DeepEqual(pub, edPubKeyBytes) { t.Errorf("verifier and public keys do not match: %v, %v", string(pub), string(keyBytes)) } } diff --git a/pkg/util/checkpoint_test.go b/pkg/util/checkpoint_test.go index 2b394d8db..bbfc53c59 100644 --- a/pkg/util/checkpoint_test.go +++ b/pkg/util/checkpoint_test.go @@ -310,20 +310,22 @@ func TestSigningRoundtripCheckpoint(t *testing.T) { if err != nil { t.Fatalf("error creating signed checkpoint") } - signer, _ := signature.LoadSigner(test.signer, crypto.SHA256) - if _, ok := test.signer.(*rsa.PrivateKey); ok { - signer, _ = signature.LoadRSAPSSSigner(test.signer.(*rsa.PrivateKey), crypto.SHA256, test.opts.(*rsa.PSSOptions)) + signerOpts := []signature.LoadOption{options.WithHash(crypto.SHA256)} + if rsaTestOpts, ok := test.opts.(*rsa.PSSOptions); ok && rsaTestOpts != nil { + signerOpts = append(signerOpts, options.WithRSAPSS(rsaTestOpts)) } + signer, _ := signature.LoadSignerWithOpts(test.signer, signerOpts...) _, err = sth.Sign(test.identity, signer, options.WithCryptoSignerOpts(test.opts)) if (err != nil) != test.wantSignErr { t.Fatalf("signing test failed: wantSignErr %v, err %v", test.wantSignErr, err) } if !test.wantSignErr { - verifier, _ := signature.LoadVerifier(test.pubKey, crypto.SHA256) - if _, ok := test.pubKey.(*rsa.PublicKey); ok { - verifier, _ = signature.LoadRSAPSSVerifier(test.pubKey.(*rsa.PublicKey), crypto.SHA256, test.opts.(*rsa.PSSOptions)) + verifierOpts := []signature.LoadOption{options.WithHash(crypto.SHA256)} + if rsaTestOpts, ok := test.opts.(*rsa.PSSOptions); ok && rsaTestOpts != nil { + verifierOpts = append(verifierOpts, options.WithRSAPSS(rsaTestOpts)) } + verifier, _ := signature.LoadVerifierWithOpts(test.pubKey, verifierOpts...) if !sth.Verify(verifier) != test.wantVerifyErr { t.Fatalf("verification test failed %v", sth.Verify(verifier))