HMAC-SHA256 is a perfectly fine MAC algorithm, and there is no need to ask the user to choose one. This does break compatibility with the previous API, but it had been live only for a weekend, so hopefully still in a window in which we can make changes with a limited blast radius. Updates golang/go#41430 Change-Id: I03741a545b25b9fcc147760cd20e9d7029844a6c Reviewed-on: https://go-review.googlesource.com/c/crypto/+/279453 Trust: Filippo Valsorda <filippo@golang.org> Run-TryBot: Filippo Valsorda <filippo@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: James Kasten <jdkasten@google.com> Reviewed-by: Roland Shoemaker <roland@golang.org>
513 lines
17 KiB
Go
513 lines
17 KiB
Go
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package acme
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"testing"
|
|
)
|
|
|
|
// The following shell command alias is used in the comments
|
|
// throughout this file:
|
|
// alias b64raw="base64 -w0 | tr -d '=' | tr '/+' '_-'"
|
|
|
|
const (
|
|
// Modulus in raw base64:
|
|
// 4xgZ3eRPkwoRvy7qeRUbmMDe0V-xH9eWLdu0iheeLlrmD2mqWXfP9IeSKApbn34
|
|
// g8TuAS9g5zhq8ELQ3kmjr-KV86GAMgI6VAcGlq3QrzpTCf_30Ab7-zawrfRaFON
|
|
// a1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosqEXeaIkVYBEhbh
|
|
// Nu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZfoyFyek380mHg
|
|
// JAumQ_I2fjj98_97mk3ihOY4AgVdCDj1z_GCoZkG5Rq7nbCGyosyKWyDX00Zs-n
|
|
// NqVhoLeIvXC4nnWdJMZ6rogxyQQ
|
|
testKeyPEM = `
|
|
-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq
|
|
WXfP9IeSKApbn34g8TuAS9g5zhq8ELQ3kmjr+KV86GAMgI6VAcGlq3QrzpTCf/30
|
|
Ab7+zawrfRaFONa1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosq
|
|
EXeaIkVYBEhbhNu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZf
|
|
oyFyek380mHgJAumQ/I2fjj98/97mk3ihOY4AgVdCDj1z/GCoZkG5Rq7nbCGyosy
|
|
KWyDX00Zs+nNqVhoLeIvXC4nnWdJMZ6rogxyQQIDAQABAoIBACIEZTOI1Kao9nmV
|
|
9IeIsuaR1Y61b9neOF/MLmIVIZu+AAJFCMB4Iw11FV6sFodwpEyeZhx2WkpWVN+H
|
|
r19eGiLX3zsL0DOdqBJoSIHDWCCMxgnYJ6nvS0nRxX3qVrBp8R2g12Ub+gNPbmFm
|
|
ecf/eeERIVxfifd9VsyRu34eDEvcmKFuLYbElFcPh62xE3x12UZvV/sN7gXbawpP
|
|
G+w255vbE5MoaKdnnO83cTFlcHvhn24M/78qP7Te5OAeelr1R89kYxQLpuGe4fbS
|
|
zc6E3ym5Td6urDetGGrSY1Eu10/8sMusX+KNWkm+RsBRbkyKq72ks/qKpOxOa+c6
|
|
9gm+Y8ECgYEA/iNUyg1ubRdH11p82l8KHtFC1DPE0V1gSZsX29TpM5jS4qv46K+s
|
|
8Ym1zmrORM8x+cynfPx1VQZQ34EYeCMIX212ryJ+zDATl4NE0I4muMvSiH9vx6Xc
|
|
7FmhNnaYzPsBL5Tm9nmtQuP09YEn8poiOJFiDs/4olnD5ogA5O4THGkCgYEA5MIL
|
|
qWYBUuqbEWLRtMruUtpASclrBqNNsJEsMGbeqBJmoMxdHeSZckbLOrqm7GlMyNRJ
|
|
Ne/5uWRGSzaMYuGmwsPpERzqEvYFnSrpjW5YtXZ+JtxFXNVfm9Z1gLLgvGpOUCIU
|
|
RbpoDckDe1vgUuk3y5+DjZihs+rqIJ45XzXTzBkCgYBWuf3segruJZy5rEKhTv+o
|
|
JqeUvRn0jNYYKFpLBeyTVBrbie6GkbUGNIWbrK05pC+c3K9nosvzuRUOQQL1tJbd
|
|
4gA3oiD9U4bMFNr+BRTHyZ7OQBcIXdz3t1qhuHVKtnngIAN1p25uPlbRFUNpshnt
|
|
jgeVoHlsBhApcs5DUc+pyQKBgDzeHPg/+g4z+nrPznjKnktRY1W+0El93kgi+J0Q
|
|
YiJacxBKEGTJ1MKBb8X6sDurcRDm22wMpGfd9I5Cv2v4GsUsF7HD/cx5xdih+G73
|
|
c4clNj/k0Ff5Nm1izPUno4C+0IOl7br39IPmfpSuR6wH/h6iHQDqIeybjxyKvT1G
|
|
N0rRAoGBAKGD+4ZI/E1MoJ5CXB8cDDMHagbE3cq/DtmYzE2v1DFpQYu5I4PCm5c7
|
|
EQeIP6dZtv8IMgtGIb91QX9pXvP0aznzQKwYIA8nZgoENCPfiMTPiEDT9e/0lObO
|
|
9XWsXpbSTsRPj0sv1rB+UzBJ0PgjK4q2zOF0sNo7b1+6nlM3BWPx
|
|
-----END RSA PRIVATE KEY-----
|
|
`
|
|
|
|
// This thumbprint is for the testKey defined above.
|
|
testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ"
|
|
|
|
// openssl ecparam -name secp256k1 -genkey -noout
|
|
testKeyECPEM = `
|
|
-----BEGIN EC PRIVATE KEY-----
|
|
MHcCAQEEIK07hGLr0RwyUdYJ8wbIiBS55CjnkMD23DWr+ccnypWLoAoGCCqGSM49
|
|
AwEHoUQDQgAE5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HThqIrvawF5
|
|
QAaS/RNouybCiRhRjI3EaxLkQwgrCw0gqQ==
|
|
-----END EC PRIVATE KEY-----
|
|
`
|
|
// openssl ecparam -name secp384r1 -genkey -noout
|
|
testKeyEC384PEM = `
|
|
-----BEGIN EC PRIVATE KEY-----
|
|
MIGkAgEBBDAQ4lNtXRORWr1bgKR1CGysr9AJ9SyEk4jiVnlUWWUChmSNL+i9SLSD
|
|
Oe/naPqXJ6CgBwYFK4EEACKhZANiAAQzKtj+Ms0vHoTX5dzv3/L5YMXOWuI5UKRj
|
|
JigpahYCqXD2BA1j0E/2xt5vlPf+gm0PL+UHSQsCokGnIGuaHCsJAp3ry0gHQEke
|
|
WYXapUUFdvaK1R2/2hn5O+eiQM8YzCg=
|
|
-----END EC PRIVATE KEY-----
|
|
`
|
|
// openssl ecparam -name secp521r1 -genkey -noout
|
|
testKeyEC512PEM = `
|
|
-----BEGIN EC PRIVATE KEY-----
|
|
MIHcAgEBBEIBSNZKFcWzXzB/aJClAb305ibalKgtDA7+70eEkdPt28/3LZMM935Z
|
|
KqYHh/COcxuu3Kt8azRAUz3gyr4zZKhlKUSgBwYFK4EEACOhgYkDgYYABAHUNKbx
|
|
7JwC7H6pa2sV0tERWhHhB3JmW+OP6SUgMWryvIKajlx73eS24dy4QPGrWO9/ABsD
|
|
FqcRSkNVTXnIv6+0mAF25knqIBIg5Q8M9BnOu9GGAchcwt3O7RDHmqewnJJDrbjd
|
|
GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ==
|
|
-----END EC PRIVATE KEY-----
|
|
`
|
|
// 1. openssl ec -in key.pem -noout -text
|
|
// 2. remove first byte, 04 (the header); the rest is X and Y
|
|
// 3. convert each with: echo <val> | xxd -r -p | b64raw
|
|
testKeyECPubX = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ"
|
|
testKeyECPubY = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk"
|
|
testKeyEC384PubX = "MyrY_jLNLx6E1-Xc79_y-WDFzlriOVCkYyYoKWoWAqlw9gQNY9BP9sbeb5T3_oJt"
|
|
testKeyEC384PubY = "Dy_lB0kLAqJBpyBrmhwrCQKd68tIB0BJHlmF2qVFBXb2itUdv9oZ-TvnokDPGMwo"
|
|
testKeyEC512PubX = "AdQ0pvHsnALsfqlraxXS0RFaEeEHcmZb44_pJSAxavK8gpqOXHvd5Lbh3LhA8atY738AGwMWpxFKQ1VNeci_r7SY"
|
|
testKeyEC512PubY = "AXbmSeogEiDlDwz0Gc670YYByFzC3c7tEMeap7CckkOtuN0Yaebqtv42dZH0MiikzSco2ROhagX-HOinG7gB78ax"
|
|
|
|
// echo -n '{"crv":"P-256","kty":"EC","x":"<testKeyECPubX>","y":"<testKeyECPubY>"}' | \
|
|
// openssl dgst -binary -sha256 | b64raw
|
|
testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU"
|
|
)
|
|
|
|
var (
|
|
testKey *rsa.PrivateKey
|
|
testKeyEC *ecdsa.PrivateKey
|
|
testKeyEC384 *ecdsa.PrivateKey
|
|
testKeyEC512 *ecdsa.PrivateKey
|
|
)
|
|
|
|
func init() {
|
|
testKey = parseRSA(testKeyPEM, "testKeyPEM")
|
|
testKeyEC = parseEC(testKeyECPEM, "testKeyECPEM")
|
|
testKeyEC384 = parseEC(testKeyEC384PEM, "testKeyEC384PEM")
|
|
testKeyEC512 = parseEC(testKeyEC512PEM, "testKeyEC512PEM")
|
|
}
|
|
|
|
func decodePEM(s, name string) []byte {
|
|
d, _ := pem.Decode([]byte(s))
|
|
if d == nil {
|
|
panic("no block found in " + name)
|
|
}
|
|
return d.Bytes
|
|
}
|
|
|
|
func parseRSA(s, name string) *rsa.PrivateKey {
|
|
b := decodePEM(s, name)
|
|
k, err := x509.ParsePKCS1PrivateKey(b)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("%s: %v", name, err))
|
|
}
|
|
return k
|
|
}
|
|
|
|
func parseEC(s, name string) *ecdsa.PrivateKey {
|
|
b := decodePEM(s, name)
|
|
k, err := x509.ParseECPrivateKey(b)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("%s: %v", name, err))
|
|
}
|
|
return k
|
|
}
|
|
|
|
func TestJWSEncodeJSON(t *testing.T) {
|
|
claims := struct{ Msg string }{"Hello JWS"}
|
|
// JWS signed with testKey and "nonce" as the nonce value
|
|
// JSON-serialized JWS fields are split for easier testing
|
|
const (
|
|
// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url"}
|
|
protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
|
|
"IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
|
|
"SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
|
|
"QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" +
|
|
"VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" +
|
|
"NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" +
|
|
"QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" +
|
|
"bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
|
|
"ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
|
|
"b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
|
|
"UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
|
|
// {"Msg":"Hello JWS"}
|
|
payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
|
|
// printf '<protected>.<payload>' | openssl dgst -binary -sha256 -sign testKey | b64raw
|
|
signature = "YFyl_xz1E7TR-3E1bIuASTr424EgCvBHjt25WUFC2VaDjXYV0Rj_" +
|
|
"Hd3dJ_2IRqBrXDZZ2n4ZeA_4mm3QFwmwyeDwe2sWElhb82lCZ8iX" +
|
|
"uFnjeOmSOjx-nWwPa5ibCXzLq13zZ-OBV1Z4oN_TuailQeRoSfA3" +
|
|
"nO8gG52mv1x2OMQ5MAFtt8jcngBLzts4AyhI6mBJ2w7Yaj3ZCriq" +
|
|
"DWA3GLFvvHdW1Ba9Z01wtGT2CuZI7DUk_6Qj1b3BkBGcoKur5C9i" +
|
|
"bUJtCkABwBMvBQNyD3MmXsrRFRTgvVlyU_yMaucYm7nmzEr_2PaQ" +
|
|
"50rFt_9qOfJ4sfbLtG1Wwae57BQx1g"
|
|
)
|
|
|
|
b, err := jwsEncodeJSON(claims, testKey, noKeyID, "nonce", "url")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var jws struct{ Protected, Payload, Signature string }
|
|
if err := json.Unmarshal(b, &jws); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if jws.Protected != protected {
|
|
t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected)
|
|
}
|
|
if jws.Payload != payload {
|
|
t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload)
|
|
}
|
|
if jws.Signature != signature {
|
|
t.Errorf("signature:\n%s\nwant:\n%s", jws.Signature, signature)
|
|
}
|
|
}
|
|
|
|
func TestJWSEncodeKID(t *testing.T) {
|
|
kid := keyID("https://example.org/account/1")
|
|
claims := struct{ Msg string }{"Hello JWS"}
|
|
// JWS signed with testKeyEC
|
|
const (
|
|
// {"alg":"ES256","kid":"https://example.org/account/1","nonce":"nonce","url":"url"}
|
|
protected = "eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vZXhhbXBsZS5" +
|
|
"vcmcvYWNjb3VudC8xIiwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
|
|
// {"Msg":"Hello JWS"}
|
|
payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
|
|
)
|
|
|
|
b, err := jwsEncodeJSON(claims, testKeyEC, kid, "nonce", "url")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var jws struct{ Protected, Payload, Signature string }
|
|
if err := json.Unmarshal(b, &jws); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if jws.Protected != protected {
|
|
t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected)
|
|
}
|
|
if jws.Payload != payload {
|
|
t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload)
|
|
}
|
|
|
|
sig, err := base64.RawURLEncoding.DecodeString(jws.Signature)
|
|
if err != nil {
|
|
t.Fatalf("jws.Signature: %v", err)
|
|
}
|
|
r, s := big.NewInt(0), big.NewInt(0)
|
|
r.SetBytes(sig[:len(sig)/2])
|
|
s.SetBytes(sig[len(sig)/2:])
|
|
h := sha256.Sum256([]byte(protected + "." + payload))
|
|
if !ecdsa.Verify(testKeyEC.Public().(*ecdsa.PublicKey), h[:], r, s) {
|
|
t.Error("invalid signature")
|
|
}
|
|
}
|
|
|
|
func TestJWSEncodeJSONEC(t *testing.T) {
|
|
tt := []struct {
|
|
key *ecdsa.PrivateKey
|
|
x, y string
|
|
alg, crv string
|
|
}{
|
|
{testKeyEC, testKeyECPubX, testKeyECPubY, "ES256", "P-256"},
|
|
{testKeyEC384, testKeyEC384PubX, testKeyEC384PubY, "ES384", "P-384"},
|
|
{testKeyEC512, testKeyEC512PubX, testKeyEC512PubY, "ES512", "P-521"},
|
|
}
|
|
for i, test := range tt {
|
|
claims := struct{ Msg string }{"Hello JWS"}
|
|
b, err := jwsEncodeJSON(claims, test.key, noKeyID, "nonce", "url")
|
|
if err != nil {
|
|
t.Errorf("%d: %v", i, err)
|
|
continue
|
|
}
|
|
var jws struct{ Protected, Payload, Signature string }
|
|
if err := json.Unmarshal(b, &jws); err != nil {
|
|
t.Errorf("%d: %v", i, err)
|
|
continue
|
|
}
|
|
|
|
b, err = base64.RawURLEncoding.DecodeString(jws.Protected)
|
|
if err != nil {
|
|
t.Errorf("%d: jws.Protected: %v", i, err)
|
|
}
|
|
var head struct {
|
|
Alg string
|
|
Nonce string
|
|
URL string `json:"url"`
|
|
KID string `json:"kid"`
|
|
JWK struct {
|
|
Crv string
|
|
Kty string
|
|
X string
|
|
Y string
|
|
} `json:"jwk"`
|
|
}
|
|
if err := json.Unmarshal(b, &head); err != nil {
|
|
t.Errorf("%d: jws.Protected: %v", i, err)
|
|
}
|
|
if head.Alg != test.alg {
|
|
t.Errorf("%d: head.Alg = %q; want %q", i, head.Alg, test.alg)
|
|
}
|
|
if head.Nonce != "nonce" {
|
|
t.Errorf("%d: head.Nonce = %q; want nonce", i, head.Nonce)
|
|
}
|
|
if head.URL != "url" {
|
|
t.Errorf("%d: head.URL = %q; want 'url'", i, head.URL)
|
|
}
|
|
if head.KID != "" {
|
|
// We used noKeyID in jwsEncodeJSON: expect no kid value.
|
|
t.Errorf("%d: head.KID = %q; want empty", i, head.KID)
|
|
}
|
|
if head.JWK.Crv != test.crv {
|
|
t.Errorf("%d: head.JWK.Crv = %q; want %q", i, head.JWK.Crv, test.crv)
|
|
}
|
|
if head.JWK.Kty != "EC" {
|
|
t.Errorf("%d: head.JWK.Kty = %q; want EC", i, head.JWK.Kty)
|
|
}
|
|
if head.JWK.X != test.x {
|
|
t.Errorf("%d: head.JWK.X = %q; want %q", i, head.JWK.X, test.x)
|
|
}
|
|
if head.JWK.Y != test.y {
|
|
t.Errorf("%d: head.JWK.Y = %q; want %q", i, head.JWK.Y, test.y)
|
|
}
|
|
}
|
|
}
|
|
|
|
type customTestSigner struct {
|
|
sig []byte
|
|
pub crypto.PublicKey
|
|
}
|
|
|
|
func (s *customTestSigner) Public() crypto.PublicKey { return s.pub }
|
|
func (s *customTestSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, error) {
|
|
return s.sig, nil
|
|
}
|
|
|
|
func TestJWSEncodeJSONCustom(t *testing.T) {
|
|
claims := struct{ Msg string }{"hello"}
|
|
const (
|
|
// printf '{"Msg":"hello"}' | b64raw
|
|
payload = "eyJNc2ciOiJoZWxsbyJ9"
|
|
// 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" +
|
|
"ODVieEo3ZVBkMWRrTzIzSFEiLCJ5IjoiNGFpSzcyc0JlVUFHa3Yw" +
|
|
"VGFMc213b2tZVVl5TnhHc1M1RU1JS3dzTklLayJ9LCJub25jZSI6" +
|
|
"Im5vbmNlIiwidXJsIjoidXJsIn0"
|
|
|
|
// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url"}
|
|
rs256phead = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
|
|
"IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
|
|
"SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
|
|
"QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" +
|
|
"VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" +
|
|
"NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" +
|
|
"QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" +
|
|
"bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
|
|
"ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
|
|
"b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
|
|
"UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
|
|
)
|
|
|
|
tt := []struct {
|
|
alg, phead string
|
|
pub crypto.PublicKey
|
|
stdsig, jwsig string
|
|
}{
|
|
{"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: stdsig,
|
|
pub: tc.pub,
|
|
}
|
|
|
|
b, err := jwsEncodeJSON(claims, signer, noKeyID, "nonce", "url")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var j jsonWebSignature
|
|
if err := json.Unmarshal(b, &j); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if j.Protected != tc.phead {
|
|
t.Errorf("j.Protected = %q\nwant %q", j.Protected, tc.phead)
|
|
}
|
|
if j.Payload != payload {
|
|
t.Errorf("j.Payload = %q\nwant %q", j.Payload, payload)
|
|
}
|
|
if j.Sig != tc.jwsig {
|
|
t.Errorf("j.Sig = %q\nwant %q", j.Sig, tc.jwsig)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJWSWithMAC(t *testing.T) {
|
|
// Example from RFC 7520 Section 4.4.3.
|
|
// https://tools.ietf.org/html/rfc7520#section-4.4.3
|
|
b64Key := "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg"
|
|
rawPayload := []byte("It\xe2\x80\x99s a dangerous business, Frodo, going out your " +
|
|
"door. You step onto the road, and if you don't keep your feet, " +
|
|
"there\xe2\x80\x99s no knowing where you might be swept off " +
|
|
"to.")
|
|
protected := "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW" +
|
|
"VlZjMxNGJjNzAzNyJ9"
|
|
payload := "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg" +
|
|
"Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h" +
|
|
"ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi" +
|
|
"gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m" +
|
|
"ZiB0by4"
|
|
sig := "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0"
|
|
|
|
key, err := base64.RawURLEncoding.DecodeString(b64Key)
|
|
if err != nil {
|
|
t.Fatalf("unable to decode key: %q", b64Key)
|
|
}
|
|
got, err := jwsWithMAC(key, "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "", rawPayload)
|
|
if err != nil {
|
|
t.Fatalf("jwsWithMAC() = %q", err)
|
|
}
|
|
if got.Protected != protected {
|
|
t.Errorf("got.Protected = %q\nwant %q", got.Protected, protected)
|
|
}
|
|
if got.Payload != payload {
|
|
t.Errorf("got.Payload = %q\nwant %q", got.Payload, payload)
|
|
}
|
|
if got.Sig != sig {
|
|
t.Errorf("got.Signature = %q\nwant %q", got.Sig, sig)
|
|
}
|
|
}
|
|
|
|
func TestJWSWithMACError(t *testing.T) {
|
|
p := "{}"
|
|
if _, err := jwsWithMAC(nil, "", "", []byte(p)); err == nil {
|
|
t.Errorf("jwsWithMAC(nil, ...) = success; want err")
|
|
}
|
|
}
|
|
|
|
func TestJWKThumbprintRSA(t *testing.T) {
|
|
// Key example from RFC 7638
|
|
const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" +
|
|
"VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6" +
|
|
"4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD" +
|
|
"W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9" +
|
|
"1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH" +
|
|
"aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"
|
|
const base64E = "AQAB"
|
|
const expected = "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
|
|
|
|
b, err := base64.RawURLEncoding.DecodeString(base64N)
|
|
if err != nil {
|
|
t.Fatalf("Error parsing example key N: %v", err)
|
|
}
|
|
n := new(big.Int).SetBytes(b)
|
|
|
|
b, err = base64.RawURLEncoding.DecodeString(base64E)
|
|
if err != nil {
|
|
t.Fatalf("Error parsing example key E: %v", err)
|
|
}
|
|
e := new(big.Int).SetBytes(b)
|
|
|
|
pub := &rsa.PublicKey{N: n, E: int(e.Uint64())}
|
|
th, err := JWKThumbprint(pub)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if th != expected {
|
|
t.Errorf("thumbprint = %q; want %q", th, expected)
|
|
}
|
|
}
|
|
|
|
func TestJWKThumbprintEC(t *testing.T) {
|
|
// Key example from RFC 7520
|
|
// expected was computed with
|
|
// printf '{"crv":"P-521","kty":"EC","x":"<base64X>","y":"<base64Y>"}' | \
|
|
// openssl dgst -binary -sha256 | b64raw
|
|
const (
|
|
base64X = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkT" +
|
|
"KqjqvjyekWF-7ytDyRXYgCF5cj0Kt"
|
|
base64Y = "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUda" +
|
|
"QkAgDPrwQrJmbnX9cwlGfP-HqHZR1"
|
|
expected = "dHri3SADZkrush5HU_50AoRhcKFryN-PI6jPBtPL55M"
|
|
)
|
|
|
|
b, err := base64.RawURLEncoding.DecodeString(base64X)
|
|
if err != nil {
|
|
t.Fatalf("Error parsing example key X: %v", err)
|
|
}
|
|
x := new(big.Int).SetBytes(b)
|
|
|
|
b, err = base64.RawURLEncoding.DecodeString(base64Y)
|
|
if err != nil {
|
|
t.Fatalf("Error parsing example key Y: %v", err)
|
|
}
|
|
y := new(big.Int).SetBytes(b)
|
|
|
|
pub := &ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y}
|
|
th, err := JWKThumbprint(pub)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if th != expected {
|
|
t.Errorf("thumbprint = %q; want %q", th, expected)
|
|
}
|
|
}
|
|
|
|
func TestJWKThumbprintErrUnsupportedKey(t *testing.T) {
|
|
_, err := JWKThumbprint(struct{}{})
|
|
if err != ErrUnsupportedKey {
|
|
t.Errorf("err = %q; want %q", err, ErrUnsupportedKey)
|
|
}
|
|
}
|