Search code examples
javaencryptiondigital-signaturebouncycastlesmime

Keeping the signature on SMIME decrypted mail


I couldn't find anything that helps me. I'm having problems with keeping the signature of a smime email. I'm trying to take a encrypted smime message, decrypt it and then save it to a database (it's for work stuff). Since the message is first signed and then encrypted I see no problem with keeping the signature after decryption. I can successfully decrypt messages. Just when I try to take a signed and encrypted mail it doesn't work. Im working with Java (1.8) and bouncycastle (jdk15on_154). It all happens in one class with static methods (because I'm actually calling the decryption from a delphi program).

Here is the class:

    public static void startSMimeDecryption (final String encryptedPath, String decryptedPath) {
    logger.info("***  Start of sMIME decryption  ***"); //$NON-NLS-1$

    Properties props = System.getProperties();
    Session session = Session.getDefaultInstance(props, null);
    try {

        logger.info("Encrypted message: [" + encryptedPath + "]"); //$NON-NLS-1$ //$NON-NLS-2$
        File messageFile = new File(encryptedPath);
        String fileExtension = getFileExtension(messageFile);

        if (fileExtension != null) {

            MimeMessage m = null;

            if (fileExtension.toUpperCase().equals("EML")) { //$NON-NLS-1$

                try (FileInputStream streamIn = new FileInputStream(encryptedPath);) {
                    logger.info("Created FileInputStream."); //$NON-NLS-1$

                    m = new MimeMessage(session, streamIn);
                    logger.info("Created MimeMessage."); //$NON-NLS-1$
                } catch (IOException e) {
                    logger.error("Error while trying to read in message via stream. " + e.getMessage());
                    return;
                }   

            } else if (fileExtension.toUpperCase().equals("MSG")) { //$NON-NLS-1$
                m = createMimeMessageFromMsg(session, messageFile);
                logger.info("Created MimeMessage."); //$NON-NLS-1$
            } else {
                throw new InvalidFileException("Not a supported file type (extension). Only '*.eml' and '*.msg' are accepted."); //$NON-NLS-1$
            }

            if ((m.getFileName() != null) && (m.getFileName().toUpperCase().equals("SMIME.P7M") || Pattern.compile("^\\s*application\\/(x-)?pkcs7-mime.*$", Pattern.DOTALL).matcher(m.getContentType()).matches())) { //$NON-NLS-1$ //$NON-NLS-2$

                m = decryptMessage(m);

                logger.info("Saving decrypted message to file..."); //$NON-NLS-1$
                if (decryptedPath == null) {
                    String tempPath = messageFile.getAbsolutePath().substring(0, messageFile.getAbsolutePath().lastIndexOf(File.separator));
                    decryptedPath = tempPath + File.separator + FilenameUtils.removeExtension(messageFile.getName()) + "_decrypted.eml"; //$NON-NLS-1$
                }

                try (OutputStream str = Files.newOutputStream(Paths.get(decryptedPath))) {

                    m.writeTo(str);

                } catch (IOException e) {
                    logger.error("Failed to write to output stream. " + e.getMessage()); //$NON-NLS-1$
                    return;
                }

                logger.info("Decrypted message: [" + decryptedPath + "]"); //$NON-NLS-1$ //$NON-NLS-2$

            } else {
                throw new NotEncryptedMessageException("Not an encrypted message. Breaking up decryption."); //$NON-NLS-1$
            }

        } else {
            throw new InvalidFileException("No file extension found."); //$NON-NLS-1$
        }

    } catch (MessagingException | UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException 
             | CMSException | InvalidFileException | InvalidMessageException e) {

        logger.error("Failed to read message. " + e.getMessage()); //$NON-NLS-1$
    } catch (NotEncryptedMessageException e) {
        logger.error(e.getMessage());
        System.exit(RETURNCODE_NODECRYPTION);
    }

    logger.info("***  End of sMIME decryption  ***"); //$NON-NLS-1$
}

private static MimeMessage createMimeMessageFromMsg (final Session session, final File messageFile) {
    MimeMessage mimeMsg = null;

    if (messageFile != null) {
        MAPIMessage mapiMsg = null;

        logger.info("Converting .msg to MimeMessage."); //$NON-NLS-1$
        try (NPOIFSFileSystem npoi = new NPOIFSFileSystem(messageFile)) {

            logger.info("Created NPOIFSFileSystem."); //$NON-NLS-1$

            mapiMsg = new MAPIMessage(npoi);
            logger.info("Created MAPIMessage."); //$NON-NLS-1$

            mimeMsg = new MimeMessage(session);
            logger.info("Creating MimeMessage..."); //$NON-NLS-1$

            // Header übertragen
            try {
                for (String current : mapiMsg.getHeaders()) {
                    try {

                        mimeMsg.addHeaderLine(current);

                    } catch (MessagingException e) {
                        logger.error("Could not add header line to MimeMessage. " + e.getMessage()); //$NON-NLS-1$
                    }
                }

                logger.info("Added header to MimeMessage."); //$NON-NLS-1$

            } catch (ChunkNotFoundException e) {
                logger.error("Could not retrieve header. " + e.getMessage()); //$NON-NLS-1$
            }

            // "Attachment" übertragen
            for (AttachmentChunks current : mapiMsg.getAttachmentFiles()) {
                try {

                    ByteArrayDataSource ds = new ByteArrayDataSource(current.getEmbeddedAttachmentObject(), "application/pkcs7-mime"); //$NON-NLS-1$
                    DataHandler dh = new DataHandler(ds);
                    mimeMsg.setDataHandler(dh);

                    logger.info("Added attachment to MimeMessage."); //$NON-NLS-1$

                } catch (MessagingException e) {
                    logger.error("Could not transfer attachment into MimeMessage. " + e.getMessage()); //$NON-NLS-1$
                }
            }

        } catch (IOException e) {
            logger.error("Error while trying to build MimeMessage. " + e.getMessage()); //$NON-NLS-1$
            mimeMsg = null;
        }
    }

    return mimeMsg;
}

private static MimeMessage decryptMessage(final MimeMessage encrypted) throws MessagingException, CMSException,
                                                                        KeyStoreException, UnrecoverableKeyException, 
                                                                        NoSuchAlgorithmException, InvalidMessageException {
    if (encrypted != null) {
        logger.info("Starting decrypting message..."); //$NON-NLS-1$
        KeyStore keystore = getKeyStore();

        SMIMEEnveloped message = new SMIMEEnveloped(encrypted);

        RecipientInformationStore recinfos = message.getRecipientInfos();
        Enumeration<String> aliases = keystore.aliases();
        RecipientInformation recid = null;
        String alias = null;

        logger.info("Decrypting message..."); //$NON-NLS-1$
        while ((recid == null) && aliases.hasMoreElements()) {
            alias = aliases.nextElement();
            if (keystore.isKeyEntry(alias)) {
                Certificate cert = keystore.getCertificate(alias);
                recid = recinfos.get(new JceKeyTransRecipientId((X509Certificate) cert));
            }
        }
        if (recid == null) {
            throw new RuntimeException("No decryption key found"); //$NON-NLS-1$
        }

        JceKeyTransEnvelopedRecipient recipient = new JceKeyTransEnvelopedRecipient((PrivateKey) keystore.getKey(alias, "changeit".toCharArray())); //$NON-NLS-1$

        byte[] content = recid.getContent(recipient);

        logger.info("Setting MimeMessage properties."); //$NON-NLS-1$
        MimeMessage decrypted = new MimeMessage(Session.getDefaultInstance(System.getProperties()), new ByteArrayInputStream(content));
        Enumeration<Header> headers = encrypted.getAllHeaders();
        while (headers.hasMoreElements()) {
            Header h = headers.nextElement();

            if (decrypted.getHeader(h.getName()) == null) {
                decrypted.addHeader(h.getName(), h.getValue());
            }

        }

        decrypted.saveChanges();            

        logger.info("Decrypted message."); //$NON-NLS-1$

        return decrypted;
    } else {
        throw new InvalidMessageException("Encrypted MimeMessage is null."); //$NON-NLS-1$
    }
}

Can anyone help me or explain, why encrypted messages work perfectly fine, signed and encrypted doesn't work at all (I get a corrupt file with non-sense). I'm sorry if I did not post this correctly (it's my first post). Thank you for a reply.

EDIT: When i run the above code with an encrypted&signed email I receive a mime message that looks like this:

Content-Type: application/x-pkcs7-mime; name=smime.p7m; smime-
type=signed-data
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7m
MIME-Version: 1.0
Message-ID: <29771042.0.1481123386349.JavaMail.NAME@COMPUTER_NAME>

   MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgMFADCABgkqhkiG9w0BBwGggCSABIIP
   0ENvbnRlbnQtVHlwZTogbXVsdGlwYXJ0L2FsdGVybmF0aXZlOw0KCWJvdW5kYXJ5PSItLS0tPV9O [...]

Solution

  • Your program is failing because a "signed-and-encrypted" S/MIME message is not an SMIMEEnveloped. It has the wrong data format.

    Signed

    If you are just signing a message you get a structure like

    • Header identifying it as a signed message
    • The data that was signed ("the message")
    • Signature information ("the signature")

    Encrypted (Enveloped)

    If you are just encrypting a message you get a structure like

    • Header identifying it as an enveloped message
    • For each recipient
      • How to know you're the recipient of this particular encrypted key
      • The message encryption key encrypted for this recipient
    • The encrypted message

    "Signed and Encrypted"

    There's not a unique structure for when this concept is applied. Instead it just uses the two previous structures:

    • Header identifying it as a signed message
    • The enveloped/encrypted message (the enveloped message is what gets signed)
    • Signature information

    So the operations when sending are

    1. Encrypt the message
    2. Sign the message to prove the encryption wasn't tampered with

    As a recipient you need to do things in the other order:

    1. Verify the signature
    2. Decrypt

    Consequences

    So for signed-and-encrypted you need to read it as a signed message first, then pull out the signed contents and read that as an enveloped message.

    What this really means is that you can't keep just the signature and the decrypted contents, since the signature covers the encrypted contents. You would need to save the whole original signed message intact to be able to verify the signature later.