From 5f5cbb41efa80799d9e518ffee374317b16148a8 Mon Sep 17 00:00:00 2001 From: Ichinose Shogo Date: Wed, 17 Aug 2022 13:12:08 +0900 Subject: [PATCH] fix base64 error handling --- provider/assume-role/github/jwk/base64.go | 21 +++- provider/assume-role/github/jwk/ecdsa.go | 6 +- provider/assume-role/github/jwk/ed25519.go | 24 ++-- provider/assume-role/github/jwk/jwk.go | 8 +- provider/assume-role/github/jwk/jwk_test.go | 111 +++++++++++++++++++ provider/assume-role/github/jwk/rsa.go | 6 +- provider/assume-role/github/jwk/symmetric.go | 16 ++- 7 files changed, 163 insertions(+), 29 deletions(-) diff --git a/provider/assume-role/github/jwk/base64.go b/provider/assume-role/github/jwk/base64.go index e6cf956..a770aba 100644 --- a/provider/assume-role/github/jwk/base64.go +++ b/provider/assume-role/github/jwk/base64.go @@ -27,8 +27,25 @@ 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) + if err != nil && ctx.err == nil { + ctx.err = &base64DecodeError{ + name: name, + err: err, + } } return ctx.dst[:n] } + +type base64DecodeError struct { + name string + err error +} + +// Error implements the error interface. +func (err *base64DecodeError) Error() string { + return fmt.Sprintf("jwk: failed to parse the parameter %s: %v", err.name, err.err) +} + +func (err *base64DecodeError) Unwrap() error { + return err.err +} diff --git a/provider/assume-role/github/jwk/ecdsa.go b/provider/assume-role/github/jwk/ecdsa.go index 6e577c5..aed19d8 100644 --- a/provider/assume-role/github/jwk/ecdsa.go +++ b/provider/assume-role/github/jwk/ecdsa.go @@ -28,11 +28,11 @@ type ecdsaPrivateKey struct { privateKey ecdsa.PrivateKey } -func (key *ecdsaPrivateKey) PrivateKey() interface{} { +func (key *ecdsaPrivateKey) PrivateKey() any { return &key.privateKey } -func (key *ecdsaPrivateKey) PublicKey() interface{} { +func (key *ecdsaPrivateKey) PublicKey() any { return &key.privateKey.PublicKey } @@ -116,7 +116,7 @@ type ecdsaPublicKey struct { publicKey ecdsa.PublicKey } -func (key *ecdsaPublicKey) PublicKey() interface{} { +func (key *ecdsaPublicKey) PublicKey() any { return &key.publicKey } diff --git a/provider/assume-role/github/jwk/ed25519.go b/provider/assume-role/github/jwk/ed25519.go index e805819..51166b4 100644 --- a/provider/assume-role/github/jwk/ed25519.go +++ b/provider/assume-role/github/jwk/ed25519.go @@ -51,29 +51,34 @@ func parseEd25519PrivateKey(data []byte) (Key, error) { return &key, nil } -func (key *ed25519PrivateKey) PrivateKey() interface{} { +func (key *ed25519PrivateKey) PrivateKey() any { return key.privateKey } -func (key *ed25519PrivateKey) PublicKey() interface{} { +func (key *ed25519PrivateKey) PublicKey() any { return key.publicKey } func (key *ed25519PrivateKey) decode() error { + const keySize = ed25519.PrivateKeySize - ed25519.PublicKeySize ctx := key.getContext() privateKey := make([]byte, ed25519.PrivateKeySize) data := ctx.decode(key.D, "d") - if len(data) != ed25519.PrivateKeySize-ed25519.PublicKeySize { + if ctx.err != nil { + return ctx.err + } + if copy(privateKey, data) != keySize { return fmt.Errorf("jwk: the parameter d has invalid size") } - copy(privateKey, data) publicKey := ctx.decode(key.X, "x") - if len(publicKey) != ed25519.PublicKeySize { + if ctx.err != nil { + return ctx.err + } + if copy(privateKey[keySize:], 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) @@ -103,7 +108,7 @@ type ed25519PublicKey struct { publicKey ed25519.PublicKey } -func (key *ed25519PublicKey) PublicKey() interface{} { +func (key *ed25519PublicKey) PublicKey() any { return key.publicKey } @@ -138,11 +143,14 @@ func parseEd25519PublicKey(data []byte) (Key, error) { func (key *ed25519PublicKey) decode() error { ctx := key.getContext() data := ctx.decode(key.X, "x") + if ctx.err != nil { + return ctx.err + } if len(data) != ed25519.PublicKeySize { return fmt.Errorf("jwk: the parameter x has invalid size") } key.publicKey = ed25519.PublicKey(data) - return ctx.err + return nil } func (key *ed25519PublicKey) getContext() base64Context { diff --git a/provider/assume-role/github/jwk/jwk.go b/provider/assume-role/github/jwk/jwk.go index 51fa5d9..a4b8e37 100644 --- a/provider/assume-role/github/jwk/jwk.go +++ b/provider/assume-role/github/jwk/jwk.go @@ -42,11 +42,11 @@ type Key interface { // PrivateKey returns the private key. // If the key doesn't contain any private key, it returns nil. - PrivateKey() interface{} + PrivateKey() any // PublicKey returns the public key. // If the key doesn't contain any public key, it returns nil. - PublicKey() interface{} + PublicKey() any } type commonKey struct { @@ -115,11 +115,11 @@ func (key *commonKey) X509CertificateSHA256() string { return key.X5tS256 } -func (key *commonKey) PrivateKey() interface{} { +func (key *commonKey) PrivateKey() any { return nil } -func (key *commonKey) PublicKey() interface{} { +func (key *commonKey) PublicKey() any { return nil } diff --git a/provider/assume-role/github/jwk/jwk_test.go b/provider/assume-role/github/jwk/jwk_test.go index 3497671..9888b6b 100644 --- a/provider/assume-role/github/jwk/jwk_test.go +++ b/provider/assume-role/github/jwk/jwk_test.go @@ -5,6 +5,7 @@ import ( "crypto/ed25519" "crypto/elliptic" "crypto/rsa" + "errors" "reflect" "testing" ) @@ -479,3 +480,113 @@ func BenchmarkKey_RFC8037AppendixA(b *testing.B) { } }) } + +func TestKey_Base64Error(t *testing.T) { + t.Run("EC Private Key", func(t *testing.T) { + rawKey := `{"kty":"EC",` + + `"crv":"P-256",` + + `"x":"!!!INVALID BASE64!!!",` + + `"y":"!!!INVALID BASE64!!!",` + + `"use":"enc",` + + `"kid":"1"}` + var e *base64DecodeError + _, err := ParseKey([]byte(rawKey)) + if !errors.As(err, &e) { + t.Errorf("want *base64DecodeError, got %T", err) + } + if e.err == nil { + t.Error("want not nil, got nil") + } + if e.name != "x" && e.name != "y" { + t.Errorf("want name is x or y, got %s", e.name) + } + }) + + t.Run("RFC 7517 A.2. Example Private Keys (EC)", func(t *testing.T) { + rawKey := `{"kty":"EC",` + + `"crv":"P-256",` + + `"x":"!!!INVALID BASE64!!!",` + + `"y":"!!!INVALID BASE64!!!",` + + `"d":"!!!INVALID BASE64!!!",` + + `"use":"enc",` + + `"kid":"1"}` + var e *base64DecodeError + _, err := ParseKey([]byte(rawKey)) + if !errors.As(err, &e) { + t.Errorf("want *base64DecodeError, got %T", err) + } + if e.err == nil { + t.Error("want not nil, got nil") + } + if e.name != "x" && e.name != "y" && e.name != "d" { + t.Errorf("want name is x or y or d, got %s", e.name) + } + }) + + t.Run("RSA Public Key", func(t *testing.T) { + rawKey := `{"kty":"RSA",` + + `"n":"!!!INVALID BASE64!!!",` + + `"e":"!!!INVALID BASE64!!!",` + + `"alg":"RS256",` + + `"kid":"2011-04-29"}` + var e *base64DecodeError + _, err := ParseKey([]byte(rawKey)) + if !errors.As(err, &e) { + t.Errorf("want *base64DecodeError, got %T", err) + } + if e.err == nil { + t.Error("want not nil, got nil") + } + if e.name != "n" && e.name != "e" { + t.Errorf("want name is n or e, got %s", e.name) + } + }) + + t.Run("Symmetric Keys (HMAC)", func(t *testing.T) { + rawKey := `{"kty":"oct","k":"!!!INVALID BASE64!!!"}` + var e *base64DecodeError + _, err := ParseKey([]byte(rawKey)) + if !errors.As(err, &e) { + t.Errorf("want *base64DecodeError, got %T", err) + } + if e.err == nil { + t.Error("want not nil, got nil") + } + if e.name != "k" { + t.Errorf("want name is k, got %s", e.name) + } + }) + + t.Run("Ed25519 Public Key", func(t *testing.T) { + rawKey := `{"kty":"OKP","crv":"Ed25519",` + + `"x":"!!!INVALID BASE64!!!"}` + var e *base64DecodeError + _, err := ParseKey([]byte(rawKey)) + if !errors.As(err, &e) { + t.Errorf("want *base64DecodeError, got %T", err) + } + if e.err == nil { + t.Error("want not nil, got nil") + } + if e.name != "x" { + t.Errorf("want name is x, got %s", e.name) + } + }) + + t.Run("Ed25519 Private Key", func(t *testing.T) { + rawKey := `{"kty":"OKP","crv":"Ed25519",` + + `"d":"!!!INVALID BASE64!!!",` + + `"x":"!!!INVALID BASE64!!!"}` + var e *base64DecodeError + _, err := ParseKey([]byte(rawKey)) + if !errors.As(err, &e) { + t.Errorf("want *base64DecodeError, got %T", err) + } + if e.err == nil { + t.Error("want not nil, got nil") + } + if e.name != "d" && e.name != "x" { + t.Errorf("want name is d or x, got %s", e.name) + } + }) +} diff --git a/provider/assume-role/github/jwk/rsa.go b/provider/assume-role/github/jwk/rsa.go index 337186a..0cb2b2d 100644 --- a/provider/assume-role/github/jwk/rsa.go +++ b/provider/assume-role/github/jwk/rsa.go @@ -77,11 +77,11 @@ func parseRSAPrivateKey(data []byte) (Key, error) { return &key, nil } -func (key *rsaPrivateKey) PrivateKey() interface{} { +func (key *rsaPrivateKey) PrivateKey() any { return &key.privateKey } -func (key *rsaPrivateKey) PublicKey() interface{} { +func (key *rsaPrivateKey) PublicKey() any { return &key.privateKey.PublicKey } @@ -192,7 +192,7 @@ type rsaPublicKey struct { publicKey rsa.PublicKey } -func (key *rsaPublicKey) PublicKey() interface{} { +func (key *rsaPublicKey) PublicKey() any { return &key.publicKey } diff --git a/provider/assume-role/github/jwk/symmetric.go b/provider/assume-role/github/jwk/symmetric.go index 8f0435e..ab33f14 100644 --- a/provider/assume-role/github/jwk/symmetric.go +++ b/provider/assume-role/github/jwk/symmetric.go @@ -1,9 +1,7 @@ package jwk import ( - "encoding/base64" "encoding/json" - "fmt" ) // RFC7518 6.4. Parameters for Symmetric Keys @@ -30,17 +28,17 @@ func parseSymmetricKey(data []byte) (Key, error) { return &key, nil } -func (key *symmetricKey) PrivateKey() interface{} { +func (key *symmetricKey) PrivateKey() any { return key.key } // decode decodes the encoded values into publicKey. func (key *symmetricKey) decode() error { - k, err := base64.RawURLEncoding.DecodeString(key.K) - if err != nil { - return fmt.Errorf("jwk: failed to parse parameter k: %w", err) - } - key.key = k + ctx := key.getContext() + key.key = ctx.decode(key.K, "k") + return ctx.err +} - return nil +func (key *symmetricKey) getContext() base64Context { + return newBase64Context(len(key.K)) }