Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ed25519ph user keys in hashedrekord #1945

Merged
merged 3 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 9 additions & 3 deletions pkg/pki/x509/x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
Expand Down
8 changes: 1 addition & 7 deletions pkg/types/hashedrekord/v0.0.1/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"bytes"
"context"
"crypto"
"crypto/ed25519"
"crypto/sha256"
"encoding/hex"
"encoding/json"
Expand Down Expand Up @@ -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)
}
Expand All @@ -162,11 +161,6 @@ func (v *V001Entry) validate() (pki.Signature, pki.PublicKey, error) {
return nil, nil, types.ValidationError(err)
}

_, isEd25519 := keyObj.CryptoPubKey().(ed25519.PublicKey)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for context, when I wrote up #1325, my intention was to support ed25519ph only for hashedrekord, and ed25519 for all other types. This might mean making some changes to the 'pkg/pki/x509/x509' library to support loading in a custom verifier like on

verifier, err := sigsig.LoadVerifier(p, crypto.SHA256)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about this way of customizing the verifier?

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"))
Expand Down
49 changes: 37 additions & 12 deletions pkg/types/hashedrekord/v0.0.1/entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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",
})

Expand All @@ -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[:])

Expand Down Expand Up @@ -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,
},
{
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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))
}
}
Expand Down
14 changes: 8 additions & 6 deletions pkg/util/checkpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down