diff --git a/go.mod b/go.mod index 0a3df4ff7..ab4085e41 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( code.cloudfoundry.org/tlsconfig v0.0.0-20230320190829-8f91c367795b github.com/benbjohnson/jmphash v0.0.0-20141216154655-2d58f234cd86 github.com/cespare/xxhash v1.1.0 - github.com/dvsekhvalnov/jose2go v1.5.0 + github.com/dvsekhvalnov/jose2go v1.6.0 github.com/emirpasic/gods v1.18.1 github.com/fortytw2/leaktest v1.3.0 github.com/go-kit/kit v0.12.0 diff --git a/go.sum b/go.sum index d02eb5e46..884d60dc1 100644 --- a/go.sum +++ b/go.sum @@ -617,8 +617,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= -github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY= +github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= diff --git a/vendor/github.com/dvsekhvalnov/jose2go/README.md b/vendor/github.com/dvsekhvalnov/jose2go/README.md index 9df801c57..bbf0ef757 100644 --- a/vendor/github.com/dvsekhvalnov/jose2go/README.md +++ b/vendor/github.com/dvsekhvalnov/jose2go/README.md @@ -12,9 +12,11 @@ Extensively unit tested and cross tested (100+ tests) for compatibility with [jo ## Status -Used in production. GA ready. Current version is 1.5. +Used in production. GA ready. Current version is 1.6. ## Important +v1.6 security tuning options + v1.5 bug fix release v1.4 changes default behavior of inserting `typ=JWT` header if not overriden. As of 1.4 no @@ -250,7 +252,7 @@ func main() { //go use token fmt.Printf("\ntoken = %v\n",token) } -} +} ``` #### AES Key Wrap key management family of algorithms @@ -330,7 +332,7 @@ func main() { //go use token fmt.Printf("\ntoken = %v\n",token) } -} +} ``` #### PBES2 using HMAC SHA with AES Key Wrap key management family of algorithms @@ -482,7 +484,7 @@ func main() { //and/or use headers fmt.Printf("\nheaders = %v\n",headers) } -} +} ``` **RSA-OAEP-256**, **RSA-OAEP** and **RSA1_5** key management algorithms expecting `*rsa.PrivateKey` private key of corresponding length: @@ -522,7 +524,7 @@ func main() { //and/or use headers fmt.Printf("\nheaders = %v\n",headers) } -} +} ``` **PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW** key management algorithms expects `string` passpharase as a key @@ -679,6 +681,8 @@ func main() { } ``` +Two phase validation can be used for implementing additional things like strict `alg` or `enc` validation, see [Customizing library for security](#customizing-library-for-security) for more information. + ### Working with binary payload In addition to work with string payloads (typical use-case) `jose2go` supports encoding and decoding of raw binary data. `jose.DecodeBytes`, `jose.SignBytes` @@ -776,7 +780,7 @@ func main() { //go use token fmt.Printf("\ntoken = %v\n",token) } -} +} ``` ### Dealing with keys **jose2go** provides several helper methods to simplify loading & importing of elliptic and rsa keys. Import `jose2go/keys/rsa` or `jose2go/keys/ecc` respectively: @@ -925,7 +929,88 @@ func main() { ### More examples Checkout `jose_test.go` for more examples. +## Customizing library for security +In response to ever increasing attacks on various JWT implementations, `jose2go` as of version v1.6 introduced number of additional security controls to limit potential attack surface on services and projects using the library. + +### Deregister algorithm implementations +One can use following methods to deregister any signing, encryption, key management or compression algorithms from runtime suite, that is considered unsafe or simply not expected by service. + +- `func DeregisterJwa(alg string) JwaAlgorithm` +- `func DeregisterJwe(alg string) JweEncryption` +- `func DeregisterJws(alg string) JwsAlgorithm` +- `func DeregisterJwc(alg string) JwcAlgorithm` + +All of them expecting alg name matching `jose` constants and returns implementation that have been deregistered. + +### Strict validation +Sometimes it is desirable to verify that `alg` or `enc` values are matching expected before attempting to decode actual payload. +`jose2go` provides helper matchers to be used within [Two-phase validation](#two-phase-validation) precheck: + +- `jose.Alg(key, alg)` - to match alg header +- `jose.Enc(key, alg)` - to match alg and enc headers + +```Go + token := "eyJhbGciOiJSUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.NL_dfVpZkhNn4bZpCyMq5TmnXbT4yiyecuB6Kax_lV8Yq2dG8wLfea-T4UKnrjLOwxlbwLwuKzffWcnWv3LVAWfeBxhGTa0c4_0TX_wzLnsgLuU6s9M2GBkAIuSMHY6UTFumJlEeRBeiqZNrlqvmAzQ9ppJHfWWkW4stcgLCLMAZbTqvRSppC1SMxnvPXnZSWn_Fk_q3oGKWw6Nf0-j-aOhK0S0Lcr0PV69ZE4xBYM9PUS1MpMe2zF5J3Tqlc1VBcJ94fjDj1F7y8twmMT3H1PI9RozO-21R0SiXZ_a93fxhE_l_dj5drgOek7jUN9uBDjkXUwJPAyp9YPehrjyLdw" + + key := Rsa.ReadPublic(....) + + // we expecting 'RS256' alg here and if matching continue to decode with a key + payload, header, err := jose.Decode(token, Alg(key, "RS256")) + + // or match both alg and enc for decrypting scenarios + payload, header, err := jose.Decode(token, Enc(key, "RSA-OAEP-256", "A192CBC-HS384")) +``` + +### Customizing PBKDF2 +As it quite easy to abuse PBES2 family of algorithms via forging header with extra large p2c values, jose-jwt library introduced iteration count limits in v1.6 to reduce runtime exposure. + +By default, maxIterations is set according to [OWASP PBKDF2](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2) Recomendations: + +``` +PBES2-HS256+A128KW: 1300000 +PBES2-HS384+A192KW: 950000 +PBES2-HS512+A256KW: 600000 +``` + +, while minIterations kept at 0 for backward compatibility. + +If it is desired to implement different limits, register new implementation with new parameters: + +```Go + jose.RegisterJwa(NewPbse2HmacAesKWAlg(128, 1300000, 1300000)) + jose.RegisterJwa(NewPbse2HmacAesKWAlg(192, 950000, 950000)) + jose.RegisterJwa(NewPbse2HmacAesKWAlg(256, 600000, 600000)) +``` + +In case you can't upgrade to latest version, but would like to have protections against PBES2 abuse, it is recommended to stick with [Two-phase validation](#two-phase-validation) precheck before decoding: + +```Go +test, headers, err := Decode(token, func(headers map[string]interface{}, payload string) interface{} { + alg := headers["alg"].(string) + p2c := headers["p2c"].(float64) + + if strings.HasPrefix(alg, "PBES2-") && int64(p2c) > 100 { + return errors.New("Too many p2c interation count, aborting") + } + + return "top secret" +}) +``` + ## Changelog +### 1.6 +- ability to deregister specific algorithms +- configurable min/max restrictions for PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW + +### 1.5 +- security and bug fixes + +### 1.4 +- removed extra headers to be inserted by library + +### 1.3 +- security fixes: Invalid Curve Attack on NIST curves + ### 1.2 - interface to access token headers after decoding - interface to provide extra headers for token encoding diff --git a/vendor/github.com/dvsekhvalnov/jose2go/jose.go b/vendor/github.com/dvsekhvalnov/jose2go/jose.go index 1f1c19edb..3549a9186 100644 --- a/vendor/github.com/dvsekhvalnov/jose2go/jose.go +++ b/vendor/github.com/dvsekhvalnov/jose2go/jose.go @@ -1,4 +1,4 @@ -//Package jose provides high level functions for producing (signing, encrypting and +// Package jose provides high level functions for producing (signing, encrypting and // compressing) or consuming (decoding) Json Web Tokens using Java Object Signing and Encryption spec package jose @@ -79,6 +79,42 @@ func RegisterJwc(alg JwcAlgorithm) { jwcCompressors[alg.Name()] = alg } +// DeregisterJwa deregister existing key management algorithm +func DeregisterJwa(alg string) JwaAlgorithm { + jwa := jwaAlgorithms[alg] + + delete(jwaAlgorithms, alg) + + return jwa +} + +// DeregisterJws deregister existing signing algorithm +func DeregisterJws(alg string) JwsAlgorithm { + jws := jwsHashers[alg] + + delete(jwsHashers, alg) + + return jws +} + +// DeregisterJws deregister existing encryption algorithm +func DeregisterJwe(alg string) JweEncryption { + jwe := jweEncryptors[alg] + + delete(jweEncryptors, alg) + + return jwe +} + +// DeregisterJwc deregister existing compression algorithm +func DeregisterJwc(alg string) JwcAlgorithm { + jwc := jwcCompressors[alg] + + delete(jwcCompressors, alg) + + return jwc +} + // JweEncryption is a contract for implementing encryption algorithm type JweEncryption interface { Encrypt(aad, plainText, cek []byte) (iv, cipherText, authTag []byte, err error) @@ -422,3 +458,28 @@ func retrieveActualKey(headers map[string]interface{}, payload string, key inter return key, nil } + +func Alg(key interface{}, jws string) func(headers map[string]interface{}, payload string) interface{} { + return func(headers map[string]interface{}, payload string) interface{} { + alg := headers["alg"].(string) + + if jws == alg { + return key + } + + return errors.New("Expected alg to be '" + jws + "' but got '" + alg + "'") + } +} + +func Enc(key interface{}, jwa string, jwe string) func(headers map[string]interface{}, payload string) interface{} { + return func(headers map[string]interface{}, payload string) interface{} { + alg := headers["alg"].(string) + enc := headers["enc"].(string) + + if jwa == alg && jwe == enc { + return key + } + + return errors.New("Expected alg to be '" + jwa + "' and enc to be '" + jwe + "' but got '" + alg + "' and '" + enc + "'") + } +} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc.go b/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc.go index b1debe486..486d81650 100644 --- a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc.go +++ b/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc.go @@ -2,44 +2,44 @@ package ecc import ( - "math/big" "crypto/ecdsa" - "crypto/elliptic" - "crypto/x509" - "encoding/pem" + "crypto/elliptic" + "crypto/x509" + "encoding/pem" "errors" + "math/big" ) // ReadPublic loads ecdsa.PublicKey from given PKCS1 X509 or PKIX blobs -func ReadPublic(raw []byte) (key *ecdsa.PublicKey,err error) { +func ReadPublic(raw []byte) (key *ecdsa.PublicKey, err error) { var encoded *pem.Block - + if encoded, _ = pem.Decode(raw); encoded == nil { return nil, errors.New("Ecc.ReadPublic(): Key must be PEM encoded PKCS1 X509 certificate or PKIX EC public key") } - + var parsedKey interface{} var cert *x509.Certificate - + if parsedKey, err = x509.ParsePKIXPublicKey(encoded.Bytes); err != nil { - if cert,err = x509.ParseCertificate(encoded.Bytes);err!=nil { + if cert, err = x509.ParseCertificate(encoded.Bytes); err != nil { return nil, err } - - parsedKey=cert.PublicKey + + parsedKey = cert.PublicKey } - + var ok bool - + if key, ok = parsedKey.(*ecdsa.PublicKey); !ok { return nil, errors.New("Ecc.ReadPublic(): Key is not a valid *ecdsa.PublicKey") } - + return key, nil } // ReadPrivate loads ecdsa.PrivateKey from given PKCS1 or PKCS8 blobs -func ReadPrivate(raw []byte) (key *ecdsa.PrivateKey,err error) { +func ReadPrivate(raw []byte) (key *ecdsa.PrivateKey, err error) { var encoded *pem.Block if encoded, _ = pem.Decode(raw); encoded == nil { @@ -48,41 +48,45 @@ func ReadPrivate(raw []byte) (key *ecdsa.PrivateKey,err error) { var parsedKey interface{} - if parsedKey,err=x509.ParseECPrivateKey(encoded.Bytes);err!=nil { - if parsedKey, err = x509.ParsePKCS8PrivateKey(encoded.Bytes);err!=nil { - return nil,err + if parsedKey, err = x509.ParseECPrivateKey(encoded.Bytes); err != nil { + if parsedKey, err = x509.ParsePKCS8PrivateKey(encoded.Bytes); err != nil { + return nil, err } } var ok bool - - if key,ok=parsedKey.(*ecdsa.PrivateKey);!ok { + + if key, ok = parsedKey.(*ecdsa.PrivateKey); !ok { return nil, errors.New("Ecc.ReadPrivate(): Key is not valid *ecdsa.PrivateKey") } - - return key,nil + + return key, nil } // NewPublic constructs ecdsa.PublicKey from given (X,Y) -func NewPublic(x,y []byte) (*ecdsa.PublicKey) { - return &ecdsa.PublicKey{ Curve: curve(len(x)), - X:new(big.Int).SetBytes(x), - Y:new(big.Int).SetBytes(y) } +func NewPublic(x, y []byte) *ecdsa.PublicKey { + return &ecdsa.PublicKey{Curve: curve(len(x)), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y)} } // NewPrivate constructs ecdsa.PrivateKey from given (X,Y) and D -func NewPrivate(x,y,d []byte) (*ecdsa.PrivateKey) { - return &ecdsa.PrivateKey {D:new(big.Int).SetBytes(d), - PublicKey: ecdsa.PublicKey{ Curve:curve(len(x)), - X:new(big.Int).SetBytes(x), - Y:new(big.Int).SetBytes(y)}} +func NewPrivate(x, y, d []byte) *ecdsa.PrivateKey { + return &ecdsa.PrivateKey{D: new(big.Int).SetBytes(d), + PublicKey: ecdsa.PublicKey{Curve: curve(len(x)), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y)}} } -func curve(size int) (elliptic.Curve) { +func curve(size int) elliptic.Curve { switch size { - case 32: return elliptic.P256() - case 48: return elliptic.P384() - case 65,66: return elliptic.P521() //adjust for P-521 curve, which can be 65 or 66 bytes - default: return nil //unsupported curve + case 31, 32: + return elliptic.P256() + case 48: + return elliptic.P384() + case 65, 66: + return elliptic.P521() //adjust for P-521 curve, which can be 65 or 66 bytes + default: + return nil //unsupported curve } -} \ No newline at end of file +} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/pbse2_hmac_aeskw.go b/vendor/github.com/dvsekhvalnov/jose2go/pbse2_hmac_aeskw.go index baeaf9c7a..2915ae6be 100644 --- a/vendor/github.com/dvsekhvalnov/jose2go/pbse2_hmac_aeskw.go +++ b/vendor/github.com/dvsekhvalnov/jose2go/pbse2_hmac_aeskw.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "crypto/sha512" "errors" + "fmt" "hash" "github.com/dvsekhvalnov/jose2go/arrays" @@ -12,15 +13,28 @@ import ( ) func init() { - RegisterJwa(&Pbse2HmacAesKW{keySizeBits: 128, aesKW: &AesKW{keySizeBits: 128}}) - RegisterJwa(&Pbse2HmacAesKW{keySizeBits: 192, aesKW: &AesKW{keySizeBits: 192}}) - RegisterJwa(&Pbse2HmacAesKW{keySizeBits: 256, aesKW: &AesKW{keySizeBits: 256}}) + RegisterJwa(NewPbse2HmacAesKWAlg(128, 1300000, 0)) + RegisterJwa(NewPbse2HmacAesKWAlg(192, 950000, 0)) + RegisterJwa(NewPbse2HmacAesKWAlg(256, 600000, 0)) } // PBSE2 with HMAC key management algorithm implementation type Pbse2HmacAesKW struct { - keySizeBits int - aesKW JwaAlgorithm + keySizeBits int + aesKW JwaAlgorithm + maxIterations int64 + minIterations int64 +} + +func NewPbse2HmacAesKWAlg(keySize int, maxIters int64, minIters int64) JwaAlgorithm { + switch keySize { + case 128: + return &Pbse2HmacAesKW{keySizeBits: 128, maxIterations: maxIters, minIterations: minIters, aesKW: &AesKW{keySizeBits: 128}} + case 192: + return &Pbse2HmacAesKW{keySizeBits: 192, maxIterations: maxIters, minIterations: minIters, aesKW: &AesKW{keySizeBits: 192}} + default: + return &Pbse2HmacAesKW{keySizeBits: 256, maxIterations: maxIters, minIterations: minIters, aesKW: &AesKW{keySizeBits: 256}} + } } func (alg *Pbse2HmacAesKW) Name() string { @@ -46,6 +60,21 @@ func (alg *Pbse2HmacAesKW) WrapNewKey(cekSizeBits int, key interface{}, header m return nil, nil, err } + // use user provided iteration counts if any + if p2c, ok := header["p2c"].(int); ok { + iterationCount = p2c + } + + if int64(iterationCount) > alg.maxIterations { + return nil, nil, errors.New( + fmt.Sprintf("Pbse2HmacAesKW.Unwrap(): expected 'p2c' to be less than %v but got %v", alg.maxIterations, iterationCount)) + } + + if int64(iterationCount) < alg.minIterations { + return nil, nil, errors.New( + fmt.Sprintf("Pbse2HmacAesKW.Unwrap(): expected 'p2c' to be higher than %v but got %v", alg.minIterations, iterationCount)) + } + header["p2c"] = iterationCount header["p2s"] = base64url.Encode(saltInput) @@ -69,8 +98,18 @@ func (alg *Pbse2HmacAesKW) Unwrap(encryptedCek []byte, key interface{}, cekSizeB return nil, errors.New("Pbse2HmacAesKW.Unwrap(): expected 'p2c' param in JWT header, but was not found.") } + if int64(p2c) > alg.maxIterations { + return nil, errors.New( + fmt.Sprintf("Pbse2HmacAesKW.Unwrap(): expected 'p2c' to be less than %v but got %v", alg.maxIterations, p2c)) + } + + if int64(p2c) < alg.minIterations { + return nil, errors.New( + fmt.Sprintf("Pbse2HmacAesKW.Unwrap(): expected 'p2c' to be higher than %v but got %v", alg.minIterations, p2c)) + } + if p2s, ok = header["p2s"].(string); !ok { - return nil, errors.New("Pbse2HmacAesKW.Unwrap(): expected 'p2s' param in JWT header, but was not found.") + return nil, errors.New("Pbse2HmacAesKW.Unwrap(): expected 'p2s' param in JWT header, but was not found") } var saltInput []byte diff --git a/vendor/modules.txt b/vendor/modules.txt index d46d059d8..3b833b680 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -177,7 +177,7 @@ github.com/docker/go-connections/tlsconfig # github.com/docker/go-units v0.5.0 ## explicit github.com/docker/go-units -# github.com/dvsekhvalnov/jose2go v1.5.0 +# github.com/dvsekhvalnov/jose2go v1.6.0 ## explicit; go 1.15 github.com/dvsekhvalnov/jose2go github.com/dvsekhvalnov/jose2go/aes