/* * Copyright 1999-2004 The Apache Software Foundation. * * Licensed 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.encryption; import java.io.File; 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 javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; 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.TestUtils; import org.apache.xml.security.utils.JavaUtils; import org.apache.xpath.XPathAPI; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * Interop test for XML Encryption * * @author Berin Lautenbach */ /* Tests for merlin-xmlenc-five not currently done bad-encrypt-content-aes128-cbc-kw-aes192.xml decryption-transform-except.xml decryption-transform.xml encrypt-content-aes192-cbc-dh-sha512.xml encrypt-data-tripledes-cbc-rsa-oaep-mgf1p-sha256.xml encrypt-element-aes256-cbc-carried-kw-aes256.xml encrypt-element-aes256-cbc-kw-aes256-dh-ripemd160.xml encrypt-element-aes256-cbc-retrieved-kw-aes256.xml encsig-hmac-sha256-dh.xml encsig-hmac-sha256-kw-tripledes-dh.xml encsig-hmac-sha256-rsa-1_5.xml encsig-hmac-sha256-rsa-oaep-mgf1p.xml encsig-ripemd160-hmac-ripemd160-kw-tripledes.xml encsig-sha256-hmac-sha256-kw-aes128.xml encsig-sha384-hmac-sha384-kw-aes192.xml encsig-sha512-hmac-sha512-kw-aes256.xml */ public class BaltimoreEncTest extends TestCase { 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; /** {@link org.apache.commons.logging} logging facility */ static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(BaltimoreEncTest.class.getName()); /** * Method suite * * */ public static Test suite() throws Exception { return new TestSuite(BaltimoreEncTest.class); } /** * Constructor BaltimoreEncTest * * @param Name_ */ public BaltimoreEncTest(String Name_) { super(Name_); } /** * Method setUp */ protected void setUp() throws Exception { // Create the comparison strings DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE); String filename = "data/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 = dbf.newDocumentBuilder(); Document doc = db.parse(new java.io.FileInputStream(f)); cardNumber = retrieveCCNumber(doc); // Test decrypt testDecryptString = new String("top secret message\n"); // Count the nodes in the document as a secondary test nodeCount = countNodes(doc); // Create the keys jebBytes = "abcdefghijklmnopqrstuvwx".getBytes("ASCII"); jobBytes = "abcdefghijklmnop".getBytes("ASCII"); jedBytes = "abcdefghijklmnopqrstuvwxyz012345".getBytes("ASCII"); // Certificate information rsaCertSerialNumber = new String("1014918766910"); // rsaKey filename = "data/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.encryption.BobKeyResolver"); // Check what algorithms are available haveISOPadding = false; String algorithmId = JCEMapper.translateURItoJCEID(org.apache.xml.security.utils.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(org.apache.xml.security.utils.EncryptionConstants.ALGO_ID_KEYWRAP_AES128) != null); } /** * Method retrieveCCNumber * * Retrieve the credit card number from the payment info document * * @param doc The document to retreive the card number from * @return The retrieved credit card number */ public static String retrieveCCNumber (Document doc) throws javax.xml.transform.TransformerException { Element nscontext = TestUtils.createDSctx(doc, "x", "urn:example:po"); Node ccnumElt = XPathAPI.selectSingleNode(doc, "//x:Number/text()", nscontext); 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, "ASCII"); Assert.assertEquals(testDecryptString, input); } /** * Method test_five_content_3des_cbc * * Check the merlin-enc-five element content test for 3DES * */ public void test_five_content_3des_cbc() throws Exception { if (haveISOPadding) { String filename = "data/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 * */ public void test_five_content_aes256_cbc() throws Exception { if (haveISOPadding) { String filename = "data/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 * */ public void test_five_content_aes128_cbc_kw_aes192() throws Exception { if (haveISOPadding && haveKeyWraps) { String filename = "data/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 * */ public void test_five_content_3des_cbc_kw_aes128() throws Exception { if (haveISOPadding && haveKeyWraps) { String filename = "data/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) * */ public void test_five_content_aes128_cbc_rsa_15() throws Exception { if (haveISOPadding) { String filename = "data/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 * */ public void test_five_element_aes192_cbc_ref() throws Exception { if (haveISOPadding) { String filename = "data/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 * */ public void test_five_data_aes128_cbc() throws Exception { if (haveISOPadding) { String filename = "data/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 * */ public void test_five_data_aes256_cbc_3des() throws Exception { if (haveISOPadding && haveKeyWraps) { String filename = "data/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 * */ public void test_five_data_aes192_cbc_aes256() throws Exception { if (haveISOPadding && haveKeyWraps) { String filename = "data/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) * */ public void test_five_data_3des_cbc_rsa_oaep() throws Exception { // Work-around for the fact that BC currently doesn't support // the standard JCE name for oaep padding java.security.Provider bc = java.security.Security.getProvider("BC"); if (bc != null) bc.put("Alg.Alias.Cipher.RSA/ECB/OAEPWithSHA1AndMGF1Padding","RSA/OAEP"); if (haveISOPadding) { String filename = "data/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 */ public Document decryptElement (String filename) throws Exception { XMLCipher cipher; // Parse the document in question javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE); String basedir = System.getProperty("basedir"); if(basedir != null && !"".equals(basedir)) { filename = basedir + "/" + filename; } File f = new File(filename); DocumentBuilder db = dbf.newDocumentBuilder(); 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 */ public byte[] decryptData (String filename) throws Exception { XMLCipher cipher; // Parse the document in question javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE); String basedir = System.getProperty("basedir"); if(basedir != null && !"".equals(basedir)) { filename = basedir + "/" + filename; } File f = new File(filename); DocumentBuilder db = dbf.newDocumentBuilder(); 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); byte[] dd = cipher.decryptToByteArray(ee); return dd; } /** * Method mapKeyName * * Create a secret key from a key name for merlin-five * * @param name Name to map a key from */ public SecretKey mapKeyName(String name) throws Exception { if (name.equals("job")) { // Jeb is a AES-128 key SecretKey key = new SecretKeySpec(jobBytes, "AES"); return key; } if (name.equals("jeb")) { // Jeb is a AES-192 key SecretKey key = new SecretKeySpec(jebBytes, "AES"); return key; } if (name.equals("jed")) { // Jeb 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 */ public 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) { if (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; } }