Skip to content

Commit 6e7ecf1

Browse files
yaronfcursoragent
andcommitted
fix: harden against malformed ECDSA keys and empty header values
Reject nil ECDSA curves at JWS key validation and in raw sign/verify paths, and return errors instead of panicking when a header map entry has no values. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 411210f commit 6e7ecf1

6 files changed

Lines changed: 34 additions & 0 deletions

File tree

crypto_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ func TestNewJWSSigner(t *testing.T) {
277277
{name: "ecdsa curve match", alg: jwa.ES256(), key: p256},
278278
{name: "ecdsa public key", alg: jwa.ES256(), key: &p256.PublicKey, wantErr: true},
279279
{name: "ecdsa curve mismatch", alg: jwa.ES384(), key: p256, wantErr: true},
280+
{name: "ecdsa nil curve", alg: jwa.ES256(), key: ecdsa.PrivateKey{}, wantErr: true},
280281
{name: "mldsa params match", alg: jwa.MLDSA44(), key: mldsa44},
281282
{name: "mldsa public key", alg: jwa.MLDSA44(), key: mldsa44.Public().(*mldsa.PublicKey), wantErr: true},
282283
{name: "mldsa params mismatch", alg: jwa.MLDSA65(), key: mldsa44, wantErr: true},

ecdsa.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ func ecdsaSignRaw(rd io.Reader, priv *ecdsa.PrivateKey, hash []byte) ([]byte, er
1313
if priv == nil {
1414
return nil, fmt.Errorf("nil private key")
1515
}
16+
if priv.Curve == nil {
17+
return nil, fmt.Errorf("nil curve on private key")
18+
}
1619
r, s, err := ecdsa.Sign(rd, priv, hash)
1720
if err != nil {
1821
return nil, err
@@ -35,6 +38,9 @@ func ecdsaVerifyRaw(pub *ecdsa.PublicKey, hash []byte, sig []byte) (bool, error)
3538
if pub == nil {
3639
return false, fmt.Errorf("nil public key")
3740
}
41+
if pub.Curve == nil {
42+
return false, fmt.Errorf("signature verification failed")
43+
}
3844
curve := pub.Params().Name
3945
lr, ls, err := sigComponentLen(curve)
4046
if err != nil {

ecdsa_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ func Test_ecdsaVerifyRaw(t *testing.T) {
7878
want: false,
7979
wantErr: true,
8080
},
81+
{
82+
name: "nil curve",
83+
args: args{
84+
pub: &ecdsa.PublicKey{},
85+
hash: bytes.Repeat([]byte{88}, 32),
86+
sig: make([]byte, 64),
87+
},
88+
want: false,
89+
wantErr: true,
90+
},
8191
{
8292
name: "bad curve",
8393
args: args{

jwskey.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ func validateECDSAKey(alg jwa.SignatureAlgorithm, key interface{}, signing bool)
9999
if !signing && isPrivate {
100100
return fmt.Errorf("algorithm %s requires an ECDSA public key for verification", alg)
101101
}
102+
if curve == nil {
103+
return fmt.Errorf("algorithm %s: ECDSA key has nil curve", alg)
104+
}
102105
var want elliptic.Curve
103106
switch alg {
104107
case jwa.ES256():

signatures.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ func (message *parsedMessage) getRawHeader(hdr string, trailer bool) ([]string,
254254
if !found {
255255
return nil, fmt.Errorf("header %s not found", hdr)
256256
}
257+
if len(vv) == 0 {
258+
return nil, fmt.Errorf("header %s has no values", hdr)
259+
}
257260
return vv, nil
258261
}
259262

@@ -918,6 +921,9 @@ func applyPolicyCreated(psi *psiSignature, message parsedMessage, config VerifyC
918921
if len(dateHdr) > 1 {
919922
return fmt.Errorf("multiple Date headers")
920923
}
924+
if len(dateHdr) == 0 {
925+
return fmt.Errorf("empty Date header")
926+
}
921927
date, err := http.ParseTime(dateHdr[0])
922928
if err != nil {
923929
return fmt.Errorf("cannot parse Date header: %w", err)

signatures_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,14 @@ func TestVerifyDateWithin(t *testing.T) {
915915
})
916916
}
917917

918+
func TestGetRawHeaderEmptyValues(t *testing.T) {
919+
msg := &parsedMessage{headers: http.Header{"x-custom": {}}}
920+
_, err := msg.getRawHeader("x-custom", false)
921+
if err == nil {
922+
t.Fatal("expected error for header with no values")
923+
}
924+
}
925+
918926
// Same as TestCreated but using Message
919927
func TestMessageCreated(t *testing.T) {
920928
testOnceWithConfig := func(t *testing.T, createdTime int64, verifyConfig *VerifyConfig, wantSuccess bool) {

0 commit comments

Comments
 (0)