| /** |
| * 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.xml.security.test.dom.encryption; |
| |
| import java.io.File; |
| import java.nio.charset.StandardCharsets; |
| import java.security.Key; |
| import java.security.KeyFactory; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.PrivateKey; |
| import java.security.cert.X509Certificate; |
| import java.security.spec.PKCS8EncodedKeySpec; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import javax.crypto.Cipher; |
| import javax.crypto.NoSuchPaddingException; |
| import javax.crypto.SecretKey; |
| import javax.crypto.spec.SecretKeySpec; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.xpath.XPath; |
| import javax.xml.xpath.XPathConstants; |
| import javax.xml.xpath.XPathExpressionException; |
| import javax.xml.xpath.XPathFactory; |
| |
| import org.apache.xml.security.algorithms.JCEMapper; |
| import org.apache.xml.security.encryption.EncryptedData; |
| import org.apache.xml.security.encryption.EncryptedKey; |
| import org.apache.xml.security.encryption.XMLCipher; |
| import org.apache.xml.security.keys.KeyInfo; |
| import org.apache.xml.security.keys.content.KeyName; |
| import org.apache.xml.security.keys.content.X509Data; |
| import org.apache.xml.security.keys.content.x509.XMLX509Certificate; |
| import org.apache.xml.security.keys.keyresolver.KeyResolver; |
| import org.apache.xml.security.test.dom.DSNamespaceContext; |
| import org.apache.xml.security.utils.EncryptionConstants; |
| import org.apache.xml.security.utils.JavaUtils; |
| import org.apache.xml.security.utils.XMLUtils; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| import static org.junit.Assert.*; |
| |
| /** |
| * Interop test for XML Encryption |
| * |
| */ |
| public class BaltimoreEncTest { |
| |
| private static String cardNumber; |
| private static String rsaCertSerialNumber; |
| private static String testDecryptString; |
| private static int nodeCount = 0; |
| private static byte[] jebBytes; |
| private static byte[] jobBytes; |
| private static byte[] jedBytes; |
| private static PrivateKey rsaKey; |
| private boolean haveISOPadding; |
| private boolean haveKeyWraps; |
| |
| static org.slf4j.Logger LOG = |
| org.slf4j.LoggerFactory.getLogger(BaltimoreEncTest.class); |
| |
| /** |
| * Constructor BaltimoreEncTest |
| */ |
| public BaltimoreEncTest() throws Exception { |
| // Create the comparison strings |
| String filename = |
| "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/plaintext.xml"; |
| String basedir = System.getProperty("basedir"); |
| if (basedir != null && !"".equals(basedir)) { |
| filename = basedir + "/" + filename; |
| } |
| File f = new File(filename); |
| |
| DocumentBuilder db = XMLUtils.createDocumentBuilder(false); |
| Document doc = db.parse(new java.io.FileInputStream(f)); |
| |
| cardNumber = retrieveCCNumber(doc); |
| |
| // Test decrypt |
| testDecryptString = "top secret message\n"; |
| |
| // Count the nodes in the document as a secondary test |
| nodeCount = countNodes(doc); |
| |
| // Create the keys |
| jebBytes = "abcdefghijklmnopqrstuvwx".getBytes(StandardCharsets.US_ASCII); |
| jobBytes = "abcdefghijklmnop".getBytes(StandardCharsets.US_ASCII); |
| jedBytes = "abcdefghijklmnopqrstuvwxyz012345".getBytes(StandardCharsets.US_ASCII); |
| |
| // Certificate information |
| rsaCertSerialNumber = "1014918766910"; |
| |
| // rsaKey |
| filename = "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/rsa.p8"; |
| if (basedir != null && !"".equals(basedir)) { |
| filename = basedir + "/" + filename; |
| } |
| |
| byte[] pkcs8Bytes = JavaUtils.getBytesFromFile(filename); |
| PKCS8EncodedKeySpec pkcs8Spec = new PKCS8EncodedKeySpec(pkcs8Bytes); |
| |
| // Create a key factory |
| KeyFactory keyFactory = KeyFactory.getInstance("RSA"); |
| rsaKey = keyFactory.generatePrivate(pkcs8Spec); |
| |
| // Initialise the library |
| org.apache.xml.security.Init.init(); |
| |
| // Register our key resolver |
| KeyResolver.register("org.apache.xml.security.test.dom.encryption.BobKeyResolver", false); |
| |
| // Check what algorithms are available |
| |
| haveISOPadding = false; |
| String algorithmId = |
| JCEMapper.translateURItoJCEID(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128); |
| |
| if (algorithmId != null) { |
| try { |
| if (Cipher.getInstance(algorithmId) != null) { |
| haveISOPadding = true; |
| } |
| } catch (NoSuchAlgorithmException nsae) { |
| // |
| } catch (NoSuchPaddingException nspe) { |
| // |
| } |
| } |
| |
| haveKeyWraps = |
| JCEMapper.translateURItoJCEID(EncryptionConstants.ALGO_ID_KEYWRAP_AES128) != null; |
| } |
| |
| /** |
| * Method test_five_content_3des_cbc |
| * |
| * Check the merlin-enc-five element content test for 3DES |
| */ |
| @org.junit.Test |
| public void test_five_content_3des_cbc() throws Exception { |
| if (haveISOPadding) { |
| String filename = |
| "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/encrypt-content-tripledes-cbc.xml"; |
| |
| Document dd = decryptElement(filename); |
| checkDecryptedDoc(dd, true); |
| } else { |
| LOG.warn( |
| "Skipping test test_five_content_3des_cbs as necessary " |
| + "crypto algorithms are not available" |
| ); |
| } |
| } |
| |
| /** |
| * Method test_five_content_aes256_cbc |
| * |
| * Check the merlin-enc-five element content test for AES256 |
| */ |
| @org.junit.Test |
| public void test_five_content_aes256_cbc() throws Exception { |
| |
| if (haveISOPadding) { |
| String filename = |
| "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/encrypt-content-aes256-cbc-prop.xml"; |
| |
| Document dd = decryptElement(filename); |
| checkDecryptedDoc(dd, true); |
| } else { |
| LOG.warn( |
| "Skipping test test_five_content_aes256_cbc as necessary " |
| + "crypto algorithms are not available" |
| ); |
| } |
| } |
| |
| /** |
| * Method test_five_content_aes128_cbc_kw_aes192 |
| * |
| * Check the merlin-enc-five element content test for AES128 with |
| * AES 192 key wrap |
| */ |
| @org.junit.Test |
| public void test_five_content_aes128_cbc_kw_aes192() throws Exception { |
| if (haveISOPadding && haveKeyWraps) { |
| String filename = |
| "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/encrypt-content-aes128-cbc-kw-aes192.xml"; |
| |
| Document dd = decryptElement(filename); |
| checkDecryptedDoc(dd, true); |
| } else { |
| LOG.warn( |
| "Skipping test test_five_content_aes128_cbc_kw_aes192 as necessary " |
| + "crypto algorithms are not available" |
| ); |
| } |
| } |
| |
| /** |
| * Method test_five_content_3des_cbc_kw_aes128 |
| * |
| * Check the merlin-enc-five element content test for 3DES with |
| * AES 128 key wrap |
| */ |
| @org.junit.Test |
| public void test_five_content_3des_cbc_kw_aes128() throws Exception { |
| |
| if (haveISOPadding && haveKeyWraps) { |
| String filename = |
| "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/encrypt-element-tripledes-cbc-kw-aes128.xml"; |
| |
| Document dd = decryptElement(filename); |
| checkDecryptedDoc(dd, true); |
| } else { |
| LOG.warn( |
| "Skipping test test_five_content_3des_cbc_kw_aes128 as necessary " |
| + "crypto algorithms are not available" |
| ); |
| } |
| } |
| |
| /** |
| * Method test_five_content_aes128_cbc_kw_rsa_15 |
| * |
| * Check the merlin-enc-five element content test for AES128 with |
| * RSA key wrap (PKCS 1.5 padding) |
| */ |
| @org.junit.Test |
| public void test_five_content_aes128_cbc_rsa_15() throws Exception { |
| if (haveISOPadding) { |
| String filename = |
| "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/encrypt-element-aes128-cbc-rsa-1_5.xml"; |
| |
| Document dd = decryptElement(filename); |
| checkDecryptedDoc(dd, true); |
| } else { |
| LOG.warn( |
| "Skipping test test_five_content_aes128_cbc_rsa_15 as necessary " |
| + "crypto algorithms are not available" |
| ); |
| } |
| } |
| |
| /** |
| * Method test_five_element_aes192_cbc_ref |
| * |
| * Check the merlin-enc-five element data test for AES192 with |
| * a CipherReference element |
| */ |
| @org.junit.Test |
| public void test_five_element_aes192_cbc_ref() throws Exception { |
| if (haveISOPadding) { |
| String filename = |
| "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/encrypt-element-aes192-cbc-ref.xml"; |
| |
| Document dd = decryptElement(filename); |
| // Note - we don't check the node count, as it will be different |
| // due to the encrypted text remainin in the reference nodes |
| checkDecryptedDoc(dd, false); |
| } else { |
| LOG.warn( |
| "Skipping test test_five_element_aes192_cbc_ref as necessary " |
| + "crypto algorithms are not available" |
| ); |
| } |
| } |
| |
| /** |
| * Method test_five_data_aes128_cbc |
| * |
| * Check the merlin-enc-five element data test for AES128 with no |
| * key wrap |
| */ |
| @org.junit.Test |
| public void test_five_data_aes128_cbc() throws Exception { |
| if (haveISOPadding) { |
| String filename = |
| "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/encrypt-data-aes128-cbc.xml"; |
| |
| byte[] decrypt = decryptData(filename); |
| checkDecryptedData(decrypt); |
| } else { |
| LOG.warn( |
| "Skipping test test_five_data_aes128_cbc as necessary " |
| + "crypto algorithms are not available" |
| ); |
| } |
| } |
| |
| /** |
| * Method test_five_data_aes256_cbc_3des |
| * |
| * Check the merlin-enc-five element data test for AES256 with 3DES |
| * key wrap |
| */ |
| @org.junit.Test |
| public void test_five_data_aes256_cbc_3des() throws Exception { |
| if (haveISOPadding && haveKeyWraps) { |
| String filename = |
| "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/encrypt-data-aes256-cbc-kw-tripledes.xml"; |
| |
| byte[] decrypt = decryptData(filename); |
| checkDecryptedData(decrypt); |
| } else { |
| LOG.warn( |
| "Skipping test test_five_data_aes256_cbc_3des as necessary " |
| + "crypto algorithms are not available" |
| ); |
| } |
| } |
| |
| /** |
| * Method test_five_data_aes192_cbc_aes256 |
| * |
| * Check the merlin-enc-five element data test for AES192 with AES256 |
| * key wrap |
| */ |
| @org.junit.Test |
| public void test_five_data_aes192_cbc_aes256() throws Exception { |
| if (haveISOPadding && haveKeyWraps) { |
| String filename = |
| "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/encrypt-data-aes192-cbc-kw-aes256.xml"; |
| |
| byte[] decrypt = decryptData(filename); |
| checkDecryptedData(decrypt); |
| } else { |
| LOG.warn( |
| "Skipping test test_five_data_aes192_cbc_aes256 as necessary " |
| + "crypto algorithms are not available" |
| ); |
| } |
| } |
| |
| /** |
| * Method test_five_data_3des_cbc_rsa_oaep |
| * |
| * Check the merlin-enc-five element data test for 3DES with |
| * RSA key wrap (OAEP and no parameters) |
| */ |
| @org.junit.Test |
| public void test_five_data_3des_cbc_rsa_oaep() throws Exception { |
| if (haveISOPadding) { |
| String filename = |
| "src/test/resources/ie/baltimore/merlin-examples/merlin-xmlenc-five/encrypt-data-tripledes-cbc-rsa-oaep-mgf1p.xml"; |
| |
| byte[] decrypt = decryptData(filename); |
| checkDecryptedData(decrypt); |
| } else { |
| LOG.warn( |
| "Skipping test test_five_data_3des_cbc_rsa_oaep as necessary " |
| + "crypto algorithms are not available" |
| ); |
| } |
| } |
| |
| /** |
| * Method decryptElement |
| * |
| * Take a key, encryption type and a file, find an encrypted element |
| * decrypt it and return the resulting document |
| * |
| * @param filename File to decrypt from |
| */ |
| private Document decryptElement(String filename) throws Exception { |
| XMLCipher cipher; |
| |
| // Parse the document in question |
| |
| String basedir = System.getProperty("basedir"); |
| if (basedir != null && !"".equals(basedir)) { |
| filename = basedir + "/" + filename; |
| } |
| File f = new File(filename); |
| |
| DocumentBuilder db = XMLUtils.createDocumentBuilder(false); |
| Document doc = db.parse(new java.io.FileInputStream(f)); |
| |
| // Now we have the document, lets build the XMLCipher element |
| Element ee = null; |
| |
| // Create the XMLCipher element |
| cipher = XMLCipher.getInstance(); |
| |
| // Need to pre-load the Encrypted Data so we can get the key info |
| ee = (Element) doc.getElementsByTagName("EncryptedData").item(0); |
| cipher.init(XMLCipher.DECRYPT_MODE, null); |
| EncryptedData encryptedData = cipher.loadEncryptedData(doc, ee); |
| |
| Key key = findKey(encryptedData); |
| cipher.init(XMLCipher.DECRYPT_MODE, key); |
| Document dd = cipher.doFinal(doc, ee); |
| |
| return dd; |
| } |
| |
| /** |
| * Method decryptData |
| * |
| * Take a file, find an encrypted element decrypt it and return the |
| * resulting byte array |
| * |
| * @param filename File to decrypt from |
| */ |
| private byte[] decryptData(String filename) throws Exception { |
| |
| XMLCipher cipher; |
| |
| // Parse the document in question |
| String basedir = System.getProperty("basedir"); |
| if (basedir != null && !"".equals(basedir)) { |
| filename = basedir + "/" + filename; |
| } |
| File f = new File(filename); |
| |
| DocumentBuilder db = XMLUtils.createDocumentBuilder(false); |
| Document doc = db.parse(new java.io.FileInputStream(f)); |
| |
| // Now we have the document, lets build the XMLCipher element |
| Element ee = null; |
| |
| // Create the XMLCipher element |
| cipher = XMLCipher.getInstance(); |
| |
| // Need to pre-load the Encrypted Data so we can get the key info |
| ee = (Element) doc.getElementsByTagName("EncryptedData").item(0); |
| cipher.init(XMLCipher.DECRYPT_MODE, null); |
| EncryptedData encryptedData = cipher.loadEncryptedData(doc, ee); |
| |
| Key key = findKey(encryptedData); |
| cipher.init(XMLCipher.DECRYPT_MODE, key); |
| |
| return cipher.decryptToByteArray(ee); |
| } |
| |
| /** |
| * Method mapKeyName |
| * |
| * Create a secret key from a key name for merlin-five |
| * |
| * @param name Name to map a key from |
| */ |
| private SecretKey mapKeyName(String name) throws Exception { |
| if ("job".equals(name)) { |
| // Job is a AES-128 key |
| SecretKey key = new SecretKeySpec(jobBytes, "AES"); |
| return key; |
| } |
| if ("jeb".equals(name)) { |
| // Jeb is a AES-192 key |
| SecretKey key = new SecretKeySpec(jebBytes, "AES"); |
| return key; |
| } |
| if ("jed".equals(name)) { |
| // Jed is a AES-256 key |
| SecretKey key = new SecretKeySpec(jedBytes, "AES"); |
| return key; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Method findKey |
| * |
| * Given an encryptedData structure, return the key that will decrypt |
| * it |
| * |
| * @param encryptedData EncryptedData to get key for |
| */ |
| private Key findKey(EncryptedData encryptedData) throws Exception { |
| KeyInfo ki = encryptedData.getKeyInfo(); |
| |
| Key key = null; |
| Key kek = null; |
| |
| if (ki == null) { |
| return null; |
| } |
| |
| // First check for a known key name |
| KeyName keyName = ki.itemKeyName(0); |
| if (keyName != null) { |
| return mapKeyName(keyName.getKeyName()); |
| } |
| |
| // Decrypt any encryptedKey structures |
| EncryptedKey encryptedKey = ki.itemEncryptedKey(0); |
| if (encryptedKey == null) { |
| return null; |
| } |
| |
| KeyInfo kiek = encryptedKey.getKeyInfo(); |
| if (kiek == null) { |
| return null; |
| } |
| |
| KeyName kekKeyName = kiek.itemKeyName(0); |
| if (kekKeyName != null) { |
| kek = mapKeyName(kekKeyName.getKeyName()); |
| } else { |
| X509Data certData = kiek.itemX509Data(0); |
| XMLX509Certificate xcert = certData.itemCertificate(0); |
| X509Certificate cert = xcert.getX509Certificate(); |
| |
| if (cert != null && cert.getSerialNumber().toString().equals(rsaCertSerialNumber)) { |
| kek = rsaKey; |
| } |
| } |
| if (kek != null) { |
| XMLCipher cipher = XMLCipher.getInstance(); |
| cipher.init(XMLCipher.UNWRAP_MODE, kek); |
| key = |
| cipher.decryptKey( |
| encryptedKey, encryptedData.getEncryptionMethod().getAlgorithm() |
| ); |
| } |
| |
| return key; |
| } |
| |
| /** |
| * Method countNodes |
| * |
| * Recursively count the number of nodes in the document |
| * |
| * @param n Node to count beneath |
| */ |
| private static int countNodes(Node n) { |
| |
| if (n == null) { |
| return 0; // Paranoia |
| } |
| |
| int count = 1; // Always count myself |
| Node c = n.getFirstChild(); |
| |
| while (c != null) { |
| count += countNodes(c); |
| c = c.getNextSibling(); |
| } |
| |
| return count; |
| } |
| |
| /** |
| * Method retrieveCCNumber |
| * |
| * Retrieve the credit card number from the payment info document |
| * |
| * @param doc The document to retrieve the card number from |
| * @return The retrieved credit card number |
| * @throws XPathExpressionException |
| */ |
| private static String retrieveCCNumber(Document doc) |
| throws javax.xml.transform.TransformerException, |
| XPathExpressionException { |
| |
| XPathFactory xpf = XPathFactory.newInstance(); |
| XPath xpath = xpf.newXPath(); |
| Map<String, String> namespace = new HashMap<>(); |
| namespace.put("x", "urn:example:po"); |
| DSNamespaceContext context = new DSNamespaceContext(namespace); |
| xpath.setNamespaceContext(context); |
| |
| String expression = "//x:Number/text()"; |
| Node ccnumElt = |
| (Node) xpath.evaluate(expression, doc, XPathConstants.NODE); |
| |
| if (ccnumElt != null) { |
| return ccnumElt.getNodeValue(); |
| } |
| |
| return null; |
| } |
| |
| /* |
| * Check we have retrieved a Credit Card number and that it is OK |
| * Check that the document has the correct number of nodes |
| */ |
| private void checkDecryptedDoc(Document d, boolean doNodeCheck) throws Exception { |
| |
| String cc = retrieveCCNumber(d); |
| LOG.debug("Retrieved Credit Card : " + cc); |
| assertTrue(cc, cc!= null && cc.equals(cardNumber)); |
| |
| // Test cc numbers |
| if (doNodeCheck) { |
| int myNodeCount = countNodes(d); |
| |
| assertTrue( |
| "Node count mismatches", myNodeCount > 0 && myNodeCount == nodeCount |
| ); |
| } |
| } |
| |
| /** |
| * Check a decrypt of data was OK |
| */ |
| private void checkDecryptedData(byte[] data) throws Exception { |
| String input = new String(data, StandardCharsets.US_ASCII); |
| assertEquals(testDecryptString, input); |
| } |
| } |