Consider integration functions for cloud-HSM

Issue #42 new
devarandom created an issue

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)

  1. Brian Campbell repo owner

    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.

            // Create the Claims, which will be the content of the JWT
            JwtClaims claims = new JwtClaims();
            claims.setIssuer("Issuer");  // who creates the token and signs it
            claims.setAudience("Audience"); // to whom the token is intended to be sent
            claims.setExpirationTimeMinutesInTheFuture(10); // time when the token will expire (10 minutes from now)
            claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago)
            claims.setSubject("subject"); // the subject/principal is whom the token is about
    
            // A JWT is a JWS and/or a JWE with JSON claims as the payload.
            // In this example it is a JWS so we create a JsonWebSignature object.
            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);
    
            // pass the signing input to some HSM API  
            byte[] signature = signWithCloudHsmUsingRsaSha256(bytesToSign, privateKeyReference);
    
            // use the signature to build the final JWT/JWS
            String encodedSignature = Base64Url.encode(signature);
            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);
    
  2. devarandom reporter

    Hi Brian,

    Traditionally yes, PKCS#11 and 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.

  3. Brian Campbell 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);
    
  4. devarandom 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 ... ;-)

  5. devarandom 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" ;-)

  6. devarandom 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. ;-)

  7. Brian Campbell repo owner

    Doh, sorry! I overlooked that last sentence. Good to hear that that much works. Were you then able to verify the signature?

  8. devarandom 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.

  9. Log in to comment