Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 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 | |
| 5 | package acme |
| 6 | |
| 7 | import ( |
Anmol Sethi | e0d166c | 2016-08-04 00:47:19 -0400 | [diff] [blame] | 8 | "crypto/ecdsa" |
| 9 | "crypto/elliptic" |
Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 10 | "crypto/rsa" |
| 11 | "crypto/x509" |
| 12 | "encoding/base64" |
| 13 | "encoding/json" |
| 14 | "encoding/pem" |
| 15 | "math/big" |
| 16 | "testing" |
| 17 | ) |
| 18 | |
| 19 | const testKeyPEM = ` |
| 20 | -----BEGIN RSA PRIVATE KEY----- |
| 21 | MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq |
| 22 | WXfP9IeSKApbn34g8TuAS9g5zhq8ELQ3kmjr+KV86GAMgI6VAcGlq3QrzpTCf/30 |
| 23 | Ab7+zawrfRaFONa1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosq |
| 24 | EXeaIkVYBEhbhNu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZf |
| 25 | oyFyek380mHgJAumQ/I2fjj98/97mk3ihOY4AgVdCDj1z/GCoZkG5Rq7nbCGyosy |
| 26 | KWyDX00Zs+nNqVhoLeIvXC4nnWdJMZ6rogxyQQIDAQABAoIBACIEZTOI1Kao9nmV |
| 27 | 9IeIsuaR1Y61b9neOF/MLmIVIZu+AAJFCMB4Iw11FV6sFodwpEyeZhx2WkpWVN+H |
| 28 | r19eGiLX3zsL0DOdqBJoSIHDWCCMxgnYJ6nvS0nRxX3qVrBp8R2g12Ub+gNPbmFm |
| 29 | ecf/eeERIVxfifd9VsyRu34eDEvcmKFuLYbElFcPh62xE3x12UZvV/sN7gXbawpP |
| 30 | G+w255vbE5MoaKdnnO83cTFlcHvhn24M/78qP7Te5OAeelr1R89kYxQLpuGe4fbS |
| 31 | zc6E3ym5Td6urDetGGrSY1Eu10/8sMusX+KNWkm+RsBRbkyKq72ks/qKpOxOa+c6 |
| 32 | 9gm+Y8ECgYEA/iNUyg1ubRdH11p82l8KHtFC1DPE0V1gSZsX29TpM5jS4qv46K+s |
| 33 | 8Ym1zmrORM8x+cynfPx1VQZQ34EYeCMIX212ryJ+zDATl4NE0I4muMvSiH9vx6Xc |
| 34 | 7FmhNnaYzPsBL5Tm9nmtQuP09YEn8poiOJFiDs/4olnD5ogA5O4THGkCgYEA5MIL |
| 35 | qWYBUuqbEWLRtMruUtpASclrBqNNsJEsMGbeqBJmoMxdHeSZckbLOrqm7GlMyNRJ |
| 36 | Ne/5uWRGSzaMYuGmwsPpERzqEvYFnSrpjW5YtXZ+JtxFXNVfm9Z1gLLgvGpOUCIU |
| 37 | RbpoDckDe1vgUuk3y5+DjZihs+rqIJ45XzXTzBkCgYBWuf3segruJZy5rEKhTv+o |
| 38 | JqeUvRn0jNYYKFpLBeyTVBrbie6GkbUGNIWbrK05pC+c3K9nosvzuRUOQQL1tJbd |
| 39 | 4gA3oiD9U4bMFNr+BRTHyZ7OQBcIXdz3t1qhuHVKtnngIAN1p25uPlbRFUNpshnt |
| 40 | jgeVoHlsBhApcs5DUc+pyQKBgDzeHPg/+g4z+nrPznjKnktRY1W+0El93kgi+J0Q |
| 41 | YiJacxBKEGTJ1MKBb8X6sDurcRDm22wMpGfd9I5Cv2v4GsUsF7HD/cx5xdih+G73 |
| 42 | c4clNj/k0Ff5Nm1izPUno4C+0IOl7br39IPmfpSuR6wH/h6iHQDqIeybjxyKvT1G |
| 43 | N0rRAoGBAKGD+4ZI/E1MoJ5CXB8cDDMHagbE3cq/DtmYzE2v1DFpQYu5I4PCm5c7 |
| 44 | EQeIP6dZtv8IMgtGIb91QX9pXvP0aznzQKwYIA8nZgoENCPfiMTPiEDT9e/0lObO |
| 45 | 9XWsXpbSTsRPj0sv1rB+UzBJ0PgjK4q2zOF0sNo7b1+6nlM3BWPx |
Alex Vaghin | 47ff8df | 2016-05-06 18:46:57 +0100 | [diff] [blame] | 46 | -----END RSA PRIVATE KEY----- |
Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 47 | ` |
| 48 | |
| 49 | // This thumbprint is for the testKey defined above. |
| 50 | const testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ" |
| 51 | |
Alex Vaghin | 3461a68 | 2016-08-21 13:39:17 +0200 | [diff] [blame] | 52 | const ( |
| 53 | // openssl ecparam -name secp256k1 -genkey -noout |
| 54 | testKeyECPEM = ` |
| 55 | -----BEGIN EC PRIVATE KEY----- |
| 56 | MHcCAQEEIK07hGLr0RwyUdYJ8wbIiBS55CjnkMD23DWr+ccnypWLoAoGCCqGSM49 |
| 57 | AwEHoUQDQgAE5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HThqIrvawF5 |
| 58 | QAaS/RNouybCiRhRjI3EaxLkQwgrCw0gqQ== |
| 59 | -----END EC PRIVATE KEY----- |
| 60 | ` |
| 61 | // 1. opnessl ec -in key.pem -noout -text |
| 62 | // 2. remove first byte, 04 (the header); the rest is X and Y |
| 63 | // 3. covert each with: echo <val> | xxd -r -p | base64 | tr -d '=' | tr '/+' '_-' |
| 64 | testKeyECPubX = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ" |
| 65 | testKeyECPubY = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk" |
Alex Vaghin | 7e016f1 | 2016-08-21 18:20:10 +0200 | [diff] [blame] | 66 | // echo -n '{"crv":"P-256","kty":"EC","x":"<testKeyECPubX>","y":"<testKeyECPubY>"}' | \ |
| 67 | // openssl dgst -binary -sha256 | base64 | tr -d '=' | tr '/+' '_-' |
| 68 | testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU" |
Alex Vaghin | 3461a68 | 2016-08-21 13:39:17 +0200 | [diff] [blame] | 69 | ) |
| 70 | |
| 71 | var ( |
| 72 | testKey *rsa.PrivateKey |
| 73 | testKeyEC *ecdsa.PrivateKey |
| 74 | ) |
Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 75 | |
| 76 | func init() { |
| 77 | d, _ := pem.Decode([]byte(testKeyPEM)) |
| 78 | if d == nil { |
| 79 | panic("no block found in testKeyPEM") |
| 80 | } |
| 81 | var err error |
| 82 | testKey, err = x509.ParsePKCS1PrivateKey(d.Bytes) |
| 83 | if err != nil { |
| 84 | panic(err.Error()) |
| 85 | } |
Alex Vaghin | 3461a68 | 2016-08-21 13:39:17 +0200 | [diff] [blame] | 86 | |
| 87 | if d, _ = pem.Decode([]byte(testKeyECPEM)); d == nil { |
| 88 | panic("no block found in testKeyECPEM") |
| 89 | } |
| 90 | testKeyEC, err = x509.ParseECPrivateKey(d.Bytes) |
| 91 | if err != nil { |
| 92 | panic(err.Error()) |
| 93 | } |
Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 94 | } |
| 95 | |
| 96 | func TestJWSEncodeJSON(t *testing.T) { |
| 97 | claims := struct{ Msg string }{"Hello JWS"} |
| 98 | // JWS signed with testKey and "nonce" as the nonce value |
| 99 | // JSON-serialized JWS fields are split for easier testing |
| 100 | const ( |
| 101 | // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce"} |
| 102 | protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" + |
| 103 | "IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" + |
| 104 | "SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" + |
| 105 | "QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" + |
| 106 | "VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" + |
| 107 | "NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" + |
| 108 | "QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" + |
| 109 | "bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" + |
| 110 | "ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" + |
| 111 | "b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" + |
| 112 | "UVEifSwibm9uY2UiOiJub25jZSJ9" |
| 113 | // {"Msg":"Hello JWS"} |
| 114 | payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ" |
| 115 | signature = "eAGUikStX_UxyiFhxSLMyuyBcIB80GeBkFROCpap2sW3EmkU_ggF" + |
| 116 | "knaQzxrTfItICSAXsCLIquZ5BbrSWA_4vdEYrwWtdUj7NqFKjHRa" + |
| 117 | "zpLHcoR7r1rEHvkoP1xj49lS5fc3Wjjq8JUhffkhGbWZ8ZVkgPdC" + |
| 118 | "4tMBWiQDoth-x8jELP_3LYOB_ScUXi2mETBawLgOT2K8rA0Vbbmx" + |
| 119 | "hWNlOWuUf-8hL5YX4IOEwsS8JK_TrTq5Zc9My0zHJmaieqDV0UlP" + |
| 120 | "k0onFjPFkGm7MrPSgd0MqRG-4vSAg2O4hDo7rKv4n8POjjXlNQvM" + |
| 121 | "9IPLr8qZ7usYBKhEGwX3yq_eicAwBw" |
| 122 | ) |
| 123 | |
| 124 | b, err := jwsEncodeJSON(claims, testKey, "nonce") |
| 125 | if err != nil { |
| 126 | t.Fatal(err) |
| 127 | } |
| 128 | var jws struct{ Protected, Payload, Signature string } |
| 129 | if err := json.Unmarshal(b, &jws); err != nil { |
| 130 | t.Fatal(err) |
| 131 | } |
| 132 | if jws.Protected != protected { |
| 133 | t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected) |
| 134 | } |
| 135 | if jws.Payload != payload { |
| 136 | t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload) |
| 137 | } |
| 138 | if jws.Signature != signature { |
| 139 | t.Errorf("signature:\n%s\nwant:\n%s", jws.Signature, signature) |
| 140 | } |
| 141 | } |
| 142 | |
Alex Vaghin | 3461a68 | 2016-08-21 13:39:17 +0200 | [diff] [blame] | 143 | func TestJWSEncodeJSONEC(t *testing.T) { |
| 144 | claims := struct{ Msg string }{"Hello JWS"} |
| 145 | |
| 146 | b, err := jwsEncodeJSON(claims, testKeyEC, "nonce") |
| 147 | if err != nil { |
| 148 | t.Fatal(err) |
| 149 | } |
| 150 | var jws struct{ Protected, Payload, Signature string } |
| 151 | if err := json.Unmarshal(b, &jws); err != nil { |
| 152 | t.Fatal(err) |
| 153 | } |
| 154 | |
| 155 | if b, err = base64.RawURLEncoding.DecodeString(jws.Protected); err != nil { |
| 156 | t.Fatalf("jws.Protected: %v", err) |
| 157 | } |
| 158 | var head struct { |
| 159 | Alg string |
| 160 | Nonce string |
| 161 | JWK struct { |
| 162 | Crv string |
| 163 | Kty string |
| 164 | X string |
| 165 | Y string |
| 166 | } `json:"jwk"` |
| 167 | } |
| 168 | if err := json.Unmarshal(b, &head); err != nil { |
| 169 | t.Fatalf("jws.Protected: %v", err) |
| 170 | } |
| 171 | if head.Alg != "ES256" { |
| 172 | t.Errorf("head.Alg = %q; want ES256", head.Alg) |
| 173 | } |
| 174 | if head.Nonce != "nonce" { |
| 175 | t.Errorf("head.Nonce = %q; want nonce", head.Nonce) |
| 176 | } |
| 177 | if head.JWK.Crv != "P-256" { |
| 178 | t.Errorf("head.JWK.Crv = %q; want P-256", head.JWK.Crv) |
| 179 | } |
| 180 | if head.JWK.Kty != "EC" { |
| 181 | t.Errorf("head.JWK.Kty = %q; want EC", head.JWK.Kty) |
| 182 | } |
| 183 | if head.JWK.X != testKeyECPubX { |
| 184 | t.Errorf("head.JWK.X = %q; want %q", head.JWK.X, testKeyECPubX) |
| 185 | } |
| 186 | if head.JWK.Y != testKeyECPubY { |
| 187 | t.Errorf("head.JWK.Y = %q; want %q", head.JWK.Y, testKeyECPubY) |
| 188 | } |
| 189 | } |
| 190 | |
Anmol Sethi | e0d166c | 2016-08-04 00:47:19 -0400 | [diff] [blame] | 191 | func TestJWKThumbprintRSA(t *testing.T) { |
Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 192 | // Key example from RFC 7638 |
| 193 | const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" + |
| 194 | "VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6" + |
| 195 | "4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD" + |
| 196 | "W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9" + |
| 197 | "1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH" + |
| 198 | "aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" |
| 199 | const base64E = "AQAB" |
| 200 | const expected = "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs" |
| 201 | |
Anmol Sethi | e0d166c | 2016-08-04 00:47:19 -0400 | [diff] [blame] | 202 | b, err := base64.RawURLEncoding.DecodeString(base64N) |
Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 203 | if err != nil { |
| 204 | t.Fatalf("Error parsing example key N: %v", err) |
| 205 | } |
Anmol Sethi | e0d166c | 2016-08-04 00:47:19 -0400 | [diff] [blame] | 206 | n := new(big.Int).SetBytes(b) |
Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 207 | |
Anmol Sethi | e0d166c | 2016-08-04 00:47:19 -0400 | [diff] [blame] | 208 | b, err = base64.RawURLEncoding.DecodeString(base64E) |
Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 209 | if err != nil { |
| 210 | t.Fatalf("Error parsing example key E: %v", err) |
| 211 | } |
Anmol Sethi | e0d166c | 2016-08-04 00:47:19 -0400 | [diff] [blame] | 212 | e := new(big.Int).SetBytes(b) |
Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 213 | |
| 214 | pub := &rsa.PublicKey{N: n, E: int(e.Uint64())} |
Anmol Sethi | e0d166c | 2016-08-04 00:47:19 -0400 | [diff] [blame] | 215 | th, err := JWKThumbprint(pub) |
| 216 | if err != nil { |
| 217 | t.Error(err) |
| 218 | } |
Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 219 | if th != expected { |
Anmol Sethi | e0d166c | 2016-08-04 00:47:19 -0400 | [diff] [blame] | 220 | t.Errorf("thumbprint = %q; want %q", th, expected) |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | func TestJWKThumbprintEC(t *testing.T) { |
| 225 | // Key example from RFC 7520 |
| 226 | // expected was computed with |
| 227 | // echo -n '{"crv":"P-521","kty":"EC","x":"<base64X>","y":"<base64Y>"}' | \ |
| 228 | // openssl dgst -binary -sha256 | \ |
| 229 | // base64 | \ |
| 230 | // tr -d '=' | tr '/+' '_-' |
| 231 | const ( |
| 232 | base64X = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkT" + |
| 233 | "KqjqvjyekWF-7ytDyRXYgCF5cj0Kt" |
| 234 | base64Y = "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUda" + |
| 235 | "QkAgDPrwQrJmbnX9cwlGfP-HqHZR1" |
| 236 | expected = "dHri3SADZkrush5HU_50AoRhcKFryN-PI6jPBtPL55M" |
| 237 | ) |
| 238 | |
| 239 | b, err := base64.RawURLEncoding.DecodeString(base64X) |
| 240 | if err != nil { |
| 241 | t.Fatalf("Error parsing example key X: %v", err) |
| 242 | } |
| 243 | x := new(big.Int).SetBytes(b) |
| 244 | |
| 245 | b, err = base64.RawURLEncoding.DecodeString(base64Y) |
| 246 | if err != nil { |
| 247 | t.Fatalf("Error parsing example key Y: %v", err) |
| 248 | } |
| 249 | y := new(big.Int).SetBytes(b) |
| 250 | |
| 251 | pub := &ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y} |
| 252 | th, err := JWKThumbprint(pub) |
| 253 | if err != nil { |
| 254 | t.Error(err) |
| 255 | } |
| 256 | if th != expected { |
| 257 | t.Errorf("thumbprint = %q; want %q", th, expected) |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | func TestJWKThumbprintErrUnsupportedKey(t *testing.T) { |
| 262 | _, err := JWKThumbprint(struct{}{}) |
| 263 | if err != ErrUnsupportedKey { |
| 264 | t.Errorf("err = %q; want %q", err, ErrUnsupportedKey) |
Alex Vaghin | 1777f3b | 2016-03-18 12:12:46 +0000 | [diff] [blame] | 265 | } |
| 266 | } |