Consider integration functions for cloud-HSM
With the advent of Azure KeyVault HSM (and the AWS equivalent) it would be nice if you could provide ways to integrate these into your library.
Even if you don't provide the full implementation yourself, just suitable functions (e.g. "stringToSign" and "inputResult" or such like) to enable devs to tie in their external functions would be great.
Comments (9)
-
repo owner -
reporter Hi Brian,
Traditionally yes, PKCS
#11and all that jazz.... I absolutely agree, that stuff is outside your remit.But I'm talking about instances where the HSM resides in a server, and the only thing exposed to the outside world is a REST API (e.g. "sign with a key" @ https://msdn.microsoft.com/en-us/library/azure/dn878096.aspx) ...you send it a hash, it sends you a signature.
So that's why I was thinking there isn't really any clean integration with jose4j in those instances where you want jose4j to give you a hash, and you give it a signature once you've got it back from the API.
-
repo owner Interesting what they're doing there. And yeah, there's not a particularly clean integration point.
I think that adapting the code from the previous comment something along the lines of the following could make it all work. Let me know if you get this working (you can verify the signature locally with the public part of the key and normal jose4j stuff) and I can expose somethings to make such an integration a little cleaner/easier.
JsonWebSignature jws = new JsonWebSignature(); // The payload of the JWS is JSON content of the JWT Claims jws.setPayload(claims.toJson()); // Set the signature algorithm on the JWT/JWS, which needs to align with how the HSM will sign jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); // Get the signing input String signingInput = CompactSerializer.serialize(jws.getHeaders().getEncodedHeader(), jws.getEncodedPayload()); byte[] bytesToSign = StringUtil.getBytesAscii(signingInput); MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] hash = messageDigest.digest(bytesToSign); String encodedHash = Base64Url.encode(hash); // put the encodedHash in the POST body of the request to send to key vault String signWithKeyBody = "{" + " \"alg\":\"RS256\"," + " \"value\":\""+ encodedHash +"\"" + "}"; // call https://mykeyvault.vault.azure.net/keys/{key-name}/sign with signWithKeyBody as the POST body // ... // pull the value out of the value member of the response, which might maybe look something like this String responseBody = " {" + " \"kid\": \"dunno\"," + " \"value\": \"HEKEjqGEji2BjPMzLG7apl25ihdNvjH5JXVtWBxdVstL0hd6la42JEz2p5ducXZhBAFR2oeyNptWKBH8ikgDSjVjdUKYOY6fdf2RluhgIdJ1yfs55ndGp1YtH_F29m85a6MXEKgYgJq_Gjc-RupxGERYnPvYGHk0SMNk7_vhXALm4ol7Kv4fAw92b2gCt5ZZ6-RAAqT4Z4h_cgLNtln2PY9j_HmPtu1FNyfggxOfwT-eQiTQDrWJgqRLf8gYNMODmmEYHFqx14L9nXkKsL7tfxCm0BVQ4u5hfRevhOUrjgLX2-0nJwZiJBmbqmBDSmjBeQEOoUJWlgR9CxIFGkNFfQ\"" + " }"; Map<String,Object> stringObjectMap = JsonUtil.parseJson(responseBody); String encodedSignature = (String)stringObjectMap.get("value"); // use that signature from key vault to build the final JWT/JWS String jwt = CompactSerializer.serialize(signingInput, encodedSignature); // Now you can do something with the JWT. Like send it to some other party // over the clouds and through the interwebs. System.out.println("JWT: " + jwt);
-
reporter So far so good, managed to get JWT out, working on testing token validation now ...
Looks like public static JsonWebKey newJwk(String json) should be helpful ... ;-)
-
reporter Looks like you might have some factory interop to contend with....
12:44:57,493 INFO [stdout] (default task-4) org.jose4j.lang.JoseException: Unknown key algorithm: 'RSA-HSM' 12:44:57,493 INFO [stdout] (default task-4) at org.jose4j.jwk.JsonWebKey$Factory.newJwk(JsonWebKey.java:190) ~[jose4j-0.4.4.jar:na] 12:44:57,493 INFO [stdout] (default task-4) at org.jose4j.jwk.PublicJsonWebKey$Factory.newPublicJwk(PublicJsonWebKey.java:250) ~[jose4j-0.4.4.jar:na] 12:44:57,493 INFO [stdout] (default task-4) at org.jose4j.jwk.PublicJsonWebKey$Factory.newPublicJwk(PublicJsonWebKey.java:263) ~[jose4j-0.4.4.jar:na]
This is what I was using as calling code :
public void setJWK(String jwk) throws Exception { RsaJsonWebKey jsonWebKey = (RsaJsonWebKey) PublicJsonWebKey.Factory.newPublicJwk(jwk); this.key = jsonWebKey.getRsaPublicKey(); }
This is what an Azure JWK looks like :
{"key":{"kid":"https://******.vault.azure.net/keys/******/******","kty":"RSA-HSM","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"******","e":"******"},"attributes":{"enabled":true,"created":1448972226,"updated":1448972226}}
I removed the wrapper before feding it into your code :
{"kid":"https://******.vault.azure.net/keys/******/******","kty":"RSA-HSM","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"******","e":"******"}
Good news however, is the rest works great if you hack "RSA-HSM" to read "RSA" ;-)
-
repo owner "kty":"RSA-HSM"
appears to be something they made up. But (except for thatkey:{...}
wrapper) it looks like it might otherwise be compatible with the standard RSA JWK key type http://tools.ietf.org/html/rfc7518#section-6.3.If you change
"kty":"RSA-HSM"
to"kty":"RSA"
, I think it should work. -
reporter If you change "kty":"RSA-HSM" to "kty":"RSA", I think it should work.
It does indeed, as per the last line of my last post. ;-)
-
repo owner Doh, sorry! I overlooked that last sentence. Good to hear that that much works. Were you then able to verify the signature?
-
reporter No worries ! I should have probably put that sentence higher up above the code boxes !
Yep. I used a cached local copy of the public key JWK to verify the signature made by the HSM.
I'll go through the testing again today just to double check everything.
- Log in to comment
My understanding is that HSM integration is generally done at the JCA layer and should be (mostly) transparent to the application/toolkit layer like jose4j.
AWS CloudHSM, for example, uses the SafeNet Luna HSM and integration with it in Java one uses the Luna security provider: http://docs.aws.amazon.com/cloudhsm/latest/userguide/cloud-hsm-write-app.html#cloud-hsm-write-app-java
I believe Azure KeyVault uses the Thales nShield HSM and I would assume there're similar integration options using a Thales security provider.
Barring that, you could use this library to get the signing input, sign it using some HSM API, and then manually assemble the final JWS (JWE would be much more complicated and is one reason why JCA integration is preferred). A little code to show kinda what that might look like follows. Exposing the now private getSigningInputBytes() on JWS could simplify the '// Get the signing input' bit below somewhat but the end result is the same.