Skip to content

Commit

Permalink
acme: expect standard ASN.1 signatures from ECDSA Client.Key
Browse files Browse the repository at this point in the history
Previously, an ECDSA crypto.Signer would have been expected to return a
signature in RFC7518 format, which violates crypto.Signer's interface
contract.

Fixes golang/go#35829

Change-Id: Id0cc2d9296cfb9f89925ab9ac02e12d68eec734b
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/209537
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
  • Loading branch information
edef1c authored and FiloSottile committed Dec 5, 2019
1 parent 72bf92a commit a28afe6
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 19 deletions.
27 changes: 15 additions & 12 deletions acme/jws.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"crypto/rsa"
"crypto/sha256"
_ "crypto/sha512" // need for EC keys
"encoding/asn1"
"encoding/base64"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -126,21 +127,23 @@ func jwkEncode(pub crypto.PublicKey) (string, error) {

// jwsSign signs the digest using the given key.
// The hash is unused for ECDSA keys.
//
// Note: non-stdlib crypto.Signer implementations are expected to return
// the signature in the format as specified in RFC7518.
// See https://tools.ietf.org/html/rfc7518 for more details.
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
if key, ok := key.(*ecdsa.PrivateKey); ok {
// The key.Sign method of ecdsa returns ASN1-encoded signature.
// So, we use the package Sign function instead
// to get R and S values directly and format the result accordingly.
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
switch pub := key.Public().(type) {
case *rsa.PublicKey:
return key.Sign(rand.Reader, digest, hash)
case *ecdsa.PublicKey:
sigASN1, err := key.Sign(rand.Reader, digest, hash)
if err != nil {
return nil, err
}
rb, sb := r.Bytes(), s.Bytes()
size := key.Params().BitSize / 8

var rs struct{ R, S *big.Int }
if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil {
return nil, err
}

rb, sb := rs.R.Bytes(), rs.S.Bytes()
size := pub.Params().BitSize / 8
if size%8 > 0 {
size++
}
Expand All @@ -149,7 +152,7 @@ func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error)
copy(sig[size*2-len(sb):], sb)
return sig, nil
}
return key.Sign(rand.Reader, digest, hash)
return nil, ErrUnsupportedKey
}

// jwsHasher indicates suitable JWS algorithm name and a hash function
Expand Down
28 changes: 21 additions & 7 deletions acme/jws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,14 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
// printf 'testsig' | b64raw
testsig = "dGVzdHNpZw"

// the example P256 curve point from https://tools.ietf.org/html/rfc7515#appendix-A.3.1
// encoded as ASN.1…
es256stdsig = "MEUCIA7RIVN5Y2xIPC9/FVgH1AKjsigDOvl8fheBmsMWnqZlAiEA" +
"xQoH04w8cOXY8S2vCEpUgKZlkMXyk1Cajz9/ioOjVNU"
// …and RFC7518 (https://tools.ietf.org/html/rfc7518#section-3.4)
es256jwsig = "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw" +
"5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"

// printf '{"alg":"ES256","jwk":{"crv":"P-256","kty":"EC","x":<testKeyECPubY>,"y":<testKeyECPubY>},"nonce":"nonce","url":"url"}' | b64raw
es256phead = "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0" +
"eSI6IkVDIiwieCI6IjVsaEV1ZzV4SzR4QkRaMm5BYmF4THRhTGl2" +
Expand All @@ -345,19 +353,25 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
)

tt := []struct {
alg, phead string
pub crypto.PublicKey
alg, phead string
pub crypto.PublicKey
stdsig, jwsig string
}{
{"ES256", es256phead, testKeyEC.Public()},
{"RS256", rs256phead, testKey.Public()},
{"ES256", es256phead, testKeyEC.Public(), es256stdsig, es256jwsig},
{"RS256", rs256phead, testKey.Public(), testsig, testsig},
}
for _, tc := range tt {
tc := tc
t.Run(tc.alg, func(t *testing.T) {
stdsig, err := base64.RawStdEncoding.DecodeString(tc.stdsig)
if err != nil {
t.Errorf("couldn't decode test vector: %v", err)
}
signer := &customTestSigner{
sig: []byte("testsig"),
sig: stdsig,
pub: tc.pub,
}

b, err := jwsEncodeJSON(claims, signer, noKeyID, "nonce", "url")
if err != nil {
t.Fatal(err)
Expand All @@ -372,8 +386,8 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
if j.Payload != payload {
t.Errorf("j.Payload = %q\nwant %q", j.Payload, payload)
}
if j.Signature != testsig {
t.Errorf("j.Signature = %q\nwant %q", j.Signature, testsig)
if j.Signature != tc.jwsig {
t.Errorf("j.Signature = %q\nwant %q", j.Signature, tc.jwsig)
}
})
}
Expand Down

0 comments on commit a28afe6

Please sign in to comment.