Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

New: Optional HTTP Header protection.

Maven Dependencies

 

Having the following dependency will let developers write JOSE JWS or JWE code:

Code Block
xml
xml
<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-rs-security-jose</artifactId>
  <version>3.1.7</version>
</dependency>

 

Having the following dependency will let developers use JAX-RS JOSE filters which will sign and/or encrypt the data streams, and decrypt or/and validate the incoming JOSE sequences and make the original data available for the processing.

...

Code Block
xml
xml
<dependency>
     <groupId>org.bouncycastle</groupId>
     <artifactId>bcprov-ext-jdk15on</artifactId>
     <version>1.54<60</version>
</dependency>

BouncyCastle provider can be registered and unregistered as follows:

Code Block
languagejava
titleBouncyCastle Provider
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

private static void registerBouncyCastle() throws Exception {
    Security.addProvider(new BouncyCastleProvider());    
}

private static void unregisterBouncyCastle() throws Exception {
    Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);    
}

 


Java and JCE Policy 

Java7 or higher is recommended in most cases.

...

Code Block
languagejs
titlePublic RSA Key
{
  "kty":"RSA",
  "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx
     4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs
     tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2
     QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI
     SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb
     w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
  "e":"AQAB",
  "alg":"RS256",
  "kid":"Public RSA Key"
}

A 'kid' property can be of special interest as it allows to identify a key but also help with the simple key rotation mechanism realized (ex, OIDC Asymmetric Key Rotation).

...

 For example, here is how an Appendix A1 example can be done in CXF:

 

Code Block
languagejava
titleCXF JWS Compact HMac
JwtClaims claims = new JwtClaims();
claims.setIssuer("joe");
claims.setExpiryTime(1300819380L);
claims.setClaim("http://example.com/is_root", Boolean.TRUE);

JwsCompactProducer jwsProducer = new JwsJwtCompactProducer(claims);

// Sign
// Load HmacJwsSignatureProvider directly, see the next example for the alternative approach
String jwsSequence = jwsProducer.signWith(new HmacJwsSignatureProvider(ENCODED_MAC_KEY, SignatureAlgorithm.HS256));

// Validate
JwsJwtCompactConsumer jwsConsumer = new JwsJwtCompactConsumer(jwsSequence);

// Load HmacJwsSignatureVerifier directly, see the next example for the alternative approach
jwsConsumer.verifySignatureWith(new HmacJwsSignatureVerifier(ENCODED_MAC_KEY, SignatureAlgorithm.HS256)));

// Get the data
JwtClaims protectedClaims = jws.getJwtClaims();

...

JwsJsonProducer and JwsJsonConsumer support producing and consuming JWS JSON sequences.

 

Code Block
languagejava
titleCXF JWS JSON
JwsJsonProducer producer = new JwsJsonProducer(UNSIGNED_PLAIN_JSON_DOCUMENT);
JwsHeaders headerEntries = new JwsHeaders(SignatureAlgorithm.HS256);
              
producer.signWith(new HmacJwsSignatureProvider(ENCODED_MAC_KEY_1, SignatureAlgorithm.HS256),
                  headerEntries);
producer.signWith(new HmacJwsSignatureProvider(ENCODED_MAC_KEY_2, SignatureAlgorithm.HS256),
                  headerEntries);
assertEquals(DUAL_SIGNED_JWS_JSON_DOCUMENT, producer.getJwsJsonSignedDocument());

JwsJsonConsumer consumer = new JwsJsonConsumer(DUAL_SIGNED_DOCUMENT); 

// Validate both signatures, see below how to validate and produce
JsonWebKeys jwks = readKeySet("jwkSet.txt");
        
List<JwsJsonSignatureEntry> sigEntries = consumer.getSignatureEntries();
assertEquals(2, sigEntries.size());

// 1st signature
String firstKid = (String)sigEntries.get(0).getKeyId();
JsonWebKey firstKey = jwks.getKey(firstKid);
assertTrue(sigEntries.get(0).verifySignatureWith(firstKey));
// 2nd signature
String secondKid = (String)sigEntries.get(1).getKeyId();
JsonWebKey secondKey = jwks.getKey(secondKid);
assertTrue(sigEntries.get(1).verifySignatureWith(secondKey));

// or if you wish to validate (ex with the firstKey loaded above) and forward it to the next consumer, do:
JwsSignatureProvider provider = JwsUtils.getSignatureProvider(firstKey);
String nextJwsJson = consumer.validateAndProduce(Collections.singletonList(provider));
// use WebClient to post nextJwsJson to the next consumer, with nextJwsJson being nearly identical to the original
// double-signed JWS JSON signature, minus the signature which was already validated, in this case nextJwsJson will 
// only have a single signature 

...

In CXF you can apply this option to both JWS Compact (embedded payloads - from CXF 3.1.7) and JWS JSON sequences, here is a JWS JSON code fragment:

 


Code Block
languagejava
titleJWS JSON Unencoded
JwsJsonProducer producer = new JwsJsonProducer(UNSIGNED_PLAIN_JSON_DOCUMENT, true);
JwsHeaders headers = new JwsHeaders(SignatureAlgorithm.HS256);
headers.setPayloadEncodingStatus(false);
producer.signWith(new HmacJwsSignatureProvider(ENCODED_MAC_KEY_1, SignatureAlgorithm.HS256),
                  headers);

...

AlgorithmJWE Header 'alg'KeyEncryptionProviderKeyDecryptionProvider
RSAES-PKCS1-v1_5

RSA1_5

RSAKeyEncryptionAlgorithm

RSAKeyDecryptionAlgorithm

RSAES OAEP

RSA-OAEP, RSA-OAEP-256

RSAKeyEncryptionAlgorithmRSAKeyDecryptionAlgorithm
AES Key Wrap

A128KW, A192KW, A256KW

AesKeyWrapEncryptionAlgorithmAesKeyWrapDecryptionAlgorithm
DirectdirDirectKeyEncryptionAlgorithmDirectKeyDecryptionAlgorithm
ECDH-ES Key Wrap

ECDH-ES+A128KW (+A192KW, +256KW)

EcdhAesWrapKeyEncryptionAlgorithmEcdhAesWrapKeyDecryptionAlgorithm
ECDH-ES Direct

ECDH-ES

EcdhDirectKeyJweEncryptionEcdhDirectKeyJweDecryption
AES-GCM Key Wrap

A128GCMKW, A192GCMKW, A256GCMKW

AesGcmWrapKeyEncryptionAlgorithmAesGcmWrapKeyDecryptionAlgorithm
PBES2

PBES2-HS256+A128KW

PBES2-HS384+A192KW

PBES2-HS512+A256KW

PbesHmacAesWrapKeyEncryptionAlgorithmPbesHmacAesWrapKeyDecryptionAlgorithm

 


RSA-OAEP algorithms are likely to be used most often at the moment due to existing JKS stores being available everywhere and a relatively easy way of making the public validation keys available.

...

Code Block
languagejava
titleCXF Jwe AesWrapAesCbcHMac
final String specPlainText = "Live long and prosper.";
        
AesWrapKeyEncryptionAlgorithm keyEncryption = new AesWrapKeyEncryptionAlgorithm(KEY_ENCRYPTION_KEY_A3, KeyAlgorithm.A128KW);
JweEncryptionProvider encryption = new AesCbcHmacJweEncryption(ContentAlgorithm.A128CBC_HS256,
                                                               keyEncryption);
String jweContent = encryption.encrypt(specPlainText.getBytes("UTF-8"), null);
        
AesWrapKeyDecryptionAlgorithm keyDecryption = new AesWrapKeyDecryptionAlgorithm(cekEncryptionKey);
JweDecryptionProvider decryption = new AesCbcHmacJweDecryption(keyDecryption);
String decryptedText = decryption.decrypt(jweContent).getContentText();
assertEquals(specPlainText, decryptedText);

...


Here is another example using RSA-OAEP key encryption and AES-GCM content encryption:

...

If you have a requirement to sign the data and then encrypt the signed payload then it can be easily achieved by selecting a required JWS Producer and creating a JWS Compact sequence, and next submitting this sequence to a JWE producer, and processing it all in the reverse sequence.

JOSE JAX-RS Filters

 


While working directly with JWS and JWE providers may be needed in the application code, JAX-RS users writing the code like this:

...

No Format
Address: https://localhost:9001/jwsjwkhmac/bookstore/books
Http-Method: POST
Content-Type: application/jose
Payload: 
eyJhbGciOiJIUzI1NiIsImN0eSI6Impzb24ifQ.
eyJCb29rIjp7ImlkIjoxMjMsIm5hbWUiOiJib29rIn19.
hg1T41ESuX6JvRR--huTA3HnbrsdIZSwkxQdyWj9j6c

org.apache.cxf.rs.security.jose.common.JoseUtils traceHeaders
INFO: JWS Headers: 
{"alg":"HS256",
 "cty":"json"}

 


You can see 3 JWS parts (put on separate lines for the better readibility) separated by dots. The 1st part is Base64Url encoded protected headers, next one - Base64Url encoded Book JSON payload, finally - the signature.

...

Code Block
languagejava
titleClient JWS SetUp
    	public void testJwsJwkBookHMac() throws Exception {
        String address = "https://localhost:" + PORT + "/jwsjwkhmac";
        BookStore bs = createJwsBookStore(address);
        Book book = bs.echoBook(new Book("book", 123L));
        assertEquals("book", book.getName());
        assertEquals(123L, book.getId());
    }
    private BookStore createJwsBookStore(String address, 
                                         List<?> mbProviders) throws Exception {
        JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
        bean.setServiceClass(BookStore.class);
        bean.setAddress(address);
        List<Object> providers = new LinkedList<Object>();
        // JWS Compact Out
        JwsWriterInterceptor jwsWriter = new JwsWriterInterceptor();
        // enable streaming 
        jwsWriter.setUseJwsOutputStream(true);
        // The payload is encoded by default, disable it if required
        // jwsWriter.setEncodePayload(false);
        providers.add(jwsWriter);
        // JWS Compact In
        providers.add(new JwsClientResponseFilter());
        // Book to/from JSON
        providers.add(new JacksonJsonProvider());
        bean.setProviders(providers);
        // point to the JWS security properties
        bean.getProperties(true).put("rs.security.signature.properties", 
            "org/apache/cxf/systest/jaxrs/security/secret.jwk.properties");
        // enable the tracing of JWS headers
        bean.getProperties(true).put("jose.debug", true);
        
        return bean.create(BookStore.class);
    }

...

Note when the attachments are accessed by the receiving application code, the read process will fail to complete if the validation fails. For example, if the application code copies a given part's InputStream to the disk then this copy operation will fail. For example: 

Code Block
languagejava
@POST
@Path("/books")
@Consumes("multipart/related")
public void uploadBookMultipart(@Multipart(type = "application/xml") Book book) {
        // This method will not be even invoked if the data signature verification fails 
        // causing the construction of Book bean to fail
}


@POST
@Path("/pdf")
@Consumes("multipart/related")
public void uploadStreamMultipart(@Multipart(type = "application/pdf") InputStream is) {
        OutputStream os = getTargetOutputStream();
        // This copy operation will fail
        IOUtils.copy(is, os); 
}

...


Note that besides the signature verification process, CXF offers some other indirect support for ensuring the attachment data have not been affected. For example, the size of the attachments can be restricted, and if the data stream is converted from XML then the conversion process will be controlled by the secure XML parser. 

...

The above code needs to be executed in the context of the current request (in server or client in/out interceptors or server service code) as it expects the current CXF Message be available in order to deduce where to load the configuration properties from. However JwsUtils and JweUtils provide a number of utility methods for loading the providers without loading the properties first which can be used when setting up the client code or when no properties are available in the current request context.

 

When the code needs to load the configuration properties it first looks for the property 'container' file which contains the specific properties instructing which keys and algorithms need to be used. Singature or encryption properties for in/out operations can be provided.  

...

rs.security.enable.unsigned-jwt.principal

Whether to allow unsigned JWT tokens as SecurityContext Principals. The default is false.

expected.claim.audienceIf this property is defined, the received JWT must have an "aud" claim with a value matching this property.

Interoperability

 

JOSE is already widely supported in OAuth2 and OIDC applications. Besides that CXF JOSE client or server will interoperate with a 3rd party client/server able to produce or consume JWS/JWE sequences.  For example, see a WebCrypto API use case and  the demo which demonstrates how a JWS sequence produced by a browser-hosted script can be validated by a server application capable of processing JWS, with the demo browser client being tested against a CXF JWS server too. 

 

Third-Party Libraries

Jose4J

Nimbus JOSE