Saturday, January 20. 2018
Certificates and WS-Security with JavaEE integration in Wildfly
This the fourth and last entry about certificate login in Wildfly for a jax-ws application. The first pair of posts were dedicated to TLS mutual authentication (the client certificate presented at TLS level was then used to identify the user inside JavaEE). The previous entry introduced WS-Security, the SOAP protocol is extended to use headers that can add different security features. Using WSS the message is signed and the X509 certificate is included in the request message as a binary token. In this last entry my intention was just using that certificate in the header and login the user with it. But I had problems implementing this last step and that was the reason to separate this topic in a new entry. I am going to explain the steps to finally make it work one by one.
By default wildfly has an interceptor that can (out of the box) log a user in with a username/password token. This interceptor class is SubjectCreatingPolicyInterceptor which obtains the token, gets the username and password inside it and validates the pair against the security domain configured in the endpoint. But the entry's goal is different, we are using a certificate token (X509), and there is no interceptor for that by default. Therefore I decided to develop a CertificateBinaryTokenInterceptor.
My class has a method to obtain the certificate from the message. The WSS4J implementation stores all the validations executed over the request in the message itself. The signature verification can be retrieved from those validations and the certificate is just part of the validation results. The method iterates over the list of verifications and gets the one that is a signature, is validated (signature has been checked) and has a certificate attached.
private X509Certificate getCertificate(SoapMessage message) {
List handlerResults = (List) message.get(WSHandlerConstants.RECV_RESULTS);
if (handlerResults != null) {
for (WSHandlerResult handlerResult : handlerResults) {
List engResults = handlerResult.getResults();
if (engResults != null) {
for (WSSecurityEngineResult engResult : engResults) {
Boolean validated = (Boolean) engResult.get(WSSecurityEngineResult.TAG_VALIDATED_TOKEN);
X509Certificate cert = (X509Certificate) engResult.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
Integer action = (Integer) engResult.get(WSSecurityEngineResult.TAG_ACTION);
if (Boolean.TRUE.equals(validated) && cert != null && action != null && WSConstants.SIGN == action) {
return cert;
}
}
}
}
}
return null;
}
The main method uses the certificate to log the user as it is done in the default SubjectCreatingPolicyInterceptor.
@Override
public void handleMessage(SoapMessage message) throws Fault {
Endpoint ep = message.getExchange().get(Endpoint.class);
X509Certificate cert = getCertificate(message);
SecurityContext context = message.get(SecurityContext.class);
if (cert == null || context == null || context.getUserPrincipal() == null) {
// no certificate, do nothing
super.handleMessage(message);
} else {
// certificate found => perform a login with it
// the principal name is going to be as in the users.jks in lowercase
Principal principal = new SimplePrincipal(cert.getSubjectX500Principal().getName().toLowerCase());
Subject subject = new Subject();
if (ep.getSecurityDomainContext().isValid(principal, cert, subject)) {
ep.getSecurityDomainContext().pushSubjectContext(subject, principal, cert);
SecurityContext sc = new DefaultSecurityContext(principal, subject);
message.put(SecurityContext.class, sc);
} else {
throw MESSAGES.authenticationFailed(principal.getName());
}
}
}
Then the interceptor is configured in the Echo service:
@Stateless
@WebService(name = "echo",
targetNamespace = "http://es.rickyepoderi.sample/ws",
serviceName = "echo-service")
@Policy(placement = Policy.Placement.BINDING, uri = "WssX509V3Token10.xml")
@DeclareRoles("Users")
@SOAPBinding(style = SOAPBinding.Style.RPC)
@EndpointConfig(configFile = "WEB-INF/jaxws-endpoint-config.xml", configName = "Custom WS-Security Endpoint")
@InInterceptors(interceptors = {
"es.rickyepoderi.sample.CertificateBinaryTokenInterceptor"
})
public class Echo {
Finally the security context is associated to the application (the same security domain used before) but using the jboss-ejb3.xml. This is now necessary, if the configuration was setup in the web.xml/jboss-web.xml files it would not work, the certificate would be searched at TLS level and now it goes in the WSS SOAP header. The file just assigns the security domain to all EJBs:
<?xml version="1.1" encoding="UTF-8"?>
<jboss:ejb-jar xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:s="urn:security:1.1"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-2_0.xsd http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"
version="3.1"
impl-version="2.0">
<assembly-descriptor>
<s:security>
<ejb-name>*</ejb-name>
<s:security-domain>certificate-sec-domain</s:security-domain>
</s:security>
</assembly-descriptor>
</jboss:ejb-jar>
I was pretty sure about this solution but...
Interceptor for {http://es.rickyepoderi.sample/ws}echo-service has thrown exception, unwinding now: java.lang.IllegalArgumentException: only string password accepted
at org.jboss.as.webservices.security.ElytronSecurityDomainContextImpl.isValid(ElytronSecurityDomainContextImpl.java:71)
at es.rickyepoderi.sample.CertificateBinaryTokenInterceptor.handleMessage(CertificateBinaryTokenInterceptor.java:98)
at es.rickyepoderi.sample.CertificateBinaryTokenInterceptor.handleMessage(CertificateBinaryTokenInterceptor.java:32)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308)
at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121)
...
It seems that the current integration between webservices and elytron subsystem just admits password logins. Looking the code this restriction is pretty clear. Elytron admits more types of login but, for the moment, the web-services just let you login using a password.
I know that in the previous security subsystem this type of login is admitted, so I just reconfigure the wildfly server to use a previous security-domain with a certificate login module.
/subsystem=security/security-domain=certificate-domain:add(cache-type=default)
/subsystem=security/security-domain=certificate-domain/authentication=classic:add()
/subsystem=security/security-domain=certificate-domain/authentication=classic/login-module=Certificate:add(code=Certificate, flag=required)
/subsystem=security/security-domain=certificate-domain/authentication=classic/login-module=Certificate:write-attribute(name=module-options, value={password-stacking=useFirstPass, securityDomain=certificate-domain})
/subsystem=security/security-domain=certificate-domain/jsse=classic:add(keystore={url="${jboss.server.config.dir}/users.jks", password=Kiosko_00})
Just the certificate module is used (stacking can be used to add roles from other modules). The user will be logged in without any role to the application.
Then I changed the jboss-ejb3.xml to use the new certificate-domain instead of the previous elytron realm. And it simply works. As I remembered, you can make a login with the certificate just passing its X509 representation as the password object. The login module just picks it up and checks if it is in the users.jks key-store. Here it is the video, as you see the current user is again the subject of the certificate.
In this last entry of the certificate security for jax-ws web services series, the application uses a WS-Security X509 token to execute a login inside an interceptor. The solution is very similar to the one that default wildfly uses for username tokens (the documented SubjectCreatingPolicyInterceptor class) but with a certificate. Nevertheless the new elytron susbsystem cannot be used but previous security domain works without any flaw. I think that elytron is still very new and it needs some improvements in these integration areas (in this case between web services and security). Throughout this series client certificate authentication for SOAP web services has been explained in detail (two types, at TLS level and using WS-Security). I hope that the entries are useful, or at least interesting, for you. The last maven project for the application can be download from here.
Certificate regards!
Saturday, January 6. 2018
Certificates and WS-Security in Wildfly
This is the third entry in the series about certificate login for SOAP web-services in wildfly. In the first post all the certificate setup was configured, the wildfly configuration used the new subsystem elytron to add mutual TLS authentication. In the next entry javaee security was integrated and the certificate was used to log the user in using the standard CLIENT-CERT method. In today's entry, instead of TLS, we are going to use the WS-Security extensions for SOAP. I have divided the subject in another two entries, same idea than in the previous two, this one is the setup for WS-Security and the next one will add the JavaEE integration.
The WS-Security or WSS is an extension to SOAP to apply different security measures to the communication (always using SOAP headers inside the message). Non-repudiation is one of the features that can be accomplished with it and, obviously, it consists in a certificate signature. My idea was singing the message and using that signature (the certificate in it) to log the user into the application. It is important to understand that this technique is completely different to the one used in the previous entries. Now the certificate is not going to be requested at TLS level (indeed that part of the configuration is going to be removed), the communication is going to be https (secure) but the client certificate is not going to be presented (no client or mutual authentication is used at this level). The SOAP message itself is going to be signed (using WS-Security extensions) and the certificate in the signature is the one used to log the user in (although the JavaEE integration will be shown in the next entry). Please, take this into account, this entry is completely different to the previous two in the series. Wildfly uses Apache WSS4J for WS-Security implementation, and it is not part of the JavaEE standard, so today's application is going to use a lot of classes from the CXF/WSS4J/Wildfly implementation (the solution is only designed for this app container).
First the configuration for compulsory client authentication was removed. Now the client is not going to send any certificate at this level.
/subsystem=elytron/server-ssl-context=localhost-context:write-attribute(name=need-client-auth, value=false)
There is a lot of documentation in the wildfly site but, in general, the process to setup WS-Security is quite complicated. The Echo endpoint needs to be annotated with two special tags to establish WS-Security. Besides the web method is again annotated as @PermitAll to permit the execution for anonymous (not logged) users.
@Stateless @WebService(name = "echo", targetNamespace = "http://es.rickyepoderi.sample/ws", serviceName = "echo-service") @Policy(placement = Policy.Placement.BINDING, uri = "WssX509V3Token10.xml") @DeclareRoles("Users") @SOAPBinding(style = SOAPBinding.Style.RPC) @EndpointConfig(configFile = "WEB-INF/jaxws-endpoint-config.xml", configName = "Custom WS-Security Endpoint") public class Echo { @Resource SessionContext ctx; @WebMethod @PermitAll public String echo(String input) { return ctx.getCallerPrincipal() + " -> " + input; } }
The @Policy annotation is used to specify we want a special WSS policy for the binding. The WssX509V3Token10.xml file includes the following:
<?xml version="1.0" encoding="UTF-8" ?> <wsp:Policy wsu:Id="SecurityPolicy" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <wsp:ExactlyOne> <wsp:All> <sp:AsymmetricBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <wsp:Policy> <sp:InitiatorToken> <wsp:Policy> <sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient"> <wsp:Policy> <sp:WssX509V1Token11/> </wsp:Policy> </sp:X509Token> </wsp:Policy> </sp:InitiatorToken> <sp:RecipientToken> <wsp:Policy> <sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Never"> <wsp:Policy> <sp:WssX509V1Token11/> </wsp:Policy> </sp:X509Token> </wsp:Policy> </sp:RecipientToken> <sp:AlgorithmSuite> <wsp:Policy> <sp:Basic256Sha256Rsa15/> </wsp:Policy> </sp:AlgorithmSuite> <sp:Layout> <wsp:Policy> <sp:Lax/> </wsp:Policy> </sp:Layout> <sp:IncludeTimestamp/> <sp:ProtectTokens /> <sp:OnlySignEntireHeadersAndBody/> </wsp:Policy> </sp:AsymmetricBinding> <sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <sp:Body/> </sp:SignedParts> <sp:Wss10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <wsp:Policy> <sp:MustSupportRefIssuerSerial/> </wsp:Policy> </sp:Wss10> </wsp:All> </wsp:ExactlyOne> </wsp:Policy>
The configuration says that the body of the message is going to be signed using SHA256 and an asymmetric RSA key. The certificate should also be added as a binary token (WssX509V1Token11). Only signature is used cos the communication is already encrypted using https at TLS level. Using this extra policy the final WSDL generated by the server adds all this WSS configuration and the client knows that extra security data should be sent. I know that understanding this WSS configuration is very complicated, but I found some good examples at OASIS site.
4. Then the @EndpointConfig is used to configure the Apache CXF/WSS4J implementation. The file jaxws-endpoint-config.xml contains the following:
The server.properties has the real information for the key-store and CRL file (all the files are packaged with the application):
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin org.apache.wss4j.crypto.merlin.x509crl.file=my.crl org.apache.ws.security.crypto.merlin.keystore.type=jks org.apache.ws.security.crypto.merlin.keystore.password=Kiosko_00 org.apache.ws.security.crypto.merlin.keystore.alias=server org.apache.ws.security.crypto.merlin.keystore.file=server.jks
The server.jks contains the private key for the server (the same certificate used in previous entries entries to configure the https port) and the trusted CA for the clients.
keytool -list -keystore ./src/main/resources/server.jks Keystore type: JKS Keystore provider: SUN Your keystore contains 2 entries ca, Dec 18, 2017, trustedCertEntry, Certificate fingerprint (SHA1): A1:A4:8B:7B:85:1B:EB:48:85:56:50:A8:34:74:41:74:2B:4E:B6:69 server, Nov 29, 2017, PrivateKeyEntry, Certificate fingerprint (SHA1): 66:AE:A8:33:BC:4F:23:79:02:E6:E3:47:97:C8:B7:AA:40:75:BA:C5
In general all the WSS4J properties and policy configuration can be assigned using the previous files.
Now we need to prepare the client, more or less the same configuration given to the server is needed for the client. But here the configuration is setup as CXF properties in the BindingProvider.
URL url = new URL("https://localhost:8443/wildfly-x509/echo-service/echo?wsdl"); EchoService service = new EchoService(url); Echo echo = service.getEchoPort(); // Properties for WS-Security configuration ((BindingProvider)echo).getRequestContext().put(SecurityConstants.CALLBACK_HANDLER, new KeystorePasswordCallback()); ((BindingProvider)echo).getRequestContext().put(SecurityConstants.SIGNATURE_PROPERTIES, Thread.currentThread().getContextClassLoader().getResource("client1.properties")); ((BindingProvider)echo).getRequestContext().put(SecurityConstants.SIGNATURE_USERNAME, "client1"); // get the client and assign a specific SSLContext configuration Client client = ClientProxy.getClient(echo); HTTPConduit http = (HTTPConduit) client.getConduit(); TLSClientParameters parameters = new TLSClientParameters(); parameters.setSSLSocketFactory(createSSLContext(args[0]).getSocketFactory()); http.setTlsClientParameters(parameters); System.err.println(echo.echo(args[1]));
If you see the same properties are specified (the properties file, the username and the password callback for the key).
The file client1.properties contains the store used by the client:
org.apache.wss4j.crypto.provider=org.apache.wss4j.common.crypto.Merlin org.apache.wss4j.crypto.merlin.keystore.type=jks org.apache.wss4j.crypto.merlin.keystore.password=Kiosko_00 org.apache.wss4j.crypto.merlin.keystore.alias=client1 org.apache.wss4j.crypto.merlin.keystore.file=client1.jks
The store is just the valid client1 certificate and key, the trusted CA and the server certificate (in this case the server cert needs to be added for the client to trust in the server response):
keytool -list -keystore ./src/main/resources/client1.jks Keystore type: JKS Keystore provider: SUN Your keystore contains 3 entries ca, Dec 18, 2017, trustedCertEntry, Certificate fingerprint (SHA1): A1:A4:8B:7B:85:1B:EB:48:85:56:50:A8:34:74:41:74:2B:4E:B6:69 client1, Dec 18, 2017, PrivateKeyEntry, Certificate fingerprint (SHA1): 2D:2D:11:BF:DB:6E:EB:2D:10:9D:09:BE:96:7C:B9:3D:AF:8B:A7:89 server, Dec 18, 2017, trustedCertEntry, Certificate fingerprint (SHA1): 66:AE:A8:33:BC:4F:23:79:02:E6:E3:47:97:C8:B7:AA:40:75:BA:C5
The same configuration is done for client2. The client is also modified to receive client1 or client2 parameter and use one or the other configuration to sign the message.
At this point everything is ready. If authentication is disabled in web.xml (no JavaEE integration) the @PermitAll annotation lets us execute the method as anonymous. I also added the system property -Dorg.apache.cxf.logging.enabled=true in the server to dump the SOAP messages interchanged. So the video now is more or less the same. First the client2 is used to sign the message and it is not valid because of the revocation. Then the maven project is executed with the client1 configuration. When executed the server receives the client1 certificate and signature, validates it and finally executes the method. The response is also signed and validated by the client. As there is no JavaEE integration, anonymous user is presented again. But the important thing is that all the WSS headers are in place, they are printed in the server.
Let's examine the messages. This is the request sent by the client when using client1:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="1"> <wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="X509-f40f60f5-64b4-4135-b7e0-fd8348d00c3a">...X509...Client1...certtificate...</wsse:BinarySecurityToken> <wsu:Timestamp wsu:Id="TS-02fe948b-e2ac-477f-ada4-abaac47fc4f1"> <wsu:Created>2017-12-23T14:02:05.321Z</wsu:Created> <wsu:Expires>2017-12-23T14:07:05.321Z</wsu:Expires> </wsu:Timestamp> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="SIG-3a786e4f-80bb-4283-a834-40de53b4da54"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap"/> </ds:CanonicalizationMethod> <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> <ds:Reference URI="#TS-02fe948b-e2ac-477f-ada4-abaac47fc4f1"> <ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="wsse soap"/> </ds:Transform> </ds:Transforms> <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> <ds:DigestValue>GUQXfHsUaDwGCEEerkqLWmIqmYdEdn52kK5eRCVd2Hg=</ds:DigestValue> </ds:Reference> <ds:Reference URI="#_e8f02c45-106a-4c8e-b0ad-4252be04b7ef"> <ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> </ds:Transforms> <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> <ds:DigestValue>TeGgjqgupN/UMYOybuACCUeN2P20z120wDhTO0rb9no=</ds:DigestValue> </ds:Reference> <ds:Reference URI="#X509-f40f60f5-64b4-4135-b7e0-fd8348d00c3a"> <ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap"/> </ds:Transform> </ds:Transforms> <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> <ds:DigestValue>dajItom81ecXPgC9DRVRPRmr/ZPtgtri85sK18ckSQc=</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>...SIGNATURE...</ds:SignatureValue> <ds:KeyInfo Id="KI-31f93730-0d96-4309-bed9-ac1440f120af"> <wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="STR-f55c14ba-58c1-4831-b393-bf7609769fc9"> <wsse:Reference URI="#X509-f40f60f5-64b4-4135-b7e0-fd8348d00c3a" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/> </wsse:SecurityTokenReference> </ds:KeyInfo> </ds:Signature> </wsse:Security> </soap:Header> <soap:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="_e8f02c45-106a-4c8e-b0ad-4252be04b7ef"> <ns1:echo xmlns:ns1="http://es.rickyepoderi.sample/ws"> <arg0>something....</arg0> </ns1:echo> </soap:Body> </soap:Envelope>
As you see, the X509 certificate is sent as a BinarySecurityToken and the signature is also present. The key information for the signature references the previous X509 certificate.
The server responds with the following message:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="1"> <wsu:Timestamp wsu:Id="TS-3c6018cd-cef4-4832-81f3-ec6fe8f482f8"> <wsu:Created>2017-12-23T14:02:06.182Z</wsu:Created> <wsu:Expires>2017-12-23T14:07:06.182Z</wsu:Expires> </wsu:Timestamp> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="SIG-469137e4-901f-4037-9206-6263ac026ad8"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap"/> </ds:CanonicalizationMethod> <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> <ds:Reference URI="#TS-3c6018cd-cef4-4832-81f3-ec6fe8f482f8"> <ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="wsse soap"/> </ds:Transform> </ds:Transforms> <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> <ds:DigestValue>jo6cDr9a+XZiQ8miJ6E09n0Qovt40lxYhemHhL1juqk=</ds:DigestValue> </ds:Reference> <ds:Reference URI="#_1b05d41b-5b7e-4d75-97c2-0d3c300c0e22"> <ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> </ds:Transforms> <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> <ds:DigestValue>Z14Q80/j3gR31KiNMINj9ylZ0ywf/INOm62iO4k4Axc=</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>...SIGNATURE...</ds:SignatureValue> <ds:KeyInfo Id="KI-d23763d6-3dd6-4338-b639-de8fbab2d824"> <wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="STR-36fb975b-769d-477d-96f8-43c8ce0d84fb"> <ds:X509Data> <ds:X509IssuerSerial> <ds:X509IssuerName>CN=ca.demo.kvm,O=demo.kvm,C=ES</ds:X509IssuerName> <ds:X509SerialNumber>6</ds:X509SerialNumber> </ds:X509IssuerSerial> </ds:X509Data> </wsse:SecurityTokenReference> </ds:KeyInfo> </ds:Signature> </wsse:Security> </soap:Header> <soap:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="_1b05d41b-5b7e-4d75-97c2-0d3c300c0e22"> <ns1:echoResponse xmlns:ns1="http://es.rickyepoderi.sample/ws"> <return>anonymous -> something....</return> </ns1:echoResponse> </soap:Body> </soap:Envelope>
Another signature is included but, in the response, the server does not add the certificate just the info to retrieve the certificate from the client key-store. That is why the server certificate (public part) needs to be included in the client key-store.
<?xml version="1.0" encoding="UTF-8"?>
<jaxws-config xmlns="urn:jboss:jbossws-jaxws-config:4.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="urn:jboss:jbossws-jaxws-config:4.0 schema/jbossws-jaxws-config_4_0.xsd">
<endpoint-config>
<config-name>Custom WS-Security Endpoint</config-name>
<property>
<property-name>ws-security.signature.properties</property-name>
<property-value>server.properties</property-value>
</property>
<property>
<property-name>ws-security.signature.username</property-name>
<property-value>server</property-value>
</property>
<property>
<property-name>ws-security.callback-handler</property-name>
<property-value>es.rickyepoderi.sample.KeystorePasswordCallback</property-value>
</property>
<property>
<property-name>ws-security.enableRevocation</property-name>
<property-value>true</property-value>
</property>
<property>
<property-name>ws-security.subject.cert.constraints</property-name>
<property-value>.*O=demo.kvm.*</property-value>
</property>
</endpoint-config>
</jaxws-config>
The properties file is used to configure the key-store used by the server to verify request signatures and sign responses. The callback is used to obtain the password for the key (my callback always return the same password, it's the same for all the stores). The revocation was enabled to discover that client2 certificate is not valid. And a subject expression is added (the subject of the certificate should match this expression) just to avoid an annoying warning message.
And that is all for now. In the current entry the WS-Security is configured to be used in our previous web-services application. Now the client, instead of using the certificate at TLS level, uses WSS headers to include a signature of the body message in a header. The configuration is a bit complicated but the main idea is that now the SOAP message is signed by the client, and that signature is validated by the server with the Apache WSS4J/CXF implementation used by wildfly. Then the response is also signed by the server and validated by the client. The missing piece is the JavaEE integration. I was going to include everything in this same entry, but the integration is giving a lot of problems to me, so I decided to explain it better in another entry and do not complicate this one even more. The project application used today can be downloaded from here.
Regards!
Comments