From 316cb978f0803724c7fa62972b07decf7352ac80 Mon Sep 17 00:00:00 2001 From: Ichinose Shogo Date: Thu, 7 Oct 2021 17:49:02 +0900 Subject: [PATCH] improve performance of parsing jwks --- provider/assume-role/github/jwk/base64.go | 34 +++++ provider/assume-role/github/jwk/ecdsa.go | 59 ++++---- provider/assume-role/github/jwk/ed25519.go | 54 +++---- provider/assume-role/github/jwk/jwk.go | 21 ++- provider/assume-role/github/jwk/jwk_test.go | 157 +++++++++++++++++++- provider/assume-role/github/jwk/rsa.go | 149 +++++++++---------- 6 files changed, 337 insertions(+), 137 deletions(-) create mode 100644 provider/assume-role/github/jwk/base64.go diff --git a/provider/assume-role/github/jwk/base64.go b/provider/assume-role/github/jwk/base64.go new file mode 100644 index 0000000..e6cf956 --- /dev/null +++ b/provider/assume-role/github/jwk/base64.go @@ -0,0 +1,34 @@ +package jwk + +import ( + "encoding/base64" + "fmt" +) + +type base64Context struct { + src []byte + dst []byte + err error +} + +// newBase64Context pre-allocates base64 decoding buffers. +func newBase64Context(n int) base64Context { + src := make([]byte, n) + dst := make([]byte, base64.RawURLEncoding.DecodedLen(n)) + return base64Context{ + src: src, + dst: dst, + } +} + +// decode decodes s as base64 raw url encoding. +// the returned slice is valid until next call. +func (ctx *base64Context) decode(s string, name string) []byte { + src := ctx.src[:len(s)] + copy(src, s) + n, err := base64.RawURLEncoding.Decode(ctx.dst, src) + if err != nil && ctx.err != nil { + ctx.err = fmt.Errorf("jwk: failed to parse the parameter %s: %w", name, err) + } + return ctx.dst[:n] +} diff --git a/provider/assume-role/github/jwk/ecdsa.go b/provider/assume-role/github/jwk/ecdsa.go index 292fe06..6e577c5 100644 --- a/provider/assume-role/github/jwk/ecdsa.go +++ b/provider/assume-role/github/jwk/ecdsa.go @@ -3,7 +3,6 @@ package jwk import ( "crypto/ecdsa" "crypto/elliptic" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -79,25 +78,26 @@ func (key *ecdsaPrivateKey) decode() error { return fmt.Errorf("jwk: unknown elliptic curve: %q", key.Crv) } - dataX, err := base64.RawURLEncoding.DecodeString(key.X) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter x: %w", err) - } - key.privateKey.X = new(big.Int).SetBytes(dataX) + ctx := key.getContext() + key.privateKey.X = new(big.Int).SetBytes(ctx.decode(key.X, "x")) + key.privateKey.Y = new(big.Int).SetBytes(ctx.decode(key.Y, "y")) + key.privateKey.D = new(big.Int).SetBytes(ctx.decode(key.D, "d")) - dataY, err := base64.RawURLEncoding.DecodeString(key.Y) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter y: %w", err) - } - key.privateKey.Y = new(big.Int).SetBytes(dataY) + return ctx.err +} - dataD, err := base64.RawURLEncoding.DecodeString(key.D) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter d: %w", err) +func (key *ecdsaPrivateKey) getContext() base64Context { + var size int + if len(key.X) > size { + size = len(key.X) } - key.privateKey.D = new(big.Int).SetBytes(dataD) - - return nil + if len(key.Y) > size { + size = len(key.Y) + } + if len(key.D) > size { + size = len(key.D) + } + return newBase64Context(size) } // RFC7518 6.2.1. Parameters for Elliptic Curve Public Keys @@ -162,17 +162,20 @@ func (key *ecdsaPublicKey) decode() error { return fmt.Errorf("jwk: unknown elliptic curve: %q", key.Crv) } - dataX, err := base64.RawURLEncoding.DecodeString(key.X) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter x: %w", err) - } - key.publicKey.X = new(big.Int).SetBytes(dataX) + ctx := key.getContext() + key.publicKey.X = new(big.Int).SetBytes(ctx.decode(key.X, "x")) + key.publicKey.Y = new(big.Int).SetBytes(ctx.decode(key.Y, "y")) - dataY, err := base64.RawURLEncoding.DecodeString(key.Y) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter y: %w", err) - } - key.publicKey.Y = new(big.Int).SetBytes(dataY) + return ctx.err +} - return nil +func (key *ecdsaPublicKey) getContext() base64Context { + var size int + if len(key.X) > size { + size = len(key.X) + } + if len(key.Y) > size { + size = len(key.Y) + } + return newBase64Context(size) } diff --git a/provider/assume-role/github/jwk/ed25519.go b/provider/assume-role/github/jwk/ed25519.go index c02c5f6..e805819 100644 --- a/provider/assume-role/github/jwk/ed25519.go +++ b/provider/assume-role/github/jwk/ed25519.go @@ -2,7 +2,6 @@ package jwk import ( "crypto/ed25519" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -61,31 +60,36 @@ func (key *ed25519PrivateKey) PublicKey() interface{} { } func (key *ed25519PrivateKey) decode() error { - if base64.RawURLEncoding.DecodedLen(len(key.X)) != ed25519.PublicKeySize { - return fmt.Errorf("jwk: the parameter x has invalid size") - } - if base64.RawURLEncoding.DecodedLen(len(key.D)) != ed25519.PrivateKeySize-ed25519.PublicKeySize { - return fmt.Errorf("jwk: the parameter d has invalid size") - } + ctx := key.getContext() - publicKey := make([]byte, ed25519.PublicKeySize) privateKey := make([]byte, ed25519.PrivateKeySize) - - var err error - _, err = base64.RawURLEncoding.Decode(publicKey, []byte(key.X)) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter x: %w", err) + data := ctx.decode(key.D, "d") + if len(data) != ed25519.PrivateKeySize-ed25519.PublicKeySize { + return fmt.Errorf("jwk: the parameter d has invalid size") } - _, err = base64.RawURLEncoding.Decode(privateKey, []byte(key.D)) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter d: %w", err) + copy(privateKey, data) + + publicKey := ctx.decode(key.X, "x") + if len(publicKey) != ed25519.PublicKeySize { + return fmt.Errorf("jwk: the parameter x has invalid size") } copy(privateKey[32:], publicKey) key.publicKey = ed25519.PublicKey(publicKey) key.privateKey = ed25519.PrivateKey(privateKey) - return nil + return ctx.err +} + +func (key *ed25519PrivateKey) getContext() base64Context { + var size int + if len(key.X) > size { + size = len(key.X) + } + if len(key.D) > size { + size = len(key.D) + } + return newBase64Context(size) } // RFC8037 2. Key Type "OKP" @@ -132,15 +136,15 @@ func parseEd25519PublicKey(data []byte) (Key, error) { // decode decodes the encoded values into publicKey. func (key *ed25519PublicKey) decode() error { - if base64.RawURLEncoding.DecodedLen(len(key.X)) != ed25519.PublicKeySize { + ctx := key.getContext() + data := ctx.decode(key.X, "x") + if len(data) != ed25519.PublicKeySize { return fmt.Errorf("jwk: the parameter x has invalid size") } - x := []byte(key.X) - data := make([]byte, ed25519.PublicKeySize) - _, err := base64.RawURLEncoding.Decode(data, x) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter x: %w", err) - } key.publicKey = ed25519.PublicKey(data) - return nil + return ctx.err +} + +func (key *ed25519PublicKey) getContext() base64Context { + return newBase64Context(len(key.X)) } diff --git a/provider/assume-role/github/jwk/jwk.go b/provider/assume-role/github/jwk/jwk.go index 8c04460..51fa5d9 100644 --- a/provider/assume-role/github/jwk/jwk.go +++ b/provider/assume-role/github/jwk/jwk.go @@ -169,9 +169,9 @@ func (key *commonKey) decode() error { // ParseKey parses a JWK. func ParseKey(data []byte) (Key, error) { var hint struct { - Kty string `json:"kty"` - Crv string `json:"crv"` - D json.RawMessage `json:"d"` + Kty string `json:"kty"` + Crv string `json:"crv"` + D hasValue `json:"d"` } if err := json.Unmarshal(data, &hint); err != nil { @@ -179,19 +179,19 @@ func ParseKey(data []byte) (Key, error) { } switch hint.Kty { case "EC": - if len(hint.D) > 0 { + if hint.D { return parseEcdsaPrivateKey(data) } else { return parseEcdsaPublicKey(data) } case "RSA": - if len(hint.D) > 0 { + if hint.D { return parseRSAPrivateKey(data) } else { return parseRSAPublicKey(data) } case "OKP": - if len(hint.D) > 0 { + if hint.D { return parseOkpPrivateKey(data, hint.Crv) } else { return parseOkpPublicKey(data, hint.Crv) @@ -202,3 +202,12 @@ func ParseKey(data []byte) (Key, error) { return nil, fmt.Errorf("jwk: unknown key type: %s", hint.Kty) } } + +// hasValue checks a JSON string has the specific key. +// it don't parse the value. +type hasValue bool + +func (v *hasValue) UnmarshalJSON(data []byte) error { + *v = true + return nil +} diff --git a/provider/assume-role/github/jwk/jwk_test.go b/provider/assume-role/github/jwk/jwk_test.go index ecebb91..3497671 100644 --- a/provider/assume-role/github/jwk/jwk_test.go +++ b/provider/assume-role/github/jwk/jwk_test.go @@ -209,9 +209,103 @@ func TestKey_RFC7517AppendixA(t *testing.T) { }) } +func BenchmarkKey_RFC7517AppendixA(b *testing.B) { + b.Run("RFC 7517 A.1. Example Public Keys (EC)", func(b *testing.B) { + rawKey := []byte(`{"kty":"EC",` + + `"crv":"P-256",` + + `"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",` + + `"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",` + + `"use":"enc",` + + `"kid":"1"}`) + for i := 0; i < b.N; i++ { + ParseKey(rawKey) + } + }) + + b.Run("RFC 7517 A.1. Example Public Keys (RSA)", func(b *testing.B) { + rawKey := []byte(`{"kty":"RSA",` + + `"n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx` + + `4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs` + + `tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2` + + `QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI` + + `SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb` + + `w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",` + + `"e":"AQAB",` + + `"alg":"RS256",` + + `"kid":"2011-04-29"}`) + for i := 0; i < b.N; i++ { + ParseKey(rawKey) + } + }) + + b.Run("RFC 7517 A.2. Example Private Keys (EC)", func(b *testing.B) { + rawKey := []byte(`{"kty":"EC",` + + `"crv":"P-256",` + + `"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",` + + `"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",` + + `"d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",` + + `"use":"enc",` + + `"kid":"1"}`) + for i := 0; i < b.N; i++ { + ParseKey(rawKey) + } + }) + + b.Run("RFC 7517 A.2. Example Private Keys (RSA)", func(b *testing.B) { + rawKey := []byte(`{"kty":"RSA",` + + `"n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4` + + `cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMst` + + `n64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2Q` + + `vzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbIS` + + `D08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw` + + `0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",` + + `"e":"AQAB",` + + `"d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9` + + `M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqij` + + `wp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d` + + `_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBz` + + `nbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFz` + + `me1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q",` + + `"p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPV` + + `nwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqV` + + `WlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs",` + + `"q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyum` + + `qjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgx` + + `kIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk",` + + `"dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oim` + + `YwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_Nmtu` + + `YZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0",` + + `"dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUU` + + `vMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9` + + `GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk",` + + `"qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzg` + + `UIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rx` + + `yR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU",` + + `"alg":"RS256",` + + `"kid":"2011-04-29"}`) + for i := 0; i < b.N; i++ { + ParseKey(rawKey) + } + }) + + b.Run("RFC 7517 A.3. Example Symmetric Keys (A128KW)", func(b *testing.B) { + rawKey := []byte(`{"kty":"oct","alg":"A128KW","k":"GawgguFyGrWKav7AX4VKUg"}`) + for i := 0; i < b.N; i++ { + ParseKey(rawKey) + } + }) + + b.Run("RFC 7517 A.3. Example Symmetric Keys (HMAC)", func(b *testing.B) { + rawKey := []byte(`{"kty":"oct","k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow","kid":"HMAC key used in JWS spec Appendix A.1 example"}`) + for i := 0; i < b.N; i++ { + ParseKey(rawKey) + } + }) +} + func TestKey_RFC7517AppendixB(t *testing.T) { // RFC7517 Appendix B. Example Use of "x5c" (X.509 Certificate Chain) Parameter - rawKey := ` {"kty":"RSA",` + + rawKey := []byte(`{"kty":"RSA",` + `"use":"sig",` + `"kid":"1b94c",` + `"n":"vrjOfz9Ccdgx5nQudyhdoR17V-IubWMeOZCwX_jj0hgAsz2J_pqYW08` + @@ -241,8 +335,8 @@ func TestKey_RFC7517AppendixB(t *testing.T) { `2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo` + `4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTq` + `gawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA=="]` + - `}` - key, err := ParseKey([]byte(rawKey)) + `}`) + key, err := ParseKey(rawKey) if err != nil { t.Fatal(err) } @@ -265,6 +359,44 @@ func TestKey_RFC7517AppendixB(t *testing.T) { } } +func BenchmarkKey_RFC7517AppendixB(b *testing.B) { + // RFC7517 Appendix B. Example Use of "x5c" (X.509 Certificate Chain) Parameter + rawKey := []byte(`{"kty":"RSA",` + + `"use":"sig",` + + `"kid":"1b94c",` + + `"n":"vrjOfz9Ccdgx5nQudyhdoR17V-IubWMeOZCwX_jj0hgAsz2J_pqYW08` + + `PLbK_PdiVGKPrqzmDIsLI7sA25VEnHU1uCLNwBuUiCO11_-7dYbsr4iJmG0Q` + + `u2j8DsVyT1azpJC_NG84Ty5KKthuCaPod7iI7w0LK9orSMhBEwwZDCxTWq4a` + + `YWAchc8t-emd9qOvWtVMDC2BXksRngh6X5bUYLy6AyHKvj-nUy1wgzjYQDwH` + + `MTplCoLtU-o-8SNnZ1tmRoGE9uJkBLdh5gFENabWnU5m1ZqZPdwS-qo-meMv` + + `VfJb6jJVWRpl2SUtCnYG2C32qvbWbjZ_jBPD5eunqsIo1vQ",` + + `"e":"AQAB",` + + `"x5c":` + + `["MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJB` + + `gNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYD` + + `VQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1` + + `wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBg` + + `NVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDV` + + `QQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1w` + + `YmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnH` + + `YMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66` + + `s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6` + + `SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpn` + + `fajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPq` + + `PvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVk` + + `aZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BA` + + `QUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL` + + `+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1` + + `zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL` + + `2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo` + + `4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTq` + + `gawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA=="]` + + `}`) + for i := 0; i < b.N; i++ { + ParseKey(rawKey) + } +} + func TestKey_RFC8037AppendixA(t *testing.T) { t.Run("A.1. Ed25519 Private Key", func(t *testing.T) { rawKey := `{"kty":"OKP","crv":"Ed25519",` + @@ -328,3 +460,22 @@ func TestKey_RFC8037AppendixA(t *testing.T) { } }) } + +func BenchmarkKey_RFC8037AppendixA(b *testing.B) { + b.Run("A.1. Ed25519 Private Key", func(b *testing.B) { + rawKey := []byte(`{"kty":"OKP","crv":"Ed25519",` + + `"d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A",` + + `"x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}`) + for i := 0; i < b.N; i++ { + ParseKey(rawKey) + } + }) + + b.Run("A.2. Ed25519 Public Key", func(b *testing.B) { + rawKey := []byte(`{"kty":"OKP","crv":"Ed25519",` + + `"x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}`) + for i := 0; i < b.N; i++ { + ParseKey(rawKey) + } + }) +} diff --git a/provider/assume-role/github/jwk/rsa.go b/provider/assume-role/github/jwk/rsa.go index 673b0b2..337186a 100644 --- a/provider/assume-role/github/jwk/rsa.go +++ b/provider/assume-role/github/jwk/rsa.go @@ -2,10 +2,8 @@ package jwk import ( "crypto/rsa" - "encoding/base64" "encoding/json" "errors" - "fmt" "math/big" ) @@ -88,64 +86,31 @@ func (key *rsaPrivateKey) PublicKey() interface{} { } func (key *rsaPrivateKey) decode() error { + ctx := key.getContext() + // parameters for public key - dataE, err := base64.RawURLEncoding.DecodeString(key.E) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter e: %w", err) - } var e int - for _, v := range dataE { + for _, v := range ctx.decode(key.E, "e") { e = (e << 8) | int(v) } - key.privateKey.E = e + key.privateKey.PublicKey.E = e - dataN, err := base64.RawURLEncoding.DecodeString(key.N) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter n: %w", err) - } - key.privateKey.N = new(big.Int).SetBytes(dataN) + key.privateKey.PublicKey.N = new(big.Int).SetBytes(ctx.decode(key.N, "n")) // parameters for private key - dataD, err := base64.RawURLEncoding.DecodeString(key.D) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter d: %w", err) - } - key.privateKey.D = new(big.Int).SetBytes(dataD) - - dataP, err := base64.RawURLEncoding.DecodeString(key.P) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter p: %w", err) - } - p := new(big.Int).SetBytes(dataP) - - dataQ, err := base64.RawURLEncoding.DecodeString(key.Q) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter q: %w", err) - } - q := new(big.Int).SetBytes(dataQ) + key.privateKey.D = new(big.Int).SetBytes(ctx.decode(key.D, "d")) + p := new(big.Int).SetBytes(ctx.decode(key.P, "p")) + q := new(big.Int).SetBytes(ctx.decode(key.Q, "q")) key.privateKey.Primes = []*big.Int{p, q} crtValues := make([]rsa.CRTValue, 0, len(key.Oth)) - for i, v := range key.Oth { - dataR, err := base64.RawURLEncoding.DecodeString(v.R) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter oth[%d].r: %w", i, err) - } - r := new(big.Int).SetBytes(dataR) + for _, v := range key.Oth { + r := new(big.Int).SetBytes(ctx.decode(v.R, "oth[].r")) key.privateKey.Primes = append(key.privateKey.Primes, r) - dataD, err := base64.RawURLEncoding.DecodeString(v.D) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter oth[%d].d: %w", i, err) - } - d := new(big.Int).SetBytes(dataD) - - dataT, err := base64.RawURLEncoding.DecodeString(v.T) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter oth[%d].d: %w", i, err) - } - t := new(big.Int).SetBytes(dataT) + d := new(big.Int).SetBytes(ctx.decode(v.D, "oth[].d")) + t := new(big.Int).SetBytes(ctx.decode(v.T, "oth[].t")) crtValues = append(crtValues, rsa.CRTValue{ Exp: d, @@ -156,23 +121,9 @@ func (key *rsaPrivateKey) decode() error { // precomputed values if key.Dp != "" && key.Dq != "" && key.Qi != "" { - dataDp, err := base64.RawURLEncoding.DecodeString(key.Q) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter dp: %w", err) - } - dp := new(big.Int).SetBytes(dataDp) - - dataDq, err := base64.RawURLEncoding.DecodeString(key.Q) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter dq: %w", err) - } - dq := new(big.Int).SetBytes(dataDq) - - dataQi, err := base64.RawURLEncoding.DecodeString(key.Qi) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter qi: %w", err) - } - qi := new(big.Int).SetBytes(dataQi) + dp := new(big.Int).SetBytes(ctx.decode(key.Dp, "dp")) + dq := new(big.Int).SetBytes(ctx.decode(key.Dp, "dq")) + qi := new(big.Int).SetBytes(ctx.decode(key.Dp, "qi")) key.privateKey.Precomputed = rsa.PrecomputedValues{ Dp: dp, @@ -182,9 +133,52 @@ func (key *rsaPrivateKey) decode() error { } } + if ctx.err != nil { + return ctx.err + } return key.privateKey.Validate() } +func (key *rsaPrivateKey) getContext() base64Context { + var size int + if len(key.E) > size { + size = len(key.E) + } + if len(key.N) > size { + size = len(key.N) + } + if len(key.D) > size { + size = len(key.D) + } + if len(key.P) > size { + size = len(key.P) + } + if len(key.Q) > size { + size = len(key.Q) + } + for _, v := range key.Oth { + if len(v.R) > size { + size = len(v.R) + } + if len(v.D) > size { + size = len(v.D) + } + if len(v.D) > size { + size = len(v.D) + } + } + if len(key.Dp) > size { + size = len(key.Dp) + } + if len(key.Dq) > size { + size = len(key.Dq) + } + if len(key.Qi) > size { + size = len(key.Qi) + } + return newBase64Context(size) +} + // RFC7518 6.3.1. Parameters for RSA Public Keys type rsaPublicKey struct { commonKey @@ -231,21 +225,26 @@ func parseRSAPublicKey(data []byte) (Key, error) { // decode decodes the encoded values into publicKey. func (key *rsaPublicKey) decode() error { - dataE, err := base64.RawURLEncoding.DecodeString(key.E) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter e: %w", err) - } + ctx := key.getContext() + var e int - for _, v := range dataE { + for _, v := range ctx.decode(key.E, "e") { e = (e << 8) | int(v) } key.publicKey.E = e - dataN, err := base64.RawURLEncoding.DecodeString(key.N) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter n: %w", err) - } - key.publicKey.N = new(big.Int).SetBytes(dataN) + key.publicKey.N = new(big.Int).SetBytes(ctx.decode(key.N, "n")) - return nil + return ctx.err +} + +func (key *rsaPublicKey) getContext() base64Context { + var size int + if len(key.E) > size { + size = len(key.E) + } + if len(key.N) > size { + size = len(key.N) + } + return newBase64Context(size) }