Visitar URL original
fix(publisher): pad ECDSA P-384 signature to fixed width (#1457) · modelcontextprotocol/registry@097bdc4 · GitHub
Skip to content

Commit 097bdc4

Browse files
fix(publisher): pad ECDSA P-384 signature to fixed width (#1457)
The in-process ECDSA P-384 signer built the signature with `append(r.Bytes(), s.Bytes()...)`. big.Int.Bytes() strips leading zero bytes, so whenever r or s had a high zero byte (roughly 1 in 128 signatures) the concatenation came out shorter than 96 bytes and the registry rejected the publish with "invalid signature size for ECDSA P-384". Intermittent and confusing to debug. Fixed by left-padding each component to 48 bytes with FillBytes, same as the googlekms signer already does. Added a test pinning the fixed-width encoding plus a sign/verify round-trip.
1 parent c434598 commit 097bdc4

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

‎cmd/publisher/auth/common.go‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,27 @@ func (c *InProcessSigner) GetSignedTimestamp(_ context.Context) (*string, []byte
127127
if err != nil {
128128
return nil, nil, fmt.Errorf("failed to sign message: %w", err)
129129
}
130-
signature := append(r.Bytes(), s.Bytes()...)
130+
signature := encodeECDSAP384Signature(r, s)
131131
return &timestamp, signature, nil
132132
default:
133133
return nil, nil, fmt.Errorf("unsupported crypto algorithm: %s", c.cryptoAlgorithm)
134134
}
135135
}
136136

137+
// encodeECDSAP384Signature encodes r and s as fixed-width R || S (48 bytes each,
138+
// 96 bytes total). big.Int.Bytes drops leading zero bytes, so appending them
139+
// directly can produce fewer than 96 bytes when r or s is small, which the
140+
// registry rejects with "invalid signature size for ECDSA P-384". FillBytes
141+
// left-pads each component to 48 bytes so verification always sees 96 bytes.
142+
// r and s come from ecdsa.Sign in this file and are bounded by the P-384 group
143+
// order, so they always fit in 48 bytes and FillBytes cannot panic.
144+
func encodeECDSAP384Signature(r, s *big.Int) []byte {
145+
signature := make([]byte, 96)
146+
r.FillBytes(signature[:48])
147+
s.FillBytes(signature[48:])
148+
return signature
149+
}
150+
137151
// parseRawPrivateKey parses a raw ECDSA private key from bytes.
138152
// This mimics crypto/ecdsa.ParseRawPrivateKey from Go 1.25+ for compatibility with Go 1.24.
139153
func parseRawPrivateKey(curve elliptic.Curve, privateKeyBytes []byte) (*ecdsa.PrivateKey, error) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package auth
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"crypto/ecdsa"
7+
"crypto/elliptic"
8+
"crypto/sha512"
9+
"encoding/hex"
10+
"math/big"
11+
"testing"
12+
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestEncodeECDSAP384Signature_FixedWidth(t *testing.T) {
17+
// r is small enough that big.Int.Bytes() drops its leading zero bytes. The
18+
// old append(r.Bytes(), s.Bytes()...) then produced fewer than 96 bytes,
19+
// which the registry rejects with "invalid signature size for ECDSA P-384".
20+
r := big.NewInt(1)
21+
s := new(big.Int).SetBytes(bytes.Repeat([]byte{0xff}, 48))
22+
23+
sig := encodeECDSAP384Signature(r, s)
24+
25+
require.Len(t, sig, 96)
26+
// The verifier splits the signature at byte 48, so a fixed-width encoding
27+
// must round-trip both components back exactly.
28+
require.Zero(t, new(big.Int).SetBytes(sig[:48]).Cmp(r))
29+
require.Zero(t, new(big.Int).SetBytes(sig[48:]).Cmp(s))
30+
}
31+
32+
func TestInProcessSigner_ECDSAP384_SignatureVerifies(t *testing.T) {
33+
// Fixed, valid P-384 private scalar (well below the group order).
34+
scalar := bytes.Repeat([]byte{0x11}, 48)
35+
priv, err := parseRawPrivateKey(elliptic.P384(), scalar)
36+
require.NoError(t, err)
37+
38+
signer, err := NewInProcessSigner(hex.EncodeToString(scalar), AlgorithmECDSAP384)
39+
require.NoError(t, err)
40+
41+
timestamp, signature, err := signer.GetSignedTimestamp(context.Background())
42+
require.NoError(t, err)
43+
require.Len(t, signature, 96)
44+
45+
digest := sha512.Sum384([]byte(*timestamp))
46+
r := new(big.Int).SetBytes(signature[:48])
47+
s := new(big.Int).SetBytes(signature[48:])
48+
require.True(t, ecdsa.Verify(&priv.PublicKey, digest[:], r, s))
49+
}

0 commit comments

Comments
 (0)