Skip to content

Commit 0e0b287

Browse files
Support other key algorithms for Rekor v2
We hardcoded ECDSA-P256-SHA256 as the only supported algorithm. This uses the algorithm registry to load the correct signing algorithm to specify its type and digest in the request to Rekor v2. This also fixes an incompatibility with Ed25519 and hashedrekord with Rekor v2, which requires Ed25519ph where the digest is provided during verification. To test this, I've added support for other signing algorithms in EphemeralKeypair, which will also make the struct useable with Cosign when a signing algorithm is provided. Signed-off-by: Hayden <[email protected]>
1 parent b9e4783 commit 0e0b287

File tree

7 files changed

+409
-100
lines changed

7 files changed

+409
-100
lines changed

pkg/sign/certificate.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"strings"
2929
"time"
3030

31+
protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
3132
"github.com/sigstore/sigstore-go/pkg/util"
3233
"github.com/sigstore/sigstore/pkg/oauthflow"
3334
)
@@ -131,6 +132,11 @@ func (f *Fulcio) GetCertificate(ctx context.Context, keypair Keypair, opts *Cert
131132
return nil, err
132133
}
133134

135+
// Fulcio doesn't support verifying Ed25519ph signatures currently.
136+
if keypair.GetSigningAlgorithm() == protocommon.PublicKeyDetails_PKIX_ED25519_PH {
137+
return nil, fmt.Errorf("ed25519ph unsupported by Fulcio")
138+
}
139+
134140
// Sign JWT subject for proof of possession
135141
subjectSignature, _, err := keypair.SignData(ctx, []byte(subject))
136142
if err != nil {

pkg/sign/certificate_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626

2727
"github.com/stretchr/testify/assert"
2828

29+
protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
2930
"github.com/sigstore/sigstore-go/pkg/testing/ca"
3031
)
3132

@@ -156,4 +157,15 @@ func Test_GetCertificate(t *testing.T) {
156157
cert, err = detachedFulcio.GetCertificate(ctx, keypair, certOpts)
157158
assert.NotNil(t, cert)
158159
assert.NoError(t, err)
160+
161+
t.Run("ed25519ph unsupported", func(t *testing.T) {
162+
// Test that Ed25519ph is rejected
163+
keypair, err := NewEphemeralKeypair(&EphemeralKeypairOptions{Algorithm: protocommon.PublicKeyDetails_PKIX_ED25519_PH})
164+
assert.Nil(t, err)
165+
certOpts.IDToken = "idtoken.eyJzdWIiOiJzdWJqZWN0In0K.stuff" // #nosec G101
166+
cert, err = fulcio.GetCertificate(ctx, keypair, certOpts)
167+
assert.Nil(t, cert)
168+
assert.ErrorContains(t, err, "ed25519ph unsupported by Fulcio")
169+
})
170+
159171
}

pkg/sign/keys.go

Lines changed: 89 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,50 +18,89 @@ import (
1818
"context"
1919
"crypto"
2020
"crypto/ecdsa"
21-
"crypto/elliptic"
21+
"crypto/ed25519"
2222
"crypto/rand"
23+
"crypto/rsa"
2324
"crypto/sha256"
2425
_ "crypto/sha512" // if user chooses SHA2-384 or SHA2-512 for hash
2526
"crypto/x509"
2627
"encoding/base64"
27-
"errors"
28+
"fmt"
2829

2930
protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
3031
"github.com/sigstore/sigstore/pkg/cryptoutils"
32+
"github.com/sigstore/sigstore/pkg/signature"
3133
)
3234

3335
type Keypair interface {
3436
GetHashAlgorithm() protocommon.HashAlgorithm
37+
GetSigningAlgorithm() protocommon.PublicKeyDetails
3538
GetHint() []byte
3639
GetKeyAlgorithm() string
40+
GetPublicKey() crypto.PublicKey
3741
GetPublicKeyPem() (string, error)
3842
SignData(ctx context.Context, data []byte) ([]byte, []byte, error)
3943
}
4044

4145
type EphemeralKeypairOptions struct {
42-
// Optional hint of for signing key
46+
// Optional fingerprint for public key
4347
Hint []byte
44-
// TODO: support additional key algorithms
48+
// Optional algorithm for generating signing key
49+
Algorithm protocommon.PublicKeyDetails
4550
}
4651

4752
type EphemeralKeypair struct {
48-
options *EphemeralKeypairOptions
49-
privateKey *ecdsa.PrivateKey
50-
hashAlgorithm protocommon.HashAlgorithm
53+
options *EphemeralKeypairOptions
54+
privKey crypto.Signer
55+
algDetails signature.AlgorithmDetails
5156
}
5257

58+
// NewEphemeralKeypair generates a signing key to be used for a single signature generation.
59+
// Defaults to ECDSA P-256 SHA-256 with a SHA-256 key hint.
5360
func NewEphemeralKeypair(opts *EphemeralKeypairOptions) (*EphemeralKeypair, error) {
5461
if opts == nil {
5562
opts = &EphemeralKeypairOptions{}
5663
}
5764

58-
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
65+
// Default signing algorithm is ECDSA P-256 SHA-256
66+
if opts.Algorithm == protocommon.PublicKeyDetails_PUBLIC_KEY_DETAILS_UNSPECIFIED {
67+
opts.Algorithm = protocommon.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256
68+
}
69+
algDetails, err := signature.GetAlgorithmDetails(opts.Algorithm)
5970
if err != nil {
6071
return nil, err
6172
}
73+
var privKey crypto.Signer
74+
switch kt := algDetails.GetKeyType(); kt {
75+
case signature.ECDSA:
76+
curve, err := algDetails.GetECDSACurve()
77+
if err != nil {
78+
return nil, err
79+
}
80+
privKey, err = ecdsa.GenerateKey(*curve, rand.Reader)
81+
if err != nil {
82+
return nil, err
83+
}
84+
case signature.RSA:
85+
bitSize, err := algDetails.GetRSAKeySize()
86+
if err != nil {
87+
return nil, err
88+
}
89+
privKey, err = rsa.GenerateKey(rand.Reader, int(bitSize))
90+
if err != nil {
91+
return nil, err
92+
}
93+
case signature.ED25519:
94+
_, privKey, err = ed25519.GenerateKey(rand.Reader)
95+
if err != nil {
96+
return nil, err
97+
}
98+
default:
99+
return nil, fmt.Errorf("unsupported key type: %T", kt)
100+
}
62101

63102
if opts.Hint == nil {
64-
pubKeyBytes, err := x509.MarshalPKIXPublicKey(privateKey.Public())
103+
pubKeyBytes, err := x509.MarshalPKIXPublicKey(privKey.Public())
65104
if err != nil {
66105
return nil, err
67106
}
@@ -70,63 +109,73 @@ func NewEphemeralKeypair(opts *EphemeralKeypairOptions) (*EphemeralKeypair, erro
70109
}
71110

72111
ephemeralKeypair := EphemeralKeypair{
73-
options: opts,
74-
privateKey: privateKey,
75-
hashAlgorithm: protocommon.HashAlgorithm_SHA2_256,
112+
options: opts,
113+
privKey: privKey,
114+
algDetails: algDetails,
76115
}
77116

78117
return &ephemeralKeypair, nil
79118
}
80119

120+
// GetHashAlgorithm returns the hash algorithm to compute the digest to sign.
81121
func (e *EphemeralKeypair) GetHashAlgorithm() protocommon.HashAlgorithm {
82-
return e.hashAlgorithm
122+
return e.algDetails.GetProtoHashType()
123+
}
124+
125+
// GetSigningAlgorithm returns the signing algorithm of the key.
126+
func (e *EphemeralKeypair) GetSigningAlgorithm() protocommon.PublicKeyDetails {
127+
return e.algDetails.GetSignatureAlgorithm()
83128
}
84129

130+
// GetHint returns the fingerprint of the public key.
85131
func (e *EphemeralKeypair) GetHint() []byte {
86132
return e.options.Hint
87133
}
88134

135+
// GetKeyAlgorithm returns the top-level key algorithm, used as part of requests
136+
// to Fulcio. Prefer PublicKeyDetails for a more precise algorithm.
89137
func (e *EphemeralKeypair) GetKeyAlgorithm() string {
90-
return "ECDSA"
138+
switch e.algDetails.GetKeyType() {
139+
case signature.ECDSA:
140+
return "ECDSA"
141+
case signature.RSA:
142+
return "RSA"
143+
case signature.ED25519:
144+
return "ED25519"
145+
default:
146+
return ""
147+
}
148+
}
149+
150+
// GetPublicKey returns the public key.
151+
func (e *EphemeralKeypair) GetPublicKey() crypto.PublicKey {
152+
return e.privKey.Public()
91153
}
92154

155+
// GetPublicKeyPem returns the public key in PEM format.
93156
func (e *EphemeralKeypair) GetPublicKeyPem() (string, error) {
94-
pubKeyBytes, err := cryptoutils.MarshalPublicKeyToPEM(e.privateKey.Public())
157+
pubKeyBytes, err := cryptoutils.MarshalPublicKeyToPEM(e.privKey.Public())
95158
if err != nil {
96159
return "", err
97160
}
98161

99162
return string(pubKeyBytes), nil
100163
}
101164

102-
func getHashFunc(hashAlgorithm protocommon.HashAlgorithm) (crypto.Hash, error) {
103-
switch hashAlgorithm {
104-
case protocommon.HashAlgorithm_SHA2_256:
105-
return crypto.Hash(crypto.SHA256), nil
106-
case protocommon.HashAlgorithm_SHA2_384:
107-
return crypto.Hash(crypto.SHA384), nil
108-
case protocommon.HashAlgorithm_SHA2_512:
109-
return crypto.Hash(crypto.SHA512), nil
110-
default:
111-
var hash crypto.Hash
112-
return hash, errors.New("unsupported hash algorithm")
113-
}
114-
}
115-
165+
// SignData returns the signature and the data to sign, which is a digest except when
166+
// signing with Ed25519.
116167
func (e *EphemeralKeypair) SignData(_ context.Context, data []byte) ([]byte, []byte, error) {
117-
hashFunc, err := getHashFunc(e.hashAlgorithm)
118-
if err != nil {
119-
return nil, nil, err
168+
hf := e.algDetails.GetHashType()
169+
dataToSign := data
170+
// RSA, ECDSA, and Ed25519ph sign a digest, while pure Ed25519's interface takes data and hashes during signing
171+
if hf != crypto.Hash(0) {
172+
hasher := hf.New()
173+
hasher.Write(data)
174+
dataToSign = hasher.Sum(nil)
120175
}
121-
122-
hasher := hashFunc.New()
123-
hasher.Write(data)
124-
digest := hasher.Sum(nil)
125-
126-
signature, err := e.privateKey.Sign(rand.Reader, digest, hashFunc)
176+
signature, err := e.privKey.Sign(rand.Reader, dataToSign, hf)
127177
if err != nil {
128178
return nil, nil, err
129179
}
130-
131-
return signature, digest, nil
180+
return signature, dataToSign, nil
132181
}

0 commit comments

Comments
 (0)