15
15
*/
16
16
package io .jsonwebtoken .impl .security ;
17
17
18
+ import io .jsonwebtoken .Identifiable ;
18
19
import io .jsonwebtoken .Jwts ;
19
20
import io .jsonwebtoken .impl .lang .Bytes ;
20
21
import io .jsonwebtoken .impl .lang .ParameterReadable ;
21
22
import io .jsonwebtoken .impl .lang .RequiredParameterReader ;
22
23
import io .jsonwebtoken .io .Encoders ;
23
24
import io .jsonwebtoken .lang .Assert ;
24
25
import io .jsonwebtoken .lang .Strings ;
26
+ import io .jsonwebtoken .security .AeadAlgorithm ;
25
27
import io .jsonwebtoken .security .InvalidKeyException ;
28
+ import io .jsonwebtoken .security .Keys ;
26
29
import io .jsonwebtoken .security .MacAlgorithm ;
27
30
import io .jsonwebtoken .security .MalformedKeyException ;
28
31
import io .jsonwebtoken .security .SecretJwk ;
29
- import io .jsonwebtoken .security .SecureDigestAlgorithm ;
32
+ import io .jsonwebtoken .security .SecretKeyAlgorithm ;
33
+ import io .jsonwebtoken .security .WeakKeyException ;
30
34
31
35
import javax .crypto .SecretKey ;
32
36
import javax .crypto .spec .SecretKeySpec ;
@@ -44,61 +48,97 @@ class SecretJwkFactory extends AbstractFamilyJwkFactory<SecretKey, SecretJwk> {
44
48
protected SecretJwk createJwkFromKey (JwkContext <SecretKey > ctx ) {
45
49
SecretKey key = Assert .notNull (ctx .getKey (), "JwkContext key cannot be null." );
46
50
String k ;
51
+ byte [] encoded = null ;
47
52
try {
48
- byte [] encoded = KeysBridge .getEncoded (key );
53
+ encoded = KeysBridge .getEncoded (key );
49
54
k = Encoders .BASE64URL .encode (encoded );
50
55
Assert .hasText (k , "k value cannot be null or empty." );
51
56
} catch (Throwable t ) {
52
57
String msg = "Unable to encode SecretKey to JWK: " + t .getMessage ();
53
58
throw new InvalidKeyException (msg , t );
59
+ } finally {
60
+ Bytes .clear (encoded );
61
+ }
62
+
63
+ MacAlgorithm mac = DefaultMacAlgorithm .findByKey (key );
64
+ if (mac != null ) {
65
+ ctx .put (AbstractJwk .ALG .getId (), mac .getId ());
54
66
}
55
67
56
68
ctx .put (DefaultSecretJwk .K .getId (), k );
57
69
58
- return new DefaultSecretJwk (ctx );
70
+ return createJwkFromValues (ctx );
59
71
}
60
72
61
73
private static void assertKeyBitLength (byte [] bytes , MacAlgorithm alg ) {
62
74
long bitLen = Bytes .bitLength (bytes );
63
75
long requiredBitLen = alg .getKeyBitLength ();
64
- if (bitLen != requiredBitLen ) {
76
+ if (bitLen < requiredBitLen ) {
65
77
// Implementors note: Don't print out any information about the `bytes` value itself - size,
66
78
// content, etc., as it is considered secret material:
67
79
String msg = "Secret JWK " + AbstractJwk .ALG + " value is '" + alg .getId () +
68
- "', but the " + DefaultSecretJwk .K + " length does not equal the '" + alg .getId () +
69
- "' length requirement of " + Bytes .bitsMsg (requiredBitLen ) +
70
- ". This discrepancy could be the result of an algorithm " +
71
- "substitution attack or simply an erroneously constructed JWK. In either case, it is likely " +
72
- "to result in unexpected or undesired security consequences." ;
73
- throw new MalformedKeyException (msg );
80
+ "', but the " + DefaultSecretJwk .K + " length is smaller than the " + alg .getId () +
81
+ " minimum length of " + Bytes .bitsMsg (requiredBitLen ) +
82
+ " required by " +
83
+ "[JWA RFC 7518, Section 3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.2), " +
84
+ "2nd paragraph: 'A key of the same size as the hash output or larger MUST be used with this " +
85
+ "algorithm.'" ;
86
+ throw new WeakKeyException (msg );
74
87
}
75
88
}
76
89
90
+ private static void assertSymmetric (Identifiable alg ) {
91
+ if (alg instanceof MacAlgorithm || alg instanceof SecretKeyAlgorithm || alg instanceof AeadAlgorithm )
92
+ return ; // valid
93
+ String msg = "Invalid Secret JWK " + AbstractJwk .ALG + " value '" + alg .getId () + "'. Secret JWKs " +
94
+ "may only be used with symmetric (secret) key algorithms." ;
95
+ throw new MalformedKeyException (msg );
96
+ }
97
+
77
98
@ Override
78
99
protected SecretJwk createJwkFromValues (JwkContext <SecretKey > ctx ) {
79
100
ParameterReadable reader = new RequiredParameterReader (ctx );
80
- byte [] bytes = reader .get (DefaultSecretJwk .K );
81
- String jcaName = null ;
82
-
83
- String id = ctx .getAlgorithm ();
84
- if (Strings .hasText (id )) {
85
- SecureDigestAlgorithm <?, ?> alg = Jwts .SIG .get ().get (id );
86
- if (alg instanceof MacAlgorithm ) {
87
- jcaName = ((CryptoAlgorithm ) alg ).getJcaName (); // valid for all JJWT alg implementations
88
- Assert .hasText (jcaName , "Algorithm jcaName cannot be null or empty." );
89
- assertKeyBitLength (bytes , (MacAlgorithm ) alg );
90
- }
91
- }
92
- if (!Strings .hasText (jcaName )) {
93
- if (ctx .isSigUse ()) {
101
+ final byte [] bytes = reader .get (DefaultSecretJwk .K );
102
+ SecretKey key ;
103
+
104
+ String algId = ctx .getAlgorithm ();
105
+ if (!Strings .hasText (algId )) { // optional per https://www.rfc-editor.org/rfc/rfc7517.html#section-4.4
106
+
107
+ // Here we try to infer the best type of key to create based on siguse and/or key length.
108
+ //
109
+ // AES requires 128, 192, or 256 bits, so anything larger than 256 cannot be AES, so we'll need to assume
110
+ // HMAC.
111
+ //
112
+ // Also, 256 bits works for either HMAC or AES, so we just have to choose one as there is no other
113
+ // RFC-based criteria for determining. Historically, we've chosen AES due to the larger number of
114
+ // KeyAlgorithm and AeadAlgorithm use cases, so that's our default.
115
+ int kBitLen = (int ) Bytes .bitLength (bytes );
116
+
117
+ if (ctx .isSigUse () || kBitLen > Jwts .SIG .HS256 .getKeyBitLength ()) {
94
118
// The only JWA SecretKey signature algorithms are HS256, HS384, HS512, so choose based on bit length:
95
- jcaName = "HmacSHA" + Bytes . bitLength (bytes );
96
- } else { // not an HS* algorithm, and all standard AeadAlgorithms use AES keys:
97
- jcaName = AesAlgorithm .KEY_ALG_NAME ;
119
+ key = Keys . hmacShaKeyFor (bytes );
120
+ } else {
121
+ key = AesAlgorithm .keyFor ( bytes ) ;
98
122
}
123
+ ctx .setKey (key );
124
+ return new DefaultSecretJwk (ctx );
125
+ }
126
+
127
+ //otherwise 'alg' was specified, ensure it's valid for secret key use:
128
+ Identifiable alg = Jwts .SIG .get ().get (algId );
129
+ if (alg == null ) alg = Jwts .KEY .get ().get (algId );
130
+ if (alg == null ) alg = Jwts .ENC .get ().get (algId );
131
+ if (alg != null ) assertSymmetric (alg ); // if we found a standard alg, it must be a symmetric key algorithm
132
+
133
+ if (alg instanceof MacAlgorithm ) {
134
+ assertKeyBitLength (bytes , ((MacAlgorithm ) alg ));
135
+ String jcaName = ((CryptoAlgorithm ) alg ).getJcaName ();
136
+ Assert .hasText (jcaName , "Algorithm jcaName cannot be null or empty." );
137
+ key = new SecretKeySpec (bytes , jcaName );
138
+ } else {
139
+ // all other remaining JWA-standard symmetric algs use AES:
140
+ key = AesAlgorithm .keyFor (bytes );
99
141
}
100
- Assert .stateNotNull (jcaName , "jcaName cannot be null (invariant)" );
101
- SecretKey key = new SecretKeySpec (bytes , jcaName );
102
142
ctx .setKey (key );
103
143
return new DefaultSecretJwk (ctx );
104
144
}
0 commit comments