Mar. 10th, 2015

Take the Pain out of Load Testing Secure Web Services

There’s no getting around the fact that security is a major consideration for any website or app. Sure, if you’re sharing public information like weather forecasts or currency exchange rates, you can allow anonymous user access. But, in many cases, you’ll need to apply some sort of restricted access to sensitive or private data.

 

This is more secure for your user...but much more of a headache for you!

 

So, in today’s post, I’m going to try to take the pain away by showing you how to test secure web services with Apache JMeter. If you’re not already familiar with load testing web services with JMeter, please take a look at the guide “Testing SOAP/REST Web Services Using JMeter” first - it will help.

 

Let’s get started!

 

First of all, here’s a rundown of the different ways that web services can be secured:

 

  1. Protocol security (when security is applied on a transport level)

  2. Username tokens (username/password combination)

  3. Timestamp

  4. Signature

  5. Policy

  6. Signature using certificates (i.e. X.509)

  7. SAML tokens

  8. A combination of the methods above

 

As you can see, there are many different options available. More than I can cover in-depth in this article! So, I’m going to give you an overview to testing secured web services with Apache JMeter. If you want more specific details on how to determine mechanisms and recommendations on bypassing them, you might need to talk to your developers.

 

Test Scenario

 

Let’s take the example of a simple “Hello World” service. This will take an arbitrary string as a parameter and returns with “Hello world, ${inputString}”.

 

Here’s an example of the request skeleton:

 

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:typ="http://www.wso2.org/types">
  <soapenv:Header/>
  <soapenv:Body>
     <typ:greet>         
        <name>Blazemeter</name>
     </typ:greet>
  </soapenv:Body>
</soapenv:Envelope>


 

..and here’s an example of the response:

 

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Body>
       <ns:greetResponse xmlns:ns="http://www.wso2.org/types">
           <return>Hello World, Blazemeter !!!</return>
       </ns:greetResponse>
   </soapenv:Body>
</soapenv:Envelope>


 

Protocol-Based Security

 

The easiest way to apply security to a web service is by using underlying protocol (HTTP or HTTPS) security. We’ve already covered this in some of our knowledgebase articles. Check these out:

 

 

JMeter’s HTTP Authorization Manager should be enough to handle protocol-based authentications.

 

Don’t forget: you have two ways that you can send requests to SOAP (XML-based) services

 

  1. SOAP/XML-RPC Request sampler. This supports the POST method only and uses underlying implementation from HTTPClient 3.1

  2. HTTP Request Sampler. This supports all HTTP methods (GET, PUT, DELETE, etc.) and you can choose any implementation (Java, HTTPClient 3.1, HttpClient 4.x etc.). So, if you want to get Kerberos authentication or if your web service lives behind the load balancer, you might need to switch to the HTTP Request. If you’ll do this, you’ll need to add the HTTP Header Manager. This will allow you to:

    • send the “SOAPAction” header for XML-based web services requiring SOAPAction

    • send the “Content-Type” header (“application/json” for REST and “text/xml” or “application/xml” for SOAP)

 

Here’s an example of a response without the Authentication Manager:

 

...and here’s the same request with the HTTP Authorization Manager enabled:

 

How to Add a Timestamp

 

Sometimes protocol-level security and/or username tokens just aren’t enough - and you need to add a timestamp. This determines the request’s “time-to-live” and is actually mandatory in the SOAP message security header. The timestamp has two parameters:

 

  • <wsu:Created> - the request start time

  • <wsu:Expires> - the time span in which responses will be accepted (starting from the request start time). If the response time exceeds the defined time frame, the response will fail.

 

A missing or incorrect timestamp could mean requests will fail.

So we need to do the following:

 

  1. Inject the security header into the message

  2. Dynamically populate the tag values: <wsu:Created> and <wsu:Expires>

 

In order to carry out point number one, we’ll need to change our request skeleton slightly. It should now look like this:

 

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:typ="http://www.wso2.org/types">
   <soapenv:Header>
       <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                      xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
           <wsu:Timestamp>
               <wsu:Created>#CREATED#</wsu:Created>
               <wsu:Expires>#EXPIRES#</wsu:Expires>
           </wsu:Timestamp>
       </wsse:Security>
   </soapenv:Header>
   <soapenv:Body>
       <typ:greet>           
           <name>Blazemeter</name>
       </typ:greet>
   </soapenv:Body>
</soapenv:Envelope>


 

Now we just need to replace the #CREATED# and #EXPIRES# placeholders with the following values:  current time and current time + time to live. I’m using the JMeter scripting feature for this, so I’ll replace them through the JSR223 PreProcessor

 

In the PreProcessor we need to do the following:

 

  1. Get the request XML data

  2. Substitute placeholders with date values

  3. Amend the request XML data so changes from point two will apply

 

Here’s the reference code for the JSR223 PreProcessor:

 

import java.text.SimpleDateFormat;

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Date now = new Date();
Date expires = new Date(now.getTime() + 300000L);
String wsuCreated = sdf.format(now);
String wsuExpires = sdf.format(expires);
String data = sampler.getXmlData();
data = data.replaceFirst("#CREATED#", wsuCreated);
data = data.replaceFirst("#EXPIRES#", wsuExpires);
sampler.setXmlData(data);

 

The PreProcessor’s code is pretty self-explanatory. It constructs timestamp entries for the current time and the current time + 5 minutes and generates them as timestamps to replace the placeholders: #CREATED# and #EXPIRES#.

How to Test Signatures With the X.509 Certificate

 

Last but not least comes the signature approach. This is the most advanced protection mechanism so, unsurprisingly, it’s also the hardest one to simulate in a JMeter test.

 

If you send an inaccurate or incomplete request to the web service with certificate-based signature requirements, you’ll get an error message like “Invalid signature” or “Message not signed”.

So you should carefully inspect the signature element of the <wsse:Security> header. This will give you basic information on accepted canonicalization, translation and digest methods and algorithms.

..and this will allow you to develop the correct signature generation code.

 

Just one thing: you’ll also need to have the X.509 certificate itself. If you don’t have it, you can get one from the application under the test uses. If you don’t have access to the application, then it’s worth asking your colleagues to get it for you. This is super important as a missing or incorrect certificate is a showstopper when it comes to running any further testing.

 

This should be available in the test side of the application. As around if you don’t access as you won’t be able to make a successful request without this certificate.

 

Another optional (but very convenient!) step is to find out what type of web service is being tested. Popular Java web services like Apache Axis and Apache CXF have client libraries which can make your life much easier.  

 

Here’s an example showing how to generate the X.509 certificate signature using pure Java. This isn’t a “swiss-army-knife” solution which works for any web service, so please use it as a reference only to get an idea of how the signature is generated and what the request should look like.

 

import com.sun.org.apache.xml.internal.security.Init;
import com.sun.org.apache.xml.internal.security.c14n.Canonicalizer;
import com.sun.org.apache.xml.internal.security.signature.XMLSignature;
import com.sun.org.apache.xml.internal.security.transforms.Transforms;
import com.sun.org.apache.xml.internal.security.utils.Constants;
import com.sun.org.apache.xml.internal.security.utils.XMLUtils;
import org.apache.jmeter.protocol.http.sampler.SoapSampler;
import org.apache.commons.io.FileUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;        

// generate timestamp
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Date now = new Date();
Date expires = new Date(now.getTime() + 300000L);
String wsuCreated = sdf.format(now);
String wsuExpires = sdf.format(expires);
String data = sampler.getXmlData();
data = data.replaceFirst("#CREATED#", wsuCreated);
data = data.replaceFirst("#EXPIRES#", wsuExpires);
sampler.setXmlData(data);


File signatureFile = new File("signature.xml");
// write sampler data to file
{        

String data = sampler.getXmlData();
FileOutputStream fout = new FileOutputStream(signatureFile);
fout.write(data.getBytes());
fout.flush();
fout.close();
}

//X509 properties

String keystoreType = "JKS";
String keystoreFile = "wso2carbon.jks";
String keystorePass = "wso2carbon";
String privateKeyAlias = "wso2carbon";
String privateKeyPass = "wso2carbon";
String certificateAlias = "wso2carbon";


Element element = null;
String BaseURI = signatureFile.toURI().toURL().toString();
//SOAP envelope to be signed


//get the private key used to sign, from the keystore
KeyStore ks = KeyStore.getInstance(keystoreType);
FileInputStream fis = new FileInputStream(keystoreFile);
ks.load(fis, keystorePass.toCharArray());
PrivateKey privateKey =

(PrivateKey) ks.getKey(privateKeyAlias, privateKeyPass.toCharArray());
//create basic structure of signature
javax.xml.parsers.DocumentBuilderFactory dbf =
     javax.xml.parsers.DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
String request = sampler.getXmlData();
ByteArrayInputStream in = new ByteArrayInputStream(request.getBytes());
Document doc = dBuilder.parse(in);
in.close();
Init.init();
XMLSignature sig =
     new XMLSignature(doc, BaseURI, XMLSignature.ALGO_ID_SIGNATURE_RSA);

element = doc.getDocumentElement();
element.normalize();
element.getElementsByTagName("soapenv:Header").item(0).appendChild(sig.getElement());

{
 Transforms transforms = new Transforms(doc);
 transforms.addTransform(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
 //Sign the content of SOAP Envelope
 sig.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);
}

//Signing procedure
{
 X509Certificate cert =
         (X509Certificate) ks.getCertificate(certificateAlias);
 sig.addKeyInfo(cert);
 sig.addKeyInfo(cert.getPublicKey());
 sig.sign(privateKey);
}

//write signature to file
FileOutputStream f = new FileOutputStream(signatureFile);
XMLUtils.outputDOMc14nWithComments(doc, f);
f.close();


//set sampler's XML data from file
String request = FileUtils.readFileToString(signatureFile);
sampler.setXmlData(request);

 

So, if you add the above code to the JSR223 PreProcessor you’ll see that the timestamp and signature are now added to the <wsse:Security> header - and that the request is properly signed.

 

This is the same for the response, which now has the timestamp and signature applied.

 

I hope that you now have a better understanding of how to test secure web services with Apache JMeter - and that I’ve taken some of the pain out of it! Regardless of how tight the security is on your web service, you can use JMeter to load test it.

 

Blazemeter’s systems also support all the approaches I’ve outlined in this article - and make testing even easier in many cases. For example: when testing the  X.509 certificate signature, you can just upload your keystore and/or certificate files along with your .jmx script file.

 

If something isn’t clear enough or you’re looking for a solution for a specific challenge that I haven’t mentioned, please let me know in the comments below. I’ll try to solve your problem individually or even update this guide with the solution.

 

Liked this post? You might also find these useful...

 

How to Set Your JMeter Load Test to Use Your Client Side Certificates
 

Top 3 Options for Running Performance Tests Behind Your Corporate Firewall

 

 

 

 

Interested in writing for our Blog? Send us a pitch!