diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 72d6679..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,56 +0,0 @@ -version: 2.1 - -orbs: - github-maven-deploy: github-maven-deploy/github-maven-deploy@1.3.0 - -mvn-build-test-command: &mvn-build-test-command - mvn-build-test-command: mvn verify -Dmaven.javadoc.skip=true -Djacoco.skip=true -Dlicense.skip=true - -mvn-deploy-command: &mvn-deploy-command - mvn-deploy-command: | - mvn -s .circleci/maven-release-settings.xml clean deploy -DdeployAtEnd=true -DperformRelease=true -DskipTests -Dspotbugs.skip=true -Denforcer.skip=true -Djacoco.skip=true - mvn license:remove - context: RELEASE_PROFILE_BBOTTEMA - -workflows: - workflow: - jobs: - - github-maven-deploy/build-and-test: - <<: *mvn-build-test-command - filters: - branches: - only: master - - - github-maven-deploy/approve-deploy-patch-version: - type: approval - requires: - - github-maven-deploy/build-and-test - - github-maven-deploy/approve-deploy-minor-version: - type: approval - requires: - - github-maven-deploy/build-and-test - - github-maven-deploy/approve-deploy-major-version: - type: approval - requires: - - github-maven-deploy/build-and-test - - github-maven-deploy/approve-deploy-as-is-version: - type: approval - requires: - - github-maven-deploy/build-and-test - - - github-maven-deploy/deploy-patch-version: - requires: - - github-maven-deploy/approve-deploy-patch-version - <<: *mvn-deploy-command - - github-maven-deploy/deploy-minor-version: - requires: - - github-maven-deploy/approve-deploy-minor-version - <<: *mvn-deploy-command - - github-maven-deploy/deploy-major-version: - requires: - - github-maven-deploy/approve-deploy-major-version - <<: *mvn-deploy-command - - github-maven-deploy/deploy-as-is-version: - requires: - - github-maven-deploy/approve-deploy-as-is-version - <<: *mvn-deploy-command diff --git a/.circleci/maven-release-settings.xml b/.circleci/maven-release-settings.xml deleted file mode 100644 index f478962..0000000 --- a/.circleci/maven-release-settings.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - ossrh - ${env.SERVER_OSSRH_USERNAME} - ${env.SERVER_OSSRH_PASSWORD} - - - - - - gpg - - gpg - ${env.GPG_PASSPHRASE} - - - - - gpg - - \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 9818eb8..0000000 --- a/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -/bin -/target -/releases -/.settings -.project -.classpath -.checkstyle -/.idea -/*.iml diff --git a/LICENSE-2.0.txt b/LICENSE-2.0.txt deleted file mode 100644 index d639b1e..0000000 --- a/LICENSE-2.0.txt +++ /dev/null @@ -1,188 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. \ No newline at end of file diff --git a/NOTICE.txt b/NOTICE.txt deleted file mode 100644 index cc7f8f1..0000000 --- a/NOTICE.txt +++ /dev/null @@ -1,18 +0,0 @@ - ========================================================================= - == NOTICE file for use with the Apache License, Version 2.0 == - ========================================================================= - - Simple Java Mail - utils-mail-smime - Copyright (C) 2021 Benny Bottema (benny@bennybottema.com) - Copyright (C) 2015 Torsten Krause - Copyright (C) 2003 Lijun Liao, Allen Petersen - - This product uses no commercial products. - - This is a continuation of the abandoned fork - https://github.com/markenwerk/java-utils-mail-smime, which itself is based - on the S/MIME specific parts of the abandonded SourceForge project - 'JavaMail-Crypto API' (http://javamail-crypto.sourceforge.net/). - - This latest reincarnation was relicensed to Apachev2 with permissions from - all past authors (email correspondence included in the project root). \ No newline at end of file diff --git a/README.MD b/README.MD deleted file mode 100644 index c28d6ec..0000000 --- a/README.MD +++ /dev/null @@ -1,251 +0,0 @@ -[![APACHE v2 License](https://img.shields.io/badge/license-apachev2-blue.svg?style=flat)](LICENSE-2.0.txt) -[![Latest Release](https://img.shields.io/maven-central/v/org.simplejavamail/utils-mail-smime.svg?style=flat)](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.simplejavamail%22%20AND%20a%3A%22utils-mail-smime%22) -[![Javadocs](http://www.javadoc.io/badge/org.simplejavamail/utils-mail-smime.svg)](http://www.javadoc.io/doc/org.simplejavamail/utils-mail-smime) -[![Codacy](https://img.shields.io/codacy/grade/4d380e585ee54d30ae67e200be1806f5?style=flat)](https://www.codacy.com/gh/simple-java-mail/java-utils-mail-smime) - -# org.simplejavamail:utils-mail-smime - -This is a simple-to-use library to use [S/MIME](https://en.wikipedia.org/wiki/S/MIME) features in conjunction with [Jakarta Mail](https://eclipse-ee4j.github.io/mail/). - -Note: This is a revival / continuation of the archived project markenwerk/java-utils-mail-smime, which itself was a continuation of an abandoned project on SourceForge. - -```xml - - org.simplejavamail - utils-mail-smime - 2.3.12 - -``` - -## Change history - -v2.2.0 - v2.3.12 - -- v2.3.12 (05-03-2025): bumped jakarta.mail-api to 2.1.3, and angus-mail to 2.0.3 -- v2.3.xx -- upgraded release pipeline, no real changes -- v2.3.3 (25-04-2024): updated bouncycastle dependency to solve vulnerability (continued) -- v2.3.2 (13-04-2024): updated dependency to solve vulnerability in bouncycastle -- v2.3.1 (19-03-2024): [#10](https://github.com/simple-java-mail/java-utils-mail-smime/issues/10) Make default algorithms public and document which signing algorithms are available -- v2.3.0 (17-01-2024): [#9](https://github.com/simple-java-mail/java-utils-mail-smime/issues/9) Update to latest Jakarta+Angus dependencies -- v2.2.0 (14-12-2023): [#8](https://github.com/simple-java-mail/java-utils-mail-smime/issues/8) Enhancement: Handle Outlook's Non-Standard S/MIME Signed Messages - - -v2.1.2 (14-07-2023) - -- Security: updated bouncy castle to 1.75, which should solve https://security.snyk.io/vuln/SNYK-JAVA-ORGBOUNCYCASTLE-5771489 - - -v2.1.1 (07-06-2023) - -- [#5](https://github.com/simple-java-mail/java-utils-mail-smime/issues/5) Enhancement: Add support for fixing MessageID - - -v2.1.0 (05-04-2023) - -- [#3](https://github.com/simple-java-mail/java-utils-mail-smime/issues/3) Feature: Make cryptographic algorithms configurable - - -v2.0.1 (31-01-2022) - -- [#1](https://github.com/simple-java-mail/java-utils-mail-smime/issues/1) Bug: determining mime state causes NPE when the "protocol" part is missing from the ContentType header - - -v2.0.0 (28-12-2021) - -- Initial release, with: -- deprecated JavaMail upgraded Jakarta Mail 2.0.1 -- Apache v2 license (with full permissions of all past authors) -- Java 8 -- Log4j security fixes - -# Original documenation follows: - -## Overview - -This library allows you to - - - sign MIME Messages according to the S/MIME standard, - - encrypt MIME Messages according to the S/MIME standard, - - check, whether a MIME Message is encrypted or signed according to the S/MIME standard, - - check, whether the signature of a MIME message that is signed according to the S/MIME standard is valid or - - decrypt a MIME message that is encrypted according to the S/MIME standard. - -This library is hosted in the [Maven Central Repository](https://maven-badges.herokuapp.com/maven-central/net.markenwerk/utils-mail-smime). You can use it with the following coordinates: - -Consult the [usage description](#usage) and [Javadoc](http://markenwerk.github.io/java-utils-mail-smime/index.html) for further information. - -## Origin and state - -The initial version of this library is based on the S/MIME specific parts of a project called [JavaMail-Crypto API](http://javamail-crypto.sourceforge.net/) which relies on the Java version of [The Legion of the Bouncy Castle](http://www.bouncycastle.org/java.html) as a provider of all sorts of cryptography witchcraft. -The JavaMail-Crypto API itself seems to be heavily influenced by the [example code](http://grepcode.com/file/repo1.maven.org/maven2/org.bouncycastle/bcmail-jdk16/1.46/org/bouncycastle/mail/smime/examples) from the Bouncy Castle project. - -We've decided to provide this library as an alternative for the S/MIME specific functionality of the JavaMail-Crypto API since the original project appears to be unmaintained since June of 2006 and is in fact incompatible with more current versions of Bouncy Castle (Some methods were deprecated for years and are now completely removed). -It is currently not our intention to provide a corresponding modernization of the PGP specific functionality. - -The project page of the JavaMail-Crypto API states that it is *currently* in an alpha state. However, we never had any major issues while we were using it in a production environment. Fixes to minor issues have been incorporated in this library. - -We used the original - and now this - library mainly to sign and encrypt system generated messages. -None of the major mail clients that actually have S/MIME support (Thunderbird, Mail for Mac & iOS, etc.) had any problems to decrypt or check the signatures of these messages. - -This library has roughly the same range of functionality regarding S/MIME as the original library. This may not be every aspect of the current [RFC](https://tools.ietf.org/html/rfc5751), but we're trying to improve this library when necessary. A further goal of this library is to provide an API that is as simple as possible to use. - -## Setup - -An application that wants to encrypt a S/MIME message has to provide a S/MIME certificate in form of a standard [`X509Certificate`][X509Certificate]. -This library imposes no obstacles on how the certificate object is obtained. - -An application that wants to sign or decrypt a S/MIME message has to provide a standard [`PrivateKey`][PrivateKey] and the [`X509Certificate`][X509Certificate] chain for the S/MIME certificate, but the preferred way is to provide these as a PKCS12 keystore. - -Some CAs provide S/MIME certificates free of charge (i.E. [COMODO](https://secure.comodo.com/products/frontpage?area=SecureEmailCertificate)). In most cases, a private key is created by the browser that is used to apply for the certificate, i.e. by using the [keygen](http://www.w3schools.com/tags/tag_keygen.asp) tag, and after validation, i.e. opening a confirmation link send via email, the corresponding certificate is installed into the browsers certificate store. The private key and the certificate, including the certificate chain, can then be exported as a PKCS12 keystore. The alias given to the private key inside the keystore is usually neither guessable nor very sensible, but you can use the [keytool](https://docs.oracle.com/javase/7/docs/technotes/tools/windows/keytool.html) to find it out. - -```shell -keytool -list -storetype pkcs12 -keystore smime.p12 -``` - -For a PKCS12 keystore called `smime.p12` this yields a output like - -``` -Keystore type: PKCS12 -Keystore provider: SunJSSE - -Your keystore contains 1 entry - -'s comodo ca limited id, Oct 8, 2015, PrivateKeyEntry, -Certificate fingerprint (SHA1): F1:A9:99:CC:35:CA:3E:C7:D3:01:EC:95:14:D7:C0:32:1C:AF:50:CF -``` - -where `'s comodo ca limited id` is the given alias. It can be changed like this: - -```shell -keytool -changealias -alias "'s comodo ca limited id" -destalias "alias" -storetype pkcs12 -keystore smime.p12 -``` - -The public certificate can be exported into a PEM encoded file like this: - -```shell -keytool -export -alias "alias" -storetype pkcs12 -keystore smime.p12 -rfc -file certificate.pem -``` - -## Usage - -First things first: The [`BouncyCastleProvider`][BouncyCastleProvider] has to be added as a JCE provider somewhere in your application before this library can be used: - -```java -Security.addProvider(new BouncyCastleProvider()); -``` - -### Importing the S/MIME certificate ... - -Depending on what you want to do with this library you must import the private key and certificate chain as a [`SmimeKey`][SmimeKey] or import the public certificate as a [`X509certificate`][X509Certificate]. - -#### ... to sign or decrypt a message - -While [`SmimeKey`][SmimeKey] has a public constructor it is recommended to use a [`SmimeKeyStore`][SmimeKeyStore], which is a thin wrapper around a PKCS12 keystore and can be created and used like this: - -```java -SmimeKeyStore smimeKeyStore = new SmimeKeyStore(pkcs12Stream, storePass); -SmimeKey smimeKey = smimeKeyStore.getPrivateKey("alias", keyPass); -``` - -To create a [`SmimeKeyStore`][SmimeKeyStore] you have to provide an [`InputStream`][InputStream] that yields the PKCS12 keystore (most likely a [`FileInputStream`][FileInputStream]) and the store password as a `char[]`. By default, the `char[]` will be overwritten after is has been used. This behaviour can be turned off with the optional third parameter. - -To obtain a [`SmimeKey`][SmimeKey] from the [`SmimeKeyStore`][SmimeKeyStore] you have to provide the alias of the private key entry and the password to decrypt the private key. Again, as a `char[]` that will be overwritten by default. - -#### ... to encrypt a message - -There are many ways to import a [`X509certificate`][X509Certificate]. One possibility is the following where you have to provide an [`InputStream`][InputStream] that yields the PEM encoded certificate: - -```java -CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC"); -X509Certificate certificate = (X509Certificate) factory.generateCertificate(pemStream); -``` - -### Using the library as a sender ... - -While is is in theory possible to sign and encrypt a message in any order and even multiple times, this is not recommended in reality because even the common mail clients don't support such usages of S/MIME very well. - -So far, we haven't encountered any problems with the following usages: - - - Signing only - - Encrypting only - - Fist signing, than encrypting - -We will assume that you already know how to create a SMTP [`Session`][Session] and how create and send a MIME Message with JavaMail, but here is a minimal example how one could send a simple message: - -```java -public void sendMail(Session session, String from, String to, String subject, String content) throws Exception { - MimeMessage message = new MimeMessage(session); - message.setFrom(new InternetAddress(from)); - message.setRecipient(RecipientType.TO, new InternetAddress(to)); - message.setSubject(subject); - message.setContent(content, "text/plain; charset=utf-8"); - MimeMessage encryptedSignedMessage = encryptMessage(session, signMessage(session, message, from), to); - Transport.send(encryptedSignedMessage); -} -``` - -#### .. to sign a message - -Just use the [`SmimeUtil`][SmimeUtil] with the [`MimeMessage`][MimeMessage] to be signed and the [`SmimeKey`][SmimeKey] to sign it with: - -```java -private MimeMessage signMessage(Session session, MimeMessage message, String from) throws Exception { - SmimeKey smimeKey = getSmimeKeyForSender(from); - return SmimeUtil.sign(session, message, smimeKey); -} - -private SmimeKey getSmimeKeyForSender(String from) { - // create your own SmimeKey from your own PKCS12 keystore - /* Example: - return new SmimeKeyStore(new ByteArrayInputStream(pkcs12ByteArray), pkcs12StorePassword) - .getPrivateKey(pkcs12KeyAlias, pkcs12KeyPassword); - */ -} -``` - -#### .. to encrypt a message - -Just use the [`SmimeUtil`][SmimeUtil] with the [`MimeMessage`][MimeMessage] to be encrypted and the [`X509certificate`][X509Certificate] to encrypt it with: - -```java -private MimeMessage encryptMessage(Session session, MimeMessage message, String to) throws Exception { - X509Certificate certificate = getCertificateForRecipient(to); - return SmimeUtil.encrypt(session, message, certificate); -} -``` - -### Using the library as a receiver - -We will assume that you already know how to create a POP or IMAP [`Session`][Session] and how receive a MIME Message with JavaMail, but here is a minimal example how one could read messages: - -```java -Store store = session.getStore(); -store.connect(host, port, user, password); -Folder inbox = store.getFolder("Inbox"); -inbox.open(Folder.READ_ONLY); - -for (int i = 1, n = inbox.getMessageCount(); i <= n; i++) { - MimeMessage mimeMessage = (MimeMessage) inbox.getMessage(i); -} -``` - -You can then use the [`SmimeUtil`][SmimeUtil] check the messages content type and find out if it has a [`SmimeState`][SmimeState] of `ENCRYPTED`, `SIGNED` or `NEITHER` like this: - -```java -SmimeState smimeState = SmimeUtil.getStatus(mimePart); -``` - -If the messages S/MIME state is `ENCRYPTED`, you can use the [`SmimeUtil`][SmimeUtil] with the encrypted [`MimeMessage`][MimeMessage] and the [`SmimeKey`][SmimeKey] to decrypt like this: - -```java -MimeMessage decryptedMessage = SmimeUtil.decrypt(session, mimeMessage, getSmimeKey()); -``` - -If the messages S/MIME state is `SIGNED` (the contains a MIME multipart with exactly two body parts: the signed content and the signature), you can use the [`SmimeUtil`][SmimeUtil] to check whether the signature is valid for the signed content and retrieve the signed content like this: - -```java -boolean validSignature = SmimeUtil.checkSignature(mimePart) -MimeBodyPart signedContent = SmimeUtil.getSignedContent(mimePart); -``` - -If the messages S/MIME state is `NEITHER` it just means that the message is neither S/MIME encrypted nor S/MIME signed. It may be encrypted or signed by some other means. \ No newline at end of file diff --git a/Relicensing approval by original authors.7z b/Relicensing approval by original authors.7z deleted file mode 100644 index bfe443a..0000000 Binary files a/Relicensing approval by original authors.7z and /dev/null differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..0101674 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + + +Going to javadoc.io/doc/org.simplejavamail/utils-mail-smime... + +If you are not redirected automatically, follow the link to javadoc.io/doc/org.simplejavamail/utils-mail-smime \ No newline at end of file diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 0f0b3f7..0000000 --- a/pom.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - 4.0.0 - - - com.github.bbottema - standard-project-parent - 1.0.45 - - - org.simplejavamail - utils-mail-smime - jar - utils-mail-smime - 2.3.12 - An S/MIME library for JavaMail - https://github.com/simple-java-mail/java-utils-mail-smime - 2021 - - - com.github.bbottema.java-utils-mail-smime - - - com/mycila/maven/plugin/license/templates/APACHE-2.txt - Benny Bottema - benny@bennybottema.com - - - - scm:git:git://github.com/bbottema/java-utils-mail-smime.git - scm:git:git@github.com:bbottema/java-utils-mail-smime.git - https://github.com/bbottema/java-utils-mail-smime - - - - - benny - Benny Bottema - benny@bennybottema.com - http://www.bennybottema.com - - developer - packager - - - - - - - Torsten Krause - - original developer (GitHub, 2015) - - - - Lijun Liao - - original developer (SourceForge, 2003) - - - - Allen Petersen - - original developer (SourceForge, 2003) - - - - - - GitHub Issues - https://github.com/bbottema/java-utils-mail-smime/issues - - - - - org.jetbrains - annotations - 16.0.2 - provided - - - - org.bouncycastle - bcjmail-jdk18on - 1.78.1 - - - jakarta.mail - jakarta.mail-api - 2.1.3 - - - org.eclipse.angus - angus-mail - 2.0.3 - - - \ No newline at end of file diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/KeyEncapsulationAlgorithm.java b/src/main/java/org/simplejavamail/utils/mail/smime/KeyEncapsulationAlgorithm.java deleted file mode 100644 index ec429e4..0000000 --- a/src/main/java/org/simplejavamail/utils/mail/smime/KeyEncapsulationAlgorithm.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.simplejavamail.utils.mail.smime; - -public enum KeyEncapsulationAlgorithm { - RSA, - RSA_OAEP_SHA224, - RSA_OAEP_SHA256, - RSA_OAEP_SHA384, - RSA_OAEP_SHA512, -} \ No newline at end of file diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/MimeUtil.java b/src/main/java/org/simplejavamail/utils/mail/smime/MimeUtil.java deleted file mode 100644 index 79713d1..0000000 --- a/src/main/java/org/simplejavamail/utils/mail/smime/MimeUtil.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.simplejavamail.utils.mail.smime; - -import jakarta.mail.MessagingException; -import jakarta.mail.internet.MimeBodyPart; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * Utilities for handling MIME messages from JavaMail. - * - * @author Allen Petersen (akp at sourceforge dot net) - * @author Torsten Krause (tk at markenwerk dot net) - * @since 1.0.0 - */ -final class MimeUtil { - - private MimeUtil() { - } - - /** - * Translates a {@link MimeBodyPart} into its MIME-canonical form. - */ - static MimeBodyPart canonicalize(MimeBodyPart mimeBodyPart) throws MessagingException, IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - OutputStream out = new MimeCanonicalOutputStream(buffer); - mimeBodyPart.writeTo(out); - out.close(); - return new MimeBodyPart(new ByteArrayInputStream(buffer.toByteArray())); - } - - /** - * A stream which filters bytes, converting all occurrences of bare - * {@code \n} into {@code \r\n}. - */ - private static class MimeCanonicalOutputStream extends java.io.FilterOutputStream { - - int lastReadByte = -1; - final byte[] crlf = new byte[] { (byte) '\r', (byte) '\n' }; - - public MimeCanonicalOutputStream(java.io.OutputStream os) { - super(os); - } - - public void write(int b) throws java.io.IOException { - if (b == '\r') { - out.write(crlf); - } else if (b == '\n') { - if (lastReadByte != '\r') - out.write(crlf); - } else { - out.write(b); - } - lastReadByte = b; - } - - public void write(byte[] b) throws java.io.IOException { - write(b, 0, b.length); - } - - public void write(byte[] b, int off, int len) throws java.io.IOException { - int start = off; - - len = off + len; - for (int i = start; i < len; i++) { - if (b[i] == '\r') { - out.write(b, start, i - start); - out.write(crlf); - start = i + 1; - } else if (b[i] == '\n') { - if (lastReadByte != '\r') { - out.write(b, start, i - start); - out.write(crlf); - } - start = i + 1; - } - lastReadByte = b[i]; - } - if ((len - start) > 0) - out.write(b, start, len - start); - } - - } -} diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeException.java b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeException.java deleted file mode 100644 index cd5bf1e..0000000 --- a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeException.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.simplejavamail.utils.mail.smime; - -/** - * A {@link RuntimeException} that is used to indicate S/MIME specific - * missbehaviors or to wrap other {@link Exception Exceptions} that were thrown - * during the processing of S/MIME specific operations. - * - * @author Torsten Krause (tk at markenwerk dot net) - * @since 1.0.0 - */ -public class SmimeException extends RuntimeException { - - private static final long serialVersionUID = 5400625787171945502L; - - /** - * Create a new {@code SmimeException} with the given message and cause. - * - * @param message - * The message of this {@code SmimeException}. - * @param cause - * The causing {@link Exception} wrapped by this - * {@code SmimeException}. - */ - public SmimeException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Create a new {@code SmimeException} with the given message. - * - * @param message - * The message of this {@code SmimeException}. - */ - public SmimeException(String message) { - super(message); - } - -} diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKey.java b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKey.java deleted file mode 100644 index 3f36d49..0000000 --- a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKey.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.simplejavamail.utils.mail.smime; - -import java.security.Principal; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.StringTokenizer; - -/** - * A wrapper around a {@link PrivateKey} and a chain of {@link X509Certificate - * X509Certificates} used to sign or decrypt a MIME message. - * - * @author Allen Petersen (akp at sourceforge dot net) - * @author Torsten Krause (tk at markenwerk dot net) - * @since 1.0.0 - */ -public class SmimeKey { - - private final PrivateKey privateKey; - private final X509Certificate[] certificateChain; - private List addresses; - - /** - * Create a new {@code SmimeKey} with the given private key and certificate - * chain. - * - * @param privateKey - * The {@link PrivateKey} of this {@code SmimeKey} - * @param certificateChain - * The chain of {@link X509Certificate X509Certificates} of this - * {@code SmimeKey} starting with the certificate that holds the - * public key that corresponds to the given private key and - * ending with the trust anchor. - */ - public SmimeKey(PrivateKey privateKey, X509Certificate... certificateChain) { - this.privateKey = privateKey; - this.certificateChain = certificateChain; - } - - /** - * Returns the private key of this {@code SmimeKey}. - * - * @return The {@link PrivateKey}. - */ - public PrivateKey getPrivateKey() { - return privateKey; - } - - /** - * Returns the certificate that holds the public key that corresponds to the - * private key of this {@code SmimeKey}. - * - * @return The {@link X509Certificate}. - */ - public X509Certificate getCertificate() { - return certificateChain[0]; - } - - /** - * Returns the chain of certificates of this {@code SmimeKey} starting with - * the certificate that holds the public key that corresponds to the private - * key of this {@code SmimeKey} and ending with the trust anchor. - * - * @return The chain of {@link X509Certificate X509Certificates}. - */ - public X509Certificate[] getCertificateChain() { - return certificateChain != null ? certificateChain.clone() : null; - } - - /** - * Compiles and returns the list of email address associated with the - * {@link #getCertificate() certificate} of this {@code SmimeKey} by - * inspecting the subjects distinguished name. - * - * @return A {@link Collections#unmodifiableList(List) unmodifiable list} of - * email addresses. - */ - public List getAssociatedAddresses() { - if (addresses == null) { - extractAssociatedAddresses(); - } - return addresses; - } - - private void extractAssociatedAddresses() { - List addresses = new ArrayList<>(); - try { - X509Certificate certificate = getCertificate(); - if (null != certificate) { - Principal principal = certificate.getSubjectX500Principal(); - if (null != principal) { - String name = principal.getName(); - StringTokenizer tokenizer = new StringTokenizer(name, ","); - while (tokenizer.hasMoreTokens()) { - String next = tokenizer.nextToken(); - if (next.startsWith("E=")) - addresses.add(next.substring(2)); - } - } - } - } catch (Exception e) { - } - this.addresses = Collections.unmodifiableList(addresses); - } - -} diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKeyStore.java b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKeyStore.java deleted file mode 100644 index ba0f689..0000000 --- a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKeyStore.java +++ /dev/null @@ -1,197 +0,0 @@ -package org.simplejavamail.utils.mail.smime; - -import java.io.InputStream; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.PrivateKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Set; - -/** - * A wrapper around a {@link KeyStore} that can be initialized with a PKCS12 - * keystore and is used to obtain {@link SmimeKey SmimeKeys}. - * - * @author Allen Petersen (akp at sourceforge dot net) - * @author Torsten Krause (tk at markenwerk dot net) - * @since 1.0.0 - */ -public class SmimeKeyStore { - - private final KeyStore keyStore; - - /** - * Creates a new {@code SmimeKeyStore} by loading a PKCS12 keystore from - * the given input stream. - * - *

- * The character array holding the password is overwritten with {@code 0s} - * after it has been used. - * - * @param stream - * The {@link InputStream} to read the PKCS12 keystore from. - * @param password - * The password to unlock the PKCS12 keystore with. - */ - public SmimeKeyStore(InputStream stream, char[] password) { - this(stream, password, true); - } - - /** - * Creates a new {@code SmimeKeyStore} by loading a PKCS12 keystore from - * the given input stream. - * - *

- * If {@code discardPassword} is set to {@code true}, the character array - * holding the password is overwritten with {@code 0s} after it has been - * used. - * - * @param stream - * The {@link InputStream} to read the PKCS12 keystore from. - * @param password - * The password to unlock the PKCS12 keystore with. - * @param discardPassword - * Whether to overwrite the {@code char[]} holding the password - * after it has been used. - */ - public SmimeKeyStore(InputStream stream, char[] password, boolean discardPassword) { - try { - keyStore = KeyStore.getInstance("PKCS12", "BC"); - keyStore.load(stream, password); - } catch (Exception e) { - throw new SmimeException("Couldn't initialize SmimeKeyStore", e); - } finally { - if (discardPassword) { - overwrite(password); - } - } - } - - private void overwrite(char[] password) { - if (null != password) { - for (int i = 0, n = password.length; i < n; i++) { - password[i] = 0; - } - } - } - - /** - * Returns the number of entries in the underlying PKCS12 keystore. - * - * @return The number of entries in the underlying {@link KeyStore}. - * - */ - public int size() { - try { - return keyStore.size(); - } catch (KeyStoreException e) { - throw new SmimeException("Couldn't retrieve the number of entries from SmimeKeyStore", e); - } - } - - /** - * Returns the S/MIME key associated with the given alias, using the given - * password to recover it. - * - *

- * The character array holding the password is overwritten with {@code 0s} - * after it has been used. - * - * @param alias - * The alias. - * @param password - * The password to unlock the {@link PrivateKey} keystore with. - * - * @return The requested {@link SmimeKey}, or null if the given alias does - * not exist or does not identify a private key entry. - */ - public SmimeKey getPrivateKey(String alias, char[] password) { - return getPrivateKey(alias, password, true); - } - - /** - * Returns the S/MIME key associated with the given alias, using the given - * password to recover it. - * - *

- * If {@code discardPassword} is set to {@code true}, the character array - * holding the password is overwritten with {@code 0s} after it has been - * used. - * - * @param alias - * The alias. - * @param password - * The password to unlock the {@link PrivateKey} keystore with. - * @param discardPassword - * Whether to overwrite the {@code char[]} holding the password - * after it has been used. - * - * @return The requested {@link SmimeKey}, or null if the given alias does - * not exist or does not identify a private key entry. - */ - public SmimeKey getPrivateKey(String alias, char[] password, boolean discardPassword) { - try { - if (containsPrivateKeyAlias(alias)) { - PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password); - Certificate[] certificateChain = keyStore.getCertificateChain(alias); - return new SmimeKey(privateKey, copy(certificateChain)); - } - return null; - } catch (Exception e) { - throw new SmimeException("Couldn't recover SmimeKey from SmimeKeyStore", e); - } finally { - if (discardPassword) { - overwrite(password); - } - } - } - - private X509Certificate[] copy(Certificate[] certificateChain) { - X509Certificate[] x509certificateChain = new X509Certificate[certificateChain.length]; - for (int i = 0, n = certificateChain.length; i < n; i++) { - x509certificateChain[i] = (X509Certificate) certificateChain[i]; - } - return x509certificateChain; - } - - /** - * Returns a set containing all aliases listed in the PKCS12 keystore. - * - * @return A {@link Collections#unmodifiableSet(Set) unmodifiable set} of - * aliases. - */ - public Set getPrivateKeyAliases() { - try { - Enumeration aliases = keyStore.aliases(); - Set aliasSet = new HashSet<>(); - while (aliases.hasMoreElements()) { - String alias = aliases.nextElement(); - if (keyStore.isKeyEntry(alias)) - aliasSet.add(alias); - } - return Collections.unmodifiableSet(aliasSet); - } catch (Exception e) { - throw new SmimeException("Couldn't recover aliases from SmimeKeyStore", e); - } - } - - /** - * Checks if the given alias exists in the PKCS12 keystore. - * - * @param alias - * The alias to look for. - * - * @return {@code true} if the alias exists, {@code false} otherwise. - */ - public boolean containsPrivateKeyAlias(String alias) { - try { - return keyStore.isKeyEntry(alias); - } catch (Exception e) { - throw new SmimeException("Couldn't recover aliases from SmimeKeyStore", e); - } - } - -} diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeMessageIdFixingMimeMessage.java b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeMessageIdFixingMimeMessage.java deleted file mode 100644 index b1d08cd..0000000 --- a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeMessageIdFixingMimeMessage.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.simplejavamail.utils.mail.smime; - -import jakarta.mail.MessagingException; -import jakarta.mail.Session; -import jakarta.mail.internet.MimeMessage; -import org.jetbrains.annotations.Nullable; - -import static java.lang.String.format; - -public class SmimeMessageIdFixingMimeMessage extends MimeMessage { - @Nullable - private final String messageId; - - public SmimeMessageIdFixingMimeMessage(Session session, @Nullable String messageId) { - super(session); - this.messageId = messageId; - } - - @Override - protected void updateMessageID() throws MessagingException { - if (messageId == null || messageId.length() == 0) { - super.updateMessageID(); - } else { - setHeader("Message-ID", messageId); - } - } - - @Override - public String toString() { - try { - return format("SmimeMimeMessage", super.getMessageID(), super.getSubject()); - } catch (MessagingException e) { - throw new IllegalStateException("should not reach here"); - } - } -} \ No newline at end of file diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeMessageIdFixingSMTPMessage.java b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeMessageIdFixingSMTPMessage.java deleted file mode 100644 index 745afe3..0000000 --- a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeMessageIdFixingSMTPMessage.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.simplejavamail.utils.mail.smime; - -import jakarta.mail.MessagingException; -import jakarta.mail.Session; -import org.eclipse.angus.mail.smtp.SMTPMessage; -import org.jetbrains.annotations.Nullable; - -import static java.lang.String.format; - -public class SmimeMessageIdFixingSMTPMessage extends SMTPMessage { - @Nullable - private final String messageId; - - public SmimeMessageIdFixingSMTPMessage(Session session, @Nullable String messageId) { - super(session); - this.messageId = messageId; - } - - @Override - protected void updateMessageID() throws MessagingException { - if (messageId == null || messageId.length() == 0) { - super.updateMessageID(); - } else { - setHeader("Message-ID", messageId); - } - } - - @Override - public String toString() { - try { - return format("SmimeSMTPMessage", super.getMessageID(), super.getSubject()); - } catch (MessagingException e) { - throw new IllegalStateException("should not reach here"); - } - } -} diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeState.java b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeState.java deleted file mode 100644 index 40419bc..0000000 --- a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeState.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.simplejavamail.utils.mail.smime; - -import jakarta.mail.internet.MimeMultipart; -import jakarta.mail.internet.MimePart; - -/** - * The {@code SmimeState} of a {@link MimePart} or {@link MimeMultipart} is - * derived from the corresponding content type and can be obtained with - * {@link SmimeUtil#checkSignature(MimePart) checkSignature()}; - * - * @author Torsten Krause (tk at markenwerk dot net) - * @since 1.0.0 - */ -public enum SmimeState { - - /** - * Indicates that the {@link MimePart} or {@link MimeMultipart} is S/MIME - * encrypted. - */ - ENCRYPTED, - - /** - * Indicates that the {@link MimePart} or {@link MimeMultipart} is probably S/MIME - * signed (type was multipart/signed, but protocol was missing). - */ - PROBABLY_SIGNED, - - /** - * Indicates that the {@link MimePart} or {@link MimeMultipart} is S/MIME - * signed. - */ - SIGNED, - - /** - * Indicates that the {@link MimePart} or {@link MimeMultipart} is S/MIME - * signed using an envelope (content is wrapped, probably as base64). - */ - SIGNED_ENVELOPED, - - /** - * Indicates that the {@link MimePart} or {@link MimeMultipart} is neither - * S/MIME encrypted nor S/MIME signed. - */ - NEITHER - -} diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeUtil.java b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeUtil.java deleted file mode 100644 index 9be12d4..0000000 --- a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeUtil.java +++ /dev/null @@ -1,714 +0,0 @@ -package org.simplejavamail.utils.mail.smime; - -import jakarta.activation.CommandMap; -import jakarta.activation.MailcapCommandMap; -import jakarta.mail.Header; -import jakarta.mail.MessagingException; -import jakarta.mail.Multipart; -import jakarta.mail.Session; -import jakarta.mail.internet.*; -import org.bouncycastle.asn1.ASN1EncodableVector; -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.cms.AttributeTable; -import org.bouncycastle.asn1.cms.IssuerAndSerialNumber; -import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; -import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute; -import org.bouncycastle.asn1.smime.SMIMECapability; -import org.bouncycastle.asn1.smime.SMIMECapabilityVector; -import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute; -import org.bouncycastle.asn1.x500.RDN; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x500.style.BCStyle; -import org.bouncycastle.asn1.x500.style.IETFUtils; -import org.bouncycastle.asn1.x509.AlgorithmIdentifier; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.jcajce.JcaCertStore; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cms.*; -import org.bouncycastle.cms.jcajce.*; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.mail.smime.*; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.OutputEncryptor; -import org.bouncycastle.operator.jcajce.JcaAlgorithmParametersConverter; -import org.bouncycastle.util.Store; -import org.eclipse.angus.mail.smtp.SMTPMessage; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.crypto.spec.OAEPParameterSpec; -import javax.crypto.spec.PSource; -import java.io.IOException; -import java.math.BigInteger; -import java.security.InvalidAlgorithmParameterException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Security; -import java.security.cert.Certificate; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.security.spec.MGF1ParameterSpec; -import java.util.*; - -/** - * Utilities for handling S/MIME specific operations on MIME messages from - * JavaMail. - * - * @author Allen Petersen (akp at sourceforge dot net) - * @author Torsten Krause (tk at markenwerk dot net) - * @since 1.0.0 - */ -public final class SmimeUtil { - - /** - * Unfortunately, these constants are not available in the Bouncy Castle, and they have to be passed as strings. - * - * @see org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder for a list of supported algorithms. - */ - public static final String DEFAULT_SIGNATURE_ALGORITHM_NAME = "SHA256withRSA"; - public static final KeyEncapsulationAlgorithm DEFAULT_KEY_ENCAPSULATION_ALGORITHM = KeyEncapsulationAlgorithm.RSA; - public static final ASN1ObjectIdentifier DEFAULT_CIPHER = CMSAlgorithm.DES_EDE3_CBC; - - static { - if (null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)) { - Security.addProvider(new BouncyCastleProvider()); - updateMailcapCommandMap(); - } - } - - @SuppressWarnings("unused") - private SmimeUtil() { - } - - private static void updateMailcapCommandMap() { - MailcapCommandMap map = (MailcapCommandMap) CommandMap.getDefaultCommandMap(); - map.addMailcap("application/pkcs7-signature;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature"); - map.addMailcap("application/pkcs7-mime;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime"); - map.addMailcap("application/x-pkcs7-signature;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature"); - map.addMailcap("application/x-pkcs7-mime;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime"); - map.addMailcap("multipart/signed;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed"); - CommandMap.setDefaultCommandMap(map); - } - - /** - * Encrypts a MIME message and yields a new S/MIME encrypted MIME message. - * - * @param session The {@link Session} that is used in conjunction with the original {@link MimeMessage}. - * @param messageId Optional MessageID that should be preserved on the encrypted MimeMessage result. - * @param mimeMessage The original {@link MimeMessage} to be encrypted. - * @param certificate The {@link X509Certificate} used to obtain the {@link PublicKey} to encrypt the original message with. - * @return The new S/MIME encrypted {@link MimeMessage}. - */ - public static MimeMessage encrypt(Session session, @Nullable String messageId, MimeMessage mimeMessage, X509Certificate certificate) { - return encrypt(session, mimeMessage, messageId, certificate, DEFAULT_KEY_ENCAPSULATION_ALGORITHM, DEFAULT_CIPHER); - } - - /** - * Encrypts a MIME message and yields a new S/MIME encrypted MIME message. - * - * @param session The {@link Session} that is used in conjunction with the - * original {@link MimeMessage}. - * @param mimeMessage The original {@link MimeMessage} to be encrypted. - * @param messageId Optional MessageID that should be preserved on the encrypted MimeMessage result. - * @param certificate The {@link X509Certificate} used to obtain the - * {@link PublicKey} to encrypt the original message with. - * @param keyEncapsulationAlgorithm Algorithm used to encapsulate the symmetric encryption key. - * Currently, RSA RSA-OAEP with various SHA digest lengths are supported. - * @param cmsAlgorithm Encryption algorithm for symmetric content encryption. - * @return The new S/MIME encrypted {@link MimeMessage}. - */ - public static MimeMessage encrypt(Session session, MimeMessage mimeMessage, @Nullable String messageId, X509Certificate certificate, KeyEncapsulationAlgorithm keyEncapsulationAlgorithm, ASN1ObjectIdentifier cmsAlgorithm) { - try { - MimeMessage encryptedMimeMessage = new SmimeMessageIdFixingMimeMessage(session, messageId); - copyHeaders(mimeMessage, encryptedMimeMessage); - - SMIMEEnvelopedGenerator generator = prepareGenerator(certificate, keyEncapsulationAlgorithm); - OutputEncryptor encryptor = prepareEncryptor(cmsAlgorithm); - - MimeBodyPart encryptedMimeBodyPart = generator.generate(mimeMessage, encryptor); - copyContent(encryptedMimeBodyPart, encryptedMimeMessage); - copyHeaders(encryptedMimeBodyPart, encryptedMimeMessage); - encryptedMimeMessage.saveChanges(); - return encryptedMimeMessage; - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Encrypts a MIME body part and yields a new S/MIME encrypted MIME body - * part. - * - * @param mimeBodyPart The original {@link MimeBodyPart} to be encrypted. - * @param certificate The {@link X509Certificate} used to obtain the - * {@link PublicKey} to encrypt the original body part with. - * @return The new S/MIME encrypted {@link MimeBodyPart}. - */ - public static MimeBodyPart encrypt(MimeBodyPart mimeBodyPart, X509Certificate certificate) { - return encrypt(mimeBodyPart, certificate, DEFAULT_KEY_ENCAPSULATION_ALGORITHM, DEFAULT_CIPHER); - } - - /** - * Encrypts a MIME body part and yields a new S/MIME encrypted MIME body - * part. - * - * @param mimeBodyPart The original {@link MimeBodyPart} to be encrypted. - * @param certificate The {@link X509Certificate} used to obtain the - * {@link PublicKey} to encrypt the original body part with. - * @param keyEncapsulationAlgorithm Algorithm used to encapsulate the symmetric encryption key. - * Currently, RSA RSA-OAEP with various SHA digest lengths are supported. - * @param cmsAlgorithm Encryption algorithm for symmetric content encryption. - * @return The new S/MIME encrypted {@link MimeBodyPart}. - */ - public static MimeBodyPart encrypt(MimeBodyPart mimeBodyPart, - X509Certificate certificate, - KeyEncapsulationAlgorithm keyEncapsulationAlgorithm, - ASN1ObjectIdentifier cmsAlgorithm) { - try { - SMIMEEnvelopedGenerator generator = prepareGenerator(certificate, keyEncapsulationAlgorithm); - OutputEncryptor encryptor = prepareEncryptor(cmsAlgorithm); - - return generator.generate(mimeBodyPart, encryptor); - - } catch (Exception e) { - throw handledException(e); - } - } - - private static void copyHeaders(MimeBodyPart fromBodyPart, MimeMessage toMessage) throws MessagingException { - Enumeration

headers = fromBodyPart.getAllHeaders(); - copyHeaders(headers, toMessage); - } - - private static void copyHeaders(MimeMessage fromMessage, MimeMessage toMessage) throws MessagingException { - Enumeration
headers = fromMessage.getAllHeaders(); - copyHeaders(headers, toMessage); - } - - private static void copyHeaders(Enumeration
headers, MimeMessage toMessage) throws MessagingException { - while (headers.hasMoreElements()) { - Header header = headers.nextElement(); - toMessage.setHeader(header.getName(), header.getValue()); - } - } - - private static SMIMEEnvelopedGenerator prepareGenerator(X509Certificate certificate, - KeyEncapsulationAlgorithm keyEncapsulationAlgorithm) - throws CertificateEncodingException, InvalidAlgorithmParameterException { - final JceKeyTransRecipientInfoGenerator infoGenerator; - if (keyEncapsulationAlgorithm == KeyEncapsulationAlgorithm.RSA) { - infoGenerator = new JceKeyTransRecipientInfoGenerator(certificate); - } else { - String digestName = determineDigestName(keyEncapsulationAlgorithm); - AlgorithmIdentifier oaepParams = new JcaAlgorithmParametersConverter().getAlgorithmIdentifier( - PKCSObjectIdentifiers.id_RSAES_OAEP, new OAEPParameterSpec( - digestName, "MGF1", new MGF1ParameterSpec(digestName), PSource.PSpecified.DEFAULT)); - infoGenerator = new JceKeyTransRecipientInfoGenerator(certificate, oaepParams); - } - infoGenerator.setProvider(BouncyCastleProvider.PROVIDER_NAME); - SMIMEEnvelopedGenerator generator = new SMIMEEnvelopedGenerator(); - generator.addRecipientInfoGenerator(infoGenerator); - return generator; - } - - @NotNull - private static String determineDigestName(KeyEncapsulationAlgorithm keyEncapsulationAlgorithm) throws InvalidAlgorithmParameterException { - if (keyEncapsulationAlgorithm == KeyEncapsulationAlgorithm.RSA_OAEP_SHA224) { - return "SHA-234"; - } else if (keyEncapsulationAlgorithm == KeyEncapsulationAlgorithm.RSA_OAEP_SHA256) { - return "SHA-256"; - } else if (keyEncapsulationAlgorithm == KeyEncapsulationAlgorithm.RSA_OAEP_SHA384) { - return "SHA-384"; - } else if (keyEncapsulationAlgorithm == KeyEncapsulationAlgorithm.RSA_OAEP_SHA512) { - return "SHA-512"; - } else { - throw new InvalidAlgorithmParameterException("Unknown S/MIME key encapsulation algorithm: " - + keyEncapsulationAlgorithm.name()); - } - } - - private static OutputEncryptor prepareEncryptor(ASN1ObjectIdentifier cmsAlgorithm) throws CMSException { - return new JceCMSContentEncryptorBuilder(cmsAlgorithm).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(); - } - - /** - * Decrypts an S/MIME encrypted MIME message and yields a new MIME message. - * - * @param session The {@link Session} that is used in conjunction with the - * encrypted {@link MimeMessage}. - * @param mimeMessage The encrypted {@link MimeMessage} to be decrypted. - * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to - * decrypt the encrypted message with. - * @return The new S/MIME decrypted {@link MimeMessage}. - */ - public static MimeMessage decrypt(Session session, MimeMessage mimeMessage, SmimeKey smimeKey) { - try { - byte[] content = decryptContent(new SMIMEEnveloped(mimeMessage), smimeKey); - MimeBodyPart mimeBodyPart = SMIMEUtil.toMimeBodyPart(content); - - MimeMessage decryptedMessage = new MimeMessage(session); - copyHeaderLines(mimeMessage, decryptedMessage); - copyContent(mimeBodyPart, decryptedMessage); - decryptedMessage.setHeader("Content-Type", mimeBodyPart.getContentType()); - return decryptedMessage; - - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Decrypts an S/MIME encrypted MIME body part and yields a new MIME body - * part. - * - * @param mimeBodyPart The encrypted {@link MimeBodyPart} to be decrypted. - * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to - * decrypt the encrypted body part with. - * @return The new S/MIME decrypted {@link MimeBodyPart}. - */ - public static MimeBodyPart decrypt(MimeBodyPart mimeBodyPart, SmimeKey smimeKey) { - try { - return SMIMEUtil.toMimeBodyPart(decryptContent(new SMIMEEnveloped(mimeBodyPart), smimeKey)); - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Decrypts an S/MIME encrypted MIME multipart and yields a new MIME body - * part. - * - * @param mimeMultipart The encrypted {@link MimeMultipart} to be decrypted. - * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to - * decrypt the encrypted multipart with. - * @return The new S/MIME decrypted {@link MimeBodyPart}. - */ - public static MimeBodyPart decrypt(MimeMultipart mimeMultipart, SmimeKey smimeKey) { - try { - MimeBodyPart mimeBodyPart = new MimeBodyPart(); - mimeBodyPart.setContent(mimeMultipart); - mimeBodyPart.setHeader("Content-Type", mimeMultipart.getContentType()); - return decrypt(mimeBodyPart, smimeKey); - } catch (Exception e) { - throw handledException(e); - } - } - - private static byte[] decryptContent(SMIMEEnveloped smimeEnveloped, SmimeKey smimeKey) throws MessagingException, CMSException { - X509Certificate certificate = smimeKey.getCertificate(); - PrivateKey privateKey = smimeKey.getPrivateKey(); - - RecipientInformationStore recipients = smimeEnveloped.getRecipientInfos(); - RecipientInformation recipient = recipients.get(new JceKeyTransRecipientId(certificate)); - - if (null == recipient) { - throw new MessagingException("no recipient"); - } - - JceKeyTransRecipient transportRecipient = new JceKeyTransEnvelopedRecipient(privateKey); - transportRecipient.setProvider(BouncyCastleProvider.PROVIDER_NAME); - return recipient.getContent(transportRecipient); - } - - private static void copyHeaderLines(MimeMessage fromMessage, MimeMessage toMessage) throws MessagingException { - Enumeration headerLines = fromMessage.getAllHeaderLines(); - while (headerLines.hasMoreElements()) { - String nextElement = headerLines.nextElement(); - toMessage.addHeaderLine(nextElement); - } - } - - private static void copyContent(MimeBodyPart fromBodyPart, MimeMessage toMessage) throws MessagingException, IOException { - toMessage.setContent(fromBodyPart.getContent(), fromBodyPart.getContentType()); - } - - /** - * Signs a MIME body part and yields a new S/MIME signed MIME body part. - * - * @param mimeBodyPart The original {@link MimeBodyPart} to be signed. - * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to - * sign the original body part with. - * @return The new S/MIME signed {@link MimeBodyPart}. - */ - public static MimeBodyPart sign(MimeBodyPart mimeBodyPart, SmimeKey smimeKey) { - return sign(mimeBodyPart, smimeKey, DEFAULT_SIGNATURE_ALGORITHM_NAME); - } - - /** - * Signs a MIME body part and yields a new S/MIME signed MIME body part. - * - * @param mimeBodyPart The original {@link MimeBodyPart} to be signed. - * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to - * sign the original body part with. - * @param algorithmName The name of the signature algorithm to use. Must be an algorithm - * supported by the Bouncy Castle security provider. - * @return The new S/MIME signed {@link MimeBodyPart}. - */ - public static MimeBodyPart sign(MimeBodyPart mimeBodyPart, SmimeKey smimeKey, String algorithmName) { - try { - SMIMESignedGenerator generator = getGenerator(smimeKey, algorithmName); - MimeMultipart signedMimeMultipart = generator.generate(MimeUtil.canonicalize(mimeBodyPart)); - MimeBodyPart signedMimeBodyPart = new MimeBodyPart(); - signedMimeBodyPart.setContent(signedMimeMultipart); - return signedMimeBodyPart; - - } catch (Exception e) { - throw handledException(e); - } - - } - - private static SMIMESignedGenerator getGenerator(SmimeKey smimeKey, String algorithmName) - throws CertificateEncodingException, OperatorCreationException { - SMIMESignedGenerator generator = new SMIMESignedGenerator(); - generator.addCertificates(getCertificateStore(smimeKey)); - generator.addSignerInfoGenerator(getInfoGenerator(smimeKey, algorithmName)); - return generator; - } - - private static SignerInfoGenerator getInfoGenerator(SmimeKey smimeKey, String algorithmName) - throws OperatorCreationException, CertificateEncodingException { - JcaSimpleSignerInfoGeneratorBuilder builder = new JcaSimpleSignerInfoGeneratorBuilder(); - builder.setSignedAttributeGenerator(new AttributeTable(getSignedAttributes(smimeKey))); - builder.setProvider(BouncyCastleProvider.PROVIDER_NAME); - - PrivateKey privateKey = smimeKey.getPrivateKey(); - X509Certificate certificate = smimeKey.getCertificate(); - return builder.build(algorithmName, privateKey, certificate); - } - - private static ASN1EncodableVector getSignedAttributes(SmimeKey smimeKey) { - ASN1EncodableVector signedAttributes = new ASN1EncodableVector(); - IssuerAndSerialNumber issuerAndSerialNumber = getIssuerAndSerialNumber(smimeKey); - signedAttributes.add(new SMIMEEncryptionKeyPreferenceAttribute(issuerAndSerialNumber)); - signedAttributes.add(new SMIMECapabilitiesAttribute(getCapabilityVector())); - return signedAttributes; - } - - private static SMIMECapabilityVector getCapabilityVector() { - SMIMECapabilityVector capabilityVector = new SMIMECapabilityVector(); - capabilityVector.addCapability(SMIMECapability.dES_EDE3_CBC); - capabilityVector.addCapability(SMIMECapability.rC2_CBC, 128); - capabilityVector.addCapability(SMIMECapability.dES_CBC); - return capabilityVector; - } - - private static IssuerAndSerialNumber getIssuerAndSerialNumber(SmimeKey smimeKey) { - X509Certificate certificate = smimeKey.getCertificate(); - BigInteger serialNumber = certificate.getSerialNumber(); - X500Name issuerName = new X500Name(certificate.getIssuerX500Principal().getName()); - return new IssuerAndSerialNumber(issuerName, serialNumber); - } - - private static JcaCertStore getCertificateStore(SmimeKey smimeKey) throws CertificateEncodingException { - Certificate[] certificateChain = smimeKey.getCertificateChain(); - X509Certificate certificate = smimeKey.getCertificate(); - - final List certificateList; - if (certificateChain != null && certificateChain.length > 0) { - certificateList = Arrays.asList(certificateChain); - } else { - certificateList = new ArrayList<>(); - certificateList.add(certificate); - } - return new JcaCertStore(certificateList); - } - - /** - * Signs a MIME message and yields a new S/MIME signed MIME message. - * - * @param session The {@link Session} that is used in conjunction with the original {@link MimeMessage}. - * @param messageId Optional MessageID that should be preserved on the signed MimeMessage. - * @param mimeMessage The original {@link MimeMessage} or {@link SMTPMessage} to be signed. - * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to sign the original message with. - * @return The new S/MIME signed {@link MimeMessage} or {@link SMTPMessage}. - */ - public static T sign(Session session, @Nullable String messageId, T mimeMessage, SmimeKey smimeKey) { - return sign(session, messageId, mimeMessage, smimeKey, DEFAULT_SIGNATURE_ALGORITHM_NAME); - } - - /** - * Signs a MIME message and yields a new S/MIME signed MIME message. - * - * @param session The {@link Session} that is used in conjunction with the original {@link MimeMessage}. - * @param messageId Optional MessageID that should be preserved on the signed MimeMessage. - * @param mimeMessage The original {@link MimeMessage} or {@link SMTPMessage} to be signed. - * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to sign the original message with. - * @param algorithmName The name of the signature algorithm to use. Must be an algorithm supported by the Bouncy Castle security provider. - * @return The new S/MIME signed {@link MimeMessage} or {@link SMTPMessage}. - */ - public static T sign(Session session, @Nullable String messageId, T mimeMessage, SmimeKey smimeKey, String algorithmName) { - //noinspection unchecked - return (mimeMessage instanceof SMTPMessage) - ? sign(mimeMessage, (T) new SmimeMessageIdFixingSMTPMessage(session, messageId), smimeKey, algorithmName) - : sign(mimeMessage, (T) new SmimeMessageIdFixingMimeMessage(session, messageId), smimeKey, algorithmName); - } - - private static T sign(T mimeMessage, T signedMessage, SmimeKey smimeKey, String algorithmName) { - try { - copyHeaderLines(mimeMessage, signedMessage); - copyContent(sign(extractMimeBodyPart(mimeMessage), smimeKey, algorithmName), signedMessage); - return signedMessage; - } catch (Exception e) { - throw handledException(e); - } - } - - private static MimeBodyPart extractMimeBodyPart(MimeMessage mimeMessage) throws IOException, MessagingException { - Object content = mimeMessage.getContent(); - UpdatableMimeBodyPart updateableMimeBodyPart = new UpdatableMimeBodyPart(); - if (content instanceof Multipart) { - updateableMimeBodyPart.setContent((Multipart) content); - } else { - updateableMimeBodyPart.setContent(content, mimeMessage.getDataHandler().getContentType()); - } - updateableMimeBodyPart.updateHeaders(); - return updateableMimeBodyPart; - } - - /** - * Checks the signature on an S/MIME signed MIME multipart. - * - * @param mimeMultipart The {@link MimeMultipart} to be checked. - * @return {@code true} if the multipart is correctly signed, {@code false} - * otherwise. - */ - public static boolean checkSignature(MimeMultipart mimeMultipart) { - try { - return checkSignature(new SMIMESigned(mimeMultipart)); - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Checks the signature on an S/MIME signed MIME part (i.e. MIME message). - * - * @param mimePart The {@link MimePart} to be checked. - * @return {@code true} if the part is correctly signed, {@code false} - * otherwise. - */ - public static boolean checkSignature(MimePart mimePart) { - try { - if (mimePart.isMimeType("multipart/signed")) { - return checkSignature(new SMIMESigned((MimeMultipart) mimePart.getContent())); - } else if (mimePart.isMimeType("application/pkcs7-mime") || mimePart.isMimeType("application/x-pkcs7-mime")) { - return checkSignature(new SMIMESigned(mimePart)); - } else { - throw new SmimeException("Message not signed"); - } - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Checks a SMIMESigned to make sure that the signature matches. - */ - private static boolean checkSignature(SMIMESigned smimeSigned) { - try { - boolean returnValue = true; - - @SuppressWarnings("rawtypes") - Store certificates = smimeSigned.getCertificates(); - Iterator signerInformations = smimeSigned.getSignerInfos().getSigners().iterator(); - - while (returnValue && signerInformations.hasNext()) { - SignerInformation signerInformation = signerInformations.next(); - X509Certificate certificate = getCertificate(certificates, signerInformation.getSID()); - SignerInformationVerifier verifier = getVerifier(certificate); - if (!signerInformation.verify(verifier)) { - returnValue = false; - } - } - return returnValue; - - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * @param mimeMultipart The {@link MimeMultipart} to be checked. - * @return The subject / address to which the certificate was issued to. Email clients may use this to show - * {@code "Signed by: "} - */ - public static String getSignedByAddress(MimeMultipart mimeMultipart) { - try { - return getSignedByAddress(new SMIMESigned(mimeMultipart)); - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * @param mimePart The {@link MimePart} to be checked. - * @return The subject / address to which the certificate was issued to. Email clients may use this to show - * {@code "Signed by: "} - */ - public static String getSignedByAddress(MimePart mimePart) { - try { - if (mimePart.isMimeType("multipart/signed")) { - return getSignedByAddress(new SMIMESigned((MimeMultipart) mimePart.getContent())); - } else if (mimePart.isMimeType("application/pkcs7-mime") || mimePart.isMimeType("application/x-pkcs7-mime")) { - return getSignedByAddress(new SMIMESigned(mimePart)); - } else { - throw new SmimeException("Message not signed"); - } - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Returns the subject / address to which the certificate was issued to. Email clients may use this to show - * {@code "Signed by: "} - */ - private static String getSignedByAddress(SMIMESigned smimeSigned) { - try { - @SuppressWarnings("rawtypes") - Store certificates = smimeSigned.getCertificates(); - - SignerInformation signerInformation = smimeSigned.getSignerInfos().getSigners().iterator().next(); - X509Certificate certificate = getCertificate(certificates, signerInformation.getSID()); - SignerInformationVerifier verifier = getVerifier(certificate); - X500Name x500name = verifier.getAssociatedCertificate().getSubject(); - RDN cn = x500name.getRDNs(BCStyle.CN)[0]; - return IETFUtils.valueToString(cn.getFirst().getValue()); - - } catch (Exception e) { - throw handledException(e); - } - } - - private static X509Certificate getCertificate(@SuppressWarnings("rawtypes") Store certificates, - SignerId signerId) throws CertificateException { - @SuppressWarnings({"unchecked"}) - X509CertificateHolder certificateHolder = (X509CertificateHolder) certificates.getMatches(signerId).iterator().next(); - JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter(); - certificateConverter.setProvider(BouncyCastleProvider.PROVIDER_NAME); - return certificateConverter.getCertificate(certificateHolder); - } - - private static SignerInformationVerifier getVerifier(X509Certificate certificate) throws OperatorCreationException { - JcaSimpleSignerInfoVerifierBuilder builder = new JcaSimpleSignerInfoVerifierBuilder(); - builder.setProvider(BouncyCastleProvider.PROVIDER_NAME); - return builder.build(certificate); - } - - /** - * Returns the signed MIME body part of an S/MIME signed MIME multipart. - * - * @param mimeMultipart The {@link MimeMultipart} to be stripped off. - * @return The signed {@link MimeBodyPart} contained in the - * {@link MimeMultipart}. - */ - public static MimeBodyPart getSignedContent(MimeMultipart mimeMultipart) { - try { - return new SMIMESigned(mimeMultipart).getContent(); - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Returns the signed MIME body part of an S/MIME signed MIME part (i.e. MIME - * message). - * - * @param mimePart The {@link MimePart} to be stripped off. - * @return The signed {@link MimeBodyPart} contained in the {@link MimePart} - * . - */ - public static MimeBodyPart getSignedContent(MimePart mimePart) { - try { - if (mimePart.isMimeType("multipart/signed")) { - return new SMIMESigned((MimeMultipart) mimePart.getContent()).getContent(); - } else if (mimePart.isMimeType("application/pkcs7-mime") || mimePart.isMimeType("application/x-pkcs7-mime")) { - return new SMIMESigned(mimePart).getContent(); - } else { - throw new SmimeException("Message not signed"); - } - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Returns the S/MIME state of a MIME multipart. - * - * @param mimeMultipart The {@link MimeMultipart} to be checked. - * @return the {@link SmimeState} of the {@link MimeMultipart}. - */ - public static SmimeState getStatus(MimeMultipart mimeMultipart) { - try { - return getStatus(new ContentType(mimeMultipart.getContentType())); - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Returns the S/MIME state of a MIME part (i.e. MIME message). - * - * @param mimePart The {@link MimePart} to be checked. - * @return the {@link SmimeState} of the {@link MimePart}. - */ - public static SmimeState getStatus(MimePart mimePart) { - try { - return getStatus(new ContentType(mimePart.getContentType())); - } catch (Exception e) { - throw handledException(e); - } - } - - private static SmimeState getStatus(ContentType contentType) { - if (isSmimeSignatureContentType(contentType)) { - return SmimeState.SIGNED; - } if (isProbablySmimeSignatureContentType(contentType)) { - return SmimeState.PROBABLY_SIGNED; - } else if (isSignatureSmimeType(contentType)) { - return SmimeState.SIGNED_ENVELOPED; - } else if (isSmimeEncryptionContenttype(contentType)) { - return SmimeState.ENCRYPTED; - } else { - return SmimeState.NEITHER; - } - } - - private static boolean isSmimeEncryptionContenttype(ContentType contentType) { - String baseContentType = contentType.getBaseType(); - return baseContentType.equalsIgnoreCase("application/pkcs7-mime") - || baseContentType.equalsIgnoreCase("application/x-pkcs7-mime"); - } - - private static boolean isSmimeSignatureContentType(ContentType contentType) { - String protocol = contentType.getParameter("protocol"); - return contentType.getBaseType().equalsIgnoreCase("multipart/signed") - && protocol != null && isSmimeSignatureProtocoll(protocol); - } - - private static boolean isProbablySmimeSignatureContentType(ContentType contentType) { - String protocol = contentType.getParameter("protocol"); - return contentType.getBaseType().equalsIgnoreCase("multipart/signed") && protocol == null; - } - - private static boolean isSignatureSmimeType(ContentType contentType) { - String baseContentType = contentType.getBaseType(); - return baseContentType.equalsIgnoreCase("application/x-pkcs7-mime") - && "signed-data".equals(contentType.getParameter("smime-type")); - } - - private static boolean isSmimeSignatureProtocoll(String protocol) { - return protocol.equalsIgnoreCase("application/pkcs7-signature") - || protocol.equalsIgnoreCase("application/x-pkcs7-signature"); - } - - private static SmimeException handledException(Exception e) { - if (e instanceof SmimeException) { - return (SmimeException) e; - } - return new SmimeException(e.getMessage(), e); - } - -} diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/UpdatableMimeBodyPart.java b/src/main/java/org/simplejavamail/utils/mail/smime/UpdatableMimeBodyPart.java deleted file mode 100644 index b119bbc..0000000 --- a/src/main/java/org/simplejavamail/utils/mail/smime/UpdatableMimeBodyPart.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.simplejavamail.utils.mail.smime; - -import jakarta.mail.MessagingException; -import jakarta.mail.internet.MimeBodyPart; - -/** - * A {@link MimeBodyPart} that exposes the method {@code updateHeaders()} with - * {@code public} visibility. - * - * @author Allen Petersen (akp at sourceforge dot net) - * @author Torsten Krause (tk at markenwerk dot net) - * @since 1.0.0 - */ -class UpdatableMimeBodyPart extends MimeBodyPart { - - /** - * Calls updateHeaders(). - */ - public void updateHeaders() throws MessagingException { - super.updateHeaders(); - } - -} diff --git a/src/test/java/org/simplejavamail/utils/mail/smime/SmimeUtilTest.java b/src/test/java/org/simplejavamail/utils/mail/smime/SmimeUtilTest.java deleted file mode 100644 index 3a1e81c..0000000 --- a/src/test/java/org/simplejavamail/utils/mail/smime/SmimeUtilTest.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.simplejavamail.utils.mail.smime; - -import jakarta.mail.MessagingException; -import jakarta.mail.Session; -import jakarta.mail.internet.InternetAddress; -import jakarta.mail.internet.MimeMessage; -import jakarta.mail.internet.MimeMultipart; -import org.bouncycastle.cms.CMSAlgorithm; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.io.InputStream; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Security; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Properties; - -import static org.assertj.core.api.Assertions.assertThat; - -public class SmimeUtilTest { - - private static final String SignatureAlgorithmRsaPss = "SHA256WITHRSAANDMGF1"; - private SmimeKeyStore alicesKeyStore; - private SmimeKeyStore bobsKeyStore; - private Session mailSession; - - @BeforeEach - public void setup() throws MessagingException, KeyStoreException, NoSuchProviderException, CertificateException, IOException, NoSuchAlgorithmException { - Security.addProvider(new BouncyCastleProvider()); - InputStream alicesKeystoreStream = this.getClass().getClassLoader().getResourceAsStream("alice.p12"); - this.alicesKeyStore = new SmimeKeyStore(alicesKeystoreStream, "alice".toCharArray()); - InputStream bobsKeystoreStream = this.getClass().getClassLoader().getResourceAsStream("bob.p12"); - this.bobsKeyStore = new SmimeKeyStore(bobsKeystoreStream, "bob".toCharArray()); - - Properties sessionProps = System.getProperties(); // new Properties(); // Fake properties for a fake session - this.mailSession = Session.getDefaultInstance(sessionProps); - } - - private MimeMessage createTestMessage(String from, String to) throws MessagingException { - MimeMessage testMessage = new MimeMessage(this.mailSession); - testMessage.setFrom(new InternetAddress(from)); - testMessage.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(to)); - testMessage.setSubject("This is a test email"); - testMessage.setContent("This is some test content for the test email's body", "text/plain; charset=utf-8"); - return testMessage; - } - - @Test - public void SuccessfullySignAndValidate() throws MessagingException, IOException { - MimeMessage testMessage = createTestMessage("alice@testcorp.com", "alice@testcorp.com"); - SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray()); - MimeMessage signedMessage = SmimeUtil.sign(this.mailSession, null, testMessage, alicesKey); - MimeMultipart multipartContent = (MimeMultipart) signedMessage.getContent(); - assertThat(SmimeUtil.getStatus(multipartContent)).isEqualTo(SmimeState.SIGNED); - assertThat(SmimeUtil.checkSignature(multipartContent)).isTrue(); - } - - @Test - public void SuccessfullyEnvelopeAndDecryptDefault() throws MessagingException { - MimeMessage testMessage = createTestMessage("alice@testcorp.com", "alice@testcorp.com"); - SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray()); - X509Certificate alicesCert = alicesKey.getCertificate(); - MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession, null, - SmimeUtil.sign(this.mailSession, null, testMessage, alicesKey), - alicesCert); - assertThat(SmimeUtil.getStatus((encryptedMessage))).isEqualTo(SmimeState.ENCRYPTED); - MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, alicesKey); - assertThat(SmimeUtil.checkSignature(decryptedMessage)).isTrue(); - } - - @Test - public void SuccessfullyEnvelopeAndDecrypt() throws MessagingException { - MimeMessage testMessage = createTestMessage("alice@testcorp.com", "alice@testcorp.com"); - SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray()); - X509Certificate alicesCert = alicesKey.getCertificate(); - MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession, - SmimeUtil.sign(this.mailSession, null, testMessage, alicesKey, SignatureAlgorithmRsaPss), - null, alicesCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA256, CMSAlgorithm.AES256_CBC); - assertThat(SmimeUtil.getStatus((encryptedMessage))).isEqualTo(SmimeState.ENCRYPTED); - MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, alicesKey); - assertThat(SmimeUtil.checkSignature(decryptedMessage)).isTrue(); - } - - @Test - public void AliceToBoEnvelopeAndDecrypt() throws MessagingException { - MimeMessage testMessage = createTestMessage("alice@testcorp.com", "bob@testcorp.com"); - SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray()); - SmimeKey bobsKey = this.bobsKeyStore.getPrivateKey("bob", "bob".toCharArray()); - X509Certificate bobsCert = bobsKey.getCertificate(); - MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession, - SmimeUtil.sign(this.mailSession, null, testMessage, alicesKey, SignatureAlgorithmRsaPss), - null, bobsCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA512, CMSAlgorithm.AES256_GCM); - assertThat(SmimeUtil.getStatus((encryptedMessage))).isEqualTo(SmimeState.ENCRYPTED); - MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, bobsKey); - assertThat(SmimeUtil.checkSignature(decryptedMessage)).isTrue(); - } - - @Test - public void BobToAliceEnvelopeAndDecrypt() throws MessagingException { - MimeMessage testMessage = createTestMessage("bob@testcorp.com", "alice@testcorp.com"); - SmimeKey bobsKey = this.bobsKeyStore.getPrivateKey("bob", "bob".toCharArray()); - SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray()); - X509Certificate alicesCert = alicesKey.getCertificate(); - MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession, - SmimeUtil.sign(this.mailSession, null, testMessage, bobsKey, SignatureAlgorithmRsaPss), - null, alicesCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA384, CMSAlgorithm.AES192_CCM); - assertThat(SmimeUtil.getStatus((encryptedMessage))).isEqualTo(SmimeState.ENCRYPTED); - MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, alicesKey); - assertThat(SmimeUtil.checkSignature(decryptedMessage)).isTrue(); - } -} \ No newline at end of file diff --git a/src/test/resources/alice-certgen-rsa.sh b/src/test/resources/alice-certgen-rsa.sh deleted file mode 100755 index b7a2063..0000000 --- a/src/test/resources/alice-certgen-rsa.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# This script can be used to generate a self-signed test-certificate for the fictional principal "Alice". -# The certificate is issued on the basis of a standard RSA key-pair. - -### Set the openssl version to use. -openssl_bin="/usr/local/opt/openssl@1.1/bin/openssl" -account_name="alice" -priv_key_name="${account_name}.priv" -certificate_config_filename="${account_name}.cnf" -validity_days=1825 # Five years, so the tests won't fail too soon. - -echo "Generating private RSA key" -$openssl_bin genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:4096 -pkeyopt rsa_keygen_pubexp:65537 -out ${priv_key_name}.rsakey -### Save the private key without password protection -$openssl_bin rsa -in ${priv_key_name}.rsakey -out ${priv_key_name}.nopass.rsakey - -echo "Generating self-signed certificate..." -$openssl_bin req -outform PEM -out ${account_name}.pem -key ${priv_key_name}.nopass.rsakey -keyform PEM -x509 -nodes -batch -days $validity_days -config $certificate_config_filename -pkeyopt rsa_keygen_bits:2048 -sha256 - -echo "Generating .p12 file with certificate and private key..." -$openssl_bin pkcs12 -export -in ${account_name}.pem -inkey ${priv_key_name}.nopass.rsakey -out ${account_name}.p12 diff --git a/src/test/resources/alice.cnf b/src/test/resources/alice.cnf deleted file mode 100644 index e7ba56a..0000000 --- a/src/test/resources/alice.cnf +++ /dev/null @@ -1,34 +0,0 @@ -[ req ] -default_bits = 4096 -distinguished_name = req_distinguished_name -x509_extensions = x509_ext -string_mask = utf8only - -[ req_distinguished_name ] -countryName = Country Name (2 letter code) -countryName_default = AA -countryName_min = 2 -countryName_max = 2 -stateOrProvinceName = State or Province Name (full name) -stateOrProvinceName_default = Testprovince -localityName = Locality Name (eg, city) -localityName_default = Testtown -0.organizationName = Organization Name (eg, company) -0.organizationName_default = Testcorp -commonName = Common Name -commonName_default = Alice -commonName_max = 64 -emailAddress = Email Address -emailAddress_default = alice@testcorp.com -emailAddress_max = 64 - -# Section x509_ext is used when generating a self-signed certificate. I.e., openssl req -x509 ... -[ x509_ext ] - -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid,issuer - -basicConstraints = CA:FALSE -keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment -extendedKeyUsage = clientAuth, emailProtection -#subjectAltName = email:copy \ No newline at end of file diff --git a/src/test/resources/alice.p12 b/src/test/resources/alice.p12 deleted file mode 100644 index e0e73b7..0000000 Binary files a/src/test/resources/alice.p12 and /dev/null differ diff --git a/src/test/resources/bob-certgen-rsa.sh b/src/test/resources/bob-certgen-rsa.sh deleted file mode 100755 index 91cbb49..0000000 --- a/src/test/resources/bob-certgen-rsa.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# This script can be used to generate a self-signed test-certificate for the fictional principal "Bob". -# The certificate is issued on the basis of a RSASSA-PSS key-pair. - -### Set the openssl version to use. Must be OpenSSL 1.1 for RSASSA-PSS support -openssl_bin="/usr/local/opt/openssl@1.1/bin/openssl" -account_name="bob" -priv_key_name="${account_name}.priv" -certificate_config_filename="${account_name}.cnf" -validity_days=1825 # Five years, so the tests won't fail too soon. - -echo "Generating private RSASSA-PSS key" -$openssl_bin genpkey -algorithm rsa-pss -pkeyopt rsa_keygen_bits:4096 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_md:sha256 -pkeyopt rsa_pss_keygen_mgf1_md:sha256 -pkeyopt rsa_pss_keygen_saltlen:32 -out ${priv_key_name}.rsapsskey -### Save the private key without password protection -$openssl_bin rsa -in ${priv_key_name}.rsapsskey -out ${priv_key_name}.nopass.rsapsskey - -echo "Generating self-signed certificate..." -$openssl_bin req -outform PEM -out ${account_name}.pem -key ${priv_key_name}.nopass.rsapsskey -keyform PEM -x509 -nodes -batch -days $validity_days -config $certificate_config_filename -pkeyopt rsa_keygen_bits:4096 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 -sigopt rsa_mgf1_md:sha256 -sha256 - -echo "Generating .p12 file with certificate and private key..." -$openssl_bin pkcs12 -export -in ${account_name}.pem -inkey ${priv_key_name}.nopass.rsapsskey -out ${account_name}.p12 diff --git a/src/test/resources/bob.cnf b/src/test/resources/bob.cnf deleted file mode 100644 index 9a7fd64..0000000 --- a/src/test/resources/bob.cnf +++ /dev/null @@ -1,34 +0,0 @@ -[ req ] -default_bits = 4096 -distinguished_name = req_distinguished_name -x509_extensions = x509_ext -string_mask = utf8only - -[ req_distinguished_name ] -countryName = Country Name (2 letter code) -countryName_default = BB -countryName_min = 2 -countryName_max = 2 -stateOrProvinceName = State or Province Name (full name) -stateOrProvinceName_default = Testprovince -localityName = Locality Name (eg, city) -localityName_default = Testtown -0.organizationName = Organization Name (eg, company) -0.organizationName_default = Testcorp -commonName = Common Name -commonName_default = Bob -commonName_max = 64 -emailAddress = Email Address -emailAddress_default = bob@testcorp.com -emailAddress_max = 64 - -# Section x509_ext is used when generating a self-signed certificate. I.e., openssl req -x509 ... -[ x509_ext ] - -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid,issuer - -basicConstraints = CA:FALSE -keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment -extendedKeyUsage = clientAuth, emailProtection -#subjectAltName = email:copy \ No newline at end of file diff --git a/src/test/resources/bob.p12 b/src/test/resources/bob.p12 deleted file mode 100644 index 030791a..0000000 Binary files a/src/test/resources/bob.p12 and /dev/null differ