Digitally signing and verifying messages

Digital signatures are a mechanism for signing a message payload using public key, also known as asymmetric, cryptography to prove the authenticity of a message. This scheme additionally provides non-repudiation to a message exchange, meaning that a sender will not be able to deny at a future point in time that the message was sent by him/her.

To use this mechanism, a system uses a pair of cryptographic keys that are made up of a private key known only to itself, and a public key that is freely given out to third parties.

Before sending a message, the system uses the private key to generate a message signature (a type of checksum) based on the message contents, and appends it to the message.

The receiving system uses the sender's public key to verify the signature against the message contents. The verification step proves that the message was not changed after being signed and that the originating system was the one who originally signed it.

Note

The digital signing of a message does not encrypt its content in any way.

This recipe will show you how to sign and verify messages using the Camel Crypto Component.

Getting ready

The Java code for this recipe is located in the org.camelcookbook.security.signatures package. The Spring XML files are located under src/main/resources/META-INF/spring and prefixed with signatures.

To use Camel's Crypto Component, you need to add a dependency on the camel-crypto library.

Add the following to the dependencies section of your Maven POM:

<dependency>
  <groupId>org.apache.camel</groupId>
  <artifactId>camel-crypto</artifactId>
  <version>${camel-version}</version>
</dependency>
<dependency>
  <groupId>commons-codec</groupId>
  <artifactId>commons-codec</artifactId>
  <version>1.8</version> <!-- Java 6+ -->
</dependency>

To make life easier around accessing keystores in the Spring XML DSL, we will use the spring-crypto-utils library. This is not strictly required, but otherwise you would need to write some Java factory methods that Spring can call for the purpose of instantiating KeyStore objects.

<dependency>
  <groupId>com.google.code.spring-crypto-utils</groupId>
  <artifactId>spring-crypto-utils</artifactId>
  <version>1.3.0</version>
</dependency>

Before using the cryptographic capabilities available in Camel you will need:

  • keystore: This is a repository of public and private keys
  • truststore: This is a keystore that holds the public keys of parties that your system trusts to communicate with

Note

In a production environment, you should ensure that access to the keystore and truststore files is restricted through appropriate file access permissions so that an unauthorized party cannot modify them.

For the purposes of this example, we will use the keytool utility that is included as part of the Java Development Kit (JDK):

  1. Generate a pair of public and private key for the sending code/system (in this example, known by the alias system_a). This will automatically produce a keystore, as follows:
    # keytool -genkeypair -v -alias system_a -keystore keystore.jks -validity 3650 -dname 'CN=Scott,O=camelbookbook.org' -storepass keystorePassword -keypass keyPasswordA
    
  2. Export the public key certificate:
    # keytool -export -alias system_a -keystore keystore.jks -storepass keystorePassword -rfc -file selfsignedcert_a.cer
    
  3. Import the public key into a truststore for use by the receiving code/system:
    # keytool -import -noprompt -alias system_a -file selfsignedcert_a.cer -keystore truststore.jks -storepass truststorePassword
    
  4. Repeat steps 1-3 for an alias of system_b and a certificate file selfsignedcert_b.cer.

The steps mentioned in the preceding list will generate keys using the DSA algorithm, and hash it using SHA1. The full signature algorithm will therefore be SHA1withDSA; the same as the default algorithm used by camel-crypto.

Note

Getting keys and certificates set up and working correctly against Java's cryptography extensions (and therefore camel-crypto) is complicated due to the number of options—in particular, there is a strong likelihood of misconfiguring the cryptographic algorithms used. See the keytool documentation in the See also section of this recipe for details of the defaults, and the Standard Algorithm Names Documentation for the strings to use to set up alternatives.

How to do it...

In order to perform message signing and verification, you will need to do the following:

  1. Configure the keystores. If using Spring, add the crypt namespace to the beans definition, as follows:
    <beans ...
      xmlns:crypt="http://springcryptoutils.com/schema/crypt"
      xsi:schemaLocation="...
        http://springcryptoutils.com/schema/crypt
          http://springcryptoutils.com/schema/crypt.xsd">

    Then load the keystore and the truststore as Spring beans (your locations will most likely vary):

    <crypt:keystore id="keyStore" 
                    location="classpath:keystore.jks"
                    password="keystorePassword"/>
    <crypt:keystore id="trustStore" 
                    location="classpath:truststore.jks"
                    password="truststorePassword"/>

    If using Java, load and register the java.security.KeyStore instances within the Camel context as follows:

    SimpleRegistry registry = new SimpleRegistry();
    
    // here we are loading keystores stored in our JAR
    ClassLoader classLoader = getClass().getClassLoader();
    
    KeyStore keyStore = KeyStore.getInstance("JKS");
    keyStore.load(classLoader.getResourceAsStream("keystore.jks"), "keystorePassword".toCharArray());
    registry.put("keyStore", keyStore);
    
    KeyStore trustStore = KeyStore.getInstance("JKS");
    trustStore.load(classLoader.getResourceAsStream("truststore.jks"), "truststorePassword".toCharArray());
    registry.put("trustStore", trustStore);
    
    CamelContext camelContext = new DefaultCamelContext(registry);

    Note

    JKS is the default keystore type used to hold key pairs.

  2. To sign a message within your route, send the message to a crypto: endpoint containing details of the key to sign the message with.

    In the XML DSL, you do this through the following statement:

    <to uri="crypto:sign://usingKeystore?keystore=#keyStore&amp;alias=system_a&amp;password=keyPasswordA"/>

    In the Java DSL, this is expressed as:

    .to("crypto:sign://usingKeystore?keystore=#keyStore&alias=system_a&password=keyPasswordA")

    Here we refer to the keystore that contains the private key of our key pair. The alias is used to distinguish between multiple keys in the same keystore. The key pair's password is required to sign the message.

    Note

    The key password is shown in plain text so as to not complicate the example. In a real-world scenario, the password should be injected as an encrypted property. See the Encrypting configuration properties recipe for a description of how to do this.

  3. To verify the message, send the message to a crypto: endpoint that defines which public key from the truststore should be used.

    In the XML DSL, this is performed as follows:

    <to uri="crypto:verify//usingKeystore?keystore=#trustStore&amp;alias=system_a"/>

    In the Java DSL, the same thing is expressed as:

    .to("crypto:verify//usingKeystore?keystore=#trustStore&alias=system_a")

    In order to verify a signed message, we need to know which public key in the truststore to use; we do this by using the alias attribute.

    Note

    When using the crypto: URI, there is no need to explicitly instantiate and register the CryptoComponent class. Camel automatically discovers and registers certain components that require no manual configuration. It does this by scanning its classpath for files included in component JARs containing metadata specifically for this purpose. These files describe which Component class should be instantiated, and the URI scheme that it corresponds to.

How it works...

In the sign phase, the Camel Crypto Component calculates a signature using the password and key specified, and Base64 encodes it before inserting it into the exchange's CamelDigitalSignature header.

These digital signature header names are defined as constants in the org.apache.camel.component.crypto.DigitalSignatureConstants class. For example, the CamelDigitalSignature header is defined in DigitalSignatureConstants.SIGNATURE.

It is up to your routing code to take that header value and pass it appropriately via the transport being used to the system you are communicating with. Likewise, if consuming from a transport, you need to take the signature from the transport mechanism and place it into the CamelDigitalSignature header for verification.

The verify phase looks up the truststore for the public key of the system it expects the message to have come from (in our case system_a), and checks the signature against the public key. If the message was modified after being signed, or the key to check the signature against is missing or is not the correct public key, the verify step will throw a java.security.SignatureException.

This ability to verify gives a certainty that the payload is exactly what was sent. It also provides a mechanism for non-repudiation since only a message signed with the private key of the sender can be verified using their public key.

There's more...

You can change the message signature header that is to be written to when signing, or that is used for verification by setting the signatureHeader URI attribute to another value:

crypto:sign://usingKeystore?keystore=#keyStore&alias=system_a&password=keyPasswordA&signatureHeader=mySignature

If you are receiving messages from a number of systems, each of which has its own key pair and signs its messages differently, it is possible to dynamically determine the alias to use from the truststore. To do this, set the CamelSignatureKeyStoreAlias (DigitalSignatureConstants.KEYSTORE_ALIAS) header before invoking the crypto: endpoint.

For example, in the following Java DSL fragment the calling routes indicate to the verification route which system signed the message so that the appropriate key can be fetched from the store. The third route ("direct:verify") sets the CamelSignatureKeyStoreAlias header, which is then used to verify the signature using the named key from its truststore:

from("direct:sign_a")
  .to("crypto:sign://usingKeystore?keystore=#keyStore&alias=system_a&password=keyPasswordA")
  .setHeader("sendingSystem", constant("a"))
  .to("direct:verify");

from("direct:sign_b")
   .to("crypto:sign://usingKeystore?keystore=#keyStore&alias=system_b&password=keyPasswordB")
   .setHeader("sendingSystem", constant("b"))
   .to("direct:verify");

from("direct:verify")
  .setHeader(DigitalSignatureConstants.KEYSTORE_ALIAS, 
      simple("system_${header[sendingSystem]}"))
  .to("crypto:verify//usingKeystore?keystore=#trustStore")
  .to("mock:verified");

A CamelSignatureKeyStorePassword (DigitalSignatureConstants.KEYSTORE_PASSWORD) header is also available for use if you would like to dynamically provide the password for the keystore during the signing phase.

The Camel Crypto Component also supports the use of public and private keys directly without using keystores. For an overview of how to do this, you should refer to the Camel Crypto for Digital Signatures documentation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.148.144.100