jjwt icon indicating copy to clipboard operation
jjwt copied to clipboard

HMAC Secret key validation incompatible with hardware security module

Open lumphrey opened this issue 5 years ago • 6 comments

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?

lumphrey avatar Aug 09 '19 16:08 lumphrey

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?

lhazlewood avatar Aug 09 '19 17:08 lhazlewood

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).

lhazlewood avatar Aug 09 '19 17:08 lhazlewood

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.

lumphrey avatar Aug 09 '19 18:08 lumphrey

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!

lhazlewood avatar Aug 09 '19 19:08 lhazlewood

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.

stale[bot] avatar Oct 08 '19 20:10 stale[bot]

Just an internal note: This issue could likely be solved with #493, whereby the implementation can avoid key strength assertions.

lhazlewood avatar Oct 25 '19 16:10 lhazlewood

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.

lhazlewood avatar Aug 11 '23 21:08 lhazlewood