Tuesday, June 30, 2015

An STS JAAS LoginModule for Apache CXF

Last year I blogged about how to use JAAS with Apache CXF, and the different LoginModules that were available. Recently, I wrote another article about using a JDBC LoginModule with CXF. This article will cover a relatively new JAAS LoginModule  added to CXF for the 3.0.3 release. It allows a service to dispatch a Username and Password to a STS (Security Token Service) instance for authentication via the WS-Trust protocol, and also to retrieve the user's roles by extracting them from a SAML token returned by the STS.

1) The STS JAAS LoginModule

The new STS JAAS LoginModule is available in the CXF WS-Security runtime module. It takes a Username and Password from the Callbackhandler passed to the LoginModule, and uses them to create a WS-Security UsernameToken structure. What happens then depends on a configuration setting in the LoginModule.

If the "require.roles" property is set, then the UsernameToken is added to a WS-Trust "Issue" request to the STS, and a "TokenType" attribute is sent in the request (defaults to the standard "SAML2" URI, but can be configured). The client also adds a WS-Trust "Claim" to the request that tells the STS to add the role of the authenticated end user to the request. How the token is added to the WS-Trust request depends on whether the "disable.on.behalf.of" property is set or not. By default, the token is added as an "OnBehalfOf" token in the WS-Trust request. However, if "disable.on.behalf.of" is set to "true", then the credentials are used according to the WS-SecurityPolicy of the STS endpoint. For example, if the policy requires a UsernameToken, then the credentials are added to the security header of the WS-Trust request. If the "require.roles" property is not set, the the UsernameToken is added to a WS-Trust "Validate" request.

The STS validates the received UsernameToken credentials supplied by the end user, and then either creates a token (if the Issue binding was used), or just returns a simple response telling the client whether the validation was successful or not. In the former use-case, the token that is returned is cached meaning that the end user does not have to re-authenticate until the token expires from the cache.

The LoginModule has the following configuration properties:
  • require.roles - If this is defined, then the WS-Trust Issue binding is used, passing the value specified for the "token.type" property as the TokenType, and the "key.type" property for the KeyType. It also adds a Claim to the request for the default "role" URI.
  • disable.on.behalf.of - Whether to disable passing Username + Password credentials via "OnBehalfOf".
  • disable.caching - Whether to disable caching of validated credentials. Default is "false". Only applies when "require.roles" is defined.
  • wsdl.location - The location of the WSDL of the STS
  • service.name - The service QName of the STS
  • endpoint.name - The endpoint QName of the STS
  • key.size - The key size to use (if requesting a SymmetricKey KeyType). Defaults to 256.
  • key.type - The KeyType to use. Defaults to the standard "Bearer" URI.
  • token.type - The TokenType to use. Defaults to the standard "SAML2" URI.
  • ws.trust.namespace - The WS-Trust namespace to use. Defaults to the standard WS-Trust 1.3 namespace.
In addition, any of the standard CXF security configuration tags that start with "ws-security." can be used as documented here. Sometimes it is necessary to set some security configuration depending on the security policy of the WSDL.

Here is an example of the new JAAS LoginModule configuration:



2) A testcase for the new LoginModule

Using an STS via WS-Trust for authentication and authorization can be quite difficult to set up and understand, but the new LoginModule makes it easy. I created a testcase + uploaded it to github:
  • cxf-jaxrs-jaas-sts: This project demonstrates how to use the new STS JAAS LoginModule in CXF to authenticate and authorize a user. It contains a "double-it" module which contains a "double-it" JAX-RS service. It is secured with JAAS at the container level, and requires a role of "boss" to access the service. The "sts" module contains a Apache CXF STS web application which can authenticate users and issue SAML tokens with embedded roles.
To run the test, download Apache Tomcat and do "mvn clean install" in the testcase above. Then copy both wars and the jaas configuration file to the Apache Tomcat install (${catalina.home}):
  • cp double-it/target/cxf-double-it.war ${catalina.home}/webapps
  • cp sts/target/cxf-sts.war ${catalina.home}/webapps
  • cp double-it/src/main/resources/jaas.conf ${catalina.home}/conf
Next set the following system property:
  • export JAVA_OPTS=-Djava.security.auth.login.config=${catalina.home}/conf/jaas.conf
Finally, start Tomcat, open a web browser and navigate to:

http://localhost:8080/cxf-double-it/doubleit/services/100

Use credentials "alice/security" when prompted. The STS JAAS LoginModule takes the username and password, and dispatches them to the STS for validation.

Monday, June 29, 2015

A new Crypto implementation in Apache WSS4J

Apache WSS4J uses the Crypto interface to get keys and certificates for asymmetric encryption/decryption and signature creation/verification. In addition, it also takes care of verifying trust in an X.509 certificate used to sign some portion of the message. WSS4J currently ships with three Crypto implementations:
  • Merlin: The standard implementation, based around two JDK keystores for key/cert retrieval, and trust verification.
  • CertificateStore: Holds an array of X509 Certificates. Can only be used for encryption and signature verification.
  • MerlinDevice: Based on Merlin, allows loading of keystores using a null InputStream - for example on a smart-card device.
The next release(s) of WSS4J, 2.0.5 and 2.1.2, will contain a fourth implementation:
  • MerlinAKI: A new Merlin-based Crypto implementation that searches the truststore for the issuing certificate using the AuthorityKeyIdentifier extension bytes of the signing certificate, as opposed to the issuer DN.
Trust verification for the standard/default Merlin implementation works as follows:
  1. Is the signing cert contained in the keystore/truststore? If yes, then trust verification succeeds. This can be combined with using regular expressions on the Subject DN as well.
  2. If not, then get the issuing cert by reading the Issuer DN from the signing cert. Then search for this cert in the keystore/truststore. 
  3. If the issuer cert is found, then form a cert path containing the signing cert, the issuing cert and any subsequent issuing cert of that cert. Then validate the cert path.
However, the retrieval of the issuing cert in step 2 above falls down under certain rare scenarios, where there may not be a 1-to-1 link between the Subject DN of a certificate and a public key. This is where the new MerlinAKI implementation comes in. Instead of searching for the issuing cert using the issuer DN of the signing cert, it instead uses BouncyCastle to retrieve the AuthorityKeyIdentifier extension bytes (if present) from the cert. It then searches for the issuing cert by seeing which of the certs in the truststore contain a SubjectKeyIdentifier extension with a matching identifier value. You can switch to use MerlinAKI simply by changing the name of the Crypto provider in the Crypto properties file:


Friday, June 26, 2015

Using AWS KMS with Apache CXF to secure passwords

The previous tutorial showed how the AWS Key Management Service (KMS) can be used to generate symmetric encryption keys that can be used with WS-Security to encrypt and decrypt a service request using Apache CXF. It is also possible to use the KMS to secure keystore passwords for asymmetric encryption and signature, that are typically stored in properties files when using WS-Security with Apache CXF.

1) Encrypting passwords in a Crypto properties file

Apache CXF uses the WSS4J Crypto interface to get keys and certificates for asymmetric encryption/decryption and for signature creation/verification. Merlin is the standard implementation, based around two JDK keystores for key/cert retrieval, and trust verification. Typically, a Crypto implementation is loaded and configured via a Crypto properties file. For example:


However one issue with this style of configuration is that the keystore password is stored in plaintext in the file. Apache WSS4J 2.0.0 introduced the ability to store encrypted passwords in the crypto properties file instead. A PasswordEncryptor interface was defined to allow for the encryption/decryption of passwords, and a default implementation based on Jasypt was made available in the release. In this case, the master password used to decrypt the encrypted keystore password was retrieved from a CallbackHandler implementation.

2) Using KMS to encrypt keystore passwords

Instead of using the Jasypt PasswordEncryptor implementation provided by default in Apache WSS4J, it is possible to use instead the AWS KMS to decrypt encrypted keystore passwords stored in crypto properties files. I've updated the test-case introduced in the previous tutorial with an asymmetric encryption test-case, where a SOAP service invocation is encrypted using a WS-SecurityPolicy AsymmetricBinding policy.

The first step in running the test-case is to follow the previous tutorial in terms of registering for AWS, creating a user "alice" and a corresponding customer master key. One you have this, then run the "testEncryptedPasswords" test available here, which outputs the encrypted passwords for the client and service keystores ("cspass" and "sspass"). Copy the output + paste them into the clientEncKeystore.properties and serviceEncKeystore.properties in the "ENC()" tags. For example:


The client and service configure a custom PasswordEncryptor implementation designed to decrypt the encrypted keystore password using KMS. The KMSPasswordEncryptor is spring-loaded in the client and service configuration, and must be updated with the access key id, secret key, master key id, etc. as defined earlier. Of course this means that the secret key is in plaintext in a spring configuration file in this example. However, it could be obtained via a system property or some other means, and is more secure than storing a plaintext keystore password in a properties file. Once the KMSPasswordEncryptor is properly configured, then the AsymmetricTest can be run, and you will see the secured service request and response in the console window.


Thursday, June 25, 2015

Integrating AWS Key Management Service with Apache CXF

Apache CXF supports a wide range of standards designed to help you secure a web service request, from WS-Security for SOAP requests, to XML Security and JWS/JWE for XML/JSON REST requests. All of these standards provide for using symmetric keys to encrypt requests, and then using a master key (typically a public key associated with an X.509 certificate) to encrypt the symmetric key, embedding this information somewhere in the request. The usual use-case is to generate random bytes for the symmetric key. But what if you wanted instead to manage the secret keys in some way? Or if your client did not have access to sufficient entropy to generate truly random bytes? In this article, we will look at how to use the AWS Key Management Service to perform this task for us, in the context of an encrypted SOAP request using WS-Security.

1) AWS Key Management Service

The AWS Key Management Service allows us to create master keys and data keys for users defined in the AWS Identity and Access Management service. Once we have created a user, and a corresponding master key for the user (which is only stored in AWS and cannot be exported), we can ask the Key Management Service to issue us a data key (using either AES 128 or 256), and an encrypted data key. The idea is that the data key is used to encrypt some data and is then disposed of. The encrypted data key is added to the request, where the recipient can ask the Key Management Service to decrypt the key, which can be then be used to decrypt the encrypted data in the request.

The first step is to register for Amazon AWS here. Once we have registered, we need to create a user in the Identity and Access Management service. Create a new user "alice", and make a note of the access key and secret access key associated with "alice". Next we need to write some code to obtain keys for "alice" (documentation). First we must create a client:

AWSCredentials creds = new BasicAWSCredentials(<access key id>, <secret key>);
AWSKMSClient kms = new AWSKMSClient(creds);
kms.setEndpoint("https://kms.eu-west-1.amazonaws.com");

Next we must create a customer master key for "alice":

String desc = "Secret encryption key";
CreateKeyRequest req = new CreateKeyRequest().withDescription(desc);
CreateKeyResult result = kms.createKey(req);

The CreateKeyResult object returned as part of the key creation process will contain a key Id, which we will need later.

2) Using AWS Key Management Service keys with WS-Security

As mentioned above, the typical process for WS-Security when encrypting a request, is to generate some random bytes to use as the symmetric encryption key, and then use a key wrap algorithm with another key (typically a public key) to encrypt the symmetric key. Instead, we will use the AWS Key Management Service to retrieve the symmetric key to encrypt the request. We will store the encrypted form of the symmetric key in the WS-Security EncryptedKey structure, which will reference the Customer Master Key via a "KeyName" pointing to the Key Id.

I have created a project that can be used to demonstrate this integration:
  • cxf-amazon-kms: This project contains a number of tests that show how to use the AWS Key Management Service with Apache CXF.
The first task in running the test (assuming the steps followed in point 1 above were followed) is to edit the client configuration, entering the correct values in the CommonCallbackHandler for the access key id, secret key, endpoint, and master key id as gathered above, ditto for the service configuration. The CommonCallbackHandler uses the AWS Key Management Service API to create the symmetric key on the sending side, and to decrypt it on the receiving side. Then to run the test simply remove the "org.junit.Ignore" annotation, and the encrypted web service request can be seen in the console:


Tuesday, June 23, 2015

Using a JDBC JAAS LoginModule with Apache CXF

Last year I wrote a blog entry giving an overview of the different ways that you can use JAAS with Apache CXF for authenticating and authorizing web service calls. I also covered some different login modules and linked to samples for authenticating a Username + Password to LDAP, as well as Kerberos Tokens to a KDC. This article covers how to use JAAS with Apache CXF to authenticate a Username + Password to a database via JDBC.

The test-case is available here:
  • cxf-jdbc: This project contains a number of tests that show how an Apache CXF service endpoint can authenticate and authorize a client using JDBC.
It contains two tests, one dealing with authentication (no roles required by the service) and the other with authorization (a specific role is required). Both tests involve a JAX-WS service invocation, where the service requires a WS-Security UsernameToken over TLS. In each case, the service configures Apache WSS4J's JAASUsernameTokenValidator using the context name "jetty". The JAAS configuration file contains an entry for the "jetty" context, which references the Jetty JDBCLoginModule:

The configuration of the JDBCLoginModule is easy to follow. The "dbUrl" refers to the JDBC connection URL (in this case an in-memory Apache Derby instance). The table containing user data is "app.users", where the fields used for usernames and passwords are "name" and "password" respectively. Similarly, the table containing role data is "app.roles", where the fields used for usernames + roles are "name" and "role" respectively.

The tests use Apache Derby as an in-memory database. It is created in code as follows:


Then the following SQL file is read in, and each statement is executed using the statement Object above:


Thursday, June 11, 2015

Apache CXF Fediz 1.2.0 tutorial - part II

This is the second in a series of blog posts on the new features and changes in Apache CXF Fediz 1.2.0. The previous blog entry gave instructions about how to deploy the Fediz IdP and a sample service application in Apache Tomcat. This article describes how different client authentication methods are supported in the IdP, and how they can be selected by the service via the "wauth" parameter. Then we will extend the previous tutorial by showing how to authenticate to the IdP using a client certificate in the browser, as opposed to entering a username + password.

1) Supporting different client authentication methods in the IdP

The Apache Fediz IdP in 1.2.0 supports different client authentication methods by default using different URL paths, as follows:
  • /federation -> the main entry point
  • /federation/up -> authentication using HTTP B/A
  • /federation/krb -> authentication using Kerberos
  • /federation/clientcert -> authentication using a client cert
The way it works is as follows. The service provider (SP) should use the URL for the main entry point (although the SP has the option of choosing one the more specific URLs as well). The IdP extracts the "wauth" parameter from the request ("default" is the default value), and looks for a matching key in the "authenticationURIs" section of the service configuration. For example:

<property name="authenticationURIs">
    <util:map>
        <entry key="default" value="federation/up" />
        <entry key="http://docs.oasis-open.org/wsfed/authorization/200706/authntypes/SslAndKey" value="federation/krb" />
        <entry key="http://docs.oasis-open.org/wsfed/authorization/200706/authntypes/default" value="federation/up" />
        <entry key="http://docs.oasis-open.org/wsfed/authorization/200706/authntypes/Ssl" value="federation/clientcert" />
    </util:map>
</property>

If a matching key is found for the wauth value, then the browser gets redirected to the associated URL. Therefore, a service provider can specify a value for "wauth" in the plugin configuration, and select the client authentication mode as a result. The values defined for "wauth" above are taken from the specification, but can be changed if required. The service provider can specify the value for "wauth" by using the "authenticationType" configuration tag, as documented here.

2) Client authentication using a certificate

A new feature of Fediz 1.2.0 is the ability for a client to authenticate to the IdP using a certificate embedded in the browser. To see how this works in practice, please follow the steps given in the previous tutorial to set up the IdP and service web application in Apache Tomcat. To switch to use client certificate authentication, only one change is required in the service provider configuration:
  • Edit ${catalina.home}/conf/fediz_config.xml, and add the following under the "protocol" section: <authenticationType>http://docs.oasis-open.org/wsfed/authorization/200706/authntypes/Ssl</authenticationType>
The next step is to add a client certificate to the browser that you are using. To avoid changing the IdP TLS configuration, we will just use the same certificate / private key that is used by the IdP on the client side for the purposes of this demo. First, we need to convert the IdP key from JKS to PKCS12. So take the idp-ssl-key.jks configured in the previous tutorial and run:
  • keytool -importkeystore -srckeystore idp-ssl-key.jks -destkeystore idp-ssl-key.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass tompass -deststorepass tompass -srcalias mytomidpkey -destalias mytomidpkey -srckeypass tompass -destkeypass tompass -noprompt
I will use Chrome for the client browser. Under Settings, Advanced Settings, "HTTPS/SSL", click on the Manage Certificates button, and add the idp-ssl-key.p12 keystore above using the password "tompass":
Next, we need to tell the STS to trust the key used by the client (you can skip these steps if using Fediz 1.2.1):
  • First, export the certificate as follows: keytool -keystore idp-ssl-key.jks -storepass tompass -export -alias mytomidpkey -file MyTCIDP.cer
  • Take the ststrust.jks + import the cert: keytool -import -trustcacerts -keystore ststrust.jks -storepass storepass -alias idpcert -file MyTCIDP.cer -noprompt
  • Finally, copy the modified ststrust.jks into the STS: ${catalina.home}/webapps/fediz-idp-sts/WEB-INF/classes
The last configuration step is to tell the STS where to retrieve claims for the cert. We will just copy the claims for Alice:
  • Edit ${catalina.home}/webapps/fediz-idp-sts/WEB-INF/userClaims.xml
  • Add the following under "userClaimsREALMA": <entry key="CN=localhost" value-ref="REALMA_aliceClaims" />
Now restart Tomcat and navigate to the service URL:
  • https://localhost:8443/fedizhelloworld/secure/fedservlet
Select the certificate that we have uploaded, and you should be able to authenticate to the IdP and be redirected back to the service, without having to enter any username/password credentials!

Wednesday, June 10, 2015

Apache CXF Fediz 1.2.0 tutorial - part I

The previous blog entry gave an overview of the new features in Apache CXF Fediz 1.2.0. This post first focuses on setting up and running the IdP (Identity Provider) and the sample simpleWebapp in Apache Tomcat.

1) Deploying the 1.2.0 Fediz IdP in Apache Tomcat

Download Fediz 1.2.0 and extract it to a new directory (${fediz.home}). We will use a Apache Tomcat 7 container to host the Idp. To deploy the IdP to Tomcat:
  • Create a new directory: ${catalina.home}/lib/fediz
  • Edit ${catalina.home}/conf/catalina.properties and append ',${catalina.home}/lib/fediz/*.jar' to the 'common.loader' property.
  • Copy ${fediz.home}/plugins/tomcat/lib/* to ${catalina.home}/lib/fediz
  • Copy ${fediz.home}/idp/war/* to ${catalina.home}/webapps
  • Download and copy the hsqldb jar (e.g. hsqldb-1.8.0.10.jar) to ${catalina.home}/lib
Now we need to set up TLS:
  • The keys that ship with Fediz 1.2.0 are 1024 bit DSA keys, which will not work with most modern browsers (this will be fixed for 1.2.1). 
  • So with 1.2.0 we need to download the keys from git, rather than use the keys in the distribution
  • Download idp-ssl-key.jks and idp-ssl-trust.jks from here.
  • Copy idp-ssl-key.jks and idp-ssl-trust.jks to ${catalina.home}.
  • Copy both jks files as well to ${catalina.home}/webapps/fediz-idp/WEB-INF/classes/ (after Tomcat is started)
  • Edit the TLS Connector in ${catalina.home}/conf/server.xml', e.g.: <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol" maxThreads="150" SSLEnabled="true" scheme="https" secure="true" clientAuth="want" sslProtocol="TLS" keystoreFile="idp-ssl-key.jks" keystorePass="tompass" keyPass="tompass" truststoreFile="idp-ssl-trust.jks" truststorePass="ispass" />
Now start Tomcat, and check that the IdP is live by opening the STS WSDL in a web browser: 'https://localhost:8443/fediz-idp-sts/REALMA/STSServiceTransport?wsdl'

For a more thorough test, enter the following in a web browser - you should be directed to the URL for the service application (404, as we have not yet configured it):

https://localhost:8443/fediz-idp/federation?wa=wsignin1.0&wreply=https%3A%2F%2Flocalhost%3A8443%2Ffedizhelloworld%2Fsecure%2Ffedservlet&wtrealm=urn%3Aorg%3Aapache%3Acxf%3Afediz%3Afedizhelloworld

2) Deploying the simpleWebapp in Apache Tomcat

To deploy the service to Tomcat:
  • Copy ${fediz.home}/examples/samplekeys/rp-ssl-server.jks (rp-ssl-key.jks from Fediz 1.2.1) and ${fediz.home}/examples/samplekeys/ststrust.jks to ${catalina.home}.
  • Copy ${fediz.home}/examples/simpleWebapp/src/main/config/fediz_config.xml to ${catalina.home}/conf/
  • Edit ${catalina.home}/conf/fediz_config.xml and replace '9443' with '8443'.
  • Do a "mvn clean install" in ${fediz.home}/examples/simpleWebapp
  • Copy ${fediz.home}/examples/simpleWebapp/target/fedizhelloworld.war to ${catalina.home}/webapps.
3) Testing the service

To test the service navigate to:
  • https://localhost:8443/fedizhelloworld/  (this is not secured) 
  • https://localhost:8443/fedizhelloworld/secure/fedservlet
With the latter URL, the browser is redirected to the IDP (select realm "A") and is prompted for a username and password. Enter "alice/ecila" or "bob/bob" or "ted/det" to test the various roles that are associated with these username/password pairs.