JBoss.orgCommunity Documentation

Chapter 38. Digital Signature Framework

38.1. Signing API
38.1.1. Using a KeyRepository
38.1.2. @Signed annotation
38.1.3. Annotation-based verification
38.1.4. Manual verification

Digital signatures allow you to protect the integrity of a message. They are used to verify that a message sent was sent by the actual user that sent the message and was modified in transit. Most web apps handle message integrity by using TLS, like HTTPS, to secure the connection between the client and server. Sometimes though, we have representations that are going to be forwarded to more than one recipient. Some representations may hop around from server to server. In this case, TLS is not enough. There needs to be a mechanism to verify who sent the original representation and that they actually sent that message. This is where digital signatures come in.

Resteasy has developed its own protocol for digital signatures. It allows you to attach one or more digital signatures to a request or response via a Content-Signature header. Signatures are comprised of the message body and an arbitrary set of metadata. Adding metadata to the signature calculation gives you a lot of flexiblity to piggyback various features like expiration and authorization. Here's what an example Content-Signature header might look like.

Content-Signature: values=signer:expiration;
                   headers=Content-Type:Date;
                   signer=bill;
                   expiration="Sunday, 06-Nov-11 08:49:37 GMT";
                   signature=0f341265ffa32211333f6ab2d1

A complete description of the protocol is available on Bill Burke's Blog. We are also submitting this protocol as an Internet-Draft at IETF to hopefully get it standardized in some way or form. There's also another blog about various uses you might find for the protocol. The resteasy distribution also has an example you can check out.

To sign a request or response using the Resteasy client or server framework you need to create an instance of org.jboss.resteasy.security.signing.ContentSignatures. ContentSignatures hold one or more signatures you want to attach to the request or response and models the Content-Signature header. You instantiate the ContentSignatures object and then set the "Content-Signature" header of the request or response. Here's an example of using it on the server-side:

import org.jboss.resteasy.security.signing.ContentSignature;
import org.jboss.resteasy.security.signing.ContentSignatures;
import java.security.PrivateKey;


@Path("/signed")
public static class SignedResource
{
   @GET
   @Produces("text/plain")
   @Path("manual")
   public Response getManual()
   {
      ContentSignature signature = new ContentSignature();
      PrivateKey privateKey = ...; // go find it
      signature.setPrivateKey(privateKey);
      Response.ResponseBuilder builder = Response.ok("hello");
      builder.header("Content-Signature", signature);
      return builder.build();
   }
}

// client example

ContentSignature signature = new ContentSignature();
PrivateKey privateKey = ...; // go find it
signature.setPrivateKey(privateKey);

ClientRequest request = new ClientRequest("http://...");
request.header("Content-Signature", signature);
request.body("text/plain", "some body to sign");
ClientResponse response = request.put();

To sign a message you need a PrivateKey. This can be generated by KeyTool or manually using regular, standard JDK Signature APIs. The default algorithm used is an SHA1 256 hash with RSA, "SHA256withRSA". Resteasy supports any algorithm that uses private/public key pairs and available through the java.security.Signature class. The ContentSignature class also allows you to add and control how various pieces of metadata are added to the Content-Signature header and the signature calculation. See the javadoc for more details.

If you are including more than one signature, then use the ContentSignatures class to hold multiple signatures.

In the above example, you are managing your own keys. Resteasy can manage keys for you through a org.jboss.resteasy.security.KeyRepository. Currently, only Java KeyStores are supported, but you can implement and plug in your own implementation of this interface if you need a different mechanism to manage keys.

If you're going to use the built in Java KeyStore support, use Java keytool to generate a private and public key, for example:

$ keytool -genkeypair -alias myKey -keyalg RSA -keysize 1024 -keystore my-apps.jks 

You can always import your own official certificates too. See the JDK documentation for more details.

You can reference a Java key store you want the Resteasy signature framework to use within web.xml using either resteasy.keystore.classpath or resteasy.keystore.filename context parameters. You must also specify the password (sorry its clear text) using the resteasy.keystore.password context parameter. For example:

    <context-param>
        <param-name>resteasy.keystore.classpath</param-name>
        <param-value>test.jks</param-value>
    </context-param>
    <context-param>
        <param-name>resteasy.keystore.password</param-name>
        <param-value>geheim</param-value>
    </context-param>

If you invoke the ContentSignature.setKeyAlias() method, resteasy will use a configured KeyRepository to lookup that key alias within the repository. Alternatively, if you do not set the key alias via setKeyAlias(), Resteasy will use the signer attribute of the ContentSignature which is set via the ContentSignature.setSigner() method. For example:

   @GET
   @Produces("text/plain")
   @Path("manual")
   public Response getManual()
   {
      ContentSignature signature = new ContentSignature();
      signature.setKeyAlias("myKey);
      Response.ResponseBuilder builder = Response.ok("hello");
      builder.header("Content-Signature", signature);
      return builder.build();
   }

You can also manually register your own instance of a KeyRepository within an Application class. For example:

import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.security.keys.KeyRepository;
import org.jboss.resteasy.security.keys.KeyStoreKeyRepository;

import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;

public class SignatureApplication extends Application
{
   private HashSet<Class<?>> classes = new HashSet<Class<?>>();
   private KeyRepository repository;

   public SignatureApplication(@Context Dispatcher dispatcher)
   {
      classes.add(SignedResource.class);

      InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("test.jks");
      repository = new KeyStoreKeyRepository(is, "password");
      dispatcher.getDefaultContextObjects().put(KeyRepository.class, repository);
   }

   @Override
   public Set<Class<?>> getClasses()
   {
      return classes;
   }
}

On the client side, you can load a KeyStore manually, by instantiating an instance of org.jboss.resteasy.security.keys.KeyStoreKeyRepository. You then set a request attribute, "org.jboss.resteasy.security.keys.KeyRepository", with the value of the created instance. Use the ClientRequest.getAttributes() method to do this. For example:

KeyStoreKeyRepository keyRepository = new KeyStoreKeyRepository("/usr/local/home/bill/my-apps.jks", "geheim");

ContentSignature signature = new ContentSignature();
signature.setSigner("myKey", true, true);

ClientRequest request = new ClientRequest("http://...");
request.getAttributes().put(KeyRepository.class.getName(), repository);
request.header("Content-Signature", signatures);

If you want fine grain control over verification, this is an API to verify signatures manually. Its a little tricky because you'll need the raw bytes of the HTTP message body in order to verify the signature. You can get at an unmarshalled message body as well as the underlying raw bytes by using a org.jboss.resteasy.spi.MarshalledEntity injection. Here's an example of doing this on the server side:

@POST
@Consumes("text/plain")
@Path("verify-manual")
public void verifyManual(@HeaderParam("Content-Signature") ContentSignature signature,
                         @Context KeyRepository repository, 
                         @Context HttpHeaders headers, 
                         MarshalledEntity<String> input) throws Exception
{
   PublicKey publicKey = repository.getPublicKey("someAlias");
   boolean verified = signature.verify(headers.getRequestHeaders(), 
                                       input.getMarshalledBytes(),
                                       publicKey);
   System.out.println("The text message posted is: " + input.getEntity());
}

MarshalledEntity is a generic interface. The template parameter should be the Java type you want the message body to be converted into. Use a ContentSignatures injection if you are expecting more than one signature within the Content-Signature header. Here's an example of doing this on the client side:

ClientRequest request = new ClientRequest(TestPortProvider.generateURL("/signed"));
ClientResponse<MarshalledEntity<String>> response = request.get(new GenericType<MarshalledEntity<String>>() {});
MarshalledEntity<String> marshalledEntity = response.getEntity();
String signatureHeader = response.getHeaders().getFirst(ContentSignature.CONTENT_SIGNATURE);

ContentSignature contentSignature = new ContentSignature(signatureHeader);
boolean verified = contentSignature.verify(response.getHeaders(), marshalledEntity.getMarshalledBytes(), publicKey);