Re: S/MIME sign and immediate verification fails

Lothar Kimmeringer <[email protected]> Mon, 17 Jun 2019 16:52:49 +0200
Newsgroups gmane.comp.encryption.bouncy-castle.devel
Message-ID <[email protected]>
Hi again,

some more data points:

  - Bouncy Castle 1.62 leads to the same problem.
  - Only the combination multipart/mixed, containing multipart/alternative
    and an attachment leads to this effect.

For the latter I've extended my test by creating different mail body parts
for signing. All signatures created from these mails successfully verify,
except the "full mail", so I start thinking that I might have found a bug
in BouncyCastle here. Attached you can find a revised version of my test case.

I can open an issue on GitHub if needed.


Thanks and cheers, Lothar
__Run_SMIMESigning.java (text/plain, 7.5 KB)

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Date;

import javax.mail.MessagingException;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;

import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.mail.smime.SMIMESigned;
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import junit.framework.TestCase;

/**
 * Test class to check out S/MIME signature and verification
 */
public class __Run_SMIMESigning extends TestCase {
    final static SecureRandom random = new SecureRandom();
    
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * Tests the issue
     * @throws Exception
     */
    public void testMailSigning() throws Exception {
        System.out.println(new BouncyCastleProvider().getVersion());
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
        kpg.initialize(1024, random);
        
        KeyPair aliceKey = kpg.generateKeyPair();
        X509Certificate aliceCert = makeCertificate(aliceKey, "CN=Alice's Certificate", aliceKey, "CN=Alice's Certificate");
        
        MimeBodyPart plainText = createPlainBodyPart();
        MimeBodyPart htmlText = createHTMLPart();
        MimeBodyPart attachment = createAttachmentBodyPart();
        performSignatureCreationAndCheck(aliceKey, aliceCert, plainText);
        performSignatureCreationAndCheck(aliceKey, aliceCert, htmlText);
        performSignatureCreationAndCheck(aliceKey, aliceCert, attachment);
        performSignatureCreationAndCheck(aliceKey, aliceCert, createAlternativeBodyPart(plainText, htmlText));
        performSignatureCreationAndCheck(aliceKey, aliceCert, createMixedBodyPart(plainText, attachment));
        performSignatureCreationAndCheck(aliceKey, aliceCert, createMixedBodyPart(htmlText, attachment));
        performSignatureCreationAndCheck(aliceKey, aliceCert, createFullMailBodyPart());
    }

    private void performSignatureCreationAndCheck(KeyPair aliceKey, X509Certificate aliceCert, MimeBodyPart mbp)
            throws Exception {
        mbp.writeTo(System.out);
        System.out.println();
        
        SMIMESignedGenerator gen = new SMIMESignedGenerator();
        JcaSimpleSignerInfoGeneratorBuilder builder = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC");
        SignerInfoGenerator sigGen = builder.build("SHA256WithRSA", aliceKey.getPrivate(), aliceCert);
        gen.addSignerInfoGenerator(sigGen);
        
        MimeMultipart signedMM = gen.generate(mbp);
        MimeBodyPart signedBody = new MimeBodyPart();
        signedBody.setContent(signedMM, signedMM.getContentType());
        
        System.out.println("signed:");
        signedBody.writeTo(System.out);
        System.out.println();
        
        MimeMultipart content = (MimeMultipart) signedBody.getContent();
        SMIMESigned sig = new SMIMESigned(content);
        SignerInformation si = sig.getSignerInfos().getSigners().iterator().next();
        SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(aliceCert);
        System.out.println("verified: " + si.verify(verifier));
    }
    
    private MimeBodyPart createFullMailBodyPart() throws MessagingException {
        MimeBodyPart plainText = createPlainBodyPart();
        MimeBodyPart htmlText = createHTMLPart();
        MimeBodyPart alternatives = createAlternativeBodyPart(plainText, htmlText);
        MimeBodyPart textAttachment = createAttachmentBodyPart();
        
        return createMixedBodyPart(alternatives, textAttachment);
    }

    private MimeBodyPart createMixedBodyPart(MimeBodyPart alternatives, MimeBodyPart textAttachment)
            throws MessagingException {
        MimeBodyPart ret = new MimeBodyPart();
        ret.setContent(new MimeMultipart(alternatives, textAttachment));
        return ret;
    }

    private MimeBodyPart createAttachmentBodyPart() throws MessagingException {
        MimeBodyPart textAttachment = new MimeBodyPart();
        textAttachment.setText("some attachment data containing non-ascii-data like €", "utf8", "plain");
        textAttachment.setHeader("Content-Transfer-Encoding", "base64");
        textAttachment.setDisposition("attachment");
        textAttachment.setFileName("filename.txt");
        return textAttachment;
    }

    private MimeBodyPart createAlternativeBodyPart(MimeBodyPart plainText, MimeBodyPart htmlText)
            throws MessagingException {
        MimeBodyPart alternatives = new MimeBodyPart();
        alternatives.setContent(new MimeMultipart("alternative", plainText, htmlText));
        return alternatives;
    }

    private MimeBodyPart createHTMLPart() throws MessagingException {
        MimeBodyPart htmlText = new MimeBodyPart();
        htmlText.setText("<h1>Some HTML</h1><p>Some text with non-ascii: €</p>", "utf8", "html");
        return htmlText;
    }

    private MimeBodyPart createPlainBodyPart() throws MessagingException {
        MimeBodyPart plainText = new MimeBodyPart();
        plainText.setText("Plain text with non-ascii: €", "utf8", "plain");
        return plainText;
    }

    // taken from the BC test case class
    // at https://github.com/bcgit/bc-java/blob/master/mail/src/main/java/org/bouncycastle/mail/smime/examples/CreateSignedMultipartMail.java
    static X509Certificate makeCertificate(
            KeyPair subKP,
            String  subDN,
            KeyPair issKP,
            String  issDN)
            throws Exception
        {
            PublicKey  subPub  = subKP.getPublic();
            PrivateKey issPriv = issKP.getPrivate();
            PublicKey  issPub  = issKP.getPublic();

            JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
            X509v3CertificateBuilder v3CertGen = new JcaX509v3CertificateBuilder(new X500Name(issDN), BigInteger.valueOf(random.nextLong()), new Date(System.currentTimeMillis()), new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)), new X500Name(subDN), subPub);

            v3CertGen.addExtension(
                    Extension.subjectKeyIdentifier,
                false,
                extUtils.createSubjectKeyIdentifier(subPub));

            v3CertGen.addExtension(
                    Extension.authorityKeyIdentifier,
                false,
                extUtils.createAuthorityKeyIdentifier(issPub));

            return new JcaX509CertificateConverter().setProvider("BC").getCertificate(v3CertGen.build(new JcaContentSignerBuilder("SHA256withRSA").setProvider("BC").build(issPriv)));
    }
}