jjwt
jjwt copied to clipboard
HMAC Secret key validation incompatible with hardware security module
Came across this error while using this library with an nShield Connect HSM. The algorithm we're using is HS512 with a 512-bit secret key that lives in the HSM.
com.ncipher.provider.nCSecurityException: The key material is not exportable outside of the HSM
at com.ncipher.provider.Utils.exportKey(Utils.java:1539) ~[nCipherKM.jar:?]
at com.ncipher.provider.Utils.getEncoded(Utils.java:1109) ~[nCipherKM.jar:?]
at com.ncipher.provider.km.KMKey.getEncoded(KMKey.java:158) ~[nCipherKM.jar:?]
at io.jsonwebtoken.SignatureAlgorithm.assertValid(SignatureAlgorithm.java:343) ~[jjwt-api-0.10.7.jar!/:?]
at io.jsonwebtoken.SignatureAlgorithm.assertValidSigningKey(SignatureAlgorithm.java:302) ~[jjwt-api-0.10.7.jar!/:?]
at io.jsonwebtoken.impl.DefaultJwtBuilder.signWith(DefaultJwtBuilder.java:123) ~[jjwt-impl-0.10.7.jar!/:?]
at io.jsonwebtoken.impl.DefaultJwtBuilder.signWith(DefaultJwtBuilder.java:148) ~[jjwt-impl-0.10.7.jar!/:?]
Older versions of jjwt (0.9, 0.9.1) didn't have this issue because secretKey.getEncoded()
isn't used.
If a fix for this isn't possible (i.e. validating the key without calling getEncoded()
), is there a way to work around this?
I'm confused. Even if JJWT doesn't call getEncoded()
, the JDK signature algorithm implementation itself will call getEncoded
during the mac.init(key)
call, producing the same problem, no?
In other words, try to run this code yourself, without JJWT involved:
byte[] dataToSign = getDataToSign(); //implement me
Mac mac = Mac.getInstance("HmacSHA512");
mac.init(key);
byte[] signature = mac.doFinal(dataToSign);
What happens?
A signature algorithm is eventually going to require the raw key bytes since all cryptographic operations, at the end of the day, are performed using byte arrays. If getEncoded
cannot be called, how does the signature implementation get the bytes?
with a 512-bit secret key that lives in the HSM
To the best of my knowledge, most HSMs (Hardware Security Module)s require that the cryptographic computations are performed inside the hardware module and not in application space.
If so, this implies that - if you still want to use JVM libraries that perform cryptographic operations in the JVM (like JJWT) - you would need to enable a Java JCE Security Provider implementation (java.security.Provider
) that delegates these key generation and/or computation operations to the HSM. This is not a trivial task however - see the docs for that here: https://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/HowToImplAProvider.html
Hopefully your HSM provides an implementation of this out of the box to allow Java applications to use it instead of the built-in Sun JCE provider.
If there isn't, and you aren't willing or able to go down the laborious task of creating a custom JCE provider for the HSM, there is currently no way of doing this with JJWT.
But - while that is the current state of affairs with JJWT, we will support custom SignatureAlgorithm
implementations (not just enum instances) in a future (probably 1.0) release of JJWT. At that time, you could plug in your own implementation of that interface to delegate to the HSM (e.g. via a shared library or REST API call, etc).
Thanks for your replies. Yes, all crypto operations are performed inside the module, so we never see the key bytes (and trying to expose them in application space will cause the error I mentioned).
The HSM did come with a security provider implementation - com.ncipher.provider.km.nCipherKM
For the code you mentioned, we have to specify the provider like so, and the work is done inside the hardware module:
byte[] dataToSign = getDataToSign(); //implement me
Mac mac = Mac.getInstance("HmacSHA512", "nCipherKM");
mac.init(key);
byte[] signature = mac.doFinal(dataToSign);
It seems we'll have to make do until we have a way to override the behaviour of SignatureAlgorithm
.
It's great to hear that they provide a JCE Provider implementation.
JJWT currently gets the bytes to enforce that the key length is greater than or equal to the length mandated by the JWT specification. It can't do that without calling getEncoded()
to inspect the byte array length.
In any event, thanks for reporting this. We'll have to think a little how this should be supported via the new SignatureAlgorithm
interface-based (and not enum-based) approach. But even with that in place, ideally you shouldn't have to implement a new algorithm because the algorithm itself isn't changing, it's just the RFC-based key validation step that is causing problems.
The difficulty is that we don't want to allow people to easily disable this validation because key lengths are mandated by the RFC - if it is easy to disable, people could cause themselves big security problems. And we've seen a lot of people making this mistake so far. It's why the length validation was added in JJWT 0.10.0 - the spec violations were significant.
We'll need to put some thought into this. Thanks again for reporting!
This issue has been automatically marked as stale due to inactivity for 60 or more days. It will be closed in 7 days if no further activity occurs.
Just an internal note: This issue could likely be solved with #493, whereby the implementation can avoid key strength assertions.
The latest version of DefaultMacAlgorithm
introduced with #279 has been merged to master
and it will only validate the key length if the key's encoded bytes are available.