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!
Can you share your sample project?
I'd like to create a JAX-WS with policy but I get "No assertion builder for type ... registered" for all security policies.
I hope I could find what is worng with my project setup if I get a working sample.
Comments