Сохранение подписи на расшифрованной почте SMIME

Я не мог найти ничего, что помогло бы мне. У меня проблемы с сохранением подписи smime-сообщения. Я пытаюсь взять зашифрованное smime-сообщение, расшифровать его, а затем сохранить в базе данных (это для работы). Поскольку сообщение сначала подписывается, а затем шифруется, я не вижу проблем с сохранением подписи после расшифровки. Я могу успешно расшифровать сообщения. Просто при попытке взять подписанную и зашифрованную почту ничего не получается. Я работаю с Java (1.8) и bouncycastle (jdk15on_154). Все это происходит в одном классе со статическими методами (поскольку на самом деле я вызываю расшифровку из программы Delphi).

Вот класс:

    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$
    }
}

Может ли кто-нибудь помочь мне или объяснить, почему зашифрованные сообщения работают отлично, подписанные и зашифрованные не работают вообще (я получаю поврежденный файл с бессмысленным). Извините, если я не правильно разместил это (это мой первый пост). Спасибо за ответ.

РЕДАКТИРОВАТЬ: Когда я запускаю приведенный выше код с зашифрованным и подписанным электронным письмом, я получаю сообщение mime, которое выглядит следующим образом:

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 [...]

person Reborok    schedule 07.12.2016    source источник


Ответы (1)


Ваша программа дает сбой, потому что "подписанное и зашифрованное" сообщение S/MIME не является SMIMEEnveloped. Он имеет неправильный формат данных.

Подписано

Если вы просто подписываете сообщение, вы получаете структуру вроде

  • Заголовок, идентифицирующий его как подписанное сообщение
  • Данные, которые были подписаны («сообщение»)
  • Информация о подписи («подпись»)

Зашифровано (в оболочке)

Если вы просто шифруете сообщение, вы получаете такую ​​структуру, как

  • Заголовок, идентифицирующий его как обернутое сообщение
  • For each recipient
    • How to know you're the recipient of this particular encrypted key
    • Ключ шифрования сообщения, зашифрованный для этого получателя
  • Зашифрованное сообщение

«Подписано и зашифровано»

Не существует уникальной структуры для применения этой концепции. Вместо этого он просто использует две предыдущие структуры:

  • Заголовок, идентифицирующий его как подписанное сообщение
  • Обернутое/зашифрованное сообщение (обернутое сообщение — это то, что подписывается)
  • Информация о подписи

Итак, операции при отправке

  1. Зашифровать сообщение
  2. Подпишите сообщение, чтобы доказать, что шифрование не было взломано

Как получатель, вам нужно сделать что-то в другом порядке:

  1. Подтвердить подпись
  2. расшифровать

Последствия

Таким образом, для подписанного и зашифрованного вам нужно сначала прочитать его как подписанное сообщение, затем вытащить подписанное содержимое и прочитать его как обернутое сообщение.

На самом деле это означает, что вы не можете оставить только подпись и расшифрованное содержимое, поскольку подпись покрывает зашифрованное содержимое. Вам нужно будет сохранить все исходное подписанное сообщение без изменений, чтобы иметь возможность проверить подпись позже.

person bartonjs    schedule 07.12.2016
comment
Спасибо за ваше объяснение и помощь. Я пробовал разные вещи самостоятельно и понял, что такое поведение происходит только тогда, когда я пытаюсь расшифровать зашифрованное и подписанное сообщение .msg, отправленное из Outlook. Когда я отправляю зашифрованное и подписанное сообщение с помощью Thunderbird и сохраняю его как .msg через Outlook (Outlook 2007), все работает нормально. Просто когда я отправляю сообщения через Outlook (даже если я сохраняю их в Thunderbird), это не работает. Я чувствую, что Outlook использует (как обычно) свой собственный способ ведения дел. - person Reborok; 08.12.2016
comment
Кроме того, заголовок зашифрованного и подписанного сообщения выглядит так, как будто оно было подписано, а затем зашифровано, потому что тип содержимого - данные в оболочке, а в поврежденном файле - подписанные данные (после попытки расшифровки). - person Reborok; 08.12.2016
comment
@Reborok В этом случае Outlook более правильный. Подписание зашифрованных данных является безопасным при большем количестве ограничений, чем шифрование подписанных данных. Когда дело доходит до электронной почты, это, вероятно, не имеет большого значения. - person bartonjs; 09.12.2016