| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.camel.converter.crypto; |
| |
| import java.io.ByteArrayInputStream; |
| import java.security.Key; |
| import java.security.SecureRandom; |
| import java.util.Collections; |
| import java.util.Map; |
| |
| import javax.crypto.Cipher; |
| import javax.crypto.KeyGenerator; |
| import javax.crypto.SecretKey; |
| import javax.crypto.spec.SecretKeySpec; |
| |
| import org.apache.camel.CamelContext; |
| import org.apache.camel.Exchange; |
| import org.apache.camel.Processor; |
| import org.apache.camel.builder.RouteBuilder; |
| import org.apache.camel.component.mock.MockEndpoint; |
| import org.apache.camel.test.junit4.CamelTestSupport; |
| import org.junit.Test; |
| |
| public class CryptoDataFormatTest extends CamelTestSupport { |
| |
| @Test |
| public void testBasicSymmetric() throws Exception { |
| doRoundTripEncryptionTests("direct:basic-encryption"); |
| } |
| |
| @Test |
| public void testSymmetricWithInitVector() throws Exception { |
| doRoundTripEncryptionTests("direct:init-vector"); |
| } |
| |
| @Test |
| public void testSymmetricWithInlineInitVector() throws Exception { |
| doRoundTripEncryptionTests("direct:inline"); |
| } |
| |
| @Test |
| public void testSymmetricWithHMAC() throws Exception { |
| doRoundTripEncryptionTests("direct:hmac"); |
| } |
| |
| @Test |
| public void testSymmetricWithMD5HMAC() throws Exception { |
| doRoundTripEncryptionTests("direct:hmac-algorithm"); |
| } |
| |
| @Test |
| public void testSymmetricWithSHA256HMAC() throws Exception { |
| doRoundTripEncryptionTests("direct:hmac-sha-256-algorithm"); |
| } |
| |
| @Test |
| public void testKeySuppliedAsHeader() throws Exception { |
| KeyGenerator generator = KeyGenerator.getInstance("DES"); |
| Key key = generator.generateKey(); |
| |
| Exchange unecrypted = getMandatoryEndpoint("direct:key-in-header-encrypt").createExchange(); |
| unecrypted.getIn().setBody("Hi Alice, Be careful Eve is listening, signed Bob"); |
| unecrypted.getIn().setHeader(CryptoDataFormat.KEY, key); |
| unecrypted = template.send("direct:key-in-header-encrypt", unecrypted); |
| validateHeaderIsCleared(unecrypted); |
| |
| MockEndpoint mock = setupExpectations(context, 1, "mock:unencrypted"); |
| Exchange encrypted = getMandatoryEndpoint("direct:key-in-header-decrypt").createExchange(); |
| encrypted.getIn().copyFrom(unecrypted.getIn()); |
| encrypted.getIn().setHeader(CryptoDataFormat.KEY, key); |
| template.send("direct:key-in-header-decrypt", encrypted); |
| |
| assertMockEndpointsSatisfied(); |
| |
| Exchange received = mock.getReceivedExchanges().get(0); |
| validateHeaderIsCleared(received); |
| } |
| |
| @Test |
| public void test3DESECBSymmetric() throws Exception { |
| doRoundTripEncryptionTests("direct:3des-ecb-encryption"); |
| } |
| |
| @Test |
| public void test3DESCBCSymmetric() throws Exception { |
| doRoundTripEncryptionTests("direct:3des-cbc-encryption"); |
| } |
| |
| @Test |
| public void testAES128ECBSymmetric() throws Exception { |
| if (checkUnrestrictedPoliciesInstalled()) { |
| doRoundTripEncryptionTests("direct:aes-128-ecb-encryption"); |
| } |
| } |
| |
| private void validateHeaderIsCleared(Exchange ex) { |
| Object header = ex.getIn().getHeader(CryptoDataFormat.KEY); |
| assertTrue(!ex.getIn().getHeaders().containsKey(CryptoDataFormat.KEY) || "".equals(header) || header == null); |
| } |
| |
| private void doRoundTripEncryptionTests(String endpointUri) throws Exception { |
| doRoundTripEncryptionTests(endpointUri, Collections.<String, Object>emptyMap()); |
| } |
| |
| private void doRoundTripEncryptionTests(String endpoint, Map<String, Object> headers) throws Exception { |
| MockEndpoint encrypted = setupExpectations(context, 3, "mock:encrypted"); |
| MockEndpoint unencrypted = setupExpectations(context, 3, "mock:unencrypted"); |
| |
| String payload = "Hi Alice, Be careful Eve is listening, signed Bob"; |
| template.sendBodyAndHeaders(endpoint, payload, headers); |
| template.sendBodyAndHeaders(endpoint, payload.getBytes(), headers); |
| template.sendBodyAndHeaders(endpoint, new ByteArrayInputStream(payload.getBytes()), headers); |
| |
| assertMocksSatisfied(encrypted, unencrypted, payload); |
| } |
| |
| private void assertMocksSatisfied(MockEndpoint encrypted, MockEndpoint unencrypted, String payload) throws Exception { |
| awaitAndAssert(unencrypted); |
| awaitAndAssert(encrypted); |
| for (Exchange e : unencrypted.getReceivedExchanges()) { |
| assertEquals(payload, e.getIn().getMandatoryBody(String.class)); |
| } |
| for (Exchange e : encrypted.getReceivedExchanges()) { |
| byte[] ciphertext = e.getIn().getMandatoryBody(byte[].class); |
| assertNotSame(payload, new String(ciphertext)); |
| } |
| } |
| |
| protected RouteBuilder[] createRouteBuilders() throws Exception { |
| return new RouteBuilder[] {new RouteBuilder() { |
| public void configure() throws Exception { |
| // START SNIPPET: basic |
| KeyGenerator generator = KeyGenerator.getInstance("DES"); |
| |
| CryptoDataFormat cryptoFormat = new CryptoDataFormat("DES", generator.generateKey()); |
| |
| from("direct:basic-encryption") |
| .marshal(cryptoFormat) |
| .to("mock:encrypted") |
| .unmarshal(cryptoFormat) |
| .to("mock:unencrypted"); |
| // END SNIPPET: basic |
| } |
| }, new RouteBuilder() { |
| public void configure() throws Exception { |
| // START SNIPPET: init-vector |
| KeyGenerator generator = KeyGenerator.getInstance("DES"); |
| byte[] initializationVector = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; |
| |
| CryptoDataFormat cryptoFormat = new CryptoDataFormat("DES/CBC/PKCS5Padding", generator.generateKey()); |
| cryptoFormat.setInitializationVector(initializationVector); |
| |
| from("direct:init-vector") |
| .marshal(cryptoFormat) |
| .to("mock:encrypted") |
| .unmarshal(cryptoFormat) |
| .to("mock:unencrypted"); |
| // END SNIPPET: init-vector |
| } |
| }, new RouteBuilder() { |
| public void configure() throws Exception { |
| // START SNIPPET: inline-init-vector |
| KeyGenerator generator = KeyGenerator.getInstance("DES"); |
| byte[] initializationVector = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; |
| SecretKey key = generator.generateKey(); |
| |
| CryptoDataFormat cryptoFormat = new CryptoDataFormat("DES/CBC/PKCS5Padding", key); |
| cryptoFormat.setInitializationVector(initializationVector); |
| cryptoFormat.setShouldInlineInitializationVector(true); |
| CryptoDataFormat decryptFormat = new CryptoDataFormat("DES/CBC/PKCS5Padding", key); |
| decryptFormat.setShouldInlineInitializationVector(true); |
| |
| from("direct:inline") |
| .marshal(cryptoFormat) |
| .to("mock:encrypted") |
| .unmarshal(decryptFormat) |
| .to("mock:unencrypted"); |
| // END SNIPPET: inline-init-vector |
| } |
| }, new RouteBuilder() { |
| public void configure() throws Exception { |
| // START SNIPPET: hmac |
| KeyGenerator generator = KeyGenerator.getInstance("DES"); |
| |
| CryptoDataFormat cryptoFormat = new CryptoDataFormat("DES", generator.generateKey()); |
| cryptoFormat.setShouldAppendHMAC(true); |
| |
| from("direct:hmac") |
| .marshal(cryptoFormat) |
| .to("mock:encrypted") |
| .unmarshal(cryptoFormat) |
| .to("mock:unencrypted"); |
| // END SNIPPET: hmac |
| } |
| }, new RouteBuilder() { |
| public void configure() throws Exception { |
| // START SNIPPET: hmac-algorithm |
| KeyGenerator generator = KeyGenerator.getInstance("DES"); |
| |
| CryptoDataFormat cryptoFormat = new CryptoDataFormat("DES", generator.generateKey()); |
| cryptoFormat.setShouldAppendHMAC(true); |
| cryptoFormat.setMacAlgorithm("HmacMD5"); |
| |
| from("direct:hmac-algorithm") |
| .marshal(cryptoFormat) |
| .to("mock:encrypted") |
| .unmarshal(cryptoFormat) |
| .to("mock:unencrypted"); |
| // END SNIPPET: hmac-algorithm |
| } |
| }, new RouteBuilder() { |
| public void configure() throws Exception { |
| // START SNIPPET: hmac-sha256-algorithm |
| KeyGenerator generator = KeyGenerator.getInstance("DES"); |
| |
| CryptoDataFormat cryptoFormat = new CryptoDataFormat("DES", generator.generateKey()); |
| cryptoFormat.setShouldAppendHMAC(true); |
| cryptoFormat.setMacAlgorithm("HmacSHA256"); |
| |
| from("direct:hmac-sha-256-algorithm") |
| .marshal(cryptoFormat) |
| .to("mock:encrypted") |
| .unmarshal(cryptoFormat) |
| .to("mock:unencrypted"); |
| // END SNIPPET: hmac-sha256-algorithm |
| } |
| }, new RouteBuilder() { |
| public void configure() throws Exception { |
| // START SNIPPET: key-in-header |
| CryptoDataFormat cryptoFormat = new CryptoDataFormat("DES", null); |
| /** |
| * Note: the header containing the key should be cleared after |
| * marshalling to stop it from leaking by accident and |
| * potentially being compromised. The processor version below is |
| * arguably better as the key is left in the header when you use |
| * the DSL leaks the fact that camel encryption was used. |
| */ |
| from("direct:key-in-header-encrypt") |
| .marshal(cryptoFormat) |
| .removeHeader(CryptoDataFormat.KEY) |
| .to("mock:encrypted"); |
| |
| from("direct:key-in-header-decrypt").unmarshal(cryptoFormat).process(new Processor() { |
| public void process(Exchange exchange) throws Exception { |
| exchange.getIn().getHeaders().remove(CryptoDataFormat.KEY); |
| exchange.getOut().copyFrom(exchange.getIn()); |
| } |
| }).to("mock:unencrypted"); |
| // END SNIPPET: key-in-header |
| } |
| }, new RouteBuilder() { |
| public void configure() throws Exception { |
| // START SNIPPET: 3DES-ECB |
| KeyGenerator generator = KeyGenerator.getInstance("DESede"); |
| |
| CryptoDataFormat cryptoFormat = new CryptoDataFormat("DESede/ECB/PKCS5Padding", generator.generateKey()); |
| |
| from("direct:3des-ecb-encryption") |
| .marshal(cryptoFormat) |
| .to("mock:encrypted") |
| .unmarshal(cryptoFormat) |
| .to("mock:unencrypted"); |
| // END SNIPPET: 3DES-ECB |
| } |
| }, new RouteBuilder() { |
| public void configure() throws Exception { |
| // START SNIPPET: 3DES-CBC |
| KeyGenerator generator = KeyGenerator.getInstance("DES"); |
| byte[] iv = new byte[8]; |
| SecureRandom random = new SecureRandom(); |
| random.nextBytes(iv); |
| Key key = generator.generateKey(); |
| |
| CryptoDataFormat encCryptoFormat = new CryptoDataFormat("DES/CBC/PKCS5Padding", key); |
| encCryptoFormat.setInitializationVector(iv); |
| encCryptoFormat.setShouldInlineInitializationVector(true); |
| |
| CryptoDataFormat decCryptoFormat = new CryptoDataFormat("DES/CBC/PKCS5Padding", key); |
| decCryptoFormat.setShouldInlineInitializationVector(true); |
| |
| from("direct:3des-cbc-encryption") |
| .marshal(encCryptoFormat) |
| .to("mock:encrypted") |
| .unmarshal(decCryptoFormat) |
| .to("mock:unencrypted"); |
| // END SNIPPET: 3DES-CBC |
| } |
| }, new RouteBuilder() { |
| public void configure() throws Exception { |
| // START SNIPPET: AES-128-ECB |
| KeyGenerator generator = KeyGenerator.getInstance("AES"); |
| |
| CryptoDataFormat cryptoFormat = new CryptoDataFormat("AES/ECB/PKCS5Padding", generator.generateKey()); |
| |
| from("direct:aes-128-ecb-encryption") |
| .marshal(cryptoFormat) |
| .to("mock:encrypted") |
| .unmarshal(cryptoFormat) |
| .to("mock:unencrypted"); |
| // END SNIPPET: AES-128-ECB |
| } |
| }}; |
| } |
| |
| private void awaitAndAssert(MockEndpoint mock) throws InterruptedException { |
| mock.assertIsSatisfied(); |
| } |
| |
| public MockEndpoint setupExpectations(CamelContext context, int expected, String mock) { |
| MockEndpoint mockEp = context.getEndpoint(mock, MockEndpoint.class); |
| mockEp.expectedMessageCount(expected); |
| return mockEp; |
| } |
| |
| public static boolean checkUnrestrictedPoliciesInstalled() { |
| try { |
| byte[] data = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; |
| |
| SecretKey key192 = new SecretKeySpec( |
| new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, |
| 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, |
| 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}, |
| "AES"); |
| Cipher c = Cipher.getInstance("AES"); |
| c.init(Cipher.ENCRYPT_MODE, key192); |
| c.doFinal(data); |
| return true; |
| } catch (Exception e) { |
| // |
| } |
| return false; |
| } |
| |
| } |