S/MIME sign and immediate verification fails

Lothar Kimmeringer <[email protected]> Fri, 14 Jun 2019 15:44:32 +0200
Newsgroups gmane.comp.encryption.bouncy-castle.devel
Message-ID <[email protected]>
Hi,

I'm struggling with this since yesterday and I don't see an error. Attached
you can find my JUnit test class that reproduces the problem I see with my
"real" test case (that's the reason why there is non-ascii data, changing that
to pure ascii has no effect on the problem). Here's what I'm doing:

    - Creating a MimeBodyPart, containing a multipart/alternative block with text/plain
      and text/html data, that has been added to a multipart/mixed block together
      with an attachment containing a text-file
    - Signing the MimeBodyPart the "usual" way, i.e. the way you e.g. find it
      in the example files that are provided with BC itself.
    - Put the resulting MimeMultipart into a MimeBodyPart and use that to
      verify the signature, again the "normal" way.

Whatever I try, I always get a verification check error:

org.bouncycastle.cms.CMSSignerDigestMismatchException: message-digest attribute value does not match calculated value
	at org.bouncycastle.cms.SignerInformation.doVerify(Unknown Source)
	at org.bouncycastle.cms.SignerInformation.verify(Unknown Source)
	at __Run_SMIMESigning.testMailSigning(__Run_SMIMESigning.java:77)

The strange thing is: When I compress before signing, the signature verifcation
check succeeds, the same is the case when I encrypt the signed data afterwards.
I can understand the former in some way but the latter is what I don't get.

I'd really appreciate if somebody can point me where I do something wrong,
because after staring on it for nearly a day, I've gone blind I suppose.


Thanks and best regards,

Lothar Kimmeringer
__Run_SMIMESigning.java (text/plain, 5.6 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 {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
        kpg.initialize(1024, random);
        
        KeyPair aliceKey = kpg.generateKeyPair();
        X509Certificate aliceCert = makeCertificate(aliceKey, "CN=Alice's Certiicate", aliceKey, "CN=Alice's Certiicate");
        
        MimeBodyPart mbp = createMailBodyPart();
        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 createMailBodyPart() throws MessagingException {
        MimeBodyPart plainText = new MimeBodyPart();
        plainText.setText("Plain text with non-ascii: €", "utf8", "plain");
        MimeBodyPart htmlText = new MimeBodyPart();
        htmlText.setText("<h1>Some HTML</h1><p>Some text with non-ascii: €</p>", "utf8", "html");
        
        MimeBodyPart alternatives = new MimeBodyPart();
        alternatives.setContent(new MimeMultipart("alternative", plainText, htmlText));
        
        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");
        
        MimeBodyPart ret = new MimeBodyPart();
        ret.setContent(new MimeMultipart(alternatives, textAttachment));
        return ret;
    }

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