Skip to content

Commit 48ba0b7

Browse files
authored
Merge pull request #32 from dvsekhvalnov/issue-31-security-tuning
Issue 31 security tuning
2 parents feb89cf + 05eb007 commit 48ba0b7

File tree

4 files changed

+432
-15
lines changed

4 files changed

+432
-15
lines changed

README.md

+91-6
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ Extensively unit tested and cross tested (100+ tests) for compatibility with [jo
1212

1313

1414
## Status
15-
Used in production. GA ready. Current version is 1.5.
15+
Used in production. GA ready. Current version is 1.6.
1616

1717
## Important
18+
v1.6 security tuning options
19+
1820
v1.5 bug fix release
1921

2022
v1.4 changes default behavior of inserting `typ=JWT` header if not overriden. As of 1.4 no
@@ -250,7 +252,7 @@ func main() {
250252
//go use token
251253
fmt.Printf("\ntoken = %v\n",token)
252254
}
253-
}
255+
}
254256
```
255257

256258
#### AES Key Wrap key management family of algorithms
@@ -330,7 +332,7 @@ func main() {
330332
//go use token
331333
fmt.Printf("\ntoken = %v\n",token)
332334
}
333-
}
335+
}
334336
```
335337

336338
#### PBES2 using HMAC SHA with AES Key Wrap key management family of algorithms
@@ -482,7 +484,7 @@ func main() {
482484
//and/or use headers
483485
fmt.Printf("\nheaders = %v\n",headers)
484486
}
485-
}
487+
}
486488
```
487489

488490
**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() {
522524
//and/or use headers
523525
fmt.Printf("\nheaders = %v\n",headers)
524526
}
525-
}
527+
}
526528
```
527529

528530
**PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW** key management algorithms expects `string` passpharase as a key
@@ -679,6 +681,8 @@ func main() {
679681
}
680682
```
681683

684+
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.
685+
682686
### Working with binary payload
683687
In addition to work with string payloads (typical use-case) `jose2go` supports
684688
encoding and decoding of raw binary data. `jose.DecodeBytes`, `jose.SignBytes`
@@ -776,7 +780,7 @@ func main() {
776780
//go use token
777781
fmt.Printf("\ntoken = %v\n",token)
778782
}
779-
}
783+
}
780784
```
781785
### Dealing with keys
782786
**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() {
925929
### More examples
926930
Checkout `jose_test.go` for more examples.
927931

932+
## Customizing library for security
933+
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.
934+
935+
### Deregister algorithm implementations
936+
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.
937+
938+
- `func DeregisterJwa(alg string) JwaAlgorithm`
939+
- `func DeregisterJwe(alg string) JweEncryption`
940+
- `func DeregisterJws(alg string) JwsAlgorithm`
941+
- `func DeregisterJwc(alg string) JwcAlgorithm`
942+
943+
All of them expecting alg name matching `jose` constants and returns implementation that have been deregistered.
944+
945+
### Strict validation
946+
Sometimes it is desirable to verify that `alg` or `enc` values are matching expected before attempting to decode actual payload.
947+
`jose2go` provides helper matchers to be used within [Two-phase validation](#two-phase-validation) precheck:
948+
949+
- `jose.Alg(key, alg)` - to match alg header
950+
- `jose.Enc(key, alg)` - to match alg and enc headers
951+
952+
```Go
953+
token := "eyJhbGciOiJSUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.NL_dfVpZkhNn4bZpCyMq5TmnXbT4yiyecuB6Kax_lV8Yq2dG8wLfea-T4UKnrjLOwxlbwLwuKzffWcnWv3LVAWfeBxhGTa0c4_0TX_wzLnsgLuU6s9M2GBkAIuSMHY6UTFumJlEeRBeiqZNrlqvmAzQ9ppJHfWWkW4stcgLCLMAZbTqvRSppC1SMxnvPXnZSWn_Fk_q3oGKWw6Nf0-j-aOhK0S0Lcr0PV69ZE4xBYM9PUS1MpMe2zF5J3Tqlc1VBcJ94fjDj1F7y8twmMT3H1PI9RozO-21R0SiXZ_a93fxhE_l_dj5drgOek7jUN9uBDjkXUwJPAyp9YPehrjyLdw"
954+
955+
key := Rsa.ReadPublic(....)
956+
957+
// we expecting 'RS256' alg here and if matching continue to decode with a key
958+
payload, header, err := jose.Decode(token, Alg(key, "RS256"))
959+
960+
// or match both alg and enc for decrypting scenarios
961+
payload, header, err := jose.Decode(token, Enc(key, "RSA-OAEP-256", "A192CBC-HS384"))
962+
```
963+
964+
### Customizing PBKDF2
965+
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.
966+
967+
By default, maxIterations is set according to [OWASP PBKDF2](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2) Recomendations:
968+
969+
```
970+
PBES2-HS256+A128KW: 1300000
971+
PBES2-HS384+A192KW: 950000
972+
PBES2-HS512+A256KW: 600000
973+
```
974+
975+
, while minIterations kept at 0 for backward compatibility.
976+
977+
If it is desired to implement different limits, register new implementation with new parameters:
978+
979+
```Go
980+
jose.RegisterJwa(NewPbse2HmacAesKWAlg(128, 1300000, 1300000))
981+
jose.RegisterJwa(NewPbse2HmacAesKWAlg(192, 950000, 950000))
982+
jose.RegisterJwa(NewPbse2HmacAesKWAlg(256, 600000, 600000))
983+
```
984+
985+
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:
986+
987+
```Go
988+
test, headers, err := Decode(token, func(headers map[string]interface{}, payload string) interface{} {
989+
alg := headers["alg"].(string)
990+
p2c := headers["p2c"].(float64)
991+
992+
if strings.HasPrefix(alg, "PBES2-") && int64(p2c) > 100 {
993+
return errors.New("Too many p2c interation count, aborting")
994+
}
995+
996+
return "top secret"
997+
})
998+
```
999+
9281000
## Changelog
1001+
### 1.6
1002+
- ability to deregister specific algorithms
1003+
- configurable min/max restrictions for PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW
1004+
1005+
### 1.5
1006+
- security and bug fixes
1007+
1008+
### 1.4
1009+
- removed extra headers to be inserted by library
1010+
1011+
### 1.3
1012+
- security fixes: Invalid Curve Attack on NIST curves
1013+
9291014
### 1.2
9301015
- interface to access token headers after decoding
9311016
- interface to provide extra headers for token encoding

jose.go

+62-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//Package jose provides high level functions for producing (signing, encrypting and
1+
// Package jose provides high level functions for producing (signing, encrypting and
22
// compressing) or consuming (decoding) Json Web Tokens using Java Object Signing and Encryption spec
33
package jose
44

@@ -79,6 +79,42 @@ func RegisterJwc(alg JwcAlgorithm) {
7979
jwcCompressors[alg.Name()] = alg
8080
}
8181

82+
// DeregisterJwa deregister existing key management algorithm
83+
func DeregisterJwa(alg string) JwaAlgorithm {
84+
jwa := jwaAlgorithms[alg]
85+
86+
delete(jwaAlgorithms, alg)
87+
88+
return jwa
89+
}
90+
91+
// DeregisterJws deregister existing signing algorithm
92+
func DeregisterJws(alg string) JwsAlgorithm {
93+
jws := jwsHashers[alg]
94+
95+
delete(jwsHashers, alg)
96+
97+
return jws
98+
}
99+
100+
// DeregisterJws deregister existing encryption algorithm
101+
func DeregisterJwe(alg string) JweEncryption {
102+
jwe := jweEncryptors[alg]
103+
104+
delete(jweEncryptors, alg)
105+
106+
return jwe
107+
}
108+
109+
// DeregisterJwc deregister existing compression algorithm
110+
func DeregisterJwc(alg string) JwcAlgorithm {
111+
jwc := jwcCompressors[alg]
112+
113+
delete(jwcCompressors, alg)
114+
115+
return jwc
116+
}
117+
82118
// JweEncryption is a contract for implementing encryption algorithm
83119
type JweEncryption interface {
84120
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
422458

423459
return key, nil
424460
}
461+
462+
func Alg(key interface{}, jws string) func(headers map[string]interface{}, payload string) interface{} {
463+
return func(headers map[string]interface{}, payload string) interface{} {
464+
alg := headers["alg"].(string)
465+
466+
if jws == alg {
467+
return key
468+
}
469+
470+
return errors.New("Expected alg to be '" + jws + "' but got '" + alg + "'")
471+
}
472+
}
473+
474+
func Enc(key interface{}, jwa string, jwe string) func(headers map[string]interface{}, payload string) interface{} {
475+
return func(headers map[string]interface{}, payload string) interface{} {
476+
alg := headers["alg"].(string)
477+
enc := headers["enc"].(string)
478+
479+
if jwa == alg && jwe == enc {
480+
return key
481+
}
482+
483+
return errors.New("Expected alg to be '" + jwa + "' and enc to be '" + jwe + "' but got '" + alg + "' and '" + enc + "'")
484+
}
485+
}

0 commit comments

Comments
 (0)