Skip to content

Commit

Permalink
implement marshal json
Browse files Browse the repository at this point in the history
  • Loading branch information
shogo82148 committed Nov 7, 2022
1 parent 0d5dfbc commit 0ba0548
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 2 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
151 changes: 151 additions & 0 deletions jws/fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"+
"."+
Expand Down
27 changes: 27 additions & 0 deletions jws/jws.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
159 changes: 158 additions & 1 deletion jws/jws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package jws

import (
"bytes"
"encoding/json"
"errors"
"testing"

Expand Down Expand Up @@ -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":` +
Expand Down Expand Up @@ -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",` +
Expand Down

0 comments on commit 0ba0548

Please sign in to comment.