Table of Contents |
---|
Introduction
...
New: Optional HTTP Header protection.
Maven Dependencies
Having the following dependency will let developers write JOSE JWS or JWE code:
Code Block | ||||
---|---|---|---|---|
| ||||
<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 | ||||
---|---|---|---|---|
| ||||
<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 | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
{
"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).
...
The following table shows the algorithms and the corresponding providers (org.apache.cxf.rs.security.jose.jws package):
Algorithm | JWS Header 'alg' | JwsSignatureProvider | JwsSignatureVerifier |
HMAC | HS256, HS384, HS512 | HmacJwsSignatureProvider | HmacJwsSignatureVerifier |
RSASSA-PKCS1-v1_5 | RS256, RS384, RS512 | PrivateKeyJwsSignatureProvider | PublicKeyJwsSignatureVerifier |
ECDSA | ES256, ES384, ES512 | EcDsaJwsSignatureProvider | EcDsaJwsSignatureVerifier |
RSASSA-PSS | PS256, PS384, PS512 | PrivateKeyJwsSignatureProvider | PublicKeyJwsSignatureVerifier |
None | none | NoneJwsSignatureProvider | NoneJwsSignatureVerifier |
Either of these providers (except for None) can be initialized with the keys loaded from JWK or Java JKS stores or from the in-memory representations.
...
For example, here is how an Appendix A1 example can be done in CXF:
Code Block | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
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); |
...
The following table shows the key encryption algorithms and the corresponding providers (org.apache.cxf.rs.security.jose.jwe package):
Algorithm | JWE Header 'alg' | KeyEncryptionProvider | KeyDecryptionProvider |
RSAES-PKCS1-v1_5 | RSA1_5 | RSAKeyEncryptionAlgorithm | RSAKeyDecryptionAlgorithm |
RSAES OAEP | RSA-OAEP, RSA-OAEP-256 | RSAKeyEncryptionAlgorithm | RSAKeyDecryptionAlgorithm |
AES Key Wrap | A128KW, A192KW, A256KW | AesKeyWrapEncryptionAlgorithm | AesKeyWrapDecryptionAlgorithm |
Direct | dir | DirectKeyEncryptionAlgorithm | DirectKeyDecryptionAlgorithm |
ECDH-ES Key Wrap | ECDH-ES+A128KW (+A192KW, +256KW) | EcdhAesWrapKeyEncryptionAlgorithm | EcdhAesWrapKeyDecryptionAlgorithm |
ECDH-ES Direct | ECDH-ES | EcdhDirectKeyJweEncryption | EcdhDirectKeyJweDecryption |
AES-GCM Key Wrap | A128GCMKW, A192GCMKW, A256GCMKW | AesGcmWrapKeyEncryptionAlgorithm | AesGcmWrapKeyDecryptionAlgorithm |
PBES2 | PBES2-HS256+A128KW PBES2-HS384+A192KW PBES2-HS512+A256KW | PbesHmacAesWrapKeyEncryptionAlgorithm | PbesHmacAesWrapKeyDecryptionAlgorithm |
...
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.
...
The following table shows the content encryption algorithms and the corresponding providers:
Algorithm | JWE Header 'enc' | ContentEncryptionProvider | ContentDecryptionProvider |
AES_CBC_HMAC_SHA2 | A128CBC-HS256(-HS384, -HS512) | AesCbcHmacJweEncryption, | AesCbcHmacJweDecryption |
AES-GCM | A128GCM, A92GCM, A256GCM | AesGcmContentEncryptionAlgorithm | AesGcmContentDecryptionAlgorithm |
All of the above providers can be initialized with the keys loaded from JWK or Java JKS stores or from the in-memory representations.
...
Code Block | ||||
---|---|---|---|---|
| ||||
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:
...
Code Block | ||||
---|---|---|---|---|
| ||||
final String text = "The true sign of intelligence is not knowledge but imagination."; // Create the secret keys for encrypting the content encryption key: SecretKey wrapperKey1 = CryptoUtils.createSecretKeySpec(WRAPPER_BYTES1, "AES"); SecretKey wrapperKey2 = CryptoUtils.createSecretKeySpec(WRAPPER_BYTES2, "AES"); // Create KeyEncryptionProviders initialized with these secret keys: KeyEncryptionProvider keyEncryption1 = JweUtils.getSecretKeyEncryptionAlgorithm(wrapperKey1, KeyAlgorithm.A128KW); KeyEncryptionProvider keyEncryption2 = JweUtils.getSecretKeyEncryptionAlgorithm(wrapperKey2, KeyAlgorithm.A128KW); // If you work with the public keys do this instead: // PublicKey publicKey1 = ...; // KeyEncryptionProvider keyEncryption1 = JweUtils.getPublicKeyEncryptionProvider(publicKey1, KeyAlgorithm.RSA_AEP); // PublicKey publicKey2 = ...; // KeyEncryptionProvider keyEncryption2 = JweUtils.getPublicKeyEncryptionProvider(publicKey2, KeyAlgorithm.RSA_AEP); // Create ContentEncryptionProvider: // Starting from CXF 3.1.11: ContentEncryptionProvider contentEncryption = new AesGcmContentEncryptionAlgorithm(ContentAlgorithm.A128GCM, true); // or // ContentEncryptionProvider contentEncryption = JweUtils.getContentEncryptionProvider(ContentAlgorithm.A128GCM, true); // Before CXF 3.1.1 a CEK needs to be pre-generated when dealing with multiple recipients: //ContentEncryptionProvider contentEncryption = new AesGcmContentEncryptionAlgorithm(CEK_BYTES, ContentAlgorithm.A128GCM); // If a single recipient then this line is enough: //ContentEncryptionProvider contentEncryption = JweUtils.getContentEncryptionProvider(ContentAlgorithm.A128GCM); // Prepare JweEncryptionProviders, one per each recipient. List<JweEncryptionProvider> jweProviders = new LinkedList<JweEncryptionProvider>(); jweProviders.add(new JweEncryption(keyEncryption1, contentEncryption)); jweProviders.add(new JweEncryption(keyEncryption2, contentEncryption)); // Let the recipients know that the key encryption algorithm is A128KW. // This step is optional if the recipients support A128KW only. // Note because these headers are shared A128KW needs to be supported by all the recipients. // Per-reciepient specific headers can be used instead to note the key encryption algorithm if required. // One can also consider setting this property in the shared protected headers, same as it is done below // with the content algorithm JweHeaders sharedUnprotectedHeaders = new JweHeaders(); sharedUnprotectedHeaders.setKeyEncryptionAlgorithm(KeyAlgorithm.A128KW); // Set some other custom shared unprotected header sharedUnprotectedHeaders.setHeader("customHeader", "customValue"); // Let the recipients know that the content encryption algorithm is A128GCM. // This step is optional if the recipients support A128GCM only. JweHeaders protectedHeaders = new JweHeaders(ContentAlgorithm.A128GCM); // Set per-recipient specific headers List<JweHeaders> perRecipientHeades = new LinkedList<JweHeaders>(); perRecipientHeades.add(new JweHeaders("key1")); perRecipientHeades.add(new JweHeaders("key2")); JweJsonProducer p = new JweJsonProducer(protectedHeaders, sharedUnprotectedHeaders, StringUtils.toBytesUTF8(text), StringUtils.toBytesUTF8(EXTRA_AAD_SOURCE), false); String jweJsonOut = p.encryptWith(jweProviders, perRecipientHeades); JweJsonConsumer consumer = new JweJsonConsumer(jweJsonOut); KeyAlgorithm keyAlgo = consumer.getSharedUnprotectedHeader().getKeyEncryptionAlgorithm(); ContentAlgorithm ctAlgo = consumer.getProtectedHeader().getContentEncryptionAlgorithm(); // first recipient: JweDecryptionProvider jwe1 = JweUtils.createJweDecryptionProvider(wrapperKey1, keyAlgo, ctAlgo); // the consumer will iterate over JWE entries and will try to find the one which can be decrypted with this decryptor // or do consumer.getRecipientsMap() returning a list of entries and their metadata to do a more precise selection. String content = consumer.decryptWith(jwe1, Collections.singletonMap("kid", "key1")).getContent(); // second recipient: JweDecryptionProvider jwe2 = JweUtils.createJweDecryptionProvider(wrapperKey2, keyAlgo, ctAlgo); content = consumer.decryptWith(jwe2, Collections.singletonMap("kid", "key2")).getContent(); |
If the sequence contains a single recipient entry only then the JWE JSON 'recipients' array will contain a single entry, or the whole sequence can be flattened instead with the actual 'recipients' array dropped. JweJsonProducer does not produce the flattened sequence when only a single encryption is done by default because 3rd party JWE JSON consumers may only be able to process the sequences with the 'recipients' array, so pass a 'canBeFlat' flag to JwEJsonProducer if needed
...
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 | ||||
---|---|---|---|---|
| ||||
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 | ||
---|---|---|
| ||
@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.
...
This approach is more effective compared to the ones where the body hash is calculated before it is submitted to a signature creation function, with the signature added as HTTP header.
Note that the "JWT" scheme is not standard, and from CXF 4.0.0 the default scheme has changed to "Bearer".
JWT authorization
CXF supports both role and claims based authorization for JAX-RS endpoints based on information contained in a received JWT. Please see the JAX-RS Token Authorization page for more information.
Optional protection of HTTP headers
...
This approach does not prevent the streaming of the outgoing data (which will also be protected by the filters) and offers a way to secure the HTTP headers which are really important for the correct processing of the incoming payloads
Configuration
JOSE in JAX-RS application code
In some cases you may need to create or process the JOSE data directly in the service or client application code. For example, one of the properties in the request or response payload needs to be JWS signed/verified and/or JWE encrypted/decrypted. The following 2 options can be tried.
Option 1: Process JOSE directly
This option is about using the CXF JOSE library to sign, encrypt, or/and decrypt and verify the data as documented above. This option should be preferred if one needs to keep a closer control, for example, set the custom JWS or JWE headers, etc.
Option 2: Use JOSE library helpers and Endpoint Configuration
This option makes it straighforward to do JOSE in the application code. One has to extend or delegate to a specific JOSE helper instance and configure the endpoint with the location of the JOSE properties file where the JWS or JWE algorithm and key store properties are set.
Produce JOSE data
If you need to protect some non JWT property - extend or delegate to JoseProducer:
Code Block | ||
---|---|---|
| ||
import org.apache.cxf.rs.security.jose.common.JoseProducer;
@Path("service")
public class SecureService extends JoseProducer {
@GET
public String getProtectedValue() {
// encrypt and/or sign the data
return super.processData("some data");
}
}
// or
@Path("service")
public class SecureService extends AbstractSecureService {
private JoseProducer producer = new JoseProducer();
@GET
public String getProtectedValue() {
// encrypt and/or sign the data
return producer.processData("some data");
}
} |
If you need to protect some JWT property then extend or delegate to JoseJwtProducer:
Code Block | ||
---|---|---|
| ||
import org.apache.cxf.rs.security.jose.jwt.JoseJwtProducer;
@Path("service")
public class SecureService extends JoseJwtProducer {
@GET
public String getProtectedToken() {
// encrypt and/or sign JWT
JwtClaims claims = new JwtClaims();
claims.setIssuer("some issuer");
// set other claims
return super.processJwt(new JwtToken(claims));
}
}
// or
@Path("service")
public class SecureService extends AbstractSecureService {
private JoseJwtProducer producer = new JoseJwtProducer();
@GET
public String getProtectedValue() {
// encrypt and/or sign JWT
return producer.processJwt(new JwtToken(new JwtClaims()));
}
} |
In both cases the producer helpers will detect the endpoint specific configuration thus they do not need to be preconfigured - however if needed they have the 'encryptionProvider' and 'signatureProvider' setters which can be used to inject JwsSignatureProvider and/or JweEncryptionProvider instances instead.
The producer helpers require a signature creation only by default. Use their 'setJwsRequired' or 'setJwsRequired' properties to customize it - example, disable JWS but require JWE, or enable JWE to get JWS-protected data encrypted as well.
Consume JOSE data
If you need to decrypt and/or verify some non-JWT JOSE property - extend or delegate to JoseConsumer:
Code Block | ||
---|---|---|
| ||
import org.apache.cxf.rs.security.jose.common.JoseConsumer;
@Path("service")
public class SecureService extends JoseConsumer {
@POST
public void acceptProtectedValue(String joseValue) {
// decrypt the value first if needed, verify the signature
String data = super.getData(joseValue);
}
}
// or
@Path("service")
public class SecureService extends AbstractSecureService {
private JoseConsumer consumer = new JoseConsumer();
@POST
public void acceptProtectedValue(String joseValue) {
// decrypt the value first if needed, verify the signature
String data = consumer.getData(joseValue);
}
} |
If you need to decrypt and/or verify some JWT property then extend or delegate to JoseJwtConsumer:
Code Block | ||
---|---|---|
| ||
import org.apache.cxf.rs.security.jose.jwt.JoseJwtConsumer;
@Path("service")
public class SecureService extends JoseJwtConsumer {
@POST
public void acceptProtectedToken(String joseValue) {
// decrypt the value first if needed, verify the signature
JwtToken data = super.getJwtToken(joseValue);
}
}
// or
@Path("service")
public class SecureService extends AbstractSecureService {
private JoseJwtConsumer consumer = new JoseJwtConsumer();
@POST
public void acceptProtectedToken(String joseValue) {
// decrypt the value first if needed, verify the signature
JwtToken data = consumer.getJwtToken(joseValue);
}
} |
In both cases the producer helpers will detect the endpoint specific configuration thus they do not need to be preconfigured - however if needed they have the 'jweDecryptor' and 'jwsVerifier' setters which can be used to inject JwsSignatureVerifier and/or JweDecryptionProvider instances instead.
The producer helpers require a signature creation only by default. Use their 'setJwsRequired' or 'setJwsRequired' properties to customize it - example, disable JWS but require JWE, or enable JWE to get JWS-protected data encrypted as well.
Produce and Consume JOSE data
If you need to produce and consumer some non-JWT JOSE properties- extend or delegate to JoseProducerConsumer:
Code Block | ||
---|---|---|
| ||
import org.apache.cxf.rs.security.jose.common.JoseProducerConsumer;
@Path("service")
public class SecureService extends JoseProducerConsumer {
@POST
public String echoProtectedValue(String joseValue) {
// decrypt the value first if needed, verify the signature
String data = super.getData(joseValue);
// sign and/or encrypt the data
return super.processData(data);
}
}
// or
@Path("service")
public class SecureService extends AbstractSecureService {
private JoseProducerConsumer jose = new JoseProducerConsumer();
@POST
public String echoProtectedValue(String joseValue) {
// decrypt the value first if needed, verify the signature
String data = jose.getData(joseValue);
// sign and/or encrypt the data
return jose.processData(data);
}
} |
If you need to decrypt and/or verify some JWT property then extend or delegate to JoseJwtProducerConsumer:
Code Block | ||
---|---|---|
| ||
import org.apache.cxf.rs.security.jose.jwt.JoseJwtProducerConsumer;
@Path("service")
public class SecureService extends JoseJwtProducerConsumer {
@POST
public String echoProtectedToken(String joseValue) {
// decrypt the value first if needed, verify the signature
JwtToken data = super.getJwtToken(joseValue);
// sign and/or encrypt the data
return super.processJwt(data);
}
}
// or
@Path("service")
public class SecureService extends AbstractSecureService {
private JoseJwtProducerConsumer jose = new JoseJwtProducerConsumer();
@POST
public String echoProtectedToken(String joseValue) {
// decrypt the value first if needed, verify the signature
JwtToken data = jose.getJwtToken(joseValue);
// sign and/or encrypt the data
return jose.processJwt(data);
}
} |
In both cases this composite producer-consumer will use the internal producer and/or consumer helpers which will detect the endpoint specific configuration but which can also be injected with some specific JWE and/or JWS handlers.
Configure the endpoint
These properties will contain a location of the key store, signature and/or encryption algorithm properties, etc. See the Configuration section for all the available configuration options.
Code Block | ||
---|---|---|
| ||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:jaxrs="http://cxf.apache.org/jaxrs">
<bean id="serviceBean" class="org.apache.cxf.systest.jaxrs.security.jose.SecureService"/>
<jaxrs:server address="/secure">
<jaxrs:serviceBeans>
<ref bean="serviceBean"/>
</jaxrs:serviceBeans>
<jaxrs:properties>
<entry key="rs.security.signature.properties" value="org/apache/cxf/systest/jaxrs/security/secret.jwk.properties"/>
<entry key="rs.security.encryption.properties" value="org/apache/cxf/systest/jaxrs/security/secret.jwk.properties"/>
</jaxrs:properties>
</jaxrs:server>
</beans |
Configuration
CXF JOSE configuration provides for CXF JOSE configuration provides for loading JWS and JWE keys and supporting various processing options. Configuration properties can be shared between JWS and JWE processors or in/out only JWS and or JWE properties can be set.
...
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.
Configuration Property Containers
Signature
rs.security.signature.out.properties | The signature properties file for Compact or JSON signature creation. If not specified then it falls back to "rs.security.signature.properties". |
rs.security.signature.in.properties | The signature properties file for Compact or JSON signature verification. If not specified then it falls back to "rs.security.signature.properties". |
rs.security.signature.properties | The signature properties file for Compact or JSON signature creation/verification. |
Encryption
rs.security.encryption.out.properties | The encryption properties file for Compact or JSON encryption creation. If not specified then it falls back to "rs.security.encryption.properties". |
rs.security.encryption.in.properties | The encryption properties file for Compact or JSON decryption. If not specified then it falls back to "rs.security.encryption.properties". |
rs.security.encryption.properties | The |
encryption properties file for encryption/decryption. |
Note that these property containers can be used for creating/processing JWS and JWE Compact and JSON sequences. If it is either JWS JSON or JWE JSON and you wish to have more than one signature or encryption be created then let the property value be a commas separated list of locations, with each location pointing to a unique signature or encryption operation property file.
...
Configuration that applies to both encryption and signature
rs.security.keystore | The Java KeyStore Object to use. This configuration tag is used if you want to pass the KeyStore Object through dynamically. |
rs.security.keystore.type | The keystore type. Suitable values are "jks" or "jwk". |
rs.security.keystore.password | The password required to access the keystore. |
rs.security.keystore.alias | The keystore alias corresponding to the key to use. You can append one of the following to this tag to get the alias for more specific operations: - jwe.out - jwe.in - jws.out - jws.in |
rs.security.keystore.aliases | The keystore aliases corresponding to the keys to use, when using the JSON serialization form. You can append one of the following to this tag to get the alias for more specific operations: - jws.out - jws.in |
rs.security.keystore.file | The path to the keystore file. |
rs.security.key.password | The password required to access the private key (in the keystore). |
rs.security.key.password.provider | A reference to a PrivateKeyPasswordProvider instance used to retrieve passwords to access keys. |
rs.security.accept.public.key |
Whether to allow using a JWK received in the header for signature validation. The default is "false". | |
rs.security.enable.revocation CXF 3.4.0 | Whether to enable revocation or not when validating a certificate chain. The default is "false". |
Configuration that applies to signature only
rs.security.signature.key.password.provider | A reference to a PrivateKeyPasswordProvider instance used to retrieve passwords to access keys for signature. If this is not specified it falls back to use "rs.security.key.password.provider". |
rs.security.signature.algorithm | The signature algorithm to use. The default algorithm if not specified is 'RS256'. |
rs.security.signature.include.public.key | Include the JWK public key for signature in the "jwk" header. |
rs.security.signature.include.cert | Include the X.509 certificate for signature in the "x5c" header. |
rs.security.signature.include.key.id | Include the JWK key id for signature in the "kid" header. |
rs.security.signature.include.cert.sha1 | Include the X.509 certificate SHA-1 digest for signature in the "x5t" header. |
rs.security.signature.include.cert.sha256 | Include the X.509 certificate SHA-256 digest for signature in the "x5t#S256" header. |
Configuration that applies to encryption only
rs.security.decryption.key.password.provider | A reference to a PrivateKeyPasswordProvider instance used to retrieve passwords to access keys for decryption. If this is not specified it falls back to use "rs.security.key.password.provider". |
rs.security.encryption.content.algorithm | The encryption content algorithm to use. The default algorithm if not specified is 'A128GCM'. |
rs.security.encryption.key.algorithm | The encryption key algorithm to use. The default algorithm if not specified is 'RSA-OAEP' if the key is an RSA key, 'ECDH-ES-A128KW' if the key is an EC key and 'A128GCMKW' if it is an octet sequence. |
rs.security.encryption.zip.algorithm | The encryption zip algorithm to use. |
rs.security.encryption.include.public.key | Include the JWK public key for encryption in the "jwk" header. |
rs.security.encryption.include.cert | Include the X.509 certificate for encryption in the "x5c" header. |
rs.security.encryption.include.key.id | Include the JWK key id for encryption in the "kid" header. |
rs.security.encryption.include.cert.sha1 | Include the X.509 certificate SHA-1 digest for encryption in the "x5t" header. |
rs.security.encryption.include.cert.sha256 | Include the X.509 certificate SHA-256 digest for encryption in the "x5t#S256" header. |
Configuration that applies to JWT tokens only
rs.security.enable.unsigned-jwt.principal | Whether to allow unsigned JWT tokens as SecurityContext Principals. The default is false. |
Interoperability
...
expected.claim.audience | If 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.