...
CXF supports HTTP Signature creation and verification on both the client and service side. Payload integrity is supported by digesting the payload and inserting the result into a "Digest" header, which is also signed by HTTP Signature.
Providers
To enable HTTP Signature in CXF, it is necessary to add one of the following providers to the client or endpoint:
- Client / Service Outbound Signature Creation: org.apache.cxf.rs.security.httpsignature.filters.CreateSignatureClientFilterCreateSignatureInterceptor
- Client Inbound Signature Verification: org.apache.cxf.rs.security.httpsignature.filters.VerifySignatureClientFilter
- Service Outbound Signature Creation: org.apache.cxf.rs.security.httpsignature.filters.CreateSignatureFilterService Inbound Signature Verification: org.apache.cxf.rs.security.httpsignature.filters.VerifySignatureFilter
...
Code Block | ||||
---|---|---|---|---|
| ||||
CreateSignatureClientFilterCreateSignatureInterceptor signatureFilter = new CreateSignatureClientFilterCreateSignatureInterceptor(); String address = "http://localhost:" + PORT + "/httpsig/bookstore/books"; WebClient client = WebClient.create(address, Collections.singletonList(signatureFilter), busFile.toString()); |
...
For outbound signature we need to configure the CreateSignatureClientFilter + CreateSignatureFilter providers CreateSignatureInterceptor provider with a MessageSigner instance. The MessageSigner contains a number of different constructors that can be used depending on the desired functionality. At a minimum, we need to supply the PrivateKey instance to sign the message, as well as the "Key Id" as defined in the spec. We can also supply the signature algorithm name - if not specified this defaults to "rsa-sha256". Similarly we can supply the security provider name, which defaults to "SunRsaSign".
...
Here is an example from the tests:
Code Block | ||
---|---|---|
| ||
CreateSignatureClientFilterCreateSignatureInterceptor signatureFilter = new CreateSignatureClientFilterCreateSignatureInterceptor(); KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(ClassLoaderUtils.getResourceAsStream("keys/alice.jks", this.getClass()), "password".toCharArray()); PrivateKey privateKey = (PrivateKey)keyStore.getKey("alice", "password".toCharArray()); assertNotNull(privateKey); MessageSigner messageSigner = new MessageSigner(keyId -> privateKey, "alice-key-id"); signatureFilter.setMessageSigner(messageSigner); String address = "http://localhost:" + PORT + "/httpsig/bookstore/books"; WebClient client = WebClient.create(address, Collections.singletonList(signatureFilter), busFile.toString()); client.type("application/xml").accept("application/xml"); |
For signature verification, we need to supply the VerifySignatureClientFilter and VerifySignatureFilter instances with a MessageVerifier instance. At a minimum, we need to configure the MessageVerifier with a PublicKeyProvider KeyProvider instance, which is an interface which supplies the public key required to verify the signature given the "Key Id" present in the message. As per MessageSigner, we can also specify the signature algorithm that is required, as well as the Security Provider. It defaults to the same values as documented for MessageSigner above. We can also specify a list of HTTP headers which must be signed. In addition to this list, the default behavior is to require that the "digest" header is signed (unless a service request with a HTTP method of GET or HEAD, and also unless a service response and the status is 204 or not "OK"), as well as the "(request-target)" header for a client request. This default behaviour can be disabled by setting the boolean addDefaultRequiredHeaders property of MessageVerifier to false.
Here is an example from the tests:
...
Configuration Tag | Default | Description |
---|---|---|
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 | JKS | The keystore type. |
rs.security.keystore.password | The password required to access the keystore. | |
rs.security.keystore.alias | The keystore alias corresponding to the key to use. | |
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.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. | |
rs.security.signature.algorithm | rsa-sha256 | The signature algorithm to use. |
rs.security.http.signature.key.id | The signature key id. This is a required configuration option on the outbound side. | |
rs.security.http.signature.out.headers | all headers incl "(request-target)" | A list of String values which correspond to the list of HTTP headers that will be signed in the outbound request. |
rs.security.http.signature.in.headers | "digest", and "(request-target)" for a client request. | A list of String values which correspond to the list of HTTP headers that must be signed in the inbound request. By default, a client request must sign "(request-target)". In addition, a client request must sign "digest", unless it is a GET/HEAD request. A service response must sign "digest" for all "OK" status codes, apart from 204. |
rs.security.http.signature.digest.algorithm | SHA-256 | The digest algorithm to use when digesting the payload. |
Here is a Java example:
Code Block | ||
---|---|---|
| ||
List<Object> providers = new ArrayList<>(); providers.add(new CreateSignatureClientFilterCreateSignatureInterceptor()); providers.add(new VerifySignatureClientFilter()); String address = "http://localhost:" + PORT + "/httpsigresponse/bookstore/books"; WebClient client = WebClient.create(address, providers, busFile.toString()); client.type("application/xml").accept("application/xml"); Map<String, Object> properties = new HashMap<>(); properties.put("rs.security.signature.out.properties", "org/apache/cxf/systest/jaxrs/security/httpsignature/alice.httpsig.properties"); properties.put("rs.security.signature.in.properties", "org/apache/cxf/systest/jaxrs/security/httpsignature/bob.httpsig.properties"); WebClient.getConfig(client).getRequestContext().putAll(properties); |
...
Code Block |
---|
<jaxrs:server address="http://localhost:${testutil.ports.jaxrs-httpsignature}/httpsigresponseprops"> <jaxrs:serviceBeans> <ref bean="serviceBean"/> </jaxrs:serviceBeans> <jaxrs:providers> <bean class="org.apache.cxf.rs.security.httpsignature.filters.VerifySignatureFilter" /> <bean class="org.apache.cxf.rs.security.httpsignature.filters.CreateSignatureFilterCreateSignatureInterceptor" /> </jaxrs:providers> <jaxrs:properties> <entry key="rs.security.signature.in.properties" value="org/apache/cxf/systest/jaxrs/security/httpsignature/alice.httpsig.properties" /> <entry key="rs.security.signature.out.properties" value="org/apache/cxf/systest/jaxrs/security/httpsignature/bob.httpsig.properties" /> </jaxrs:properties> </jaxrs:server> |
...