mitm.common.security.cms.CMSSignedInspectorImplTest.java Source code

Java tutorial

Introduction

Here is the source code for mitm.common.security.cms.CMSSignedInspectorImplTest.java

Source

/*
 * 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()));
    }
}