For certificate-based authentication, the client presents its X.509 certificate chain to the server. The server then verifies this certificate chain using its truststore. The truststore only needs to contain certificates for root certificate authorities or intermediate certificate authorities, it doesn’t need to contain the individual client certificates. Once a server has successfully verified a client’s certificate chain, it checks if the corresponding identity is authorized to access a particular resource. This authorization check is done using a security realm that contains the roles that are associated with identities. This security realm also does not need to contain individual client certificates. Instead, it can contain principals that can be derived from a portion of the client’s certificate. As mentioned in a previous blog post, we are enhancing the way an X.509 certificate can be mapped to an underlying identity using Elytron.
In this blog post, we’ll take a look at how to secure a web application deployed to WildFly using the CLIENT_CERT HTTP authentication mechanism with two-way SSL and authorization. The server’s truststore will only contain an example CA certificate, it won’t contain any client certificates. Similarly, the security realm used for authorization in this example won’t contain any client certificates. It will contain principals that are derived from a portion of the CN value from the subject name from a client’s X.509 certificate.
Certificate generation
First, clone the elytron-examples repo locally:
git clone https://github.com/wildfly-security-incubator/elytron-examples cd elytron-examples
Next, let's generate some client and server certificates that will be used in this example to set up two-way SSL:
cd dynamic-certificates mvn clean install exec:java -Dexec.mainClass="org.wildfly.security.examples.CertificateGenerationExample" -Dexec.args="CN=Bob.Smith.123456 CN=Alice.Smith.456789"
Notice that the above command generates the following keystores and truststores in the dynamic-certificates/target directory:
client1.keystore
client2.keystore
server.keystore
server.truststore
- client1.keystore contains a certificate with distinguished name: CN=Bob.Smith.123456
- client2.keystore contains a certificate with distinguished name: CN=Alice.Smith.456789
- Both client certificates are issued by an example certificate authority with distinguished name: CN=Elytron CA, ST=Elytron, C=UK, EMAILADDRESS=elytron@wildfly.org, O=Root Certificate Authority
- server.truststore contains only this certificate authority's certificate
Next, convert the client keystores into PKCS12 format and import them into your browser so you can pick which one to present to the server later on:
keytool -importkeystore -srckeystore client1.keystore -srcstoretype jks -destkeystore client1.keystore.pkcs12 -deststoretype pkcs12 -srcstorepass keystorepass -deststorepass keystorepass
keytool -importkeystore -srckeystore client2.keystore -srcstoretype jks -destkeystore client2.keystore.pkcs12 -deststoretype pkcs12 -srcstorepass keystorepass -deststorepass keystorepass
Finally, copy the server.keystore and server.truststore files to your WildFly server instance:
cp /PATH/TO/ELYTRON/EXAMPLES/dynamic-certificates/target/server.* $WILDFLY_HOME/standalone/configuration
Server configuration
A WildFly CLI script that contains all of the commands that are used in this example can be found in the client-cert-with-authorization project in elytron-examples repository:
Security domain configuration
We’re going to configure a security domain such that only the client with ID "123456" will be able to access our secured web application.
First, create a principal decoder that can be used to obtain the CN value from the subject name from an X.509 client certificate:
/subsystem=elytron/x500-attribute-principal-decoder=cnDecoder:add(attribute-name=CN, maximum-segments=1)
Next, create a principal transformer that can be used to extract the ID portion from the CN value:
/subsystem=elytron/regex-principal-transformer=myRegexTransformer:add(pattern=".*\\.([0-9]+)",replacement="$1")
Now, let’s combine these two principal transformers into a chained principal transformer:
/subsystem=elytron/chained-principal-transformer=myChainedTransformer:add(principal-transformers=[cnDecoder, myRegexTransformer])
As an example, for client1’s certificate, the above chained-principal-transformer would map the subject name "CN=Bob.Smith.123456" to just the ID portion: "123456".
Next, create a file-system based security realm that will be used to store the client IDs and their roles:
/subsystem=elytron/filesystem-realm=idsRealm:add(path=ids,relative-to=jboss.server.config.dir)
Now, let’s add the two IDs from the client certificates to our filesystem-realm. Notice that the user with ID "123546" has “Users” role but the second user with ID "456879" does not have any roles.
/subsystem=elytron/filesystem-realm=idsRealm:add-identity(identity=123456) /subsystem=elytron/filesystem-realm=idsRealm:add-identity-attribute(identity=123456,name=Roles,value=[Users]) /subsystem=elytron/filesystem-realm=idsRealm:add-identity(identity=456789)
Finally, we’ll configure a security domain that references our security realm and makes use of our chained principal transformer:
/subsystem=elytron/security-domain=clientCertDomain:add(realms=[{realm=idsRealm}], default-realm=idsRealm, pre-realm-principal-transformer=myChainedTransformer, permission-mapper=default-permission-mapper)
Two-way SSL configuration
We’re now going to enable two-way SSL for web applications deployed to the server.
First, configure a key-store using our server.truststore file. Remember this only contains the example certificate authority’s certificate, it doesn’t contain the individual client certificates:
/subsystem=elytron/key-store=serverTS:add(path=server.truststore,relative-to=jboss.server.config.dir,credential-reference={clear-text=truststorepass},type=JKS)
Next, enable two-way SSL:
security enable-ssl-http-server --key-store-path=server.keystore --key-store-path-relative-to=jboss.server.config.dir --key-store-password=keystorepass --trust-store-name=serverTS
CLIENT_CERT configuration
We’re now going to configure the CLIENT_CERT HTTP authentication mechanism so we can secure our web application using this mechanism.
The CLIENT_CERT HTTP authentication mechanism makes use of the verified X.509 client certificate chain that is established on the SSL connection. By default, this authentication mechanism will attempt to use the configured security realm to validate this certificate chain using client certificates that are stored in the configured security realm. Since our security realm does not contain client certificates and only contains role information, there won’t be a way for the realm itself to verify the client certificate. This is fine since we know the certificate chain was already verified when establishing the SSL connection so we’re going to set the org.wildfly.security.http.skip-certificate-verification property to true for the CLIENT_CERT mechanism, as shown below. Our security realm will still be used for the authorization check.
/subsystem=elytron/configurable-http-server-mechanism-factory=configuredCert:add(http-server-mechanism-factory=global, properties={org.wildfly.security.http.skip-certificate-verification=true})
Now let’s finish configuring the CLIENT_CERT mechanism:
/subsystem=elytron/http-authentication-factory=clientCertAuth:add(http-server-mechanism-factory=configuredCert, security-domain=clientCertDomain, mechanism-configurations=[{mechanism-name=CLIENT_CERT}]) /subsystem=undertow/application-security-domain=other:add(http-authentication-factory=clientCertAuth,override-deployment-config=true)
Finally, reload the server:
reload
Deploying and accessing the application
We’re going to make use of the simple-webapp project in the elytron-examples repository. It can be deployed using the following commands:
cd /PATH/TO/ELYTRON/EXAMPLES/simple-webapp mvn clean install wildfly:deploy
Then try accessing the application using https://localhost:8443/simple-webapp
Note that since the server's certificate won't be trusted by your browser, you'll need to manually confirm that this certificate is trusted or configure your browser to trust it.
First, select the certificate for "Alice.Smith.456789". Then try clicking on “Access Secured Servlet”. Notice that a Forbidden message occurs. This is because accessing the secured servlet requires “Users” role but the “456789” identity that we configured has no roles.
Now, try accessing the application again. This time, select the certificate for "Bob.Smith.123456" and then click on “Access Secured Servlet”. This time, this succeeds since the “123456” identity that we configured has “Users” role.
Summary
This example has shown to secure a web application deployed to WildFly using the CLIENT_CERT HTTP authentication mechanism with two-way SSL with authorization. It has also demonstrated that individual client certificates do not need to be stored in either the server’s truststore or in its security realm.