NullPointerException in HmacUsingShaAlgorithm.validateKey() in case of non-extractable key data

Issue #39 closed
Horst Reiterer created an issue

If an HMAC is used for JWT signing/verification, the verification throws a NullPointerException if the key data is not accessible via SecretKey.getEncoded() (e.g. because the key resides in a PKCS11 key store and is marked as non-extractable). In HSM-scenarios, keys are usually non-extractable - the implementation fails in those cases.

IMHO, the library should handle this internally by skipping the check if the required data is not available. An alternative is to require affected users to apply the workaround mentioned below. The latter strategy would effectively mean that the behavior must be configurable in software solutions using the library because the key might happen to be non-extractable at some point. In any case, it should not throw a NullPointerException. As for PKCS11, there is the attribute CKA_VALUE_LEN that is readable even for non-extractable keys. I'm not aware of a way to access that information in the JCE context, however.

HMAC-SHA-256 (extractable):

CKA_CLASS:              CKO_SECRET_KEY
CKA_LABEL:              test
CKA_ID:                 test
CKA_TOKEN:              CK_TRUE
CKA_PRIVATE:            CK_TRUE
CKA_MODIFIABLE:         CK_TRUE
CKA_KEY_TYPE:           CKK_GENERIC_SECRET
CKA_DERIVE:             CK_FALSE
CKA_LOCAL:              CK_TRUE
CKA_ENCRYPT:            CK_FALSE
CKA_VERIFY:             CK_TRUE
CKA_VERIFY_RECOVER:     CK_FALSE
CKA_WRAP:               CK_TRUE
CKA_TRUSTED:            CK_FALSE
CKA_DECRYPT:            CK_FALSE
CKA_SIGN:               CK_TRUE
CKA_WRAP_WITH_TRUSTED:  CK_FALSE
CKA_UNWRAP:             CK_TRUE
CKA_SENSITIVE:          CK_FALSE
CKA_ALWAYS_SENSITIVE:   CK_FALSE
CKA_EXTRACTABLE:        CK_TRUE
CKA_NEVER_EXTRACTABLE:  CK_FALSE
CKA_CHECK_VALUE:        3 bytes (24 bits)
                        33 83 0e
CKA_VALUE_LEN:          32
CKA_VALUE:              32 bytes (256 bits)
                        6b 65 e8 59 10 28 bc ad 9a 1f c6 8e 30 dc c6 cc
                        92 45 21 64 4c 3a 5a 47 04 9b 05 da e1 cd 2d b8

HMAC-SHA-256 (non-extractable):

CKA_CLASS:              CKO_SECRET_KEY
CKA_LABEL:              test
CKA_ID:                 test
CKA_TOKEN:              CK_TRUE
CKA_PRIVATE:            CK_TRUE
CKA_MODIFIABLE:         CK_TRUE
CKA_KEY_TYPE:           CKK_GENERIC_SECRET
CKA_DERIVE:             CK_FALSE
CKA_LOCAL:              CK_TRUE
CKA_ENCRYPT:            CK_FALSE
CKA_VERIFY:             CK_TRUE
CKA_VERIFY_RECOVER:     CK_FALSE
CKA_WRAP:               CK_TRUE
CKA_TRUSTED:            CK_FALSE
CKA_DECRYPT:            CK_FALSE
CKA_SIGN:               CK_TRUE
CKA_WRAP_WITH_TRUSTED:  CK_FALSE
CKA_UNWRAP:             CK_TRUE
CKA_SENSITIVE:          CK_TRUE
CKA_ALWAYS_SENSITIVE:   CK_TRUE
CKA_EXTRACTABLE:        CK_FALSE
CKA_NEVER_EXTRACTABLE:  CK_TRUE
CKA_CHECK_VALUE:        3 bytes (24 bits)
                        e0 40 84
CKA_VALUE_LEN:          32
CKA_VALUE:              N/A (object is not extractable)

Workarounds:

Using JwtConsumer.setRelaxVerificationKeyValidation(), the key validation can be disabled.

Test case:

I attached a test case that illustrates the issue without PKCS11 dependencies. The strack trace:

Exception in thread "main" org.jose4j.jwt.consumer.InvalidJwtException: Unexpected exception encountered while processing JOSE object (java.lang.NullPointerException): JsonWebSignature{"alg":"HS256"}->eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NDcyNzI5MTMsInN1YiI6InN1YmplY3QiLCJpc3MiOiJpc3N1ZXIifQ.ZwGoOYezN9TcqGFh1IjNbdCwFQuGMLVOr4h8CKyvVuM
        at org.jose4j.jwt.consumer.JwtConsumer.processContext(JwtConsumer.java:223)
        at org.jose4j.jwt.consumer.JwtConsumer.process(JwtConsumer.java:345)
        at org.jose4j.jwt.consumer.JwtConsumer.processToClaims(JwtConsumer.java:128)
        at Test.main(Test.java:28)
Caused by: java.lang.NullPointerException
        at org.jose4j.lang.ByteUtil.bitLength(ByteUtil.java:136)
        at org.jose4j.jws.HmacUsingShaAlgorithm.validateKey(HmacUsingShaAlgorithm.java:77)
        at org.jose4j.jws.HmacUsingShaAlgorithm.validateVerificationKey(HmacUsingShaAlgorithm.java:93)
        at org.jose4j.jws.JsonWebSignature.verifySignature(JsonWebSignature.java:101)
        at org.jose4j.jwt.consumer.JwtConsumer.processContext(JwtConsumer.java:163)

Comments (7)

  1. Brian Campbell repo owner

    Thanks for the detailed description and test case. And yes, I agree that it shouldn't NPE in the case that the raw key data isn't available due to being non-extractable (I typically think of private keys as being the non-extractable things for HSMs but apparently better handling is needed for secret keys too). Will probably have to just skip the length check in this case.

    Couple questions:

    What does SecretKey.getFormat() return for non-extractable secret keys in your HSM?

    Are you using JWE (encryption) from this library at all? There are probably similar problems with AES key wrapping and direct encryption algs.

  2. Horst Reiterer reporter

    In case of PKCS11 and a non-extractable secret key, SecretKey.getFormat() returns null - so does SecretKey.getAlgorithm().

    I'm not using JWE at the moment but I agree that this issue should also affect encryption use cases if Key.getEncoded() is used for checking length constraints.

  3. Log in to comment