A common way to protect a server from the access of malicious is to identify the client; in my opinion, the best way to do that is the mutual SSL authentication. To understand what is the mutual SSL Authentication and other good practices for the protection of an endpoint you can read this article.
You can implement two-way authentication SSL using a WEB Server, for this example I used apache web server.
In the web there are more abstract examples of configuring two-way authentication SSL with Apache for development environment, but no one has a complete example. I hope this is quite complete!
Yes, I’m talking about development environment, because usually in this step certificates are self signed and there is much more work to do (you have to simulate a CA and create certificates).
Here there are the three marco steps:
- Create the server certificate
- Create the client certificate and the PKCS12 container
- Configure the apache web server
1. Create the server certificate
Before creating a certificate, you have to create a CA:
Create the structure directory and protect from other users:
mkdir ssl chmod 0700 ssl cd ssl mkdir certs private
Create a database to keep track of each certificate signed:
echo '100001' > serial touch certindex.txt
Make a custom config file for openssl to use:
# # OpenSSL configuration file. # # Establish working directory. dir = . [ ca ] default_ca = CA_default [ CA_default ] serial = $dir/serial database = $dir/certindex.txt new_certs_dir = $dir/certs certificate = $dir/cacert.pem private_key = $dir/private/cakey.pem default_days = 365 default_md = md5 preserve = no email_in_dn = no nameopt = default_ca certopt = default_ca policy = policy_match [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] default_bits = 2048 # Size of keys default_keyfile = key.pem # name of generated keys default_md = md5 # message digest algorithm string_mask = nombstr # permitted characters distinguished_name = req_distinguished_name req_extensions = v3_req [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = IT countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Italy localityName = Locality Name (eg, city) localityName_default = Milan 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Organization default # we can do this but it is not needed normally :-) #1.organizationName = Second Organization Name (eg, company) #1.organizationName_default = World Wide Web Pty Ltd organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = Organization unit name default commonName = Common Name (eg, YOUR name) commonName_max = 64 commonName_default = www.stefanocapitanio.com emailAddress = Email Address emailAddress_max = 64 # SET-ex3 = SET extension number 3 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = IT countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Italy localityName = Locality Name (eg, city) 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Organization default # we can do this but it is not needed normally :-) #1.organizationName = Second Organization Name (eg, company) #1.organizationName_default = World Wide Web Pty Ltd organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = Organization unit default commonName = Common Name (eg, YOUR name) commonName_max = 64 commonName_default = www.stefanocapitanio.com emailAddress = Email Address emailAddress_max = 64 # SET-ex3 = SET extension number 3 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name [ v3_ca ] basicConstraints = CA:TRUE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer:always [ v3_req ] basicConstraints = CA:FALSE subjectKeyIdentifier = hash
Create root certificate:
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf
This script will create private/cakey.pem that is the private key of the CA and the cacert.pem that is the which is the one you can give to others for import in their browsers.
Create a key and signing request for the server
openssl req -new -nodes -out server-req.pem -keyout private/server-key.pem -days 365 -config ./openssl.cnf
The output of this script is server-req.pem that is the CSR and the private/server-key.pem that is the private key
Sign the request
openssl ca -out server-cert.pem -days 365 -config ./openssl.cnf -infiles server-req.pem
This will produce server-cert.pem that is the public certificate
2. Create the client certificate and the PKCS12 container
Create a key and signing request for each client
openssl req -new -nodes -out client-req.pem -keyout private/client-key.pem -days 365 -config ./openssl.cnf *on the common name you have to specify a different name
Sign each request
openssl ca -out name-cert.pem -days 365 -config ./openssl.cnf -infiles client-req.pem
Create the PKCS12 file
openssl pkcs12 -export -in name-cert.pem -inkey private/name-key.pem -certfile cacert.pem -name "[friendly name]" -out name-cert.p12
The produced file is the file that the server will require to comunicate, and you have to install on the client
3. Configure APACHE
After installing and configuring apache and enabling SSL, you can get a VirtualHost and configure them:
<VirtualHost *:7001> ServerAdmin name@yourdomain.com SSLEngine on SSLCertificateFile /home/sempla1/ssl/server-cert.pem SSLCertificateKeyFile /home/sempla1/ssl/private/server-key.pem SSLVerifyClient require SSLVerifyDepth 10 SSLCACertificateFile /home/sempla1/ssl/cacert.pem <location /> Order allow,deny allow from all SSLRequire (%{SSL_CLIENT_S_DN_CN} eq "clientcn") </location> ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/ </VirtualHost>
One-way (standard) SSL auth configuration:
SSLEngine on -> to enable the single way SSL authentication
SSLCertificateFile -> to specify the public certificate that the WebServer will show to the users
SSLCertificateKeyFIle -> to specify the private key that will be used to encrypt the data sent
Two-way SSL auth configuration:
SSLVerifyClient -> to enable the two-way SSL authentication
SSLVerifyDepth -> to specify the depth of the check if the certificate has an approved CA
SSLCACertificateFile -> the public key that will be used to decrypt the data recieved
SSLRequire -> Allows only requests that satisfy the expression
Ok, now you are ready to test the two-way SSL authentication in your browser: install the client certificate (.p12 file) and add the CA in the trusted CA (to avoid the alert on the not secure https connection).
Now you can try to call a web service (with CXF and JAVA):
Download server certificate server.crt and take the client X509 certificate client-cert.p12.
Convert .p12 file in a Java Key Store using keytool JDK:
$JAVA_HOME/bin/keytool -importkeystore -srckeystore client-cert.p12 -srcstoretype PKCS12 -destkeystore keyManager.jks -deststoretype JKS
With keytool you can add the server certificate to a trust manager keystore:
$JAVA_HOME/bin/keytool -import -alias server-file server.crt -keystore trustManager.jks
Now you can configure on CXF configuration the call to the web service:
<http-conf:conduit name="*.http-conduit"> <http-conf:tlsClientParameters secureSocketProtocol="SSL" disableCNCheck="true" > <sec:keyManagers keyPassword="123456"> <sec:keyStore type="jks" password="123456" file="C:/var/lib/jbossas/p2p/certificati/keyManager.jks" /> </sec:keyManagers> <sec:trustManagers> <sec:keyStore type="jks" password="123456" file="C:/var/lib/jbossas/p2p/certificati/trustManager.jks" /> </sec:trustManagers> <sec:cipherSuitesFilter> <sec:include>.*_EXPORT_.*</sec:include> <sec:include>.*_EXPORT1024_.*</sec:include> <sec:include>.*_WITH_DES_.*</sec:include> <sec:include>.*_WITH_AES_.*</sec:include> <sec:include>.*_WITH_NULL_.*</sec:include> <sec:exclude>.*_DH_anon_.*</sec:exclude> </sec:cipherSuitesFilter> </http-conf:tlsClientParameters> </http-conf:conduit>
A big thank you to www.flatmtn.com, I used its guides to start creating CA, keys and certificates.
UPDATED: Thanks to @anjana_j for pointing out a mistake!