99 "crypto/ecdsa"
1010 "crypto/elliptic"
1111 "crypto/rsa"
12+ "crypto/sha256"
1213 "crypto/x509"
1314 "encoding/base64"
1415 "encoding/json"
@@ -19,7 +20,18 @@ import (
1920 "testing"
2021)
2122
23+ // The following shell command alias is used in the comments
24+ // throughout this file:
25+ // alias b64raw="base64 -w0 | tr -d '=' | tr '/+' '_-'"
26+
2227const (
28+ // Modulus in raw base64:
29+ // 4xgZ3eRPkwoRvy7qeRUbmMDe0V-xH9eWLdu0iheeLlrmD2mqWXfP9IeSKApbn34
30+ // g8TuAS9g5zhq8ELQ3kmjr-KV86GAMgI6VAcGlq3QrzpTCf_30Ab7-zawrfRaFON
31+ // a1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosqEXeaIkVYBEhbh
32+ // Nu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZfoyFyek380mHg
33+ // JAumQ_I2fjj98_97mk3ihOY4AgVdCDj1z_GCoZkG5Rq7nbCGyosyKWyDX00Zs-n
34+ // NqVhoLeIvXC4nnWdJMZ6rogxyQQ
2335 testKeyPEM = `
2436-----BEGIN RSA PRIVATE KEY-----
2537MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq
@@ -82,7 +94,7 @@ GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ==
8294`
8395 // 1. openssl ec -in key.pem -noout -text
8496 // 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 '/+' '_-'
97+ // 3. convert each with: echo <val> | xxd -r -p | b64raw
8698 testKeyECPubX = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ"
8799 testKeyECPubY = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk"
88100 testKeyEC384PubX = "MyrY_jLNLx6E1-Xc79_y-WDFzlriOVCkYyYoKWoWAqlw9gQNY9BP9sbeb5T3_oJt"
@@ -91,7 +103,7 @@ GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ==
91103 testKeyEC512PubY = "AXbmSeogEiDlDwz0Gc670YYByFzC3c7tEMeap7CckkOtuN0Yaebqtv42dZH0MiikzSco2ROhagX-HOinG7gB78ax"
92104
93105 // echo -n '{"crv":"P-256","kty":"EC","x":"<testKeyECPubX>","y":"<testKeyECPubY>"}' | \
94- // openssl dgst -binary -sha256 | base64 | tr -d '=' | tr '/+' '_-'
106+ // openssl dgst -binary -sha256 | b64raw
95107 testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU"
96108)
97109
@@ -140,7 +152,7 @@ func TestJWSEncodeJSON(t *testing.T) {
140152 // JWS signed with testKey and "nonce" as the nonce value
141153 // JSON-serialized JWS fields are split for easier testing
142154 const (
143- // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce"}
155+ // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url" }
144156 protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
145157 "IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
146158 "SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
@@ -151,19 +163,20 @@ func TestJWSEncodeJSON(t *testing.T) {
151163 "bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
152164 "ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
153165 "b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
154- "UVEifSwibm9uY2UiOiJub25jZSJ9 "
166+ "UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9 "
155167 // {"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"
168+ payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
169+ // printf '<protected>.<payload>' | openssl dgst -binary -sha256 -sign testKey | b64raw
170+ signature = "YFyl_xz1E7TR-3E1bIuASTr424EgCvBHjt25WUFC2VaDjXYV0Rj_" +
171+ "Hd3dJ_2IRqBrXDZZ2n4ZeA_4mm3QFwmwyeDwe2sWElhb82lCZ8iX" +
172+ "uFnjeOmSOjx-nWwPa5ibCXzLq13zZ-OBV1Z4oN_TuailQeRoSfA3" +
173+ "nO8gG52mv1x2OMQ5MAFtt8jcngBLzts4AyhI6mBJ2w7Yaj3ZCriq" +
174+ "DWA3GLFvvHdW1Ba9Z01wtGT2CuZI7DUk_6Qj1b3BkBGcoKur5C9i" +
175+ "bUJtCkABwBMvBQNyD3MmXsrRFRTgvVlyU_yMaucYm7nmzEr_2PaQ" +
176+ "50rFt_9qOfJ4sfbLtG1Wwae57BQx1g"
164177 )
165178
166- b , err := jwsEncodeJSON (claims , testKey , "nonce" )
179+ b , err := jwsEncodeJSON (claims , testKey , noKeyID , "nonce" , "url " )
167180 if err != nil {
168181 t .Fatal (err )
169182 }
@@ -182,6 +195,46 @@ func TestJWSEncodeJSON(t *testing.T) {
182195 }
183196}
184197
198+ func TestJWSEncodeKID (t * testing.T ) {
199+ kid := keyID ("https://example.org/account/1" )
200+ claims := struct { Msg string }{"Hello JWS" }
201+ // JWS signed with testKeyEC
202+ const (
203+ // {"alg":"ES256","kid":"https://example.org/account/1","nonce":"nonce","url":"url"}
204+ protected = "eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vZXhhbXBsZS5" +
205+ "vcmcvYWNjb3VudC8xIiwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
206+ // {"Msg":"Hello JWS"}
207+ payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
208+ )
209+
210+ b , err := jwsEncodeJSON (claims , testKeyEC , kid , "nonce" , "url" )
211+ if err != nil {
212+ t .Fatal (err )
213+ }
214+ var jws struct { Protected , Payload , Signature string }
215+ if err := json .Unmarshal (b , & jws ); err != nil {
216+ t .Fatal (err )
217+ }
218+ if jws .Protected != protected {
219+ t .Errorf ("protected:\n %s\n want:\n %s" , jws .Protected , protected )
220+ }
221+ if jws .Payload != payload {
222+ t .Errorf ("payload:\n %s\n want:\n %s" , jws .Payload , payload )
223+ }
224+
225+ sig , err := base64 .RawURLEncoding .DecodeString (jws .Signature )
226+ if err != nil {
227+ t .Fatalf ("jws.Signature: %v" , err )
228+ }
229+ r , s := big .NewInt (0 ), big .NewInt (0 )
230+ r .SetBytes (sig [:len (sig )/ 2 ])
231+ s .SetBytes (sig [len (sig )/ 2 :])
232+ h := sha256 .Sum256 ([]byte (protected + "." + payload ))
233+ if ! ecdsa .Verify (testKeyEC .Public ().(* ecdsa.PublicKey ), h [:], r , s ) {
234+ t .Error ("invalid signature" )
235+ }
236+ }
237+
185238func TestJWSEncodeJSONEC (t * testing.T ) {
186239 tt := []struct {
187240 key * ecdsa.PrivateKey
@@ -194,7 +247,7 @@ func TestJWSEncodeJSONEC(t *testing.T) {
194247 }
195248 for i , test := range tt {
196249 claims := struct { Msg string }{"Hello JWS" }
197- b , err := jwsEncodeJSON (claims , test .key , "nonce" )
250+ b , err := jwsEncodeJSON (claims , test .key , noKeyID , "nonce" , "url " )
198251 if err != nil {
199252 t .Errorf ("%d: %v" , i , err )
200253 continue
@@ -212,6 +265,8 @@ func TestJWSEncodeJSONEC(t *testing.T) {
212265 var head struct {
213266 Alg string
214267 Nonce string
268+ URL string `json:"url"`
269+ KID string `json:"kid"`
215270 JWK struct {
216271 Crv string
217272 Kty string
@@ -228,6 +283,13 @@ func TestJWSEncodeJSONEC(t *testing.T) {
228283 if head .Nonce != "nonce" {
229284 t .Errorf ("%d: head.Nonce = %q; want nonce" , i , head .Nonce )
230285 }
286+ if head .URL != "url" {
287+ t .Errorf ("%d: head.URL = %q; want 'url'" , i , head .URL )
288+ }
289+ if head .KID != "" {
290+ // We used noKeyID in jwsEncodeJSON: expect no kid value.
291+ t .Errorf ("%d: head.KID = %q; want empty" , i , head .KID )
292+ }
231293 if head .JWK .Crv != test .crv {
232294 t .Errorf ("%d: head.JWK.Crv = %q; want %q" , i , head .JWK .Crv , test .crv )
233295 }
@@ -256,18 +318,19 @@ func (s *customTestSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, e
256318func TestJWSEncodeJSONCustom (t * testing.T ) {
257319 claims := struct { Msg string }{"hello" }
258320 const (
259- // printf '{"Msg":"hello"}' | base64 | tr -d '=' | tr '/+' '_-'
321+ // printf '{"Msg":"hello"}' | b64raw
260322 payload = "eyJNc2ciOiJoZWxsbyJ9"
261- // printf 'testsig' | base64 | tr -d '='
323+ // printf 'testsig' | b64raw
262324 testsig = "dGVzdHNpZw"
263325
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"
326+ // printf '{"alg":"ES256","jwk":{"crv":"P-256","kty":"EC","x":<testKeyECPubY>,"y":<testKeyECPubY>},"nonce":"nonce","url":"url"}' | b64raw
327+ es256phead = "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0" +
328+ "eSI6IkVDIiwieCI6IjVsaEV1ZzV4SzR4QkRaMm5BYmF4THRhTGl2" +
329+ "ODVieEo3ZVBkMWRrTzIzSFEiLCJ5IjoiNGFpSzcyc0JlVUFHa3Yw" +
330+ "VGFMc213b2tZVVl5TnhHc1M1RU1JS3dzTklLayJ9LCJub25jZSI6" +
331+ "Im5vbmNlIiwidXJsIjoidXJsIn0"
269332
270- // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce"}
333+ // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url" }
271334 rs256phead = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
272335 "IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
273336 "SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
@@ -278,15 +341,15 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
278341 "bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
279342 "ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
280343 "b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
281- "UVEifSwibm9uY2UiOiJub25jZSJ9 "
344+ "UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9 "
282345 )
283346
284347 tt := []struct {
285348 alg , phead string
286349 pub crypto.PublicKey
287350 }{
288- {"RS256" , rs256phead , testKey .Public ()},
289351 {"ES256" , es256phead , testKeyEC .Public ()},
352+ {"RS256" , rs256phead , testKey .Public ()},
290353 }
291354 for _ , tc := range tt {
292355 tc := tc
@@ -295,7 +358,7 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
295358 sig : []byte ("testsig" ),
296359 pub : tc .pub ,
297360 }
298- b , err := jwsEncodeJSON (claims , signer , "nonce" )
361+ b , err := jwsEncodeJSON (claims , signer , noKeyID , "nonce" , "url " )
299362 if err != nil {
300363 t .Fatal (err )
301364 }
@@ -352,10 +415,8 @@ func TestJWKThumbprintRSA(t *testing.T) {
352415func TestJWKThumbprintEC (t * testing.T ) {
353416 // Key example from RFC 7520
354417 // 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 '/+' '_-'
418+ // printf '{"crv":"P-521","kty":"EC","x":"<base64X>","y":"<base64Y>"}' | \
419+ // openssl dgst -binary -sha256 | b64raw
359420 const (
360421 base64X = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkT" +
361422 "KqjqvjyekWF-7ytDyRXYgCF5cj0Kt"
0 commit comments