Thursday, 4 March 2010

Setting up WS-Security with Rampart module in Apache Axis web services framework

NOTE: This is from a work document I wrote that I published to Blogger via Google Docs. I'll edit it later, remove Appendices, make it more blog-y and nicer looking, but the info should be useful to someone out there.

Setting up WS-Security with Rampart module in Axis2, for authentication using Password Digest

Instructions

 

These instructions assume that the user is using Axis2 and is packaging their web service into an .aar package

This has been tested using Axis2 1.4.1 - 1.5.1 on JDK 5.

1. Download these:

Apache Axis2 1.4.1 (or latest version)

Apache Rampart 1.4 (or latest version) - security module for Axis2

Bouncycastle Java Cryptography Extensions (JCE) provider for your Java version
http://www.bouncycastle.org/latest_releases.html


2. Install Bouncycastle in JVM, or copy it to Axis webapp's /WEB-INF/lib/

Bouncycastle library is a Rampart dependency, so we need to include the Bouncycastle JCE provider in either the webapp's /WEB-INF/lib or install it in the JVM

2.1 Adding bouncycastle as a security provider in JVM

2.1.1 Add the bcprov-jdk15-***.jar to your service's / client's classpath.
Don't get the file that ends in "-ext.jar" because these ones contain patented, non-free code that is not needed by Rampart.

bcprov-jdk15 is specified by Rampart in its dependency listing but bcprov-jdk16 should also work,  

2.1.2 Add the following line to  file which can be found in JRE's lib/security/java.security directory as the last line.

security.provider.X=org.bouncycastle.jce.provider.BouncyCastleProvider

- "X" is the next unused number available for the listed security provider)

Also see: http://www.bouncycastle.org/documentation.html


3.1 To modify an existing axis2 webapp: 

Copy the *-codegen-*.jar files from Axis installation /lib into your existing webapp's /WEB-INF/lib directory.


3.2 To create a new axis2.war webapp:

You can use the Ant build.xml provided in the Axis2 installation /webapp directory to create axis2.war, provided you make one change: delete the line <exclude name="axis2-codegen*.jar"/> near the end of the file.

Then open a console to the Axis2 webapp directory and run ant.

After the build.xml runs, you can find the created axis2.war Web application in the Axis2 installation /dist directory


4. Copy from Rampart  /lib  to Axis2 webapp /WEB-INF/lib

opensaml-1.1.jar
rampart-core-1.4.jar
rampart-policy-1.4.jar
rampart-trust-1.4.jar
wss4j-1.5.4.jar
xmlsec-1.4.1.jar


5. Rampart needs the library backport-util-concurrent-3.1.jar. It's no longer present in Axis2 1.5 and higher, so for these versions you'll need to get it from:

http://backport-jsr166.sourceforge.net/

or

Axis2 1.4.1  /lib

And copy the jar file to  /WEB-INF/lib of your Axis2 webapp

In case of any further dependency issues with Rampart, see: http://ws.apache.org/rampart/dependencies.html


6. Copy from Rampart /modules to Axis2 webapp /WEB-INF/modules

rahas{version}.mar
rampart-{version}.mar

ie,

rahas-1.4.mar
rampart-1.4.mar

Then add these filenames to /WEB-INF/modules/modules.list , one per line


7. Create a password callback handler that will be used by Rampart to handle authentication. The class must implement the interface javax.security.auth.callback.CallbackHandler.

If multiple services in different code bases or .aar will be using the same authentication functionality, it is recommended that the callback handler be set up in its own jar file then copied to the Axis2 webapp's /WEB-INF/lib.
 

See Appendix for structure of callback handler.



8. Update the services.xml for each web service and add the following xml fragments between the <service> </service> tags.

The following are the WS-Policy to use for authentication using password plaintext and password digest.

 

The <module> and <ramp:RampartConfig> elements must be present when using Rampart, and are only relevant when using the Axis2 framework.


8.1 WS-Policy using PlainText password - username and password will be sent in soap headers, and password sent in plaintext

<module ref="rampart"/>

<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:SupportingTokens
              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:SupportingTokens>

          <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
            <ramp:passwordCallbackClass>[REPLACE THIS WITH OUR PASSWORD CALLBACK CLASS, ie, security.package.PWCallBackHandler]</ramp:passwordCallbackClass>
          </ramp:RampartConfig>

        </wsp:All>
      </wsp:ExactlyOne>
</wsp:Policy>



8.2  WS-Policy using PasswordDigest

<module ref="rampart"/>

<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:SupportingTokens
          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:SupportingTokens>

      <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
        <ramp:passwordCallbackClass>[REPLACE THIS WITH OUR PASSWORD CALLBACK CLASS, ie, security.package.PWCallBackHandler]</ramp:passwordCallbackClass>
      </ramp:RampartConfig>

    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>


9. Test that everything is working by configuring plaintext authentication. Plaintext authentication is easier to test with a tool like SoapUI because you can simply cut and paste XML and enter the login details in the header.

 

Testing of password digest authentication will require code of some kind to generate the various elements and calculate the digest before sending the client request.
 

 

Appendix A.

 

About Password Digest authentication


In the case where the communicating parties - the requester and the service - uses an insecure transport channel they should take steps to protect the passwords being exposed to others. Here the requester creates a digest of the actual password concatenated with a set of random bytes (nonce) and another value that is dependent on the creation time (created).


This digest is computed as follows :

digest = Base64_encode(SHA-1(nonce+created+password))

This is the base 64 encoded, SHA-1 digest of the concatenation of the nonce, created and password values.

The requester will send the username, nonce, created, and the digest values in within
the UsernameToken to the service.

 

To authenticate the request the service will compute
the digest value using the password bound to the received usename and will compare the
received digest value and the computed digest value.

 

An example UsernameToken that uses a digest of the password is shown in following figure:

<soapenv:Header>
     <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
        <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-26752749">
         <wsse:Username>libuser</wsse:Username>
         <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">/Wt/2yDdZwa8a5qd7U70hrp29/w=</wsse:Password>
         <wsse:Nonce>4ZQz5ytME/RXfChuKJ03iA==</wsse:Nonce>
         <wsu:Created>2009-03-17T11:20:57.467Z</wsu:Created>
      </wsse:UsernameToken>
    </wsse:Security>
 </soapenv:Header>

If password in the username token is digested, the password callback handler only needs to provide the password for the client, to allow Rampart to verify it with the password received with the username token.
 

 

Appendix B


Password Callback handler implementation
 

The general structure of the server-side class will be like this:

public class PWCBHandler implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException,
            UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
            WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
            String username = pwcb.getIdentifer();
            String password = pwcb.getPassword();

            int usage = pwcb.getUsage();
 

/**

Usage is WSPasswordCallback.USERNAME_TOKEN for a digested password, and WSPasswordCallback.USERNAME_TOKEN_UNKNOWN for a plaintext password or a password of unknown type.

 

*/


            // used when plaintext password in message
            if (usage == WSPasswordCallback.USERNAME_TOKEN_UNKNOWN) {

/**

Verify username and password with existing authentication service
 

eg, authService.authenticate(username, password); 

If authentication fails, you should throw an UnsupportedCallbackException

If password digest is mandatory, then this section should either be
removed or just throw an UnsupportedCallbackException so that requests using plaintext password will fail.

*/
                             
            // when hashed password in message
            } else if (pwcb.getUsage() == WSPasswordCallback.USERNAME_TOKEN) {

    

/**                       
In actual app, this should be replaced by code that will get the password for given username and specify it in pwcb.setPassword()


eg, pwcb.setPassword(authService.getPassword(id));

 

Rampart will take care of calculating the digest and comparing it with the digest sent in the request.

 

This assumes we have a way of retrieving passwords in plaintext.

 

If the auth system only returns password hashes, then client should use also use password hashIn such cases, the client should use same hash method as the auth system, so if the password is only available as an md5 hash, then the client should also generate an md5 hash of the password before calculating the digest.

 

*/


            }
        }
    }
}

Appendix C

 

References:

Web Services Security, UsernameToken Profile 1.0
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0.pdf
Contains explanation for design of password digest.

Java Web services: Axis2 WS-Security basics
http://www.ibm.com/developerworks/webservices/library/j-jws4/index.html


Password Callback Handlers Explained
http://wso2.org/library/3733


UsernameToken Authentication with Rampart
http://wso2.org/library/240#digest

The sample code for this article is incomplete and does not contain examples for password digest.
 

 

Appendix D

 

Examples of various authentication requests and error responses

 

1. Plaintext – simple XML request

 

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

    <soapenv:Header>
       <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                 soapenv:mustUnderstand="1">

        <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
                    wsu:Id="UsernameToken-1815911473">
                          <wsse:Username>client</wsse:Username>
                       <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">apache</wsse:Password>
         </wsse:UsernameToken>
       </wsse:Security>
    </soapenv:Header>
 

   <soapenv:Body/>
</soapenv:Envelope>
 


2.  Password Digest - Bad Request with correct format but incorrect values.
 

Request:


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

    <soapenv:Header>

         <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
            <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-26752749">
               <wsse:Username>libuser</wsse:Username>
               <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">wOaGIASh06nYuDs6RejYky0nsec=</wsse:Password>
               <wsse:Nonce>ole1X/60hjUqbUt3P2ak6w==</wsse:Nonce>
               <wsu:Created>2009-10-28T06:14:55.092Z</wsu:Created>
            </wsse:UsernameToken>
         </wsse:Security>

    </soapenv:Header>

   <soapenv:Body/>
</soapenv:Envelope>

Response:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Body>
      <soapenv:Fault xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
         <faultcode>wsse:InvalidSecurity</faultcode>
         <faultstring>The security token could not be authenticated or authorized</faultstring>
         <detail/>
      </soapenv:Fault>
   </soapenv:Body>
</soapenv:Envelope>

 

3. Password Digest - Bad Request with incorrect format, plaintext password
 

Request:


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

    <soapenv:Header>

             <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                 soapenv:mustUnderstand="1">

               <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
                    wsu:Id="UsernameToken-1815911473">
                 <wsse:Username>client</wsse:Username>
                 <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">apache</wsse:Password>
              </wsse:UsernameToken>
            </wsse:Security>

    </soapenv:Header>
   <soapenv:Body/>
</soapenv:Envelope>


Response:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Body>
      <soapenv:Fault xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
         <faultcode>wsse:InvalidSecurity</faultcode>
         <faultstring>The security token could not be authenticated or authorized</faultstring>
         <detail/>
      </soapenv:Fault>
   </soapenv:Body>
</soapenv:Envelope>


4. Password Digest - Bad Request, incomplete format, password digest is required, but plaintext is sent instead of digest, and without nonce and createdtime
 

Request:


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

    <soapenv:Header>

             <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                 soapenv:mustUnderstand="1">

               <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
                    wsu:Id="UsernameToken-1815911473">
                 <wsse:Username>client</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">plaintext password</wsse:Password>
              </wsse:UsernameToken>
            </wsse:Security>
    </soapenv:Header>
   <soapenv:Body/>
</soapenv:Envelope>

Response:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Body>
      <soapenv:Fault xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
         <faultcode>wsse:InvalidSecurity</faultcode>
         <faultstring>An invalid security token was provided (Bad UsernameToken Values)</faultstring>
         <detail/>
      </soapenv:Fault>
   </soapenv:Body>
</soapenv:Envelope>
 

New YouTube feature - "Turn down the lights"

This is probably the most awesome and stupidly simple feature that I've seen on YouTube. Hover your mouse over the lightbulb and a tooltip comes up saying "Turn down the lights". Click on it and the page background turns dark grey, making it easier for your eyes to focus on the video.

Tuesday, 2 March 2010

WTF? Weblogic has a lower-case class called "service"

While writing up some unit tests, I was trying to get IntelliJ IDEA to
automatically create a variable declaration like such:


request.setService(service);

-- normally, what would happen is IDEA would pick up that no
declaration exists for "service", and base on the parameter type
expected by the method call "setService", it would insert a
declaration for "service" of the expected type.

But this didn't seem to happen as normal, and then I noticed in the
imports list:


import weblogic.jms.interception.service;


I clicked on "service" and was taken to this decompiled stub:


package weblogic.jms.interception;

public static final class service {
private static weblogic.jms.interception.service singleton;

private service() throws
weblogic.messaging.interception.exceptions.InterceptionServiceException
{ /* compiled code */ }

public static void initialize() throws
weblogic.messaging.interception.exceptions.InterceptionServiceException
{ /* compiled code */ }

private void registerWithInterceptionService() throws
weblogic.messaging.interception.exceptions.InterceptionServiceException
{ /* compiled code */ }

private class JMSInterceptionPointNameDescriptor extends
weblogic.messaging.interception.interfaces.InterceptionPointNameDescriptor
{
private java.lang.String title;

public JMSInterceptionPointNameDescriptor(java.lang.String p2)
{ /* compiled code */ }

public java.lang.String getTitle() { /* compiled code */ }

public int getTotalNumberOfUniqueValue() { /* compiled code */ }

public boolean isValid(java.lang.String s) { /* compiled code */ }
}
}

Friday, 12 February 2010

questions automatically piped to /dev/null

"Is there any way you can deliver early some of your web services?
They want us to release in March."

Asked in mid-february. The project is actually planned for production
release after june.

was wondering if i wanted to LOL or groan.

Saturday, 6 February 2010

Just approved a whole list of moderated comments. Oops!

Just noticed that I had comments needing moderation, and some were from early 2009! I approved all of them except for one spammer promoting some outlook shit. Doh! I'm really sorry, if anyone who has posted a comment actually gets to read this.

Thursday, 21 January 2010

EasyMock gotchas

1. Using Easymock matchers when passing in known values to mock classes.

When we get errors like:

java.lang.IllegalStateException: 2 matchers expected, 1 recorded.
at org.easymock.internal.ExpectedInvocation.createMissingMatchers(ExpectedInvocation.java:42)
at org.easymock.internal.ExpectedInvocation.<init>(ExpectedInvocation.java:34)


It means we are missing a matcher for our mocked classes.

Take this code, for example:

Map<String, Object> params = new HashMap<String, Object>();
List<Integer> groupIds = Arrays.asList(1,2,3);
params.put("groupIds", groupIds);

expect(mockSimpleJdbcTemplate
.queryForList((String)anyObject(), params))
.andReturn(results);


Replace the last line with:

expect(mockSimpleJdbcTemplate
.queryForList((String)anyObject(), eq(params)))
.andReturn(results);

"If you would like to use matchers in a call, you have to specify matchers for all arguments of the method call."

Otherwise, it should only be known values.

If you are mixing known values with matchers, you will have to use the eq() ("equals) static method

See: http://www.easymock.org/EasyMock2_2_Documentation.html


2. when mocking classes that are not an interface, like
SimpleJdbcTemplate, we need to use the createMock, replay, and verify
methods FROM org.easymock.classextension.EasyMock NOT FROM
org.easymock.EasyMock


otherwise, we get this error when running the test:

java.lang.IllegalArgumentException: not a proxy instance
at java.lang.reflect.Proxy.getInvocationHandler(Proxy.java:637)
at org.easymock.EasyMock.getControl(EasyMock.java:1440)
at org.easymock.EasyMock.reset(EasyMock.java:1397)


Update 11 November 2010:
You no longer need to do the easymock.classextension package in (2) if you are using EasyMock 3. The package and classes still exist in EasyMock 3 only for backward compatibility. See the documentation.

Saturday, 16 January 2010

SRSLY? My email to my work address bounced because it had the word "SHIT"

"Delivery to the following recipient failed permanently:

<username removed of course>@optus.com.au

Technical details of permanent failure:
Google tried to deliver your message, but it was rejected by the
recipient domain. We recommend contacting the other email provider for
further information about the cause of this error. The error that the
other server returned was: 550 550 5.7.1 Inappropriate language
used:SHIT (state 18)."

Unfuckingbelievable.

I wonder if Telstra do the same to their work emails.

Monday, 11 January 2010

WTF? Error message that has 1st letter removed from each package name.

Got this error message when trying to run a test in IntelliJ IDEA 9.

NoClassDefFoundError: org/pache/ommons/bcp/asicDataSource

I don't really know what's going on, but somehow each of the package
groups has the 1st character missing. Bizarre.

I'll clean out the IDEA cache using "File --> Invalidate Caches" and
see if that fixes it.

Nope, didn't.

Further down the stacktrace it shows the right name:

Caused by: java.lang.ClassNotFoundException:
org.apache.commons.dbcp.BasicDataSource
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)

Thursday, 3 December 2009

Linux desktop adventures: Trying out Loxomo - 'Password Safe V3 compatible Password Vault'

I've been using Password Safe for a while, but my new workplace uses
Linux desktops (fedora 11), so I've found this compatible substitute,
as Password Safe is Windows-only.

http://www.christoph-sommer.de/loxodo/

But first, I had to install wxWidgets, which was easy enough:

sudo yum install wxWidgets

then another 9.5Mb installation later, created a launcher for the app,
pointed to the loxomo.py file, then voila.

The interface seems a bit limited, and you don't have options like
changing the encryption method or the rules it uses for password
generation but it's functional enough. I'll see how it goes in the
next few days.

Friday, 20 November 2009

Linux app install FAIL - TrueCrypt Suse RPM on Fedora 11

Trying to install truecrypt Suse RPM on Fedora 11, as I don't want to
waste my time compiling source. Ironic, cos I end up wasting time
trying to install dependencies anyway!


1. Failed after install attempt. Missing dependencies.

Do you accept and agree to be bound by the license terms? (yes/no): yes

Running package installer...
error: Failed dependencies:
libfuse2 is needed by truecrypt-6.3-0.i586
xorg-x11-libSM is needed by true


2. Hmmm.. okay, try and get dependencies:

sudo yum install libfuse2
Loaded plugins: refresh-packagekit
Setting up Install Process
No package libfuse2 available.
Nothing to do

3. SHIT! I give up.