Skip to the content.

Google Cloud KMS provider for Nimbus JOSE

This library provides JWS utilities for Nimbus JOSE on top of Google Cloud KMS: You can sign and verify JWS objects backed by keys in GCP KMS.

Maven

<dependency>
  <groupId>io.github.fungrim.nimbus</groupId>
  <artifactId>gcp-kms-nimbus-provider</artifactId>
  <version>1.0.1</version>
</dependency>

Gradle

implementation 'io.github.fungrim.nimbus:gcp-kms-nimbus-provider:1.0.1'

Prerequisites

You need to configure the GCP application credentials, e.g.:

export GOOGLE_APPLICATION_CREDENTIALS=my-sa.json

Create a key ring if you don’t already have one, e.g.:

gcloud kms keyrings create jws-keys --location=us-east1

Create at least one key to use, e.g.:

gcloud kms keys create jwd-ec-1 \
   --location=us-east1 \
   --keyring=jws-keys \
   --purpose=asymmetric-signing \
   --default-algorithm=ec-sign-p256-sha256

Make sure you, or the SA you’re using, have rights to use the keys, for example via the role roles/cloudkms.signerVerifier

TL;DR

try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {

    // you need the resource ID of the key ring to use
    String keyRingResourceName = "projects/you-project/locations/europe/keyRings/your-keyring";
    
    // the key handle factory is your key access point, and caches keys in memory for you
    KmsKeyHandleFactory factory = KmsKeyHandleFactory.builder(client, KeyRingName.parse(keyRingResourceName))
                    .withKeyCacheDuration(Duration.ofSeconds(60))
                    .build();

    // there's several ways of getting hold of a key, for example
    // by a key version resource name, but below we don't care and ask
    // for a key by algorithm - the provider will pick the a matching key and
    // the latest version
    KmsKeyHandle handle = factory.find(JWSAlgorithm.ES256);

    // create claims
    JWTClaimsSet claims = new JWTClaimsSet.Builder()
            .subject("bob")
            .issuer("https://www.google.com")
            .expirationTime(new Date(LocalDateTime.now().plusHours(24).toInstant(ZoneOffset.UTC).toEpochMilli()))
            .build();

    // let the handle create the header, this will set the algorithm and key ID automagically
    JWSHeader header = handle.createHeaderBuilder().build();

    // create and sign 
    SignedJWT jwt = new SignedJWT(header, claims);
    jwt.sign(handler.getSigner());

    // verify
    String token = jwt.serialize();
    SignedJWT parsed = SignedJWT.parse(token);
    if(!partsed.verify(handler.getVerifier())) {
        System.out.println("Help! Help! I'm being repressed!");
    }
}

Algorithm support

All keys for asymmetric signing in GCP KMS are supported - with the exception of secp256k1, see below - as well as HMAC signing.

secp256k1

Note that the EC curve secp256k1 was removed from Java 15 by Oracle, and Java 16 by OpenJDK. This library retains the necesarry code, but it is not well tested.

How-to: List all keys for a specific JWS algorithm

try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {

    // you need the resource ID of the key ring to use
    String keyRingResourceName = "projects/you-project/locations/europe/keyRings/your-keyring";
    
    // the key handle factory is your key access point, and caches keys in memory for you
    KmsKeyHandleFactory factory = KmsKeyHandleFactory.builder(client, KeyRingName.parse(keyRingResourceName))
                    .withKeyCacheDuration(Duration.ofSeconds(60))
                    .build();

    // list by a specific algorithm
    for (KmsKeyHandle h : factory.listByAlgorithm(a -> a.equals(JWSAlgorithm.ES256))) {
        // TODO: do something smart here
    }
}

How-to: Create a JWK set for a set of public keys

try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {

    // you need the resource ID of the key ring to use
    String keyRingResourceName = "projects/you-project/locations/europe/keyRings/your-keyring";
    
    // the key handle factory is your key access point, and caches keys in memory for you
    KmsKeyHandleFactory factory = KmsKeyHandleFactory.builder(client, KeyRingName.parse(keyRingResourceName))
                    .withKeyCacheDuration(Duration.ofSeconds(60))
                    .build();

    // create a JWK set, only public keys will be included, and HMAC keys will be filtered out
    JWKSet jwks = PublicJwkSetCreator.of(factory.list());
}