Java tutorial
/* * Copyright (c) 2008-2012, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.common.security.cms; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.math.BigInteger; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CertificateException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.security.auth.x500.X500Principal; import mitm.common.mail.MailUtils; import mitm.common.security.SecurityFactory; import mitm.common.security.SecurityFactoryFactory; import mitm.common.security.bouncycastle.InitializeBouncycastle; import mitm.common.security.digest.Digest; import mitm.common.util.DateTimeUtils; import mitm.test.TestUtils; import org.apache.log4j.PropertyConfigurator; import org.bouncycastle.mail.smime.SMIMESigned; import org.bouncycastle.mail.smime.SMIMESignedParser; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.junit.BeforeClass; import org.junit.Test; public class CMSSignedInspectorImplTest { private static final File testDir = new File("test/resources/testdata/mail"); private static SecurityFactory securityFactory; private static KeyStore keyStore; @BeforeClass public static void setUpBeforeClass() throws Exception { PropertyConfigurator.configure("conf/log4j.properties"); InitializeBouncycastle.initialize(); securityFactory = SecurityFactoryFactory.getSecurityFactory(); keyStore = loadKeyStore(new File("test/resources/testdata/keys/testCertificates.p12"), "test"); } private static KeyStore loadKeyStore(File file, String password) throws KeyStoreException { try { KeyStore keyStore = securityFactory.createKeyStore("PKCS12"); // initialize key store keyStore.load(new FileInputStream(file), password.toCharArray()); return keyStore; } catch (NoSuchProviderException e) { throw new KeyStoreException(e); } catch (NoSuchAlgorithmException e) { throw new KeyStoreException(e); } catch (CertificateException e) { throw new KeyStoreException(e); } catch (FileNotFoundException e) { throw new KeyStoreException(e); } catch (IOException e) { throw new KeyStoreException(e); } } private static MimeMessage loadMessage(String filename) throws FileNotFoundException, MessagingException { File mail = new File(testDir, filename); MimeMessage message = MailUtils.loadMessage(mail); return message; } @Test public void testClearSigned() throws Exception { MimeMessage signedMessage = loadMessage("clear-signed-validcertificate.eml"); MimeMultipart multipart = (MimeMultipart) signedMessage.getContent(); SMIMESigned signedData = new SMIMESigned(multipart); CMSSignedDataAdapter signedDataAdapter = CMSAdapterFactory.createAdapter(signedData); assertTrue(signedDataAdapter instanceof CMSSignedDataAdapterImpl); testClearSigned(signedDataAdapter); } @Test public void testClearSignedParser() throws Exception { MimeMessage signedMessage = loadMessage("clear-signed-validcertificate.eml"); MimeMultipart multipart = (MimeMultipart) signedMessage.getContent(); SMIMESignedParser signedDataParser = new SMIMESignedParser( new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), multipart); CMSSignedDataAdapter signedDataAdapter = CMSAdapterFactory.createAdapter(signedDataParser); assertTrue(signedDataAdapter instanceof CMSSignedDataParserAdapterImpl); testClearSigned(signedDataAdapter); } public void testClearSigned(CMSSignedDataAdapter signedDataAdapter) throws Exception { CMSSignedInspector inspector = new CMSSignedInspectorImpl(signedDataAdapter, securityFactory.getNonSensitiveProvider(), securityFactory.getSensitiveProvider()); assertEquals(1, inspector.getVersion()); List<X509Certificate> certificates = inspector.getCertificates(); assertEquals(3, certificates.size()); Map<BigInteger, X509Certificate> certMap = new HashMap<BigInteger, X509Certificate>(); for (X509Certificate certificate : certificates) { certMap.put(certificate.getSerialNumber(), certificate); } // the ca and root certificate should be there X509Certificate caCertificate = certMap.get(new BigInteger("115FCAD6B536FD8D49E72922CD1F0DA", 16)); assertNotNull(caCertificate); assertTrue(certMap.containsKey(new BigInteger("115FCAC409FB2022B7D06920A00FE42", 16))); // the signing certificate should be there X509Certificate signerCertificate = certMap.get(new BigInteger("115fcd741088707366e9727452c9770", 16)); assertNotNull(signerCertificate); List<X509CRL> crls = inspector.getCRLs(); assertEquals(0, crls.size()); List<SignerInfo> signers = inspector.getSigners(); assertEquals(1, signers.size()); SignerInfo signerInfo = signers.get(0); SignerIdentifier signerId = signerInfo.getSignerId(); assertEquals(new X500Principal("EMAILADDRESS=ca@example.com, CN=MITM Test CA, L=Amsterdam, ST=NH, C=NL"), signerId.getIssuer()); assertEquals(new BigInteger("115fcd741088707366e9727452c9770", 16), signerId.getSerialNumber()); assertEquals(null, signerId.getSubjectKeyIdentifier()); assertTrue(signerId.match(signerCertificate)); assertEquals(Digest.SHA1, Digest.fromOID(signerInfo.getDigestAlgorithmOID())); // SHA-1 has DERNull as parameter. // //-- sha-1 OBJECT IDENTIFIER ::= {iso(1) identified-organization(3) oiw(14) //-- secsig(3) algorithm(2) 26} -- -- [MSG] //-- (The parameters field must be present and is defined as an //-- ASN.1 NULL type. Implementations should also accept identifiers //-- where the parameters field is absent.) // See http://www.imc.org/ietf-smime/other-smime-oids.asn assertArrayEquals(new byte[] { 5, 0 }, signerInfo.getDigestAlgorithmParams()); // RSA has OID 1.2.840.113549.1.1.1 assertEquals("1.2.840.113549.1.1.1", signerInfo.getEncryptionAlgorithmOID()); // DERNull parameter assertArrayEquals(new byte[] { 5, 0 }, signerInfo.getEncryptionAlgorithmParams()); assertEquals(1, signerInfo.getVersion()); Date signingTime = signerInfo.getSigningTime(); Date expected = TestUtils.parseDate("03-Nov-2007 17:56:52 GMT"); // we need to compensate for different timezones assertTrue(Math.abs(DateTimeUtils.diffDays(expected, signingTime)) <= 1); assertTrue(signerInfo.verify(signerCertificate.getPublicKey())); assertFalse(signerInfo.verify(caCertificate.getPublicKey())); // try validate the first again assertTrue(signerInfo.verify(signerCertificate.getPublicKey())); } @Test(expected = SignerInfoException.class) public void testClearSignedIncorrectHash() throws Exception { MimeMessage signedMessage = loadMessage("clear-signed-hash-incorrect.eml"); MimeMultipart multipart = (MimeMultipart) signedMessage.getContent(); SMIMESigned signedData = new SMIMESigned(multipart); CMSSignedDataAdapter signedDataAdapter = CMSAdapterFactory.createAdapter(signedData); assertTrue(signedDataAdapter instanceof CMSSignedDataAdapterImpl); testClearSignedIncorrectHash(signedDataAdapter); } @Test(expected = SignerInfoException.class) public void testClearSignedIncorrectHashParser() throws Exception { MimeMessage signedMessage = loadMessage("clear-signed-hash-incorrect.eml"); MimeMultipart multipart = (MimeMultipart) signedMessage.getContent(); SMIMESignedParser signedData = new SMIMESignedParser( new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), multipart); CMSSignedDataAdapter signedDataAdapter = CMSAdapterFactory.createAdapter(signedData); assertTrue(signedDataAdapter instanceof CMSSignedDataParserAdapterImpl); testClearSignedIncorrectHash(signedDataAdapter); } public void testClearSignedIncorrectHash(CMSSignedDataAdapter signedDataAdapter) throws Exception { CMSSignedInspector inspector = new CMSSignedInspectorImpl(signedDataAdapter, securityFactory.getNonSensitiveProvider(), securityFactory.getSensitiveProvider()); X509Certificate signingCertificate = (X509Certificate) keyStore.getCertificate("ValidCertificate"); List<SignerInfo> signers = inspector.getSigners(); SignerInfo signerInfo = signers.get(0); // should throw VerificationFailedException signerInfo.verify(signingCertificate.getPublicKey()); } @Test public void testClearSignedMultipleSigners() throws Exception { MimeMessage signedMessage = loadMessage("clear-signed-multiple-signers-validcertificate.eml"); MimeMultipart multipart = (MimeMultipart) signedMessage.getContent(); SMIMESigned signedData = new SMIMESigned(multipart); CMSSignedDataAdapter signedDataAdapter = CMSAdapterFactory.createAdapter(signedData); assertTrue(signedDataAdapter instanceof CMSSignedDataAdapterImpl); testClearSignedMultipleSigners(signedDataAdapter); } @Test public void testClearSignedMultipleSignersParser() throws Exception { MimeMessage signedMessage = loadMessage("clear-signed-multiple-signers-validcertificate.eml"); MimeMultipart multipart = (MimeMultipart) signedMessage.getContent(); SMIMESignedParser signedData = new SMIMESignedParser( new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), multipart); CMSSignedDataAdapter signedDataAdapter = CMSAdapterFactory.createAdapter(signedData); assertTrue(signedDataAdapter instanceof CMSSignedDataParserAdapterImpl); testClearSignedMultipleSigners(signedDataAdapter); } public void testClearSignedMultipleSigners(CMSSignedDataAdapter signedDataAdapter) throws Exception { CMSSignedInspector inspector = new CMSSignedInspectorImpl(signedDataAdapter, securityFactory.getNonSensitiveProvider(), securityFactory.getSensitiveProvider()); assertEquals(1, inspector.getVersion()); List<X509Certificate> certificates = inspector.getCertificates(); assertEquals(6, certificates.size()); List<X509CRL> crls = inspector.getCRLs(); assertEquals(0, crls.size()); List<SignerInfo> signers = inspector.getSigners(); assertEquals(2, signers.size()); // Note: since BC 1.47, the first and second signer have changed places SignerInfo signerInfo1 = signers.get(1); SignerInfo signerInfo2 = signers.get(0); // first signer SignerIdentifier signerId1 = signerInfo1.getSignerId(); assertEquals(new X500Principal("EMAILADDRESS=ca@example.com, CN=MITM Test CA, L=Amsterdam, ST=NH, C=NL"), signerId1.getIssuer()); assertEquals(new BigInteger("115fcd741088707366e9727452c9770", 16), signerId1.getSerialNumber()); assertEquals(null, signerId1.getSubjectKeyIdentifier()); assertEquals(Digest.SHA1, Digest.fromOID(signerInfo1.getDigestAlgorithmOID())); assertArrayEquals(new byte[] { 5, 0 }, signerInfo1.getDigestAlgorithmParams()); assertEquals("1.2.840.113549.1.1.1", signerInfo1.getEncryptionAlgorithmOID()); assertArrayEquals(new byte[] { 5, 0 }, signerInfo1.getEncryptionAlgorithmParams()); assertEquals(1, signerInfo1.getVersion()); Date signingTime = signerInfo1.getSigningTime(); Date expected = TestUtils.parseDate("04-Nov-2007 20:32:46 GMT"); // we need to compensate for different timezones assertTrue(Math.abs(DateTimeUtils.diffDays(expected, signingTime)) <= 1); // second signer SignerIdentifier signerId2 = signerInfo2.getSignerId(); assertEquals(new X500Principal("EMAILADDRESS=ca@example.com, CN=MITM Test CA, L=Amsterdam, ST=NH, C=NL"), signerId2.getIssuer()); assertEquals(new BigInteger("115FD1392A8FF07AA727558FA50B262", 16), signerId2.getSerialNumber()); assertEquals(null, signerId2.getSubjectKeyIdentifier()); assertEquals(Digest.MD5, Digest.fromOID(signerInfo2.getDigestAlgorithmOID())); assertArrayEquals(new byte[] { 5, 0 }, signerInfo2.getDigestAlgorithmParams()); assertEquals("1.2.840.113549.1.1.1", signerInfo2.getEncryptionAlgorithmOID()); assertArrayEquals(new byte[] { 5, 0 }, signerInfo2.getEncryptionAlgorithmParams()); assertEquals(1, signerInfo2.getVersion()); signingTime = signerInfo1.getSigningTime(); // we need to compensate for different timezones assertTrue(Math.abs(DateTimeUtils.diffDays(expected, signingTime)) <= 1); X509Certificate signingCertificate1 = (X509Certificate) keyStore.getCertificate("ValidCertificate"); X509Certificate signingCertificate2 = (X509Certificate) keyStore.getCertificate("md5Hash"); assertTrue(signerId1.match(signingCertificate1)); assertTrue(signerId2.match(signingCertificate2)); assertTrue(signerInfo1.verify(signingCertificate1.getPublicKey())); assertTrue(signerInfo2.verify(signingCertificate2.getPublicKey())); } @Test public void testOpaqueSigned() throws Exception { MimeMessage signedMessage = loadMessage("signed-opaque-validcertificate.eml"); SMIMESigned signedData = new SMIMESigned(signedMessage); CMSSignedDataAdapter signedDataAdapter = CMSAdapterFactory.createAdapter(signedData); assertTrue(signedDataAdapter instanceof CMSSignedDataAdapterImpl); testOpaqueSigned(signedDataAdapter); } @Test public void testOpaqueSignedParser() throws Exception { MimeMessage signedMessage = loadMessage("signed-opaque-validcertificate.eml"); SMIMESignedParser signedData = new SMIMESignedParser( new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), signedMessage); CMSSignedDataAdapter signedDataAdapter = CMSAdapterFactory.createAdapter(signedData); assertTrue(signedDataAdapter instanceof CMSSignedDataParserAdapterImpl); testOpaqueSigned(signedDataAdapter); } public void testOpaqueSigned(CMSSignedDataAdapter signedDataAdapter) throws Exception { CMSSignedInspector inspector = new CMSSignedInspectorImpl(signedDataAdapter, securityFactory.getNonSensitiveProvider(), securityFactory.getSensitiveProvider()); assertEquals(1, inspector.getVersion()); List<X509Certificate> certificates = inspector.getCertificates(); assertEquals(3, certificates.size()); Map<BigInteger, X509Certificate> certMap = new HashMap<BigInteger, X509Certificate>(); for (X509Certificate certificate : certificates) { certMap.put(certificate.getSerialNumber(), certificate); } // the ca and root certificate should be there X509Certificate caCertificate = certMap.get(new BigInteger("115FCAD6B536FD8D49E72922CD1F0DA", 16)); assertNotNull(caCertificate); assertTrue(certMap.containsKey(new BigInteger("115FCAC409FB2022B7D06920A00FE42", 16))); // the signing certificate should be there X509Certificate signerCertificate = certMap.get(new BigInteger("115fcd741088707366e9727452c9770", 16)); assertNotNull(signerCertificate); List<X509CRL> crls = inspector.getCRLs(); assertEquals(0, crls.size()); List<SignerInfo> signers = inspector.getSigners(); assertEquals(1, signers.size()); SignerInfo signerInfo = signers.get(0); assertEquals(Digest.SHA1, Digest.fromOID(signerInfo.getDigestAlgorithmOID())); // SHA-1 has DERNull as parameter. // //-- sha-1 OBJECT IDENTIFIER ::= {iso(1) identified-organization(3) oiw(14) //-- secsig(3) algorithm(2) 26} -- -- [MSG] //-- (The parameters field must be present and is defined as an //-- ASN.1 NULL type. Implementations should also accept identifiers //-- where the parameters field is absent.) // See http://www.imc.org/ietf-smime/other-smime-oids.asn assertArrayEquals(new byte[] { 5, 0 }, signerInfo.getDigestAlgorithmParams()); // RSA has OID 1.2.840.113549.1.1.1 assertEquals("1.2.840.113549.1.1.1", signerInfo.getEncryptionAlgorithmOID()); // DERNull parameter assertArrayEquals(new byte[] { 5, 0 }, signerInfo.getEncryptionAlgorithmParams()); assertEquals(1, signerInfo.getVersion()); Date signingTime = signerInfo.getSigningTime(); Date expected = TestUtils.parseDate("04-Nov-2007 20:32:47 GMT"); // we need to compensate for different timezones assertTrue(Math.abs(DateTimeUtils.diffDays(expected, signingTime)) <= 1); assertTrue(signerInfo.verify(signerCertificate.getPublicKey())); assertFalse(signerInfo.verify(caCertificate.getPublicKey())); // try validate the first again assertTrue(signerInfo.verify(signerCertificate.getPublicKey())); } }