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.
This recipe will show you how to sign and verify messages using the Camel Crypto Component.
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:
For the purposes of this example, we will use the keytool
utility that is included as part of the Java Development Kit (JDK):
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
# keytool -export -alias system_a -keystore keystore.jks -storepass keystorePassword -rfc -file selfsignedcert_a.cer
# keytool -import -noprompt -alias system_a -file selfsignedcert_a.cer -keystore truststore.jks -storepass truststorePassword
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
.
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.
In order to perform message signing and verification, you will need to do the following:
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);
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&alias=system_a&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.
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&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.
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.
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.
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.
keytool
): http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/keytool.html3.145.173.199