From 0ba05480e4eb38fcb0685fd85533d3dbef898116 Mon Sep 17 00:00:00 2001 From: Ichinose Shogo Date: Mon, 7 Nov 2022 13:17:36 +0900 Subject: [PATCH 1/6] implement marshal json --- go.mod | 3 +- go.sum | 2 + jws/fuzz_test.go | 151 +++++++++++++++++ jws/jws.go | 27 +++ jws/jws_test.go | 159 +++++++++++++++++- ...6643ef9239cb74af81e5d7a08fc5f1e3bc599ebc4e | 0 ...8551dc95b446c75b3c3f903d888e81714127e53ad9 | 0 ...61ed3ca85cdf6e83c3f1ee2caf5cc4ec784ece8173 | 0 ...22772847ba21dd86bc1e9692793505205cdd04bc03 | 0 ...9ab145521028a4d3ca6b221b24c083bbf46d865109 | 0 ...622352602bb967d84e8cbb13b40ebdd4ddabea7e98 | 0 ...115695c3d96fe587eb0d4cf4bafcebf807cc54e4ad | 0 12 files changed, 340 insertions(+), 2 deletions(-) rename jws/testdata/fuzz/{FuzzJWS => FuzzJWSCompact}/03e56becdff763a4b3cceb6643ef9239cb74af81e5d7a08fc5f1e3bc599ebc4e (100%) rename jws/testdata/fuzz/{FuzzJWS => FuzzJWSCompact}/04ae3b7d597ad0746745cf8551dc95b446c75b3c3f903d888e81714127e53ad9 (100%) rename jws/testdata/fuzz/{FuzzJWS => FuzzJWSCompact}/15bb34ecdfb84f2aa52c3e61ed3ca85cdf6e83c3f1ee2caf5cc4ec784ece8173 (100%) rename jws/testdata/fuzz/{FuzzJWS => FuzzJWSCompact}/5b7c500ef50e24e31e204022772847ba21dd86bc1e9692793505205cdd04bc03 (100%) rename jws/testdata/fuzz/{FuzzJWS => FuzzJWSCompact}/9f36fb0a8eaa39cdd0b3bb9ab145521028a4d3ca6b221b24c083bbf46d865109 (100%) rename jws/testdata/fuzz/{FuzzJWS => FuzzJWSCompact}/a758fc09962c730050f778622352602bb967d84e8cbb13b40ebdd4ddabea7e98 (100%) rename jws/testdata/fuzz/{FuzzJWS => FuzzJWSCompact}/e346c856959f11ef0653d2115695c3d96fe587eb0d4cf4bafcebf807cc54e4ad (100%) diff --git a/go.mod b/go.mod index caaee2f..0f714b4 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,8 @@ go 1.19 require ( github.com/shogo82148/memoize v0.0.2 + github.com/shogo82148/pointer v1.2.0 golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be ) -require github.com/shogo82148/pointer v1.2.0 +require golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect diff --git a/go.sum b/go.sum index 506b7f7..45d9c98 100644 --- a/go.sum +++ b/go.sum @@ -4,3 +4,5 @@ github.com/shogo82148/pointer v1.2.0 h1:MEPjAx9hK17sdEVhaqHROphdy+RxTH70vaBypZzZ github.com/shogo82148/pointer v1.2.0/go.mod h1:agZ5JFpavFPXznbWonIvbG78NDfvDTFppe+7o53up5w= golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/jws/fuzz_test.go b/jws/fuzz_test.go index 463b41a..c08cd0b 100644 --- a/jws/fuzz_test.go +++ b/jws/fuzz_test.go @@ -14,6 +14,157 @@ import ( ) func FuzzJWS(f *testing.F) { + f.Add( + `{`+ + `"payload":`+ + `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF`+ + `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",`+ + `"signatures":[`+ + `{"protected":"eyJhbGciOiJSUzI1NiJ9",`+ + `"header":`+ + `{"kid":"2010-12-29"},`+ + `"signature":`+ + `"cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZ`+ + `mh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjb`+ + `KBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHl`+ + `b1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZES`+ + `c6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AX`+ + `LIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"},`+ + `{"protected":"eyJhbGciOiJFUzI1NiJ9",`+ + `"header":`+ + `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},`+ + `"signature":`+ + `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS`+ + `lSApmWQxfKTUJqPP3-Kg6NU1Q"}]`+ + `}`, + `{"kty":"RSA",`+ + `"n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx`+ + `HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs`+ + `D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH`+ + `SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV`+ + `MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8`+ + `NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ",`+ + `"e":"AQAB",`+ + `"d":"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I`+ + `jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0`+ + `BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn`+ + `439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT`+ + `CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh`+ + `BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ",`+ + `"p":"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi`+ + `YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG`+ + `BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc",`+ + `"q":"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa`+ + `ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA`+ + `-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc",`+ + `"dp":"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q`+ + `CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb`+ + `34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0",`+ + `"dq":"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa`+ + `7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky`+ + `NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU",`+ + `"qi":"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o`+ + `y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU`+ + `W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U"`+ + `}`, + ) + + f.Add( + `{`+ + `"payload":`+ + `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF`+ + `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",`+ + `"signatures":[`+ + `{"protected":"eyJhbGciOiJSUzI1NiJ9",`+ + `"header":`+ + `{"kid":"2010-12-29"},`+ + `"signature":`+ + `"cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZ`+ + `mh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjb`+ + `KBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHl`+ + `b1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZES`+ + `c6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AX`+ + `LIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"},`+ + `{"protected":"eyJhbGciOiJFUzI1NiJ9",`+ + `"header":`+ + `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},`+ + `"signature":`+ + `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS`+ + `lSApmWQxfKTUJqPP3-Kg6NU1Q"}]`+ + `}`, + `{"kty":"EC",`+ + `"crv":"P-256",`+ + `"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",`+ + `"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",`+ + `"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"`+ + `}`, + ) + + f.Add(`{`+ + `"payload":`+ + `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF`+ + `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",`+ + `"protected":"eyJhbGciOiJFUzI1NiJ9",`+ + `"header":`+ + `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},`+ + `"signature":`+ + `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS`+ + `lSApmWQxfKTUJqPP3-Kg6NU1Q"`+ + `}`, + `{"kty":"EC",`+ + `"crv":"P-256",`+ + `"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",`+ + `"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",`+ + `"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"`+ + `}`, + ) + f.Fuzz(func(t *testing.T, raw, rawKey string) { + var msg1 Message + if err := msg1.UnmarshalJSON([]byte(raw)); err != nil { + return + } + + key, err := jwk.ParseKey([]byte(rawKey)) + if err != nil { + return + } + + var header1 *Header + protected1, payload1, err := msg1.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { + header1 = header + return protected.Algorithm().New().NewSigningKey(key), nil + })) + if err != nil { + return + } + + msg2 := NewMessage(payload1) + if err := msg2.Sign(protected1, header1, protected1.Algorithm().New().NewSigningKey(key)); err != nil { + t.Fatal(err) + } + + data, err := msg2.MarshalJSON() + if err != nil { + t.Fatal(err) + } + + var msg3 Message + if err := msg3.UnmarshalJSON(data); err != nil { + t.Fatal(err) + } + _, payload3, err := msg1.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { + return protected.Algorithm().New().NewSigningKey(key), nil + })) + if err != nil { + return + } + if string(payload1) != string(payload3) { + t.Errorf("want %s, got %s", string(payload1), string(payload3)) + } + }) +} + +func FuzzJWSCompact(f *testing.F) { f.Add( "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"+ "."+ diff --git a/jws/jws.go b/jws/jws.go index 707be72..76ae6c0 100644 --- a/jws/jws.go +++ b/jws/jws.go @@ -405,6 +405,33 @@ func (msg *Message) UnmarshalJSON(data []byte) error { return nil } +func (msg *Message) MarshalJSON() ([]byte, error) { + raw := map[string]any{ + "payload": string(msg.payload), + } + if len(msg.Signatures) == 1 { + // Flattened JWS JSON Serialization + sig := msg.Signatures[0] + raw["protected"] = string(sig.raw) + raw["signature"] = string(sig.b64signature) + } else { + // Complete JWS JSON Serialization Representation + signatures := make([]any, 0, len(msg.Signatures)) + for _, sig := range msg.Signatures { + raw := map[string]any{ + "protected": string(sig.raw), + "signature": string(sig.b64signature), + } + if sig.header != nil { + raw["header"] = sig.header + } + signatures = append(signatures, raw) + } + raw["signatures"] = signatures + } + return json.Marshal(raw) +} + func decodeHeader(raw map[string]any) (*Header, error) { d := jsonutils.NewDecoder("jws", raw) h := &Header{ diff --git a/jws/jws_test.go b/jws/jws_test.go index bc6a688..8a2ceac 100644 --- a/jws/jws_test.go +++ b/jws/jws_test.go @@ -2,6 +2,7 @@ package jws import ( "bytes" + "encoding/json" "errors" "testing" @@ -290,7 +291,7 @@ func TestVerify(t *testing.T) { }) } -func TestParseJSON(t *testing.T) { +func TestUnmarshalJSON(t *testing.T) { t.Run("RFC 7515 Appendix A.6. Example JWS Using General JWS JSON Serialization", func(t *testing.T) { raw := `{` + `"payload":` + @@ -472,6 +473,162 @@ func TestParseJSON(t *testing.T) { }) } +func TestMarshalJSON(t *testing.T) { + t.Run("RFC 7515 Appendix A.6. Example JWS Using General JWS JSON Serialization", func(t *testing.T) { + msg := NewMessage([]byte(`{"iss":"joe",` + "\r\n" + + ` "exp":1300819380,` + "\r\n" + + ` "http://example.com/is_root":true}`)) + + protected1 := NewHeader() + protected1.SetAlgorithm(jwa.RS256) + header1 := NewHeader() + header1.SetKeyID("2010-12-29") + rawKey1 := `{"kty":"RSA",` + + `"n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx` + + `HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs` + + `D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH` + + `SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV` + + `MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8` + + `NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ",` + + `"e":"AQAB",` + + `"d":"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I` + + `jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0` + + `BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn` + + `439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT` + + `CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh` + + `BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ",` + + `"p":"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi` + + `YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG` + + `BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc",` + + `"q":"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa` + + `ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA` + + `-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc",` + + `"dp":"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q` + + `CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb` + + `34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0",` + + `"dq":"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa` + + `7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky` + + `NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU",` + + `"qi":"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o` + + `y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU` + + `W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U"` + + `}` + key1, err := jwk.ParseKey([]byte(rawKey1)) + if err != nil { + t.Fatal(err) + } + if err := msg.Sign(protected1, header1, jwa.RS256.New().NewSigningKey(key1)); err != nil { + t.Fatal(err) + } + + protected2 := NewHeader() + protected2.SetAlgorithm(jwa.ES256) + header2 := NewHeader() + header2.SetKeyID("e9bc097a-ce51-4036-9562-d2ade882db0d") + rawKey2 := `{"kty":"EC",` + + `"crv":"P-256",` + + `"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",` + + `"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",` + + `"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"` + + `}` + key2, err := jwk.ParseKey([]byte(rawKey2)) + if err != nil { + t.Fatal(err) + } + if err := msg.Sign(protected2, header2, jwa.ES256.New().NewSigningKey(key2)); err != nil { + t.Fatal(err) + } + + data, err := msg.MarshalJSON() + if err != nil { + t.Fatal(err) + } + + var tmp any + if err := json.Unmarshal(data, &tmp); err != nil { + t.Fatal(err) + } + }) + + t.Run("RFC 7515 Appendix A.7. Example JWS Using Flattened JWS JSON Serialization", func(t *testing.T) { + raw := `{` + + `"payload":` + + `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF` + + `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",` + + `"protected":"eyJhbGciOiJFUzI1NiJ9",` + + `"header":` + + `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},` + + `"signature":` + + `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS` + + `lSApmWQxfKTUJqPP3-Kg6NU1Q"` + + `}` + var msg Message + if err := msg.UnmarshalJSON([]byte(raw)); err != nil { + t.Fatal(err) + } + _, payload, err := msg.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { + if header.KeyID() != "e9bc097a-ce51-4036-9562-d2ade882db0d" { + return nil, errors.New("unknown key id") + } + rawKey := `{"kty":"EC",` + + `"crv":"P-256",` + + `"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",` + + `"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",` + + `"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"` + + `}` + key, err := jwk.ParseKey([]byte(rawKey)) + if err != nil { + return nil, err + } + return protected.Algorithm().New().NewSigningKey(key), nil + })) + if err != nil { + t.Fatal(err) + } + want := []byte(`{"iss":"joe",` + "\r\n" + + ` "exp":1300819380,` + "\r\n" + + ` "http://example.com/is_root":true}`) + if !bytes.Equal(payload, want) { + t.Errorf("unexpected payload: want %q, got %q", string(want), string(payload)) + } + }) + + // test for b64 header parameter. + t.Run("RFC 7797 Section 4.2. Example with Header Parameters", func(t *testing.T) { + raw := `{` + + `"protected":` + + `"eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19",` + + `"payload":` + + `"$.02",` + + `"signature":` + + `"A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY"` + + `}` + var msg Message + if err := msg.UnmarshalJSON([]byte(raw)); err != nil { + t.Fatal(err) + } + _, payload, err := msg.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { + rawKey := `{` + + `"kty":"oct",` + + `"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75` + + `aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"` + + `}` + key, err := jwk.ParseKey([]byte(rawKey)) + if err != nil { + return nil, err + } + return protected.Algorithm().New().NewSigningKey(key), nil + })) + if err != nil { + t.Fatal(err) + } + want := []byte(`$.02`) + if !bytes.Equal(payload, want) { + t.Errorf("unexpected payload: want %q, got %q", string(want), string(payload)) + } + }) +} + func TestSign(t *testing.T) { t.Run("RFC7515 Appendix A.1 Example JWS Using HMAC SHA-256", func(t *testing.T) { rawKey := `{"kty":"oct",` + diff --git a/jws/testdata/fuzz/FuzzJWS/03e56becdff763a4b3cceb6643ef9239cb74af81e5d7a08fc5f1e3bc599ebc4e b/jws/testdata/fuzz/FuzzJWSCompact/03e56becdff763a4b3cceb6643ef9239cb74af81e5d7a08fc5f1e3bc599ebc4e similarity index 100% rename from jws/testdata/fuzz/FuzzJWS/03e56becdff763a4b3cceb6643ef9239cb74af81e5d7a08fc5f1e3bc599ebc4e rename to jws/testdata/fuzz/FuzzJWSCompact/03e56becdff763a4b3cceb6643ef9239cb74af81e5d7a08fc5f1e3bc599ebc4e diff --git a/jws/testdata/fuzz/FuzzJWS/04ae3b7d597ad0746745cf8551dc95b446c75b3c3f903d888e81714127e53ad9 b/jws/testdata/fuzz/FuzzJWSCompact/04ae3b7d597ad0746745cf8551dc95b446c75b3c3f903d888e81714127e53ad9 similarity index 100% rename from jws/testdata/fuzz/FuzzJWS/04ae3b7d597ad0746745cf8551dc95b446c75b3c3f903d888e81714127e53ad9 rename to jws/testdata/fuzz/FuzzJWSCompact/04ae3b7d597ad0746745cf8551dc95b446c75b3c3f903d888e81714127e53ad9 diff --git a/jws/testdata/fuzz/FuzzJWS/15bb34ecdfb84f2aa52c3e61ed3ca85cdf6e83c3f1ee2caf5cc4ec784ece8173 b/jws/testdata/fuzz/FuzzJWSCompact/15bb34ecdfb84f2aa52c3e61ed3ca85cdf6e83c3f1ee2caf5cc4ec784ece8173 similarity index 100% rename from jws/testdata/fuzz/FuzzJWS/15bb34ecdfb84f2aa52c3e61ed3ca85cdf6e83c3f1ee2caf5cc4ec784ece8173 rename to jws/testdata/fuzz/FuzzJWSCompact/15bb34ecdfb84f2aa52c3e61ed3ca85cdf6e83c3f1ee2caf5cc4ec784ece8173 diff --git a/jws/testdata/fuzz/FuzzJWS/5b7c500ef50e24e31e204022772847ba21dd86bc1e9692793505205cdd04bc03 b/jws/testdata/fuzz/FuzzJWSCompact/5b7c500ef50e24e31e204022772847ba21dd86bc1e9692793505205cdd04bc03 similarity index 100% rename from jws/testdata/fuzz/FuzzJWS/5b7c500ef50e24e31e204022772847ba21dd86bc1e9692793505205cdd04bc03 rename to jws/testdata/fuzz/FuzzJWSCompact/5b7c500ef50e24e31e204022772847ba21dd86bc1e9692793505205cdd04bc03 diff --git a/jws/testdata/fuzz/FuzzJWS/9f36fb0a8eaa39cdd0b3bb9ab145521028a4d3ca6b221b24c083bbf46d865109 b/jws/testdata/fuzz/FuzzJWSCompact/9f36fb0a8eaa39cdd0b3bb9ab145521028a4d3ca6b221b24c083bbf46d865109 similarity index 100% rename from jws/testdata/fuzz/FuzzJWS/9f36fb0a8eaa39cdd0b3bb9ab145521028a4d3ca6b221b24c083bbf46d865109 rename to jws/testdata/fuzz/FuzzJWSCompact/9f36fb0a8eaa39cdd0b3bb9ab145521028a4d3ca6b221b24c083bbf46d865109 diff --git a/jws/testdata/fuzz/FuzzJWS/a758fc09962c730050f778622352602bb967d84e8cbb13b40ebdd4ddabea7e98 b/jws/testdata/fuzz/FuzzJWSCompact/a758fc09962c730050f778622352602bb967d84e8cbb13b40ebdd4ddabea7e98 similarity index 100% rename from jws/testdata/fuzz/FuzzJWS/a758fc09962c730050f778622352602bb967d84e8cbb13b40ebdd4ddabea7e98 rename to jws/testdata/fuzz/FuzzJWSCompact/a758fc09962c730050f778622352602bb967d84e8cbb13b40ebdd4ddabea7e98 diff --git a/jws/testdata/fuzz/FuzzJWS/e346c856959f11ef0653d2115695c3d96fe587eb0d4cf4bafcebf807cc54e4ad b/jws/testdata/fuzz/FuzzJWSCompact/e346c856959f11ef0653d2115695c3d96fe587eb0d4cf4bafcebf807cc54e4ad similarity index 100% rename from jws/testdata/fuzz/FuzzJWS/e346c856959f11ef0653d2115695c3d96fe587eb0d4cf4bafcebf807cc54e4ad rename to jws/testdata/fuzz/FuzzJWSCompact/e346c856959f11ef0653d2115695c3d96fe587eb0d4cf4bafcebf807cc54e4ad From da4720f4dfc0fa89c17a51557272c294c2c79e14 Mon Sep 17 00:00:00 2001 From: Ichinose Shogo Date: Mon, 7 Nov 2022 13:51:08 +0900 Subject: [PATCH 2/6] improve unmarshaling JWS --- jws/jws.go | 115 ++++++++------ jws/jws_test.go | 150 +++++++++--------- ...8c9af4a906b973bad40b3ceca15ba4fce0aef24a56 | 3 + 3 files changed, 142 insertions(+), 126 deletions(-) create mode 100644 jws/testdata/fuzz/FuzzJWS/696f972beece8df754082d8c9af4a906b973bad40b3ceca15ba4fce0aef24a56 diff --git a/jws/jws.go b/jws/jws.go index 76ae6c0..d8ff20b 100644 --- a/jws/jws.go +++ b/jws/jws.go @@ -37,20 +37,6 @@ var knownParams = [...]string{ jwa.Base64URLEncodePayloadKey, } -type jsonJWS struct { - Payload *string `json:"payload"` - Protected *string `json:"protected,omitempty"` - Header map[string]any `json:"header,omitempty"` - Signature *string `json:"signature,omitempty"` - Signatures []jsonSignature `json:"signatures,omitempty"` -} - -type jsonSignature struct { - Protected *string `json:"protected,omitempty"` - Header map[string]any `json:"header,omitempty"` - Signature *string `json:"signature"` -} - // Header is a decoded JSON Object Signing and Encryption (JOSE) Header. type Header struct { // Raw is the raw data of JSON-decoded JOSE header. @@ -316,21 +302,25 @@ func Parse(data []byte) (*Message, error) { // UnmarshalJSON implements [encoding/json.Unmarshaler]. // It parses data as JSON Serialized JWS. func (msg *Message) UnmarshalJSON(data []byte) error { - var jws jsonJWS + var raw map[string]any dec := json.NewDecoder(bytes.NewReader(data)) dec.UseNumber() - if err := dec.Decode(&jws); err != nil { + if err := dec.Decode(&raw); err != nil { return fmt.Errorf("jws: failed to parse JWS: %w", err) } // decode payload - if jws.Payload == nil { + payloadAny, ok := raw["payload"] + if !ok { return errors.New("jws: failed to parse JWS: payload is missing") } - payload := []byte(*jws.Payload) + payload, ok := payloadAny.(string) + if !ok { + return fmt.Errorf("jws: invalid type of payload: %T", payloadAny) + } - hasSigs := jws.Signatures != nil - flattened := jws.Signature != nil + sigsAny, hasSigs := raw["signatures"] + sigAny, flattened := raw["signature"] if hasSigs && flattened { return errors.New("jws: failed to parse JWS: both signatures and signature are set") @@ -339,51 +329,74 @@ func (msg *Message) UnmarshalJSON(data []byte) error { return errors.New("jws: failed to parse JWS: neither signatures nor signature are set") } - sigs := jws.Signatures if flattened { - sigs = []jsonSignature{ - { - Protected: jws.Protected, - Header: jws.Header, - Signature: jws.Signature, - }, + protected, ok := raw["protected"] + if !ok { + return errors.New("jws: failed to parse JWS: protected header is missing") } + sigs := map[string]any{ + "protected": protected, + "signature": sigAny, + } + if header, ok := raw["header"]; ok { + sigs["header"] = header + } + sigsAny = []any{sigs} } + sigsArray, ok := sigsAny.([]any) + if !ok { + return fmt.Errorf("jws: invalid type of signatures: %T", sigsAny) + } // decode signatures - signatures := make([]*Signature, 0, len(jws.Signatures)) - for _, sig := range sigs { + signatures := make([]*Signature, 0, len(sigsArray)) + for _, sigAny := range sigsArray { + sigObject, ok := sigAny.(map[string]any) + if !ok { + return fmt.Errorf("jws: invalid type of signatures[]: %T", sigAny) + } + // decode protected header - var err error - var protected *Header - if sig.Protected != nil { - raw, err := b64.DecodeString(*sig.Protected) - if err != nil { - return fmt.Errorf("jws: failed to parse protected header: %w", err) - } - protected = new(Header) - if err := protected.UnmarshalJSON(raw); err != nil { - return fmt.Errorf("jws: failed to parse protected header: %w", err) - } + protectedAny, ok := sigObject["protected"] + if !ok { + return errors.New("jws: protected header is missing") + } + protectedString, ok := protectedAny.(string) + if !ok { + return fmt.Errorf("jws: invalid type of signatures[].protected: %T", protectedAny) + } + raw, err := b64.DecodeString(protectedString) + if err != nil { + return fmt.Errorf("jws: failed to parse protected header: %w", err) + } + protected := NewHeader() + if err := protected.UnmarshalJSON(raw); err != nil { + return fmt.Errorf("jws: failed to parse protected header: %w", err) } // decode unprotected header var header *Header - if sig.Header != nil { - header, err = decodeHeader(sig.Header) + if unprotectedAny, ok := sigObject["header"]; ok { + unprotectedObject, ok := unprotectedAny.(map[string]any) + if !ok { + return fmt.Errorf("jws: invalid type of signatures[].header: %T", unprotectedAny) + } + header, err = decodeHeader(unprotectedObject) if err != nil { - return fmt.Errorf("jws: failed to parse unprotected header: %w", err) + return fmt.Errorf("jws: failed to parse header: %w", err) } } - if protected == nil && header == nil { - return errors.New("jws: failed to parse JWS: both protected and unprotected header are missing") - } // decode signature - if sig.Signature == nil { + signatureAny, ok := sigObject["signature"] + if !ok { return errors.New("jws: failed to parse signature: signature is missing") } - signature, err := b64.DecodeString(*sig.Signature) + signatureString, ok := signatureAny.(string) + if !ok { + return fmt.Errorf("jws: invalid type of signatures[].signature: %T", signatureAny) + } + signature, err := b64.DecodeString(signatureString) if err != nil { return fmt.Errorf("jws: failed to parse signature: %w", err) } @@ -391,14 +404,14 @@ func (msg *Message) UnmarshalJSON(data []byte) error { signatures = append(signatures, &Signature{ protected: protected, header: header, - raw: []byte(*sig.Protected), - b64signature: []byte(*sig.Signature), + raw: []byte(protectedString), + b64signature: []byte(signatureString), signature: signature, }) } *msg = Message{ - payload: payload, + payload: []byte(payload), b64: signatures[0].protected.b64, Signatures: signatures, } diff --git a/jws/jws_test.go b/jws/jws_test.go index 8a2ceac..9c99097 100644 --- a/jws/jws_test.go +++ b/jws/jws_test.go @@ -550,83 +550,83 @@ func TestMarshalJSON(t *testing.T) { } }) - t.Run("RFC 7515 Appendix A.7. Example JWS Using Flattened JWS JSON Serialization", func(t *testing.T) { - raw := `{` + - `"payload":` + - `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF` + - `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",` + - `"protected":"eyJhbGciOiJFUzI1NiJ9",` + - `"header":` + - `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},` + - `"signature":` + - `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS` + - `lSApmWQxfKTUJqPP3-Kg6NU1Q"` + - `}` - var msg Message - if err := msg.UnmarshalJSON([]byte(raw)); err != nil { - t.Fatal(err) - } - _, payload, err := msg.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { - if header.KeyID() != "e9bc097a-ce51-4036-9562-d2ade882db0d" { - return nil, errors.New("unknown key id") - } - rawKey := `{"kty":"EC",` + - `"crv":"P-256",` + - `"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",` + - `"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",` + - `"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"` + - `}` - key, err := jwk.ParseKey([]byte(rawKey)) - if err != nil { - return nil, err - } - return protected.Algorithm().New().NewSigningKey(key), nil - })) - if err != nil { - t.Fatal(err) - } - want := []byte(`{"iss":"joe",` + "\r\n" + - ` "exp":1300819380,` + "\r\n" + - ` "http://example.com/is_root":true}`) - if !bytes.Equal(payload, want) { - t.Errorf("unexpected payload: want %q, got %q", string(want), string(payload)) - } - }) + // t.Run("RFC 7515 Appendix A.7. Example JWS Using Flattened JWS JSON Serialization", func(t *testing.T) { + // raw := `{` + + // `"payload":` + + // `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF` + + // `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",` + + // `"protected":"eyJhbGciOiJFUzI1NiJ9",` + + // `"header":` + + // `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},` + + // `"signature":` + + // `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS` + + // `lSApmWQxfKTUJqPP3-Kg6NU1Q"` + + // `}` + // var msg Message + // if err := msg.UnmarshalJSON([]byte(raw)); err != nil { + // t.Fatal(err) + // } + // _, payload, err := msg.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { + // if header.KeyID() != "e9bc097a-ce51-4036-9562-d2ade882db0d" { + // return nil, errors.New("unknown key id") + // } + // rawKey := `{"kty":"EC",` + + // `"crv":"P-256",` + + // `"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",` + + // `"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",` + + // `"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"` + + // `}` + // key, err := jwk.ParseKey([]byte(rawKey)) + // if err != nil { + // return nil, err + // } + // return protected.Algorithm().New().NewSigningKey(key), nil + // })) + // if err != nil { + // t.Fatal(err) + // } + // want := []byte(`{"iss":"joe",` + "\r\n" + + // ` "exp":1300819380,` + "\r\n" + + // ` "http://example.com/is_root":true}`) + // if !bytes.Equal(payload, want) { + // t.Errorf("unexpected payload: want %q, got %q", string(want), string(payload)) + // } + // }) // test for b64 header parameter. - t.Run("RFC 7797 Section 4.2. Example with Header Parameters", func(t *testing.T) { - raw := `{` + - `"protected":` + - `"eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19",` + - `"payload":` + - `"$.02",` + - `"signature":` + - `"A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY"` + - `}` - var msg Message - if err := msg.UnmarshalJSON([]byte(raw)); err != nil { - t.Fatal(err) - } - _, payload, err := msg.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { - rawKey := `{` + - `"kty":"oct",` + - `"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75` + - `aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"` + - `}` - key, err := jwk.ParseKey([]byte(rawKey)) - if err != nil { - return nil, err - } - return protected.Algorithm().New().NewSigningKey(key), nil - })) - if err != nil { - t.Fatal(err) - } - want := []byte(`$.02`) - if !bytes.Equal(payload, want) { - t.Errorf("unexpected payload: want %q, got %q", string(want), string(payload)) - } - }) + // t.Run("RFC 7797 Section 4.2. Example with Header Parameters", func(t *testing.T) { + // raw := `{` + + // `"protected":` + + // `"eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19",` + + // `"payload":` + + // `"$.02",` + + // `"signature":` + + // `"A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY"` + + // `}` + // var msg Message + // if err := msg.UnmarshalJSON([]byte(raw)); err != nil { + // t.Fatal(err) + // } + // _, payload, err := msg.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { + // rawKey := `{` + + // `"kty":"oct",` + + // `"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75` + + // `aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"` + + // `}` + // key, err := jwk.ParseKey([]byte(rawKey)) + // if err != nil { + // return nil, err + // } + // return protected.Algorithm().New().NewSigningKey(key), nil + // })) + // if err != nil { + // t.Fatal(err) + // } + // want := []byte(`$.02`) + // if !bytes.Equal(payload, want) { + // t.Errorf("unexpected payload: want %q, got %q", string(want), string(payload)) + // } + // }) } func TestSign(t *testing.T) { diff --git a/jws/testdata/fuzz/FuzzJWS/696f972beece8df754082d8c9af4a906b973bad40b3ceca15ba4fce0aef24a56 b/jws/testdata/fuzz/FuzzJWS/696f972beece8df754082d8c9af4a906b973bad40b3ceca15ba4fce0aef24a56 new file mode 100644 index 0000000..1c383a7 --- /dev/null +++ b/jws/testdata/fuzz/FuzzJWS/696f972beece8df754082d8c9af4a906b973bad40b3ceca15ba4fce0aef24a56 @@ -0,0 +1,3 @@ +go test fuzz v1 +string("{\"pAYloAd\":\"\",\"heAder\":{},\"signAture\":\"\"}") +string("0") From ef58735a945ed52497938ccd843e415aaf9094fd Mon Sep 17 00:00:00 2001 From: Ichinose Shogo Date: Mon, 7 Nov 2022 14:55:33 +0900 Subject: [PATCH 3/6] fix panic --- jws/fuzz_test.go | 6 ++++++ ...3cee533a6d22ddddc7bdff6be1a4548c86eced168f8fd75775463f28 | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 jws/testdata/fuzz/FuzzJWS/9bfbd4f53cee533a6d22ddddc7bdff6be1a4548c86eced168f8fd75775463f28 diff --git a/jws/fuzz_test.go b/jws/fuzz_test.go index c08cd0b..53d4d51 100644 --- a/jws/fuzz_test.go +++ b/jws/fuzz_test.go @@ -131,6 +131,9 @@ func FuzzJWS(f *testing.F) { var header1 *Header protected1, payload1, err := msg1.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { + if !protected.Algorithm().Available() { + return nil, errors.New("algorithm not available") + } header1 = header return protected.Algorithm().New().NewSigningKey(key), nil })) @@ -153,6 +156,9 @@ func FuzzJWS(f *testing.F) { t.Fatal(err) } _, payload3, err := msg1.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { + if !protected.Algorithm().Available() { + return nil, errors.New("algorithm not available") + } return protected.Algorithm().New().NewSigningKey(key), nil })) if err != nil { diff --git a/jws/testdata/fuzz/FuzzJWS/9bfbd4f53cee533a6d22ddddc7bdff6be1a4548c86eced168f8fd75775463f28 b/jws/testdata/fuzz/FuzzJWS/9bfbd4f53cee533a6d22ddddc7bdff6be1a4548c86eced168f8fd75775463f28 new file mode 100644 index 0000000..de977ee --- /dev/null +++ b/jws/testdata/fuzz/FuzzJWS/9bfbd4f53cee533a6d22ddddc7bdff6be1a4548c86eced168f8fd75775463f28 @@ -0,0 +1,3 @@ +go test fuzz v1 +string("{\"payload\":\"\",\"protected\":\"eyJ0000iOiI00CJ9\",\"signature\":\"\"}") +string("{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEX\",\"y\":\"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0\"}") From ee639ee5b0496e33b1be5615bf46abb7da11be68 Mon Sep 17 00:00:00 2001 From: Ichinose Shogo Date: Mon, 7 Nov 2022 15:08:28 +0900 Subject: [PATCH 4/6] the key doesn't support signing, we skip it. --- jws/fuzz_test.go | 4 ++++ ...8a7e3458132a43d81efd67a7a016468409e14b65574cf0433c58512cc4 | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 jws/testdata/fuzz/FuzzJWS/7239b98a7e3458132a43d81efd67a7a016468409e14b65574cf0433c58512cc4 diff --git a/jws/fuzz_test.go b/jws/fuzz_test.go index 53d4d51..4b90939 100644 --- a/jws/fuzz_test.go +++ b/jws/fuzz_test.go @@ -141,6 +141,10 @@ func FuzzJWS(f *testing.F) { return } + if key.PrivateKey() == nil { + return // the key doesn't support signing, we skip it. + } + msg2 := NewMessage(payload1) if err := msg2.Sign(protected1, header1, protected1.Algorithm().New().NewSigningKey(key)); err != nil { t.Fatal(err) diff --git a/jws/testdata/fuzz/FuzzJWS/7239b98a7e3458132a43d81efd67a7a016468409e14b65574cf0433c58512cc4 b/jws/testdata/fuzz/FuzzJWS/7239b98a7e3458132a43d81efd67a7a016468409e14b65574cf0433c58512cc4 new file mode 100644 index 0000000..c7a96dd --- /dev/null +++ b/jws/testdata/fuzz/FuzzJWS/7239b98a7e3458132a43d81efd67a7a016468409e14b65574cf0433c58512cc4 @@ -0,0 +1,3 @@ +go test fuzz v1 +string("{\"payload\":\"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ\",\"protected\":\"eyJhbGciOiJFUzI1NiJ9\",\"\":{\"2\":\"\xa2\xa2\xa27221101001292010\"},\"signature\":\"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1a\"}") +string("{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEX\",\"y\":\"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0\"}") From 8539cb9f3dcdd67cb3ed670466fd4e9f64852b81 Mon Sep 17 00:00:00 2001 From: Ichinose Shogo Date: Mon, 7 Nov 2022 15:13:33 +0900 Subject: [PATCH 5/6] add test case for Flattened JWS JSON Serialization --- jws/fuzz_test.go | 20 +++++++++++++ jws/jws_test.go | 77 +++++++++++++++++++++--------------------------- 2 files changed, 54 insertions(+), 43 deletions(-) diff --git a/jws/fuzz_test.go b/jws/fuzz_test.go index 4b90939..5eece53 100644 --- a/jws/fuzz_test.go +++ b/jws/fuzz_test.go @@ -118,6 +118,26 @@ func FuzzJWS(f *testing.F) { `"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"`+ `}`, ) + + f.Add(`{`+ + `"payload":`+ + `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF`+ + `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",`+ + `"protected":"eyJhbGciOiJFUzI1NiJ9",`+ + `"header":`+ + `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},`+ + `"signature":`+ + `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS`+ + `lSApmWQxfKTUJqPP3-Kg6NU1Q"`+ + `}`, + `{"kty":"EC",`+ + `"crv":"P-256",`+ + `"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",`+ + `"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",`+ + `"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"`+ + `}`, + ) + f.Fuzz(func(t *testing.T, raw, rawKey string) { var msg1 Message if err := msg1.UnmarshalJSON([]byte(raw)); err != nil { diff --git a/jws/jws_test.go b/jws/jws_test.go index 9c99097..a2610f0 100644 --- a/jws/jws_test.go +++ b/jws/jws_test.go @@ -521,6 +521,40 @@ func TestMarshalJSON(t *testing.T) { t.Fatal(err) } + protected := NewHeader() + protected.SetAlgorithm(jwa.ES256) + header := NewHeader() + header.SetKeyID("e9bc097a-ce51-4036-9562-d2ade882db0d") + rawKey2 := `{"kty":"EC",` + + `"crv":"P-256",` + + `"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",` + + `"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",` + + `"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"` + + `}` + key2, err := jwk.ParseKey([]byte(rawKey2)) + if err != nil { + t.Fatal(err) + } + if err := msg.Sign(protected, header, jwa.ES256.New().NewSigningKey(key2)); err != nil { + t.Fatal(err) + } + + data, err := msg.MarshalJSON() + if err != nil { + t.Fatal(err) + } + + var tmp any + if err := json.Unmarshal(data, &tmp); err != nil { + t.Fatal(err) + } + }) + + t.Run("RFC 7515 Appendix A.7. Example JWS Using Flattened JWS JSON Serialization", func(t *testing.T) { + msg := NewMessage([]byte(`{"iss":"joe",` + "\r\n" + + ` "exp":1300819380,` + "\r\n" + + ` "http://example.com/is_root":true}`)) + protected2 := NewHeader() protected2.SetAlgorithm(jwa.ES256) header2 := NewHeader() @@ -550,49 +584,6 @@ func TestMarshalJSON(t *testing.T) { } }) - // t.Run("RFC 7515 Appendix A.7. Example JWS Using Flattened JWS JSON Serialization", func(t *testing.T) { - // raw := `{` + - // `"payload":` + - // `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF` + - // `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",` + - // `"protected":"eyJhbGciOiJFUzI1NiJ9",` + - // `"header":` + - // `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},` + - // `"signature":` + - // `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS` + - // `lSApmWQxfKTUJqPP3-Kg6NU1Q"` + - // `}` - // var msg Message - // if err := msg.UnmarshalJSON([]byte(raw)); err != nil { - // t.Fatal(err) - // } - // _, payload, err := msg.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { - // if header.KeyID() != "e9bc097a-ce51-4036-9562-d2ade882db0d" { - // return nil, errors.New("unknown key id") - // } - // rawKey := `{"kty":"EC",` + - // `"crv":"P-256",` + - // `"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",` + - // `"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",` + - // `"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"` + - // `}` - // key, err := jwk.ParseKey([]byte(rawKey)) - // if err != nil { - // return nil, err - // } - // return protected.Algorithm().New().NewSigningKey(key), nil - // })) - // if err != nil { - // t.Fatal(err) - // } - // want := []byte(`{"iss":"joe",` + "\r\n" + - // ` "exp":1300819380,` + "\r\n" + - // ` "http://example.com/is_root":true}`) - // if !bytes.Equal(payload, want) { - // t.Errorf("unexpected payload: want %q, got %q", string(want), string(payload)) - // } - // }) - // test for b64 header parameter. // t.Run("RFC 7797 Section 4.2. Example with Header Parameters", func(t *testing.T) { // raw := `{` + From ddf9be7a4281c241f3dc83def325fafcab20145c Mon Sep 17 00:00:00 2001 From: Ichinose Shogo Date: Mon, 7 Nov 2022 16:05:02 +0900 Subject: [PATCH 6/6] add test case for b64 --- jws/fuzz_test.go | 62 ++++++++++++++++++++++++++++--------------- jws/jws_test.go | 69 +++++++++++++++++++++++++----------------------- 2 files changed, 76 insertions(+), 55 deletions(-) diff --git a/jws/fuzz_test.go b/jws/fuzz_test.go index 5eece53..987f5f1 100644 --- a/jws/fuzz_test.go +++ b/jws/fuzz_test.go @@ -100,17 +100,18 @@ func FuzzJWS(f *testing.F) { `}`, ) - f.Add(`{`+ - `"payload":`+ - `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF`+ - `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",`+ - `"protected":"eyJhbGciOiJFUzI1NiJ9",`+ - `"header":`+ - `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},`+ - `"signature":`+ - `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS`+ - `lSApmWQxfKTUJqPP3-Kg6NU1Q"`+ - `}`, + f.Add( + `{`+ + `"payload":`+ + `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF`+ + `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",`+ + `"protected":"eyJhbGciOiJFUzI1NiJ9",`+ + `"header":`+ + `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},`+ + `"signature":`+ + `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS`+ + `lSApmWQxfKTUJqPP3-Kg6NU1Q"`+ + `}`, `{"kty":"EC",`+ `"crv":"P-256",`+ `"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",`+ @@ -119,17 +120,18 @@ func FuzzJWS(f *testing.F) { `}`, ) - f.Add(`{`+ - `"payload":`+ - `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF`+ - `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",`+ - `"protected":"eyJhbGciOiJFUzI1NiJ9",`+ - `"header":`+ - `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},`+ - `"signature":`+ - `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS`+ - `lSApmWQxfKTUJqPP3-Kg6NU1Q"`+ - `}`, + f.Add( + `{`+ + `"payload":`+ + `"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF`+ + `tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",`+ + `"protected":"eyJhbGciOiJFUzI1NiJ9",`+ + `"header":`+ + `{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},`+ + `"signature":`+ + `"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS`+ + `lSApmWQxfKTUJqPP3-Kg6NU1Q"`+ + `}`, `{"kty":"EC",`+ `"crv":"P-256",`+ `"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",`+ @@ -138,6 +140,22 @@ func FuzzJWS(f *testing.F) { `}`, ) + f.Add( + `{`+ + `"protected":`+ + `"eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19",`+ + `"payload":`+ + `"$.02",`+ + `"signature":`+ + `"A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY"`+ + `}`, + `{`+ + `"kty":"oct",`+ + `"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75`+ + `aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"`+ + `}`, + ) + f.Fuzz(func(t *testing.T, raw, rawKey string) { var msg1 Message if err := msg1.UnmarshalJSON([]byte(raw)); err != nil { diff --git a/jws/jws_test.go b/jws/jws_test.go index a2610f0..3733acf 100644 --- a/jws/jws_test.go +++ b/jws/jws_test.go @@ -585,39 +585,42 @@ func TestMarshalJSON(t *testing.T) { }) // test for b64 header parameter. - // t.Run("RFC 7797 Section 4.2. Example with Header Parameters", func(t *testing.T) { - // raw := `{` + - // `"protected":` + - // `"eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19",` + - // `"payload":` + - // `"$.02",` + - // `"signature":` + - // `"A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY"` + - // `}` - // var msg Message - // if err := msg.UnmarshalJSON([]byte(raw)); err != nil { - // t.Fatal(err) - // } - // _, payload, err := msg.Verify(FindKeyFunc(func(protected, header *Header) (sig.SigningKey, error) { - // rawKey := `{` + - // `"kty":"oct",` + - // `"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75` + - // `aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"` + - // `}` - // key, err := jwk.ParseKey([]byte(rawKey)) - // if err != nil { - // return nil, err - // } - // return protected.Algorithm().New().NewSigningKey(key), nil - // })) - // if err != nil { - // t.Fatal(err) - // } - // want := []byte(`$.02`) - // if !bytes.Equal(payload, want) { - // t.Errorf("unexpected payload: want %q, got %q", string(want), string(payload)) - // } - // }) + t.Run("RFC 7797 Section 4.2. Example with Header Parameters", func(t *testing.T) { + msg := NewRawMessage([]byte("$.02")) + header := NewHeader() + header.SetAlgorithm(jwa.HS256) + header.SetBase64(false) + + rawKey := `{` + + `"kty":"oct",` + + `"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75` + + `aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"` + + `}` + key, err := jwk.ParseKey([]byte(rawKey)) + if err != nil { + t.Fatal(err) + } + + if err := msg.Sign(header, nil, jwa.HS256.New().NewSigningKey(key)); err != nil { + t.Fatal(err) + } + + got, err := msg.MarshalJSON() + if err != nil { + t.Fatal(err) + } + want := `{` + + `"payload":` + + `"$.02",` + + `"protected":` + + `"eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19",` + + `"signature":` + + `"A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY"` + + `}` + if string(got) != want { + t.Errorf("want %s, got %s", want, got) + } + }) } func TestSign(t *testing.T) {