JSON Web Signature (JWS) with JSON serialisation and multiple signatures

The JSON Web Signature (JWS) standard specifies two types of serialisation, a compact one that is URL-safe and intended for tokens, and a JSON serialisation which can carry multiple signatures.

Example general JSON serialisation with two signatures applied to the payload:

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.*;

// Generate two signing keys in JWK format

// 2048-bit RSA signing key for RS256 alg
RSAKey rsaJWK = new RSAKeyGenerator(2048)
    .algorithm(JWSAlgorithm.RS256)
    .keyUse(KeyUse.SIGNATURE)
    .keyID("1")
    .generate();

// EC signing key with P-256 curve for ES256 alg
ECKey ecJWK = new ECKeyGenerator(Curve.P_256)
    .algorithm(JWSAlgorithm.ES256)
    .keyUse(KeyUse.SIGNATURE)
    .keyID("2")
    .generate();

// The payload to sign
Payload payload = new Payload("Hello, world!");

// Create the JWS secured object for JSON serialisation
JWSObjectJSON jwsObjectJSON = new JWSObjectJSON(payload);

// Apply the first signature using the RSA key
jwsObjectJSON.sign(
    new JWSHeader.Builder((JWSAlgorithm) rsaJWK.getAlgorithm())
        .keyID(rsaJWK.getKeyID())
        .build(),
    new RSASSASigner(rsaJWK)
);

// Apply the second signature using the EC key
jwsObjectJSON.sign(
    new JWSHeader.Builder((JWSAlgorithm) ecJWK.getAlgorithm())
        .keyID(ecJWK.getKeyID())
        .build(),
    new ECDSASigner(ecJWK)
);

// Serialise to JSON
String json = jwsObjectJSON.serializeGeneral();

// Get the public keys to allow recipients to verify the signatures
RSAKey rsaPublicJWK = rsaJWK.toPublicJWK();
ECKey ecPublicJWK = ecJWK.toPublicJWK();

Example output:

{
  "payload"    : "SGVsbG8sIHdvcmxkIQ",
  "signatures" : [
    {
      "protected" : "eyJraWQiOiIxIiwiYWxnIjoiUlMyNTYifQ",
      "signature" : "XAwNAgj-Dw5CBeWG4_6LwQyJrQaAGVJmtqkl21QcIxedNV8Ft0he02eU8Ih60jjNe5FbQxrgfA84JA0isb7NkdczEW_kfX9Fknh-tdypyymrPTsP9bhLKUYfQ7nglWgVf1tukFqkAVZOLdfV7ri9we_bqZblM0pD5ysbu6hjhkLbXSSe_ZD0QfKmJFDaIHWBlB2Z0BeqSmyGQTbO6ZpmxXzICz0ANqTsCrJe6TU2CE6i1mDm0arL12VdcqO9JjD7iQkWppfD3kmRCGsSk3jdJpyWUDCYSKlPVaJJElaffwYjIBevCgfMHFO8ALwpUJc_cFcwBsyalo25JzUSzBNaXg"
    },
    {
      "protected" : "eyJraWQiOiIyIiwiYWxnIjoiRVMyNTYifQ",
      "signature" : "ckfVpM4ECSrhDGitxe5smT-z65t3C238JyrHkJw3kiOAunPTRYzHD50wzvNGXG45nUlwl7Ybg8GPlOCNyJeonw"
    }
  ]
}

How to verify a JWS secured object with multiple signatures:

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.*;

// Parse the JWS JSON
JWSObjectJSON jwsObjectJSON = JWSObjectJSON.parse(json);

// Verify the signatures with the available public JWKSs
for (JWSObjectJSON.Signature sig: jwsObjectJSON.getSignatures()) {

    // The JWS kid header parameter is used to identify the signing key

    if (rsaPublicJWK.getKeyID().equals(sig.getHeader().getKeyID())) {
        if (! sig.verify(new RSASSAVerifier(rsaPublicJWK))) {
            System.out.println("Invalid RSA signature for key " + rsaPublicJWK.getKeyID());
        }
    }

    if (ecPublicJWK.getKeyID().equals(sig.getHeader().getKeyID())) {
        if (! sig.verify(new ECDSAVerifier(ecPublicJWK))) {
            System.out.println("Invalid EC signature for key " + ecJWK.getKeyID());
        }
    }
}

if (JWSObjectJSON.State.VERIFIED.equals(jwsObjectJSON.getState())) {
    System.out.println("JWS JSON verified");
} else {
    System.out.println("JWS JSON invalid");
}

These examples are based on version 9.16-preview.1 (2021-10-09).