1// Copyright 2015 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package acme 6 7import ( 8 "crypto" 9 "crypto/ecdsa" 10 "crypto/elliptic" 11 "crypto/rsa" 12 "crypto/x509" 13 "encoding/base64" 14 "encoding/json" 15 "encoding/pem" 16 "fmt" 17 "io" 18 "math/big" 19 "testing" 20) 21 22const ( 23 testKeyPEM = ` 24-----BEGIN RSA PRIVATE KEY----- 25MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq 26WXfP9IeSKApbn34g8TuAS9g5zhq8ELQ3kmjr+KV86GAMgI6VAcGlq3QrzpTCf/30 27Ab7+zawrfRaFONa1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosq 28EXeaIkVYBEhbhNu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZf 29oyFyek380mHgJAumQ/I2fjj98/97mk3ihOY4AgVdCDj1z/GCoZkG5Rq7nbCGyosy 30KWyDX00Zs+nNqVhoLeIvXC4nnWdJMZ6rogxyQQIDAQABAoIBACIEZTOI1Kao9nmV 319IeIsuaR1Y61b9neOF/MLmIVIZu+AAJFCMB4Iw11FV6sFodwpEyeZhx2WkpWVN+H 32r19eGiLX3zsL0DOdqBJoSIHDWCCMxgnYJ6nvS0nRxX3qVrBp8R2g12Ub+gNPbmFm 33ecf/eeERIVxfifd9VsyRu34eDEvcmKFuLYbElFcPh62xE3x12UZvV/sN7gXbawpP 34G+w255vbE5MoaKdnnO83cTFlcHvhn24M/78qP7Te5OAeelr1R89kYxQLpuGe4fbS 35zc6E3ym5Td6urDetGGrSY1Eu10/8sMusX+KNWkm+RsBRbkyKq72ks/qKpOxOa+c6 369gm+Y8ECgYEA/iNUyg1ubRdH11p82l8KHtFC1DPE0V1gSZsX29TpM5jS4qv46K+s 378Ym1zmrORM8x+cynfPx1VQZQ34EYeCMIX212ryJ+zDATl4NE0I4muMvSiH9vx6Xc 387FmhNnaYzPsBL5Tm9nmtQuP09YEn8poiOJFiDs/4olnD5ogA5O4THGkCgYEA5MIL 39qWYBUuqbEWLRtMruUtpASclrBqNNsJEsMGbeqBJmoMxdHeSZckbLOrqm7GlMyNRJ 40Ne/5uWRGSzaMYuGmwsPpERzqEvYFnSrpjW5YtXZ+JtxFXNVfm9Z1gLLgvGpOUCIU 41RbpoDckDe1vgUuk3y5+DjZihs+rqIJ45XzXTzBkCgYBWuf3segruJZy5rEKhTv+o 42JqeUvRn0jNYYKFpLBeyTVBrbie6GkbUGNIWbrK05pC+c3K9nosvzuRUOQQL1tJbd 434gA3oiD9U4bMFNr+BRTHyZ7OQBcIXdz3t1qhuHVKtnngIAN1p25uPlbRFUNpshnt 44jgeVoHlsBhApcs5DUc+pyQKBgDzeHPg/+g4z+nrPznjKnktRY1W+0El93kgi+J0Q 45YiJacxBKEGTJ1MKBb8X6sDurcRDm22wMpGfd9I5Cv2v4GsUsF7HD/cx5xdih+G73 46c4clNj/k0Ff5Nm1izPUno4C+0IOl7br39IPmfpSuR6wH/h6iHQDqIeybjxyKvT1G 47N0rRAoGBAKGD+4ZI/E1MoJ5CXB8cDDMHagbE3cq/DtmYzE2v1DFpQYu5I4PCm5c7 48EQeIP6dZtv8IMgtGIb91QX9pXvP0aznzQKwYIA8nZgoENCPfiMTPiEDT9e/0lObO 499XWsXpbSTsRPj0sv1rB+UzBJ0PgjK4q2zOF0sNo7b1+6nlM3BWPx 50-----END RSA PRIVATE KEY----- 51` 52 53 // This thumbprint is for the testKey defined above. 54 testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ" 55 56 // openssl ecparam -name secp256k1 -genkey -noout 57 testKeyECPEM = ` 58-----BEGIN EC PRIVATE KEY----- 59MHcCAQEEIK07hGLr0RwyUdYJ8wbIiBS55CjnkMD23DWr+ccnypWLoAoGCCqGSM49 60AwEHoUQDQgAE5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HThqIrvawF5 61QAaS/RNouybCiRhRjI3EaxLkQwgrCw0gqQ== 62-----END EC PRIVATE KEY----- 63` 64 // openssl ecparam -name secp384r1 -genkey -noout 65 testKeyEC384PEM = ` 66-----BEGIN EC PRIVATE KEY----- 67MIGkAgEBBDAQ4lNtXRORWr1bgKR1CGysr9AJ9SyEk4jiVnlUWWUChmSNL+i9SLSD 68Oe/naPqXJ6CgBwYFK4EEACKhZANiAAQzKtj+Ms0vHoTX5dzv3/L5YMXOWuI5UKRj 69JigpahYCqXD2BA1j0E/2xt5vlPf+gm0PL+UHSQsCokGnIGuaHCsJAp3ry0gHQEke 70WYXapUUFdvaK1R2/2hn5O+eiQM8YzCg= 71-----END EC PRIVATE KEY----- 72` 73 // openssl ecparam -name secp521r1 -genkey -noout 74 testKeyEC512PEM = ` 75-----BEGIN EC PRIVATE KEY----- 76MIHcAgEBBEIBSNZKFcWzXzB/aJClAb305ibalKgtDA7+70eEkdPt28/3LZMM935Z 77KqYHh/COcxuu3Kt8azRAUz3gyr4zZKhlKUSgBwYFK4EEACOhgYkDgYYABAHUNKbx 787JwC7H6pa2sV0tERWhHhB3JmW+OP6SUgMWryvIKajlx73eS24dy4QPGrWO9/ABsD 79FqcRSkNVTXnIv6+0mAF25knqIBIg5Q8M9BnOu9GGAchcwt3O7RDHmqewnJJDrbjd 80GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ== 81-----END EC PRIVATE KEY----- 82` 83 // 1. openssl ec -in key.pem -noout -text 84 // 2. remove first byte, 04 (the header); the rest is X and Y 85 // 3. convert each with: echo <val> | xxd -r -p | base64 -w 100 | tr -d '=' | tr '/+' '_-' 86 testKeyECPubX = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ" 87 testKeyECPubY = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk" 88 testKeyEC384PubX = "MyrY_jLNLx6E1-Xc79_y-WDFzlriOVCkYyYoKWoWAqlw9gQNY9BP9sbeb5T3_oJt" 89 testKeyEC384PubY = "Dy_lB0kLAqJBpyBrmhwrCQKd68tIB0BJHlmF2qVFBXb2itUdv9oZ-TvnokDPGMwo" 90 testKeyEC512PubX = "AdQ0pvHsnALsfqlraxXS0RFaEeEHcmZb44_pJSAxavK8gpqOXHvd5Lbh3LhA8atY738AGwMWpxFKQ1VNeci_r7SY" 91 testKeyEC512PubY = "AXbmSeogEiDlDwz0Gc670YYByFzC3c7tEMeap7CckkOtuN0Yaebqtv42dZH0MiikzSco2ROhagX-HOinG7gB78ax" 92 93 // echo -n '{"crv":"P-256","kty":"EC","x":"<testKeyECPubX>","y":"<testKeyECPubY>"}' | \ 94 // openssl dgst -binary -sha256 | base64 | tr -d '=' | tr '/+' '_-' 95 testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU" 96) 97 98var ( 99 testKey *rsa.PrivateKey 100 testKeyEC *ecdsa.PrivateKey 101 testKeyEC384 *ecdsa.PrivateKey 102 testKeyEC512 *ecdsa.PrivateKey 103) 104 105func init() { 106 testKey = parseRSA(testKeyPEM, "testKeyPEM") 107 testKeyEC = parseEC(testKeyECPEM, "testKeyECPEM") 108 testKeyEC384 = parseEC(testKeyEC384PEM, "testKeyEC384PEM") 109 testKeyEC512 = parseEC(testKeyEC512PEM, "testKeyEC512PEM") 110} 111 112func decodePEM(s, name string) []byte { 113 d, _ := pem.Decode([]byte(s)) 114 if d == nil { 115 panic("no block found in " + name) 116 } 117 return d.Bytes 118} 119 120func parseRSA(s, name string) *rsa.PrivateKey { 121 b := decodePEM(s, name) 122 k, err := x509.ParsePKCS1PrivateKey(b) 123 if err != nil { 124 panic(fmt.Sprintf("%s: %v", name, err)) 125 } 126 return k 127} 128 129func parseEC(s, name string) *ecdsa.PrivateKey { 130 b := decodePEM(s, name) 131 k, err := x509.ParseECPrivateKey(b) 132 if err != nil { 133 panic(fmt.Sprintf("%s: %v", name, err)) 134 } 135 return k 136} 137 138func TestJWSEncodeJSON(t *testing.T) { 139 claims := struct{ Msg string }{"Hello JWS"} 140 // JWS signed with testKey and "nonce" as the nonce value 141 // JSON-serialized JWS fields are split for easier testing 142 const ( 143 // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce"} 144 protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" + 145 "IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" + 146 "SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" + 147 "QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" + 148 "VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" + 149 "NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" + 150 "QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" + 151 "bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" + 152 "ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" + 153 "b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" + 154 "UVEifSwibm9uY2UiOiJub25jZSJ9" 155 // {"Msg":"Hello JWS"} 156 payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ" 157 signature = "eAGUikStX_UxyiFhxSLMyuyBcIB80GeBkFROCpap2sW3EmkU_ggF" + 158 "knaQzxrTfItICSAXsCLIquZ5BbrSWA_4vdEYrwWtdUj7NqFKjHRa" + 159 "zpLHcoR7r1rEHvkoP1xj49lS5fc3Wjjq8JUhffkhGbWZ8ZVkgPdC" + 160 "4tMBWiQDoth-x8jELP_3LYOB_ScUXi2mETBawLgOT2K8rA0Vbbmx" + 161 "hWNlOWuUf-8hL5YX4IOEwsS8JK_TrTq5Zc9My0zHJmaieqDV0UlP" + 162 "k0onFjPFkGm7MrPSgd0MqRG-4vSAg2O4hDo7rKv4n8POjjXlNQvM" + 163 "9IPLr8qZ7usYBKhEGwX3yq_eicAwBw" 164 ) 165 166 b, err := jwsEncodeJSON(claims, testKey, "nonce") 167 if err != nil { 168 t.Fatal(err) 169 } 170 var jws struct{ Protected, Payload, Signature string } 171 if err := json.Unmarshal(b, &jws); err != nil { 172 t.Fatal(err) 173 } 174 if jws.Protected != protected { 175 t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected) 176 } 177 if jws.Payload != payload { 178 t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload) 179 } 180 if jws.Signature != signature { 181 t.Errorf("signature:\n%s\nwant:\n%s", jws.Signature, signature) 182 } 183} 184 185func TestJWSEncodeJSONEC(t *testing.T) { 186 tt := []struct { 187 key *ecdsa.PrivateKey 188 x, y string 189 alg, crv string 190 }{ 191 {testKeyEC, testKeyECPubX, testKeyECPubY, "ES256", "P-256"}, 192 {testKeyEC384, testKeyEC384PubX, testKeyEC384PubY, "ES384", "P-384"}, 193 {testKeyEC512, testKeyEC512PubX, testKeyEC512PubY, "ES512", "P-521"}, 194 } 195 for i, test := range tt { 196 claims := struct{ Msg string }{"Hello JWS"} 197 b, err := jwsEncodeJSON(claims, test.key, "nonce") 198 if err != nil { 199 t.Errorf("%d: %v", i, err) 200 continue 201 } 202 var jws struct{ Protected, Payload, Signature string } 203 if err := json.Unmarshal(b, &jws); err != nil { 204 t.Errorf("%d: %v", i, err) 205 continue 206 } 207 208 b, err = base64.RawURLEncoding.DecodeString(jws.Protected) 209 if err != nil { 210 t.Errorf("%d: jws.Protected: %v", i, err) 211 } 212 var head struct { 213 Alg string 214 Nonce string 215 JWK struct { 216 Crv string 217 Kty string 218 X string 219 Y string 220 } `json:"jwk"` 221 } 222 if err := json.Unmarshal(b, &head); err != nil { 223 t.Errorf("%d: jws.Protected: %v", i, err) 224 } 225 if head.Alg != test.alg { 226 t.Errorf("%d: head.Alg = %q; want %q", i, head.Alg, test.alg) 227 } 228 if head.Nonce != "nonce" { 229 t.Errorf("%d: head.Nonce = %q; want nonce", i, head.Nonce) 230 } 231 if head.JWK.Crv != test.crv { 232 t.Errorf("%d: head.JWK.Crv = %q; want %q", i, head.JWK.Crv, test.crv) 233 } 234 if head.JWK.Kty != "EC" { 235 t.Errorf("%d: head.JWK.Kty = %q; want EC", i, head.JWK.Kty) 236 } 237 if head.JWK.X != test.x { 238 t.Errorf("%d: head.JWK.X = %q; want %q", i, head.JWK.X, test.x) 239 } 240 if head.JWK.Y != test.y { 241 t.Errorf("%d: head.JWK.Y = %q; want %q", i, head.JWK.Y, test.y) 242 } 243 } 244} 245 246type customTestSigner struct { 247 sig []byte 248 pub crypto.PublicKey 249} 250 251func (s *customTestSigner) Public() crypto.PublicKey { return s.pub } 252func (s *customTestSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, error) { 253 return s.sig, nil 254} 255 256func TestJWSEncodeJSONCustom(t *testing.T) { 257 claims := struct{ Msg string }{"hello"} 258 const ( 259 // printf '{"Msg":"hello"}' | base64 | tr -d '=' | tr '/+' '_-' 260 payload = "eyJNc2ciOiJoZWxsbyJ9" 261 // printf 'testsig' | base64 | tr -d '=' 262 testsig = "dGVzdHNpZw" 263 264 // printf '{"alg":"ES256","jwk":{"crv":"P-256","kty":"EC","x":<testKeyECPubY>,"y":<testKeyECPubY>,"nonce":"nonce"}' | \ 265 // base64 | tr -d '=' | tr '/+' '_-' 266 es256phead = "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjVsaEV1" + 267 "ZzV4SzR4QkRaMm5BYmF4THRhTGl2ODVieEo3ZVBkMWRrTzIzSFEiLCJ5IjoiNGFpSzcyc0JlVUFH" + 268 "a3YwVGFMc213b2tZVVl5TnhHc1M1RU1JS3dzTklLayJ9LCJub25jZSI6Im5vbmNlIn0" 269 270 // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce"} 271 rs256phead = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" + 272 "IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" + 273 "SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" + 274 "QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" + 275 "VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" + 276 "NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" + 277 "QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" + 278 "bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" + 279 "ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" + 280 "b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" + 281 "UVEifSwibm9uY2UiOiJub25jZSJ9" 282 ) 283 284 tt := []struct { 285 alg, phead string 286 pub crypto.PublicKey 287 }{ 288 {"RS256", rs256phead, testKey.Public()}, 289 {"ES256", es256phead, testKeyEC.Public()}, 290 } 291 for _, tc := range tt { 292 tc := tc 293 t.Run(tc.alg, func(t *testing.T) { 294 signer := &customTestSigner{ 295 sig: []byte("testsig"), 296 pub: tc.pub, 297 } 298 b, err := jwsEncodeJSON(claims, signer, "nonce") 299 if err != nil { 300 t.Fatal(err) 301 } 302 var j struct{ Protected, Payload, Signature string } 303 if err := json.Unmarshal(b, &j); err != nil { 304 t.Fatal(err) 305 } 306 if j.Protected != tc.phead { 307 t.Errorf("j.Protected = %q\nwant %q", j.Protected, tc.phead) 308 } 309 if j.Payload != payload { 310 t.Errorf("j.Payload = %q\nwant %q", j.Payload, payload) 311 } 312 if j.Signature != testsig { 313 t.Errorf("j.Signature = %q\nwant %q", j.Signature, testsig) 314 } 315 }) 316 } 317} 318 319func TestJWKThumbprintRSA(t *testing.T) { 320 // Key example from RFC 7638 321 const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" + 322 "VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6" + 323 "4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD" + 324 "W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9" + 325 "1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH" + 326 "aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" 327 const base64E = "AQAB" 328 const expected = "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs" 329 330 b, err := base64.RawURLEncoding.DecodeString(base64N) 331 if err != nil { 332 t.Fatalf("Error parsing example key N: %v", err) 333 } 334 n := new(big.Int).SetBytes(b) 335 336 b, err = base64.RawURLEncoding.DecodeString(base64E) 337 if err != nil { 338 t.Fatalf("Error parsing example key E: %v", err) 339 } 340 e := new(big.Int).SetBytes(b) 341 342 pub := &rsa.PublicKey{N: n, E: int(e.Uint64())} 343 th, err := JWKThumbprint(pub) 344 if err != nil { 345 t.Error(err) 346 } 347 if th != expected { 348 t.Errorf("thumbprint = %q; want %q", th, expected) 349 } 350} 351 352func TestJWKThumbprintEC(t *testing.T) { 353 // Key example from RFC 7520 354 // expected was computed with 355 // echo -n '{"crv":"P-521","kty":"EC","x":"<base64X>","y":"<base64Y>"}' | \ 356 // openssl dgst -binary -sha256 | \ 357 // base64 | \ 358 // tr -d '=' | tr '/+' '_-' 359 const ( 360 base64X = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkT" + 361 "KqjqvjyekWF-7ytDyRXYgCF5cj0Kt" 362 base64Y = "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUda" + 363 "QkAgDPrwQrJmbnX9cwlGfP-HqHZR1" 364 expected = "dHri3SADZkrush5HU_50AoRhcKFryN-PI6jPBtPL55M" 365 ) 366 367 b, err := base64.RawURLEncoding.DecodeString(base64X) 368 if err != nil { 369 t.Fatalf("Error parsing example key X: %v", err) 370 } 371 x := new(big.Int).SetBytes(b) 372 373 b, err = base64.RawURLEncoding.DecodeString(base64Y) 374 if err != nil { 375 t.Fatalf("Error parsing example key Y: %v", err) 376 } 377 y := new(big.Int).SetBytes(b) 378 379 pub := &ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y} 380 th, err := JWKThumbprint(pub) 381 if err != nil { 382 t.Error(err) 383 } 384 if th != expected { 385 t.Errorf("thumbprint = %q; want %q", th, expected) 386 } 387} 388 389func TestJWKThumbprintErrUnsupportedKey(t *testing.T) { 390 _, err := JWKThumbprint(struct{}{}) 391 if err != ErrUnsupportedKey { 392 t.Errorf("err = %q; want %q", err, ErrUnsupportedKey) 393 } 394} 395