- changed status to open
Revise setting of key use in JWK.load(KeyStore,String,char[]) when the cert key use cannot be mapped 1:1
When a key has Digital Signature and Key Encipherment usages, the from method of KeyUse class detects KeyUse.ENCRYPTION. This causes issues when JWKMatcher is build to detect KeyUse.SIGNATURE or null, even though the key has Digital Signature usage and can be used to create signatures.
Shouldn’t KeyUse.SIGNATURE be always returned when a key has signature usage, no matter what other usages are?
Comments (17)
-
-
reporter Then maybe this part of KeyUse class should be changed to KeyUse.SIGNATURE?
// digitalSignature && keyEncipherment // (e.g. RSA TLS certificate for authenticated encryption) if (cert.getKeyUsage()[0] && cert.getKeyUsage()[2]) { return KeyUse.ENCRYPTION; }
-
Would you paste the code where the JWKMatcher is constructed?
Where does the cert come from?
-
reporter This is the code, it comes from Spring Security’s NimbusJwtEncoder class.
return new JWKMatcher.Builder() .keyType(KeyType.forAlgorithm(jwsAlgorithm)) .keyID(headers.getKeyId()) .keyUses(KeyUse.SIGNATURE, null) .algorithms(jwsAlgorithm, null) .x509CertSHA256Thumbprint(Base64URL.from(headers.getX509SHA256Thumbprint())) .build(); }
The certificate is issued by my corporate CA.
-
Where and how does the
KeyUse from(final X509Certificate cert)
get called? -
reporter It is called from method parse(final X509Certificate cert) in com.nimbusds.jose.jwk.RSAKey class:
return new RSAKey.Builder(publicKey) .keyUse(KeyUse.from(cert)) .keyID(cert.getSerialNumber().toString(10)) .x509CertChain(Collections.singletonList(Base64.encode(cert.getEncoded()))) .x509CertSHA256Thumbprint(Base64URL.encode(sha256.digest(cert.getEncoded()))) .build();
-
Is this in the app code or in Spring Security?
-
reporter This is in nimbus-jose-jwt library. It all starts jn JWK.load method called like this:
JWK jwk = JWK.load(temp, kid, password);
where temp is a keystore containing the certificate and private key, kid is the certificate alias and password is the keystore password.
If you would like to reproduce this, I can share the keystore with you.
-
The underlying issue is that JWK “use” can either be signature or encryption, so changing the heuristics to return signature for
cert.getKeyUsage()[0] && cert.getKeyUsage()[2]
(which implies authenticated encryption) will solve the particular issue you have, but then will break other things. To provide a better mapping of the cert key usage fields the JWK has another parameter called “key_ops”, which enables a JWK to have multiple uses:https://datatracker.ietf.org/doc/html/rfc7517#section-4.3
Do you have a possibility to change the JWK use in your code after it gets loaded from the key store but before the JWKMatcher is built?
-
It also occurs to be now that perhaps JWK.load shouldn’t set the KeyUse when it’s ambiguous (which can come up with certificates), so only set key_ops.
Does the JWK.load happen in you app’s code?
-
reporter I am constructing the JWK with
JWK jwk = JWK.load(temp, kid, password);
and then pass it to Spring Security, so I think I can do this, but will need an example.
-
jwk = new RSAKey.Builder((RSAKay)jwk) .keyUse(KeyUse.SIGNATURE) .build();
If the loaded JWK is of type RSA. For EC the logic is similar.
-
- changed title to Revise setting of key use in JWK.load(X509Certificate) when the cert key use cannot be mapped 1:1
-
reporter Looks like it’s working, I had to copy some code from the load method though:
Key key = keyStore.getKey(alias, keyPassword); X509Certificate xcert = (X509Certificate) temp.getCertificate(kid); RSAKey rsaJWK = RSAKey.parse(xcert); jwk = new RSAKey.Builder(rsaJWK) .keyID(kid) .privateKey((RSAPrivateKey)key) .keyUse(KeyUse.SIGNATURE) .keyStore(temp) .build();
It would be nice if you allowed the KeyUse to be passed to load method though.
Possibility to override the kid attribute would also be nice cause when using KeyCloak as identity provider, it expects the kid attribute to be set to a specific value, calculated like this:
PublicKey publicKey = keyStore.getCertificate(alias).getPublicKey(); String kid = Base64Url.encode(MessageDigest.getInstance("SHA-256").digest(publicKey.getEncoded())).toString();
The workaround for this is to prepare a temporary keystore and set the alias to the calculated value and then pass it as alias in JWK.load method.
-
- changed title to Revise setting of key use in JWK.load(KeyStore,String,char[]) when the cert key use cannot be mapped 1:1
-
The first change I made is to update
KeyUse.from(X509Certificate)
to return null (undefined) when the public key use in the cert doesn’t map to a single JWK use value: -
Re: adding new
parse(X509Certificate)
method to allow passing of a preferred KeyUse - if the cert key use doesn’t map to a single JWK use value I think it’s best to keep it undefined. The cited JWKMatcher will still work because it’s set to select signature use as well as keys with an undefined use. - Log in to comment
Well, in the context of signature handling the KeyUse.SIGNATURE should be the one having priority. We'll need to see how such context aware KeyUse detection may work. If you have ideas feel free to chime in :)