Today I will talk about transport level security such as basic and mutual authentication, and SSL.
Theory
Basic authentication is the most basic authentication mechanism provided by HTTP protocol. Simply in HTTP headers there is one additional header containing user's credentials.
But it is very easy to decode username and password. Thus basic authentication in most often used with SSL to provide confidentiality.
SSL/TLS is used in HTTPS.
Most commonly when server is accessed over HTTPS server authenticates itself with a certificate signed by a trusted authority.
In some cases where security is crucial aspect of the whole system architects may decide to enable mutual authentication. In this scenario not only server, but also client must present a valid certificate.
Practice - the Web Service
I implemented simple web-tier Web Service:
@WebService
public class HiHeyHelloWebService {
@Resource
private WebServiceContext context;
public String sayHiHeyHello(String name) {
Principal principal = context.getUserPrincipal();
return "Hi, hey, hello " + name + "! You have invoked me as '" + principal.getName() + "' and I think you are in admin role: " + context.isUserInRole("admin");
}
}and deployed it on Apache Geronimo 2.1.3 (Tomcat based).Web Service unit test
Using
wsimport I created Web Service client. Then I wrote the stub of my IT case:public class HiHeyHelloWebServiceITCase {
@Test
public void testSayHiHeyHello() {
HiHeyHelloWebServiceService service = new HiHeyHelloWebServiceService();
HiHeyHelloWebService port = service.getHiHeyHelloWebServicePort();
String response = port.sayHiHeyHello("Lukasz");
Assert.assertEquals("Hi, hey, hello Lukasz! You have invoked me as 'system' and I think you are in admin role: true", response);
}
}Basic authenticationIn
web.xml I added the following <security-constraint />:<security-constraint> <web-resource-collection> <web-resource-name>Protected</web-resource-name> <url-pattern>/HiHeyHelloWebServiceService</url-pattern> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> </login-config> <security-role> <role-name>admin</role-name> </security-role>Note that your application server specific web descriptor has to be updated as well.
In my case, in
geronimo-web.xml I used geronimo-admin server-wide security realm:<security-realm-name>geronimo-admin</security-realm-name> <security> <default-principal> <principal name="anonymous" class="org.apache.geronimo.security.realm.providers.GeronimoUserPrincipal" /> </default-principal> <role-mappings> <role role-name="admin"> <principal name="admin" designated-run-as="true" class="org.apache.geronimo.security.realm.providers.GeronimoGroupPrincipal" /> </role> </role-mappings> </security>I packaged and redeployed the web app. Then I ran the test. It failed:
com.sun.xml.internal.ws.client.ClientTransportException: request requires HTTP authentication: Unauthorized at com.sun.xml.internal.ws.transport.http.client.HttpClientTransport.checkResponseCode(HttpClientTransport.java:197) at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.process(HttpTransportPipe.java:137) at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.processRequest(HttpTransportPipe.java:74)As expected.
Basic authentication was enabled and I had to supply valid username and passowrd to access the service.
I added the following two lines to my test (before the invocation of Web Service):
((BindingProvider) port).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "system"); ((BindingProvider) port).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "manager");And re-ran the test. It passed then.
Transport guarantee confidential
In
web.xml as a child of <security-constraint /> element I added the following element:<user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint>I packaged and redeployed the web app.
When I re-ran the test I got the following exception:
javax.xml.ws.WebServiceException: No Content-type in the header! at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.process(HttpTransportPipe.java:143) at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.processRequest(HttpTransportPipe.java:74)In tcpmon I saw that Geronimo returned 302 Moved Temporarly with a new https address of my Web Service. JAX-WS cannot handle HTTP redirects. That's not a problem, in Java code I added the following line:
((BindingProvider) port).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "https://localhost/secure-web-service/HiHeyHelloWebServiceService");I ran the test again. It failed because Geronimo's default certificate was not signed by trusted CA:
com.sun.xml.internal.ws.client.ClientTransportException: HTTP transport error: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at com.sun.xml.internal.ws.transport.http.client.HttpClientTransport.getOutput(HttpClientTransport.java:119) at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.process(HttpTransportPipe.java:128) at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.processRequest(HttpTransportPipe.java:74)I had to generate my own trust CA and import Geronimo's certificate into it. Here's how I did it:
- I exported server's certificate:
keytool -exportcert -alias geronimo -file server.cer -keystore %GERONIMO_HOME%\var\security\keystores\geronimo-default
- then I imported it into my own trust store:
keytool -importcert -trustcacerts -alias geronimo -keystore ws_client_cacerts.jks -file server.cer
As a password I setsecretca.
- I added
javax.net.ssl.trustStoreproperties to my IT case:
System.setProperty("javax.net.ssl.trustStore", new File("src/test/resources/ws_client_cacerts.jks").getAbsolutePath()); System.setProperty("javax.net.ssl.trustStorePassword", "secretca");
Mutual authentication
In
web.xml I changed authentication method to:<auth-method>CLIENT-CERT</auth-method>I packaged and redeployed the web application.
When I run the IT case, on the client side the exception was:
javax.xml.ws.WebServiceException: java.net.SocketException: Software caused connection abort: recv failed at com.sun.xml.internal.ws.transport.http.client.HttpClientTransport.checkResponseCode(HttpClientTransport.java:223) at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.process(HttpTransportPipe.java:137) at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.processRequest(HttpTransportPipe.java:74)On the server side the exception was:
10:12:18,146 WARN [Http11Processor] Exception getting SSL attributes javax.net.ssl.SSLHandshakeException: null cert chain at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1591) at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:187) at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:177) at com.sun.net.ssl.internal.ssl.ServerHandshaker.clientCertificate(ServerHandshaker.java:1206)As expected. My client had to send its certificate to Geronimo.
By default Apache Geronimo does not have trust store CA. I know that Glassfish has one by default. But that's not a problem.
- I signed in into Geronimo's console, navigated to
http://localhost/console/portal/Security/Certificate%20Authorityand clickedSetup Certification Authority.
It's a very simple 2 step "wizard", I entered some dummy data, on the confirmation screen I saw:CA Setup is successful!
- I navigated to
http://localhost/console/portal/Server/Web%20ServerselectedTomcatWebSSLConnectorand clicked edit. I found and set the following properties:- var/security/keystores/ca-keystore
truststoreFile- secret (set previously in Setup Certification Authority!)
truststorePassword
I clickedSavebutton.
- Using command line I generated my keystore
keytool -genkey -alias ws_client -keystore ws_client.jks
As a password I set:secretks.
Important here! Thews_clientalias and keystore password must be the same! Otherwise you'll get the following error when connecting using SSL:UnrecoverableKeyException: Cannot recover key.
It took me more than an hour to figure out what was happening.
- I exported client certificate
keytool -exportcert -alias ws_client -keystore ws_client.jks -file ws_client.cer
- I imported client certificate into server's ca-keystore:
keytool -importcert -alias ws_client -file ws_client.cer -keystore %GERONIMO_HOME%\var\security\keystores\ca-keystore
- and verified if was added
keytool -list -keystore %GERONIMO_HOME%\var\security\keystores\ca-keystore
- I restarted Geronimo.
- In my IT case I added my key store:
System.setProperty("javax.net.ssl.keyStore", new File("src/test/resources/ws_client.jks").getAbsolutePath()); System.setProperty("javax.net.ssl.keyStorePassword", "secretks");
I was almost successful... Almost becuase I the mutual authentication took place, SSL connection was established, but I got HTTP/1.1 401 Unauthorized response and the following exceptions:
10:57:40,926 WARN [TomcatGeronimoRealm] Login exception authenticating username "CN=Lukasz Budnik,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=PL" javax.security.auth.login.LoginExceptionroot cause:
Caused by: javax.security.auth.callback.UnsupportedCallbackException: Wrong call back type: class javax.security.auth.callback.NameCallback at org.apache.geronimo.security.realm.providers.CertificateChainCallback Handler.handle(CertificateChainCallbackHandler.java:67)I asked people on
users@geronimo.apache.org, but no one knew the answer. I also checked the Geronimo SVN repository and couldn't find unit test for mutual authentication.So I guess in Geronimo you cannot have auth-constraint when using mutual authentication... :(
Summary
I'm quite disappointed that Geronimo doesn't support CLIENT-CERT authentication method...
Very similar to web tier, the EJB tier security works. Plus when using EJB tier security you can benefit from fine-grained class/method level security.
Next post - message level security: XML Signature, XML Encryption.
Stay tuned,
Łukasz

5 comments:
I was asked to create, with Netbeans (glassfish), Web service asynchrono (in Java) that communicates with a BPEL process content. Can someone help me? give me some examples?
thanks
sorry for my English, I am Italian
I tried Mutual Certificate Security - out of the box - using Metro 2.0 and Tomcat. I wish they put the example together with maven. Check out the Metro 2.0 samples/ws-security/src/mcs (Mutual Certificate Security). Make some careful adjustments to the properties files and watch any initial errors in the wsdl files.
At least it works!!!
As for Tony, get Netbeans 6.5 with the SOA examples. They will probably get you started...
from the Geronimo documentation :
"In Geronimo 1.0, the first time a login module is called an UnsupportedCallbackException is thrown. This pass is just to establish which callbacks the login module requires, so the server can create a consolidated list of all the callbacks required by all login modules for the realm. Don't despair! The next time the login module is called it will be for real."
Really useful blog but I want to add my experience that is I got issued a certificate for my website say www.example.com it was working fine with this domain but if the site opens with url http://example.com i.e. without www then it gave security warnings.I was amazed that why Its behavior is like this
i THINK the logic of "www.example.com" vs "example.com" goes something like this:
the server DNS number should be the same for both addresses. i.e., for localhost the DNS is "127.0.0.1". that translates to "localhost". for some reason, "www.example.com" and "example.com" do NOT translate to the same DNS server address. i hope that helps.
also note the example's "ENDPOINT_ADDRESS_PROPERTY" property. if set to "https://www.example.com/..." that is not the same as "https://example.com/..."
Post a Comment