We discuss in this article different scenarios for creating, deploying and consuming secured web services with Apache Axis2 and Apache Rampart. These tools come with rich samples libraries, implementing different security scenarios to which the interested reader may refer for more details.
The main purpose of this article is to provide real-production-example configuration and implementation for secured web services but also for their clients (the latter being generally less-documented).
The security policies addressed by this article are the following:
- Authentication by User Name Token: the client of the generated service must provide a valid user name/password pair before consuming the service. Both cases of hashed and plain-text password are treated.
- Mutual Signature: the client (respectively the service) must sign the body of the request's (respectively the response's) SOAP envelope using the client's (respectively the service's) private key.
- Mutual Signature and Encryption: the client (respectively the service) must sign the body of the request's (respectively the response's) SOAP envelope using the client's (respectively the service's) private key and encrypt the body of the request's (respectively the response's) SOAP envelope using the service's (respectively the client's) public key.
All these policies may be combined with a transport layer constraint: namely the use of HTTPS. This option is especially useful in combination with the authentication by User Name Token when plain-text password is used.
The article is organized as follows: in Section I, we give a brief operational description of Apache Axis2, in Section II we discuss (through examples) configuration and implementation of web services constrained by security policies, then in Section III we provide configuration and implementation of the clients capable of consuming the secured web services and finally in Section IV we discuss options and give hints about configuring and installing Apache Axis2 Server and deploying the secured web services object of this study to that server.
Mainly, the tool produces a Axis2 Archive (a zip file having .aar extension), which contains Java classes (and their needed run-time dependencies) and a configuration file (services.xml) describing the desired web service interface, logic (or more precisely how it maps to Java programs) and policy.
The figure above represents the structure of a Axis2 Service Archive. It contains:
In the configuration file above, we give a name and a description to our service, then we specify a messageReceiver class (provided by Apache Axis2) for the operation corresponding to the method named doTheJob of the serviceClass: tn.nat.cnss.service.logic.Sample0NoSecurityService.
This is all what is needed by Apache Axis2 to deploy our service (in addition to all the Java dependencies of Sample0NoSecurityService). To get the Apache Axis2 archive, we have to zip all these items while respecting the structure in Figure 1.
In next sections we will consider variations on the web service described above, introducing different security policies.
Note that the use of SSL requires more configuration of the Apache Axis2 Server. This will be discussed in Section IV.
The use of a UserNameToken implies that the service should be able to validate the token: to check if the password corresponds to the supplied username and if the username is authorized to use the service. These checks are delegated to the tn.nat.cnss.service.callback.PasswordCallBackHandler class and Apache Rampart is configured to use this class in the tag <ramp:RampartConfig />.
Note that the PasswordCallBackHandler class presented above, is implemented to cover the two UserNameToken-based policies considered in this article: hashed password and plain-text password + SSL. This class implements javax.security.auth.callback.CallbackHandler interface, which requires the method handle(). This central method gets as input an array (generally reduced to one cell) of javax.security.auth.callback.Callback objects. A Callback object can be seen as a container of three values: a username, a password and usage type. The usage type for hashed and plain-text password is the same: WSPasswordCallback.USERNAME_TOKEN. All other usage types are to be ignored (see here for more details on other usage types). The behavior of the method handle() depends then on whether the password is supposed to be hashed or not:
The PasswordCallBackHandler class is the same as the one used for the case with hashed password.
Before testing SSL versions, you need to update the trust-store file (server.jks) and password (client implementation) to match the keystore of your Apache Tomcat instance. An easier way would be to configure your Apache Tomcat instance to use the supplied server.jks (for testing purpose).
The article is organized as follows: in Section I, we give a brief operational description of Apache Axis2, in Section II we discuss (through examples) configuration and implementation of web services constrained by security policies, then in Section III we provide configuration and implementation of the clients capable of consuming the secured web services and finally in Section IV we discuss options and give hints about configuring and installing Apache Axis2 Server and deploying the secured web services object of this study to that server.
I - Apache Axis2:
Apache Axis2 is a Web Services/SOAP/WSDL engine. Apache Axis2 not only supports SOAP 1.1 and SOAP 1.2, but it also has integrated support for the widely popular REST style of Web services. The same business logic implementation can offer both a WS-* style interface as well as a REST/POX style interface simultaneously. Apache Axis2 is efficient, modular and XML-oriented. It is carefully designed to support the easy addition of plug-in "modules" that extend their functionality for features such as security and reliability. The Modules currently available or under development include: WS-Security, supported by Apache Rampart and WS-Addressing, included as part of the core distribution.We put the focus here on the Java version of Apache Axis2 which proposes a tool that enables easy deployment of web services starting from existing Java programs. This mechanism referred to as Bottom-Up web services design, permits to wrap existing business logic into web services. This tool can be integrated into IDEs, such as Eclipse, see Service Archive Wizard - Eclipse Plug-in.
[Apache Axis2 Homepage]
Mainly, the tool produces a Axis2 Archive (a zip file having .aar extension), which contains Java classes (and their needed run-time dependencies) and a configuration file (services.xml) describing the desired web service interface, logic (or more precisely how it maps to Java programs) and policy.
Figure 1 - Axis2 Service Archive Sample |
- a META-INF/ folder which holds the configuration file: services.xml,
- a lib/ folder which contains jar libraries needed by Java classes,
- a package hierarchy of Java classes containing the implementation of the web service logic.
We will describe in details in next section, contents of .aar sample archives. Once these archived generated (or created manually), deployment of corresponding web services, is as simple as dragging and dropping them under the services/ folder of a valid Apache Axis2 Server instance.
II - Secured Web Services Samples:
We first discuss the logic of the web service while abstracting away the security policies.
II.1 - Web Service without Security Policy:
We will concentrate in this section on a web service with a simple logic: it exposes one operation called doTheJob. This operation receives 3 string values and returns the result of the execution of a SQL query parameterized by the 3 values.
We suppose that a Oracle database is available with a schema defining the following table:
CREATE TABLE UTILISATEUR ( ID NUMBER(6) NOT NULL, NOM_UTILISATEUR VARCHAR2(10) NOT NULL UNIQUE, MOT_DE_PASSE VARCHAR2(10) NOT NULL, IDENTITE VARCHAR2(256) NOT NULL, AFFECTATION VARCHAR2(256) NULL, INACTIF NUMBER(1) NULL, PRIMARY KEY (ID) );
The request that will be executed by the web service is the following:
SELECT * FROM utilisateur WHERE id BETWEEN [param1] AND [param2] OR id = [param3]
where [param1], [param2] and [param3] will be replaced by the corresponding values in the request message addressing the doTheJob operation.
The Utilisateur table is here needed:
The Sample0NoSecurityService class defines a method: doTheJob, that takes as parameter a ServiceRequest object and returns a ServiceResponse object. Note that all the information needed to execute the SQL query are available to this method. doTheJob only replaces the parameters appearing in the parameterized query with values gathered from the ServiceRequest object then calls a new instance of the utility class OracleRequestExecutor (not represented here) that will execute the query and store the result in the ServiceResponse object. Note that the parameters' values are sanitized (using SQLInjectionEscaper.escapeString() method) to avoid SQL injection problems.
The ServiceRequest and ServiceResponse classes are illustrated below:
As expected, the ServiceRequest class defines properties to hold the 3 parameters values.
The ServiceResponse class defines several properties to hold information about the query execution result:
The Utilisateur table is here needed:
- first, to provide the support for some realistic logic of the web service operation,
- second, to provide a mechanism to define authorized users, identified by usernames (NOM_UTILISATEUR field) and passwords (MOT_DE_PASSE field). We will see how this can be helpful for the security policies considered in next sections.
We then consider the Java class that will define the logic of the operation doTheJob:
package tn.nat.cnss.service.logic; import tn.nat.cnss.service.io.ServiceRequest; import tn.nat.cnss.service.io.ServiceResponse; import tn.nat.cnss.sql.OracleRequestExecutor; import tn.nat.cnss.sql.SQLInjectionEscaper; public class Sample0NoSecurityService { public ServiceResponse doTheJob(ServiceRequest serviceRequest) { ServiceResponse serviceResponse = new ServiceResponse(); String mainDBHost = "localhost"; String mainDBPort = "1521"; String mainDBSid = "xe"; String mainDBUsername = "skeleton"; String mainDBPassword = "skeleton"; String mainDBQuery = "SELECT * FROM utilisateur WHERE id BETWEEN [param1] AND [param2] OR id = [param3]"; mainDBQuery = mainDBQuery .replace("[param1]", SQLInjectionEscaper.escapeString(serviceRequest.getParam1())) .replace("[param2]", SQLInjectionEscaper.escapeString(serviceRequest.getParam2())) .replace("[param3]", SQLInjectionEscaper.escapeString(serviceRequest.getParam3())) ; new OracleRequestExecutor(serviceResponse).doTheJob( mainDBHost, Integer.parseInt(mainDBPort), mainDBSid, mainDBUsername, mainDBPassword, mainDBQuery ); mainDBHost = null; mainDBPort = null; mainDBSid = null; mainDBUsername = null; mainDBPassword = null; mainDBQuery = null; return(serviceResponse); } }
The Sample0NoSecurityService class defines a method: doTheJob, that takes as parameter a ServiceRequest object and returns a ServiceResponse object. Note that all the information needed to execute the SQL query are available to this method. doTheJob only replaces the parameters appearing in the parameterized query with values gathered from the ServiceRequest object then calls a new instance of the utility class OracleRequestExecutor (not represented here) that will execute the query and store the result in the ServiceResponse object. Note that the parameters' values are sanitized (using SQLInjectionEscaper.escapeString() method) to avoid SQL injection problems.
The ServiceRequest and ServiceResponse classes are illustrated below:
package tn.nat.cnss.service.io; import java.io.Serializable; public class ServiceRequest implements Serializable { private static final long serialVersionUID = 1L; private String param1; private String param2; private String param3; public String getParam1() { return this.param1; } public void setParam1(String param1) { this.param1 = param1; } public String getParam2() { return this.param2; } public void setParam2(String param2) { this.param2 = param2; } public String getParam3() { return this.param3; } public void setParam3(String param3) { this.param3 = param3; } }
As expected, the ServiceRequest class defines properties to hold the 3 parameters values.
package tn.nat.cnss.service.io; import java.io.Serializable; public class ServiceResponse implements Serializable { private static final long serialVersionUID = 1L; private String result; private Integer numberColumns; private Integer numberLines; private String[][] dataSet; public String getResult() { return result; } public void setResult(String result) { this.result = result; } public Integer getNumberColumns() { return numberColumns; } public void setNumberColumns(Integer numberColumns) { this.numberColumns = numberColumns; } public Integer getNumberLines() { return numberLines; } public void setNumberLines(Integer numberLines) { this.numberLines = numberLines; } public String[][] getDataSet() { return dataSet; } public void setDataSet(String[][] dataSet) { this.dataSet = dataSet; } }
The ServiceResponse class defines several properties to hold information about the query execution result:
- result: a text message describing the type of the result (execution OK, errors, ...),
- numberColumns, numberLines: respectively the number of fields in a single row and the total number of selected rows,
- dataSet: a numberLines x numberColumns matrix holding the contents of selected rows.
In order to correctly deploy the web service exposing the doTheJob() method as an operation, Apache Axis2 needs a configuration file, services.xml:
<service name="Sample0NoSecurityService" > <description> A Toy Sample Service with No Security </description> <operation name="doTheJob"> <messageReceiver class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/> </operation> <parameter name="ServiceClass">tn.nat.cnss.service.logic.Sample0NoSecurityService</parameter> </service>
In the configuration file above, we give a name and a description to our service, then we specify a messageReceiver class (provided by Apache Axis2) for the operation corresponding to the method named doTheJob of the serviceClass: tn.nat.cnss.service.logic.Sample0NoSecurityService.
This is all what is needed by Apache Axis2 to deploy our service (in addition to all the Java dependencies of Sample0NoSecurityService). To get the Apache Axis2 archive, we have to zip all these items while respecting the structure in Figure 1.
In next sections we will consider variations on the web service described above, introducing different security policies.
II.2 - Adding SSL support (Transport Binding):
To add SSL suport we need to enrich the services.xml configuration file with a policy (WS-Policy) that will be bound to the web service. The policy defines a Transport Binding (WS-SX) that precises the algorithm used for ciphering communications (Basic256), that a time stamp should be included (to avoid replay attacks) and that only the service should possess a X509 certificate.
Note also that the Apache Rampart module (in charge of enforcing the security policy) should be engaged for the web service (<module ref="rampart"/>).
Note also that the Apache Rampart module (in charge of enforcing the security policy) should be engaged for the web service (<module ref="rampart"/>).
<service name="Sample0NoSecurityServiceSSL" > <description> A Toy Sample Service with No Security (but SSL) </description> <operation name="doTheJob"> <messageReceiver class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/> </operation> <parameter name="ServiceClass">tn.nat.cnss.service.logic.Sample0NoSecurityServiceSSL</parameter> <module ref="rampart"/> <wsp:Policy wsu:Id="SSLTransport" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"> <wsp:ExactlyOne> <wsp:All> <sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <wsp:Policy> <sp:TransportToken> <wsp:Policy> <sp:HttpsToken RequireClientCertificate="false"/> </wsp:Policy> </sp:TransportToken> <sp:AlgorithmSuite> <wsp:Policy> <sp:Basic256/> </wsp:Policy> </sp:AlgorithmSuite> <sp:Layout> <wsp:Policy> <sp:Lax/> </wsp:Policy> </sp:Layout> <sp:IncludeTimestamp/> </wsp:Policy> </sp:TransportBinding> </wsp:All> </wsp:ExactlyOne> </wsp:Policy> </service>
Note that the use of SSL requires more configuration of the Apache Axis2 Server. This will be discussed in Section IV.
II.3 - UserNameToken (Hashed Password) Policy:
The services.xml file introduces a policy (that requires the Apache Rampart module to be engaged) and the use (by the client of the service) of a pair username/password (UserNameToken) where the password is hashed.
<service name="Sample1UserNameTokenService" > <description> A Toy Sample Service with UserNameToken </description> <module ref="rampart"/> <operation name="doTheJob"> <messageReceiver class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/> </operation> <parameter name="ServiceClass">tn.nat.cnss.service.logic.Sample1UserNameTokenService</parameter> <wsp:Policy wsu:Id="UserNameToken" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"> <wsp:ExactlyOne> <wsp:All> <sp:SignedSupportingTokens xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"> <wsp:Policy> <sp:UsernameToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"> <wsp:Policy> <sp:HashPassword/> </wsp:Policy> </sp:UsernameToken> </wsp:Policy> </sp:SignedSupportingTokens> <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> <ramp:passwordCallbackClass>tn.nat.cnss.service.callback.PasswordCallBackHandler</ramp:passwordCallbackClass> </ramp:RampartConfig> </wsp:All> </wsp:ExactlyOne> </wsp:Policy> </service>
The use of a UserNameToken implies that the service should be able to validate the token: to check if the password corresponds to the supplied username and if the username is authorized to use the service. These checks are delegated to the tn.nat.cnss.service.callback.PasswordCallBackHandler class and Apache Rampart is configured to use this class in the tag <ramp:RampartConfig />.
The PasswordCallBackHandler class is given below:
package tn.nat.cnss.service.callback; import java.io.IOException; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import org.apache.ws.security.WSPasswordCallback; import tn.nat.cnss.sql.OracleRequestExecutor; public class PasswordCallBackHandler implements CallbackHandler { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { WSPasswordCallback pwcb = (WSPasswordCallback)callback; if (pwcb.getUsage() == WSPasswordCallback.USERNAME_TOKEN) { if(getPasswordByIdentifier(pwcb)) { return; } else { throw new UnsupportedCallbackException(callback, "Echec de l'Authentification"); } } else { throw new UnsupportedCallbackException(callback, "Le Hachage des Mots de Passe est Obligatoire"); } } } private boolean getPasswordByIdentifier(WSPasswordCallback pwcb) { try { for (String[] line : getAllIdentifierPasswordFromDB()) { if (pwcb.getIdentifier().equals(line[0])) { if (pwcb.getPassword() != null) { if (pwcb.getPassword().equals(line[1])) { return(true); } else { return(false); } } else { pwcb.setPassword(line[1]); return(true); } } } } catch (Exception e) { return(false); } return(false); } private String[][] getAllIdentifierPasswordFromDB() { String callBackDBHost = "localhost"; String callBackDBPort = "1521"; String callBackDBSid = "xe"; String callBackDBUsername = "skeleton"; String callBackDBPassword = "skeleton"; String callBackDBQuery = "SELECT nom_utilisateur, mot_de_passe FROM utilisateur"; String[][] result = new OracleRequestExecutor().doTheJobForCallBack( callBackDBHost, Integer.parseInt(callBackDBPort), callBackDBSid, callBackDBUsername, callBackDBPassword, callBackDBQuery ); callBackDBHost = null; callBackDBPort = null; callBackDBSid = null; callBackDBUsername = null; callBackDBPassword = null; callBackDBQuery = null; return(result); } }
Note that the PasswordCallBackHandler class presented above, is implemented to cover the two UserNameToken-based policies considered in this article: hashed password and plain-text password + SSL. This class implements javax.security.auth.callback.CallbackHandler interface, which requires the method handle(). This central method gets as input an array (generally reduced to one cell) of javax.security.auth.callback.Callback objects. A Callback object can be seen as a container of three values: a username, a password and usage type. The usage type for hashed and plain-text password is the same: WSPasswordCallback.USERNAME_TOKEN. All other usage types are to be ignored (see here for more details on other usage types). The behavior of the method handle() depends then on whether the password is supposed to be hashed or not:
- plain-text password: the Callback object transmitted to the handle() method contains values for the username and password. The method than verifies if the pair is a valid one, and if it is not the case it will raise a javax.security.auth.callback.UnsupportedCallbackException exception.
- hashed password: the Callback object transmitted to the handle() method contains a value only for the username. The method is supposed to find the password corresponding to that username and then set it in the Callback object. A validator (outside the callback handler) uses the Callback object for the checking: whether the password corresponds to the hashed value supplied by the client of the service. If the username is not recognized, a javax.security.auth.callback.UnsupportedCallbackException exception is raised.
As mentioned earlier, the Utilisateur table is used to store pairs of valid usernames and corresponding passwords. The handle() method relies than on utility methods that access the database where leaves this table to validate username tokens.
II.4 - UserNameToken (Plain-text Password + SSL) Policy:
The services.xml file introduces a policy (that requires the Apache Rampart module to be engaged) and the use (by the client of the service) of a pair username/password (UserNameToken).
A Transport Binding (SSL) is also required to protect the password which is sent over the network.
<service name="Sample1UserNameTokenServiceSSL" > <description> A Toy Sample Service with UserNameToken + SSL </description> <module ref="rampart"/> <operation name="doTheJob"> <messageReceiver class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/> </operation> <parameter name="ServiceClass">tn.nat.cnss.service.logic.Sample1UserNameTokenServiceSSL</parameter> <wsp:Policy wsu:Id="UserNameTokenOverSSL" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"> <wsp:ExactlyOne> <wsp:All> <sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <wsp:Policy> <sp:TransportToken> <wsp:Policy> <sp:HttpsToken RequireClientCertificate="false"/> </wsp:Policy> </sp:TransportToken> <sp:AlgorithmSuite> <wsp:Policy> <sp:Basic256/> </wsp:Policy> </sp:AlgorithmSuite> <sp:Layout> <wsp:Policy> <sp:Lax/> </wsp:Policy> </sp:Layout> <sp:IncludeTimestamp/> </wsp:Policy> </sp:TransportBinding> <sp:SignedSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <wsp:Policy> <sp:UsernameToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient" /> </wsp:Policy> </sp:SignedSupportingTokens> <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> <ramp:passwordCallbackClass>tn.nat.cnss.service.callback.PasswordCallBackHandler</ramp:passwordCallbackClass> </ramp:RampartConfig> </wsp:All> </wsp:ExactlyOne> </wsp:Policy> </service>
The PasswordCallBackHandler class is the same as the one used for the case with hashed password.
II.5 - Body Signature Policy:
The services.xml file is given below. In addition to engaging the Apache Rampart module, this configuration file specifies a policy (WS-Policy) defining an AsymmetricBinding (WS-SX). Most important parts of the policy are:
The PasswordCallBackHandler class is given below. We recall this class is used to provide the password protecting the service private key. For simplicity, we assume here that key aliases and corresponding passwords are also respectively stored in NOM_UTILISATEUR and MOT_DE_PASSE fields of the Utilisateur table. So the handle() methods only sets the password for the service private key, given the key alias.
The PasswordCallBackHandler class is the same as the one used in the case of signature only (previous section). So one must make sure that at least two entries exists in the Utilisateur table, mapping the service private key and the client public key aliases to the corresponding passwords.
Remark 1: We did not discuss the case of requiring the use of SSL (Transport Binding) together with policies requiring signature or encryption. In theory this would be possible by adding a <sp:TransportBinding/> to the policy of the service in addition to the <sp:AsymmetricBinding/>. In practice, Apache Rampart interprets only one binding a time (see this topic). A solution would be to forget about the <sp:TransportBinding/> and invite the client of the service to address it on the HTTPS URL. The difference between the theory and this practical workaround is important: it is no longer possible to impose the use of SSL at the Apache Rampart level. This means that if both HTTP and HTTPS access are allowed at the server level, the client may choose to use HTTP ...
Remark 2: Once the .aar archive is generated for a web service, deploying it under the Apache Axis2 Server makes available a WSDL description of the service. Assume the service endpoint is "SOME_URL", this WSDL description is available at the URL: "SOME_URL?wsdl". This WSDL description is automatically generated by Apache Axis2 by interpreting the services.xml configuration file and the Java classes involved in the service logic. We will see in next section how this is important for obtaining a Java implementation of a client capable of consuming a service given its WSDL description.
An example of the use of wsdl2java.sh is given below:
We briefly discuss some of the options of the command:
In the PasswordCallBackHandler class we just statically (no database access) provide the password protecting the client private key.
In the PasswordCallBackHandler class we just statically (no database access) provide passwords respectively protecting the client private key and the service public key.
Remark 4: We did not present cases where SSL is used by clients to access web services bound to policies requiring use of UserNameToken or sigining and/or encrypting exchanged SOAP messages.
This could be easily done by adding SSL configuration given for the client described in Section III.2:
- defining the signature algorithm (TripleDesRsa15), through the <sp:AlgorithmSuite/> tag,
- defining the parts of the SOAP messages to be signed, through the <sp:SignedParts/> tag, here the hole body part.
Moreover, an Apache Rampart configuration section (<ramp:RampartConfig/> tag) is also needed. It specifies:
- the alias of the service private key which will be used to sign the response SOAP envelope's body,
- the callback handler, which will be in charge for providing the password protecting the service private key (stored in the key store defined later on),
- the cryptography provider,
- the type of the key store holding the service key,
- the file containing the key store,
- the password protecting the key store file.
Let it be clear, the service private key is stored in a key store (a file on the file system) and access to this file is protected by a password: the one given in the Apache Rampart configuration section.
Once access to the key store file is granted, to use the service private key one needs a second password, the one protecting the key which is provided by the PasswordCallBackHandler class.
<service name="Sample2SignBodyService" > <description> A Toy Sample Service with Body Signature </description> <module ref="rampart"/> <operation name="doTheJob"> <messageReceiver class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/> </operation> <parameter name="ServiceClass">tn.nat.cnss.service.logic.Sample2SignBodyService</parameter> <wsp:Policy wsu:Id="SignBody" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"> <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/Never"> <wsp:Policy> <sp:WssX509V3Token10/> </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:WssX509V3Token10/> </wsp:Policy> </sp:X509Token> </wsp:Policy> </sp:RecipientToken> <sp:AlgorithmSuite> <wsp:Policy> <sp:TripleDesRsa15/> </wsp:Policy> </sp:AlgorithmSuite> <sp:Layout> <wsp:Policy> <sp:Strict/> </wsp:Policy> </sp:Layout> <sp:IncludeTimestamp/> <sp:OnlySignEntireHeadersAndBody/> </wsp:Policy> </sp:AsymmetricBinding> <sp:Wss10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <wsp:Policy> <sp:MustSupportRefKeyIdentifier/> <sp:MustSupportRefIssuerSerial/> </wsp:Policy> </sp:Wss10> <sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <sp:Body/> </sp:SignedParts> <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> <ramp:user>servicekey</ramp:user> <ramp:passwordCallbackClass>tn.nat.cnss.service.callback.PasswordCallBackHandler</ramp:passwordCallbackClass> <ramp:signatureCrypto> <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin"> <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type">JKS</ramp:property> <ramp:property name="org.apache.ws.security.crypto.merlin.file">service.jks</ramp:property> <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.password">serviceStorePW</ramp:property> </ramp:crypto> </ramp:signatureCrypto> </ramp:RampartConfig> </wsp:All> </wsp:ExactlyOne> </wsp:Policy> </service>
The PasswordCallBackHandler class is given below. We recall this class is used to provide the password protecting the service private key. For simplicity, we assume here that key aliases and corresponding passwords are also respectively stored in NOM_UTILISATEUR and MOT_DE_PASSE fields of the Utilisateur table. So the handle() methods only sets the password for the service private key, given the key alias.
package tn.nat.cnss.service.callback; import java.io.IOException; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import org.apache.ws.security.WSPasswordCallback; import tn.nat.cnss.sql.OracleRequestExecutor; public class PasswordCallBackHandler implements CallbackHandler { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { /* * To use the private key to sign messages, * we need to provide the private key password */ WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i]; if(getPasswordByIdentifier(pwcb)) { return; } } } private boolean getPasswordByIdentifier(WSPasswordCallback pwcb) { try { for (String[] line : getAllIdentifierPasswordFromDB()) { if (pwcb.getIdentifier().equals(line[0])) { pwcb.setPassword(line[1]); return(true); } } } catch (Exception e) { return(false); } return(false); } private String[][] getAllIdentifierPasswordFromDB() { String callBackDBHost = "localhost"; String callBackDBPort = "1521"; String callBackDBSid = "xe"; String callBackDBUsername = "skeleton"; String callBackDBPassword = "skeleton"; String callBackDBQuery = "SELECT nom_utilisateur, mot_de_passe FROM utilisateur"; String[][] result = new OracleRequestExecutor().doTheJobForCallBack( callBackDBHost, Integer.parseInt(callBackDBPort), callBackDBSid, callBackDBUsername, callBackDBPassword, callBackDBQuery ); callBackDBHost = null; callBackDBPort = null; callBackDBSid = null; callBackDBUsername = null; callBackDBPassword = null; callBackDBQuery = null; return(result); } }
II.6 - Body Signature and Encryption Policy:
The services.xml file is given below. In addition to engaging the Apache Rampart module, this configuration file specifies a policy (WS-Policy) defining an AsymmetricBinding (WS-SX). Most important parts of the policy are:
- defining the encryption and signature algorithm (again TripleDesRsa15), through the <sp:AlgorithmSuite/> tag,
- defining the parts of the SOAP messages to be signed, through the <sp:SignedParts/> tag, and those to be encrypted, through the <sp:EncryptedParts/> tag, here the hole body part.
Moreover, an Apache Rampart configuration section (<ramp:RampartConfig/> tag) is also needed. It specifies:
- the alias of the service private key which will be used to sign the response SOAP envelope's body,
- the alias of the client public key which will be used to encrypt the response SOAP envelope's body,
- the callback handler, which will be in charge for providing the passwords respectively protecting the service private key and the client public key (both stored in the key store defined later on),
- the cryptography provider,
- the types of the key stores holding the service private key and the client public key,
- the files containing the key stores,
- the passwords protecting the key store files.
<service name="Sample4SignEncryptBodyService" > <description> A Toy Sample Service with Body Signature and Encryption </description> <module ref="rampart"/> <operation name="doTheJob"> <messageReceiver class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/> </operation> <parameter name="ServiceClass">tn.nat.cnss.service.logic.Sample4SignEncryptBodyService</parameter> <wsp:Policy wsu:Id="EncryptBody" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"> <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/Never"> <wsp:Policy> <sp:WssX509V3Token10/> </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:WssX509V3Token10/> </wsp:Policy> </sp:X509Token> </wsp:Policy> </sp:RecipientToken> <sp:AlgorithmSuite> <wsp:Policy> <sp:TripleDesRsa15/> </wsp:Policy> </sp:AlgorithmSuite> <sp:Layout> <wsp:Policy> <sp:Strict/> </wsp:Policy> </sp:Layout> <sp:IncludeTimestamp/> <sp:OnlySignEntireHeadersAndBody/> </wsp:Policy> </sp:AsymmetricBinding> <sp:Wss10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <wsp:Policy> <sp:MustSupportRefKeyIdentifier/> <sp:MustSupportRefIssuerSerial/> </wsp:Policy> </sp:Wss10> <sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <sp:Body/> </sp:SignedParts> <sp:EncryptedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"> <sp:Body/> </sp:EncryptedParts> <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy"> <ramp:user>servicekey</ramp:user> <ramp:encryptionUser>clientkey</ramp:encryptionUser> <ramp:passwordCallbackClass>tn.nat.cnss.service.callback.PasswordCallBackHandler</ramp:passwordCallbackClass> <ramp:signatureCrypto> <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin"> <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type">JKS</ramp:property> <ramp:property name="org.apache.ws.security.crypto.merlin.file">service.jks</ramp:property> <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.password">serviceStorePW</ramp:property> </ramp:crypto> </ramp:signatureCrypto> <ramp:encryptionCypto> <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin"> <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type">JKS</ramp:property> <ramp:property name="org.apache.ws.security.crypto.merlin.file">client.jks</ramp:property> <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.password">clientStorePW</ramp:property> </ramp:crypto> </ramp:encryptionCypto> </ramp:RampartConfig> </wsp:All> </wsp:ExactlyOne> </wsp:Policy> </service>
The PasswordCallBackHandler class is the same as the one used in the case of signature only (previous section). So one must make sure that at least two entries exists in the Utilisateur table, mapping the service private key and the client public key aliases to the corresponding passwords.
Remark 1: We did not discuss the case of requiring the use of SSL (Transport Binding) together with policies requiring signature or encryption. In theory this would be possible by adding a <sp:TransportBinding/> to the policy of the service in addition to the <sp:AsymmetricBinding/>. In practice, Apache Rampart interprets only one binding a time (see this topic). A solution would be to forget about the <sp:TransportBinding/> and invite the client of the service to address it on the HTTPS URL. The difference between the theory and this practical workaround is important: it is no longer possible to impose the use of SSL at the Apache Rampart level. This means that if both HTTP and HTTPS access are allowed at the server level, the client may choose to use HTTP ...
Remark 2: Once the .aar archive is generated for a web service, deploying it under the Apache Axis2 Server makes available a WSDL description of the service. Assume the service endpoint is "SOME_URL", this WSDL description is available at the URL: "SOME_URL?wsdl". This WSDL description is automatically generated by Apache Axis2 by interpreting the services.xml configuration file and the Java classes involved in the service logic. We will see in next section how this is important for obtaining a Java implementation of a client capable of consuming a service given its WSDL description.
III - Web Services Clients Generation:
Apache Axis2 provides also the possibility to generates Java code implementation for a client capable of consuming a web service, given its WSDL description. This can be achieved using the wsdl2java command that ships with the Apache Axis2 binary distribution (wsdl2java.sh for linux and wsdl2java.bat for windows) or the org.apache.axis2.wsdl.WSDL2Java class.An example of the use of wsdl2java.sh is given below:
./wsdl2java.sh -uri http://localhost:8080/axis2/services/Sample0NoSecurityService?wsdl -p tn.nat.cnss.client -d adb -s -o /home/mekkimoh/Bureau/Work/client
We briefly discuss some of the options of the command:
- -uri: indicates the WSDL of the web service to be considered,
- -p: indicates the package under which the Java classes will be created,
- -o: indicates the folder under which Java classes will be generated.
For more details, the reader may refer to the Axis2 Reference Guide.
The Java class generated by the wsdl2java command contains all necessary code to consume the corresponding web service, but remains complicated to read and thus to use. We present in the following sections Java wrappers that will make their use easier by putting the focus on constructing the SOAP request, calling the web service with that request, getting the SOAP response then parsing it to display the results.
III.1 - Client for Web Service (With No Security Policy):
The Java class generated by the wsdl2java command for the web service developed in Section II.1 is named Sample0NoSecurityServiceStub. We create the Sample0NoSecurityServiceClient class (the wrapper) to make the use of the former class easier. Let's describe the latter's main() method logic:
- A new instance of Sample0NoSecurityServiceStub (called stub) is created using the URL of the web service endpoint.
- A new instance of ServiceRequest is created and fed with the expected values for the 3 parameters.
- A new instance d of DoTheJob (representing an instance of the operation of the service) is created and fed with the ServiceRequest prepared formerly.
- The doTheJob() method of the service stub is called, using the d object and returning a ServiceResponse object as result.
- The ServiceResponse result is parsed and displayed to the console.
package tn.nat.cnss.client; import tn.nat.cnss.client.Sample0NoSecurityServiceStub.ArrayOfString; import tn.nat.cnss.client.Sample0NoSecurityServiceStub.DoTheJob; import tn.nat.cnss.client.Sample0NoSecurityServiceStub.DoTheJobResponse; import tn.nat.cnss.client.Sample0NoSecurityServiceStub.ServiceRequest; public class Sample0NoSecurityServiceClient { public static void main(String[] args) throws Exception { org.apache.log4j.Logger.getRootLogger().setLevel(org.apache.log4j.Level.OFF); Sample0NoSecurityServiceStub stub = new Sample0NoSecurityServiceStub(null,"http://localhost:8080/axis2/services/Sample0NoSecurityService"); ServiceRequest req = new ServiceRequest(); req.setParam1("0"); req.setParam2("99999"); req.setParam3("99999"); DoTheJob d = new DoTheJob(); d.setServiceRequest(req); DoTheJobResponse resp = stub.doTheJob(d); System.out.println(resp.get_return().getResult()); System.out.println("Nombre de colonnes: " + resp.get_return().getNumberColumns()); System.out.println("Nombre de lignes: " + resp.get_return().getNumberLines()); for (ArrayOfString array : resp.get_return().getDataSet()) { for (String s : array.localArray) { System.out.print(s + "\t"); } System.out.println(); } stub.cleanup(); } }
III.2 - Client for Web Service (With SSL):
We recognize the same logic as in the previous case (without SSL) with the following differences:
- The service stub is constructed using the HTTPS endpoint for the service, and a configuration context which will make available Apache Rampart libraries to the client (the sub-folder client-repo contains the Apache Rampart module).
- The Apache Rampart module is engaged for the client, this is needed because SSL is used.
- We have to set the trust-store containing the certificate of the server hosting the web service and the password to access this store. This is needed since we use a self-signed certificate for our server, we need in this case to tell the client to trust the certificate.
package tn.nat.cnss.client; import org.apache.axis2.client.ServiceClient; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.ConfigurationContextFactory; import tn.nat.cnss.client.Sample0NoSecurityServiceSSLStub.ArrayOfString; import tn.nat.cnss.client.Sample0NoSecurityServiceSSLStub.DoTheJob; import tn.nat.cnss.client.Sample0NoSecurityServiceSSLStub.DoTheJobResponse; import tn.nat.cnss.client.Sample0NoSecurityServiceSSLStub.ServiceRequest; public class Sample0NoSecurityServiceSSLClient { public static void main(String[] args) throws Exception { org.apache.log4j.Logger.getRootLogger().setLevel(org.apache.log4j.Level.OFF); System.setProperty("javax.net.ssl.trustStore", "trust-store/server.jks"); System.setProperty("javax.net.ssl.trustStorePassword", "password"); ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem("client-repo", null); Sample0NoSecurityServiceSSLStub stub = new Sample0NoSecurityServiceSSLStub(ctx,"https://localhost:8443/axis2/services/Sample0NoSecurityServiceSSL"); ServiceClient sc = stub._getServiceClient(); sc.engageModule("rampart"); ServiceRequest req = new ServiceRequest(); req.setParam1("0"); req.setParam2("99999"); req.setParam3("99999"); DoTheJob d = new DoTheJob(); d.setServiceRequest(req); DoTheJobResponse resp = stub.doTheJob(d); System.out.println(resp.get_return().getResult()); System.out.println("Nombre de colonnes: " + resp.get_return().getNumberColumns()); System.out.println("Nombre de lignes: " + resp.get_return().getNumberLines()); for (ArrayOfString array : resp.get_return().getDataSet()) { for (String s : array.localArray) { System.out.print(s + "\t"); } System.out.println(); } stub.cleanup(); } }
III.3 - Client for Web Service (With UserNameToken Policy):
We recognize the same logic as in previous cases, the only difference is that we have to specify a username and a password within the request to be sent to the web service operation.
This is achieved using the Options object attached to the ServiceClient, which will be fed with a valide username/password pair.
Remark 3: There is no need to manually hash the password in the client implementation. Apache Rampart will take care of that according to the policy attached to the web service.
This is achieved using the Options object attached to the ServiceClient, which will be fed with a valide username/password pair.
Remark 3: There is no need to manually hash the password in the client implementation. Apache Rampart will take care of that according to the policy attached to the web service.
package tn.nat.cnss.client; import org.apache.axis2.client.Options; import org.apache.axis2.client.ServiceClient; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.ConfigurationContextFactory; import tn.nat.cnss.client.Sample1UserNameTokenServiceStub.ArrayOfString; import tn.nat.cnss.client.Sample1UserNameTokenServiceStub.DoTheJob; import tn.nat.cnss.client.Sample1UserNameTokenServiceStub.DoTheJobResponse; import tn.nat.cnss.client.Sample1UserNameTokenServiceStub.ServiceRequest; public class Sample1UserNameTokenServiceClient { public static void main(String[] args) throws Exception { org.apache.log4j.Logger.getRootLogger().setLevel(org.apache.log4j.Level.OFF); ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem("client-repo", null); Sample1UserNameTokenServiceStub stub = new Sample1UserNameTokenServiceStub(ctx,"http://localhost:8080/axis2/services/Sample1UserNameTokenService"); ServiceClient sc = stub._getServiceClient(); sc.engageModule("rampart"); Options options = sc.getOptions(); options.setUserName("99999"); options.setPassword("99999"); ServiceRequest req = new ServiceRequest(); req.setParam1("0"); req.setParam2("99999"); req.setParam3("99999"); DoTheJob d = new DoTheJob(); d.setServiceRequest(req); DoTheJobResponse resp = stub.doTheJob(d); System.out.println(resp.get_return().getResult()); System.out.println("Nombre de colonnes: " + resp.get_return().getNumberColumns()); System.out.println("Nombre de lignes: " + resp.get_return().getNumberLines()); for (ArrayOfString array : resp.get_return().getDataSet()) { for (String s : array.localArray) { System.out.print(s + "\t"); } System.out.println(); } stub.cleanup(); } }
III.4 - Client for Web Service (With Signing Policy):
As expected the client logic is still the same, but some security configuration is needed to tell the client which private key is to be used for signing the request. This configuration is computed by the getRampartConfig() method and added to the ServiceClient. Mainly, this configuration includes definitions of:
- The alias of the client's private key.
- The password callback handler class: the class that will provide the password protecting the client private.
- The cryptography provider.
- The key store type and file.
- The password protecting access to the key store file.
Note that this configuration is dual to the one we used for the corresponding web service.
package tn.nat.cnss.client; import java.util.Properties; import org.apache.axis2.client.ServiceClient; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.ConfigurationContextFactory; import org.apache.neethi.Policy; import org.apache.rampart.policy.model.CryptoConfig; import org.apache.rampart.policy.model.RampartConfig; import tn.nat.cnss.client.Sample2SignBodyServiceStub.ArrayOfString; import tn.nat.cnss.client.Sample2SignBodyServiceStub.DoTheJob; import tn.nat.cnss.client.Sample2SignBodyServiceStub.DoTheJobResponse; import tn.nat.cnss.client.Sample2SignBodyServiceStub.ServiceRequest; public class Sample2SignBodyServiceClient { public static void main(String[] args) throws Exception { org.apache.log4j.Logger.getRootLogger().setLevel(org.apache.log4j.Level.OFF); ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem("client-repo", null); Sample2SignBodyServiceStub stub = new Sample2SignBodyServiceStub(ctx,"http://localhost:8080/axis2/services/Sample2SignBodyService"); ServiceClient sc = stub._getServiceClient(); sc.engageModule("rampart"); Policy rampartConfig = getRampartConfig(); sc.getAxisService().getPolicySubject().attachPolicy(rampartConfig); ServiceRequest req = new ServiceRequest(); req.setParam1("0"); req.setParam2("99999"); req.setParam3("99999"); DoTheJob d = new DoTheJob(); d.setServiceRequest(req); DoTheJobResponse resp = stub.doTheJob(d); System.out.println(resp.get_return().getResult()); System.out.println("Nombre de colonnes: " + resp.get_return().getNumberColumns()); System.out.println("Nombre de lignes: " + resp.get_return().getNumberLines()); for (ArrayOfString array : resp.get_return().getDataSet()) { for (String s : array.localArray) { System.out.print(s + "\t"); } System.out.println(); } stub.cleanup(); } private static Policy getRampartConfig() { RampartConfig rampartConfig = new RampartConfig(); rampartConfig.setUser("clientkey"); rampartConfig.setPwCbClass("tn.nat.cnss.client.PasswordCallBackHandler"); CryptoConfig sigCrypto = new CryptoConfig(); sigCrypto.setProvider("org.apache.ws.security.components.crypto.Merlin"); Properties props = new Properties(); props.setProperty("org.apache.ws.security.crypto.merlin.keystore.type", "JKS"); props.setProperty("org.apache.ws.security.crypto.merlin.file","keys/client.jks"); props.setProperty("org.apache.ws.security.crypto.merlin.keystore.password", "clientStorePW"); sigCrypto.setProp(props); rampartConfig.setSigCryptoConfig(sigCrypto); Policy policy = new Policy(); policy.addAssertion(rampartConfig); return policy; } }
In the PasswordCallBackHandler class we just statically (no database access) provide the password protecting the client private key.
package tn.nat.cnss.client; import org.apache.ws.security.WSPasswordCallback; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import java.io.IOException; public class PasswordCallBackHandler implements CallbackHandler { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i]; if(pwcb.getIdentifier().equals("clientkey")) { pwcb.setPassword("clientPW"); return; } } } }
III.5 - Client for Web Service (With Signing and Encrypting Policy):
As expected the client logic is still the same, but some security configuration is needed to tell the client which private key is to be used for signing the request and which public key is used for encrypting it. This configuration is computed by the getRampartConfig() method and added to the ServiceClient. Mainly, this configuration includes definitions of:
Note that this configuration is dual to the one we used for the corresponding web service.
- The aliases of the client private key and the service public key.
- The password callback handler class: the class that will provide passwords respectively protecting the client private key (for signing the request) and the service public key (for encrypting the request).
- The cryptography provider.
- The key store types and files.
- Passwords protecting access to the key store files.
Note that this configuration is dual to the one we used for the corresponding web service.
package tn.nat.cnss.client; import java.util.Properties; import org.apache.axis2.client.ServiceClient; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.ConfigurationContextFactory; import org.apache.neethi.Policy; import org.apache.rampart.policy.model.CryptoConfig; import org.apache.rampart.policy.model.RampartConfig; import tn.nat.cnss.client.Sample4SignEncryptBodyServiceStub.ArrayOfString; import tn.nat.cnss.client.Sample4SignEncryptBodyServiceStub.DoTheJob; import tn.nat.cnss.client.Sample4SignEncryptBodyServiceStub.DoTheJobResponse; import tn.nat.cnss.client.Sample4SignEncryptBodyServiceStub.ServiceRequest; public class Sample4SignEncryptBodyServiceClient { public static void main(String[] args) throws Exception { org.apache.log4j.Logger.getRootLogger().setLevel(org.apache.log4j.Level.OFF); ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem("client-repo", null); Sample4SignEncryptBodyServiceStub stub = new Sample4SignEncryptBodyServiceStub(ctx,"http://localhost:8080/axis2/services/Sample4SignEncryptBodyService"); ServiceClient sc = stub._getServiceClient(); sc.engageModule("rampart"); Policy rampartConfig = getRampartConfig(); sc.getAxisService().getPolicySubject().attachPolicy(rampartConfig); ServiceRequest req = new ServiceRequest(); req.setParam1("0"); req.setParam2("99999"); req.setParam3("99999"); DoTheJob d = new DoTheJob(); d.setServiceRequest(req); DoTheJobResponse resp = stub.doTheJob(d); System.out.println(resp.get_return().getResult()); System.out.println("Nombre de colonnes: " + resp.get_return().getNumberColumns()); System.out.println("Nombre de lignes: " + resp.get_return().getNumberLines()); for (ArrayOfString array : resp.get_return().getDataSet()) { for (String s : array.localArray) { System.out.print(s + "\t"); } System.out.println(); } stub.cleanup(); } private static Policy getRampartConfig() { RampartConfig rampartConfig = new RampartConfig(); rampartConfig.setUser("clientkey"); rampartConfig.setEncryptionUser("servicekey"); rampartConfig.setPwCbClass("tn.nat.cnss.client.PasswordCallBackHandler"); CryptoConfig sigCrypto = new CryptoConfig(); sigCrypto.setProvider("org.apache.ws.security.components.crypto.Merlin"); Properties props = new Properties(); props.setProperty("org.apache.ws.security.crypto.merlin.keystore.type", "JKS"); props.setProperty("org.apache.ws.security.crypto.merlin.file","keys/client.jks"); props.setProperty("org.apache.ws.security.crypto.merlin.keystore.password", "clientStorePW"); sigCrypto.setProp(props); rampartConfig.setSigCryptoConfig(sigCrypto); Policy policy = new Policy(); policy.addAssertion(rampartConfig); return policy; } }
In the PasswordCallBackHandler class we just statically (no database access) provide passwords respectively protecting the client private key and the service public key.
package tn.nat.cnss.client; import org.apache.ws.security.WSPasswordCallback; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import java.io.IOException; public class PasswordCallBackHandler implements CallbackHandler { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i]; if(pwcb.getIdentifier().equals("clientkey")) { pwcb.setPassword("clientPW"); return; } if(pwcb.getIdentifier().equals("servicekey")) { pwcb.setPassword("servicePW"); return; } } } }
Remark 4: We did not present cases where SSL is used by clients to access web services bound to policies requiring use of UserNameToken or sigining and/or encrypting exchanged SOAP messages.
This could be easily done by adding SSL configuration given for the client described in Section III.2:
- define the trust-store and its password,
- construct the service stub using the HTTPS endpoint of the web service.
IV - Apache Axis2 Server Install and Configuration:
In this section we discuss the install instruction and needed configuration to build a working Apache Axis2 server.IV.1 - Apache Axis2 Different Install Modes:
There are 3 possible ways to install an Apache Axis2 Server:- A stand-alone server: in this scenario we need to download the binary distribution of Apache Axis2. The server is independent and includes a container into which services (.aar archives) can be deployed.
- An integrated container under Apache Tomcat Server: in this scenario we need to download the .war archive of Apache Axis2. The server is then deployed as a web application under Apache Tomcat Server. This is the scenario we choose
- An integrated container under Apache Ode (Orchestration) Engine: in this scenario we need to download the .war archive of Apache Ode which comes with a built-in Apache Axis2 container. In this setting we can take advantage of the Apache Ode orchestration features but we will be forced to use the version of Apache Axis2 that is supported by the Apache Ode (which is not necessarily the last Apache Axis2 available version).
In next sections, we describe install and configuration for the 2nd scenario.
IV.2 - Apache Tomcat Configuration:
We will discuss two configuration topics for Apache Tomcat:- Enabling SSL
- Hashing Apache Tomcat users' passwords
The first point is mandatory if we want to take advantage of Apache Axis2 Transport-Level security policies. The second point is optional.
Enabling SSL for Apache Tomcat:
- Generating a key pair for the server:
Execute the following command:
$JAVA_HOME/bin/keytool -genkey -alias tomcat -keyalg RSA -keystore $CATALINA_HOME/keys/server.jks
After executing this command, you will first be prompted for the keystore password. The default password used by Tomcat is "changeit" (all lower case), although you can specify a custom password if you like.
You will also need to specify the custom password in the server.xml configuration file, as described later.
Next, you will be prompted for general information about this Certificate, such as company, contact name, and so on. This information will be displayed to users who attempt to access a secure page in your application, so make sure that the information provided here matches what they will expect.
Finally, you will be prompted for the key password, which is the password specifically for this Certificate (as opposed to any other Certificates stored in the same keystore file). The keytool prompt will tell you that pressing the ENTER key automatically uses the same password for the key as the keystore. You are free to use the same password or to select a custom one. If you select a different password to the keystore password, you will also need to specify the custom password in the server.xml configuration file. - Copy the resut to: $CATALINA_HOME/keys/server.jks
- Edit $CATALINA_HOME/conf/server.xml, and add the following:
<Connector port="8443" maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" disableUploadTimeout="true" acceptCount="100" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" keystoreFile="$CATALINA_HOME/conf/server.jks" keystorePass="keystorePassword" keyAlias="tomcat" keyPass="tomcatKeyPassword" SSLEnabled="true" />
Hashing Apache Tomcat users' passwords:
- Edit $CATALINA_HOME/conf/server.xml, and change:
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
to:
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase" digest="sha-256"/>
- Create the hash for the wanted password:
$CATALINA_HOME/bin/digest.sh -a sha-256 password
The results is: password: <hashed_password> - Edit $CATALINA_HOME/conf/tomcat-users.xml and change:
<user username = "admin" password ="password" roles = "tomcat,admin,manager-gui,manager-script,manager-jmx,manager-status" />
to:<user username = "admin" password = "<hashed_password>" roles = "tomcat,admin,manager-gui,manager-script,manager-jmx,manager-status" />
IV.3 - Apache Axis2 Configuration:
We discuss in this section deployment of Apache Axis2 .war archive under existing Apache Tomcat instance and enabling SSL for Apache Axis2.
- Deploy axis2.war under $CATALINA_HOME/webapps/
- Engage Apache Rampart module:
- copy rampart-1.7.0.mar to $CATALINA_HOME/webapps/axis2/WEB-INF/modules/
- copy all .jar archives in lib/ folder of Apache Rampart binary distribution to $CATALINA_HOME/webapps/axis2/WEB-INF/lib/
-
Enable SSL receiving for axis:
Edit $CATALINA_HOME/webapps/axis2/WEB-INF/conf/axis2.xml and add the following:
<transportReceiver name="https" class="org.apache.axis2.transport.http.AxisServletListener" />
just after the existing HTTP configuration:
<transportReceiver name="http" class="org.apache.axis2.transport.http.AxisServletListener" />
You may need to specify ports for each <transportReceiver />, if they are different from default ones (8080 for HTTP and 8443 for HTTPS), as follows:
<transportReceiver name="http" class="org.apache.axis2.transport.http.AxisServletListener" > <parameter name="port">9080</parameter> <transportReceiver/> <transportReceiver name="https" class="org.apache.axis2.transport.http.AxisServletListener" > <parameter name="port">9443</parameter> <transportReceiver/>
IV.4 - BouncyCastle Security Provider:
The install of BouncyCastle Security Provider is discussed below:- Edit $JAVA_HOME/jre/lib/security/java.security for the JRE/JDK you are using.
Look for a list of lines with security.provider.X where X is some number.
At the bottom of the list add the line:
security.provider.N=org.bouncycastle.jce.provider.BouncyCastleProvider
where N is one more than the last number in the list. - Copy bcprov-jdk15on-154.jar to $JAVA_HOME/jre/lib/ext/
- Download jce_policy-8.zip (for Java 8) from here.
- Unzip the file and copy the two jar:
- local_policy.jar
- US_export_policy.jar
V - Download Services (and Clients) implementations:
You can download below .zip files containing services and their clients implementations as Eclipse Projects. Each Eclipse project contains implementation for the corresponding service and an ant build file (ant-aar-generator.xml) permitting to generate the service .aar archive. So feel free to update the service logic and use the build file to get the updated .aar archive. Each Eclipse Project contains also Java classes implementing the client of the corresponding service. Thus, after deploying the .aar archive of the service under Apache Axis2 Server, you can use these classes to test it.
To use UserNameToken authentication and other security policy samples you need to insert the lines above to the UTILISATEUR table (arabic letters are used to verify that UTF-8 encoding works perfectly with Apache Axis2 and Rampart 1.7.0 : that was not the case in previous versions):
To use UserNameToken authentication and other security policy samples you need to insert the lines above to the UTILISATEUR table (arabic letters are used to verify that UTF-8 encoding works perfectly with Apache Axis2 and Rampart 1.7.0 : that was not the case in previous versions):
INSERT INTO "SKELETON"."UTILISATEUR" (ID, NOM_UTILISATEUR, MOT_DE_PASSE, IDENTITE, AFFECTATION) VALUES ('99999', '99999', '99999', '99999', 'Dir. Informatique'); INSERT INTO "SKELETON"."UTILISATEUR" (ID, NOM_UTILISATEUR, MOT_DE_PASSE, IDENTITE, AFFECTATION) VALUES ('88888', 'clientkey', 'clientPW', 'المستعمل', 'Dir. Informatique'); INSERT INTO "SKELETON"."UTILISATEUR" (ID, NOM_UTILISATEUR, MOT_DE_PASSE, IDENTITE, AFFECTATION) VALUES ('77777', 'servicekey', 'servicePW', 'الخادم', 'Dir. Informatique');
Before testing SSL versions, you need to update the trust-store file (server.jks) and password (client implementation) to match the keystore of your Apache Tomcat instance. An easier way would be to configure your Apache Tomcat instance to use the supplied server.jks (for testing purpose).
Concerning the provided client.jks (respectively service.jks), the keystore containing the client's key pair and the service's public key (respectively containing the service's key pair and the client's public key) they were generated using the keytool command as follows:
$JAVA_HOME/bin/keytool -genkey -alias servicekey -keypass servicePW -keyalg RSA -sigalg SHA1withRSA -keystore service.jks -storepass serviceStorePW $JAVA_HOME/bin/keytool -genkey -alias clientKey -keypass clientPW -keyalg RSA -sigalg SHA1withRSA -keystore client.jks -storepass clientStorePW $JAVA_HOME/bin/keytool -export -alias servicekey -keystore service.jks -storepass serviceStorePW -file service_cert.cer $JAVA_HOME/bin/keytool -import -alias servicekey -keystore client.jks -storepass clientStorePW -file service_cert.cer $JAVA_HOME/bin/keytool -export -alias clientKey -keystore client.jks -storepass clientStorePW -file client_cert.cer $JAVA_HOME/bin/keytool -import -alias clientKey -keystore service.jks -storepass serviceStorePW -file client_cert.cer
Keywords: Apache Axis2, Apache Rampart, Encryption, Signature, UserNameToken,
No comments:
Post a Comment