| // 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 TestJWSEncodeNoNonce(t *testing.T) { |
| kid := KeyID("https://example.org/account/1") |
| claims := "RawString" |
| const ( |
| // {"alg":"ES256","kid":"https://example.org/account/1","nonce":"nonce","url":"url"} |
| protected = "eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYWNjb3VudC8xIiwidXJsIjoidXJsIn0" |
| // "Raw String" |
| payload = "RawString" |
| ) |
| |
| b, err := jwsEncodeJSON(claims, testKeyEC, kid, "", "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 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) |
| } |
| } |