├── .classpath ├── .gitignore ├── .project ├── DKIMforJavaMail.jar ├── LICENSE ├── README.md ├── build.xml ├── keys ├── default.pub.pem ├── private.key.der └── private.key.pem ├── lib ├── activation.jar ├── dsn.jar ├── imap.jar ├── mail.jar ├── mailapi.jar ├── pop3.jar └── smtp.jar ├── src └── de │ └── agitos │ └── dkim │ ├── Canonicalization.java │ ├── DKIMSigner.java │ ├── DKIMSignerException.java │ ├── DKIMUtil.java │ ├── SMTPDKIMMessage.java │ └── SigningAlgorithm.java ├── test.properties.template └── test └── de └── agitos └── dkim ├── FullExample.java ├── MimeMailExample.java ├── MultipleMailExample.java ├── SimpleExample.java └── TestUtil.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.war 8 | *.ear 9 | 10 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 11 | hs_err_pid* 12 | /bin/ 13 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | DKIMforJavaMail 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /DKIMforJavaMail.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/DKIMforJavaMail.jar -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DKIM-for-JavaMail 2 | 3 | DKIM for JavaMail is a small JAR that can be easily implemented in existing Java projects using JavaMail for sending DKIM signed emails. 4 | 5 | This github repository replaces the original repository at http://dkim-javamail.sourceforge.net 6 | See http://sourceforge.net/p/dkim-javamail/discussion/893011/ for old forum posts. 7 | 8 | 9 | ## Setup DKIM for JavaMail API 10 | 11 | What is required to use DKIM for JavaMail 12 | --- 13 | 14 | - a project using JavaMail: http://java.sun.com/products/javamail/ 15 | (DKIM for JavaMail was tested with version JavaMail 1.4.1 but should be compatible with 16 | older versions too, see TODO below) 17 | 18 | 19 | - a DKIM key setup: 20 | 21 | There are test keys in the keys/ directory but be aware to use those only for your tests. 22 | 23 | You need: 24 | 25 | a) a private key on your hard disc (e.g. in keys/); you can generate a new key by 26 | 27 | > openssl genrsa -out private.key.pem 28 | 29 | DKIM for JavaMail needs the private key in DER format, you can transform a PEM key 30 | with openssl: 31 | 32 | > openssl pkcs8 -topk8 -nocrypt -in private.key.pem -out private.key.der -outform der 33 | 34 | 35 | b) a public key in your DNS; here is a sample ressource record with selector "default": 36 | default._domainkey IN TXT "v=DKIM1; g=*; k=rsa; p=MIG...the_public_key_here...AQAB" 37 | (see http://www.ietf.org/rfc/rfc4871.txt for details) 38 | 39 | You can use openssl to get a public key from the private key: 40 | 41 | > openssl rsa -inform PEM -in private.key.pem -pubout 42 | 43 | 44 | 45 | Run Tests 46 | --- 47 | 48 | - if you want to run the examples provided in DKIM for JavaMail you have to 49 | 50 | a) configure the config file test.properties (see comments inside) 51 | 52 | b) run `ant runTests` 53 | 54 | 55 | Implement DKIM for JavaMail into your code 56 | --- 57 | 58 | - add DKIMforJavaMail.jar to your classpath 59 | 60 | - see the example files in test/de/agitos/dkim/* : it's very simple to implement, enjoy! 61 | 62 | 63 | ## TODO 64 | 65 | - DKIM for JavaMail is in principal compatible with all libraries using JavaMail, 66 | an example for Apache Commons Email should be added to this project 67 | 68 | - test of DKIM for JavaMail with older JavaMail versions, maybe a JAR for Java 1.4; 69 | I will do this if needed upon request 70 | 71 | - verification functionality could be added; this has minor priority at time, I will 72 | do this if needed upon request (there will be other libraries doing this maybe) 73 | 74 | - the optional public key check before sending should be fully implemented; this is 75 | out of the DKIM basic functionality and therefore isn't mandatory 76 | 77 | 78 | ## RELEASE-HISTORY 79 | 80 | 1.0, 2008-11-30 81 | 82 | 1.1, 2009-04-13 83 | Bugfix line-encoding before body canonicalization on *nix systems 84 | Added partial line-folding of the DKIM-Signature header field 85 | 86 | 1.2, 2009-05-10 87 | Bugfix in signing mime messages that are loaded by an InputStream, 88 | Added a MimeMessageTest for testing the signing of loaded mime messages. 89 | 90 | 1.3, 2009-07-29 91 | Changed the insertion of the DKIM-Signature header to the top most position 92 | when sending mail. This is compatible with the Yahoo! DKIM verifier. 93 | 94 | 95 | ## Further DKIM Links 96 | 97 | http://www.dkim.org/deploy/ : DKIM implementations 98 | On this website you can find most MTA-DKIM implementations and complementary products 99 | and services. 100 | 101 | http://java.sun.com/products/javamail/ : Sun JavaMail API 102 | 103 | --- 104 | 105 | http://www.agitos.de/dkim-for-java-mail-open-source-library-2.html 106 | Florian Sager, sager@agitos.de, 22.11.2008 107 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ----------------------------------------------------------------- 22 | Build DKIMforJavaMail.jar 23 | ----------------------------------------------------------------- 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ----------------------------------------------------------------- 39 | Run tests, be aware to set your settings in test.properties first 40 | ----------------------------------------------------------------- 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | ----------------------------------------------------------------- 54 | Sending 1 mail with SimpleExample 55 | 56 | 57 | 58 | 59 | 60 | 61 | Sending several mails with MultipleMailExample 62 | 63 | 64 | 65 | 66 | 67 | 68 | Sending 1 mail with FullExample 69 | 70 | 71 | 72 | 73 | 74 | 75 | Sending 1 mail with MimeMailExample 76 | 77 | 78 | 79 | 80 | 81 | 82 | ----------------------------------------------------------------- 83 | If something failed, check your settings in the test.properties file 84 | ----------------------------------------------------------------- 85 | 86 | 87 | -------------------------------------------------------------------------------- /keys/default.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPgwLEqUfo+oaSgYGqLg7U7rpdYM2JPE 3 | lJ4dQur+1BJz7Gm2JCTfmFxc3STeUSyTEtiGkowhdwl0UksU+K1laBMCAwEAAQ== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /keys/private.key.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/keys/private.key.der -------------------------------------------------------------------------------- /keys/private.key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIBOwIBAAJBAPgwLEqUfo+oaSgYGqLg7U7rpdYM2JPElJ4dQur+1BJz7Gm2JCTf 3 | mFxc3STeUSyTEtiGkowhdwl0UksU+K1laBMCAwEAAQJBAPGaQ6Oehs3mTeff/x4M 4 | BDfNojSLcNH4jgyVw6zrwRrrn7GGOufOAZpBIiY+59JGteOiSXqcARLhASXVihk9 5 | b1ECIQD+0UYpT5jhiSyJzZd9VyWW7N+e7S4V06WlpVI+PH45BwIhAPlXBfSY5/IG 6 | 2lct9UH8EUfHUBCg/b/t+vVD7dtPRVGVAiB027ZoZZd//u+M9tr9YUTc1R6sfKsA 7 | 2YIC2DFCV22vawIhAPfpyOgcqHKC0dFgxrzm7h+aU0dVazfMXXxEIFmwhdHJAiA7 8 | hfwWXz1j9IemT24+ukiggSRW+Rifzg20+rI6KDhr5A== 9 | -----END RSA PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /lib/activation.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/activation.jar -------------------------------------------------------------------------------- /lib/dsn.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/dsn.jar -------------------------------------------------------------------------------- /lib/imap.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/imap.jar -------------------------------------------------------------------------------- /lib/mail.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/mail.jar -------------------------------------------------------------------------------- /lib/mailapi.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/mailapi.jar -------------------------------------------------------------------------------- /lib/pop3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/pop3.jar -------------------------------------------------------------------------------- /lib/smtp.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/smtp.jar -------------------------------------------------------------------------------- /src/de/agitos/dkim/Canonicalization.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 The Apache Software Foundation or its licensors, as 3 | * applicable. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008 18 | */ 19 | package de.agitos.dkim; 20 | 21 | import java.io.IOException; 22 | 23 | /* 24 | * Provides Simple and Relaxed Canonicalization according to DKIM RFC 4871. 25 | * 26 | * @author Florian Sager, http://www.agitos.de, 22.11.2008 27 | */ 28 | 29 | public class Canonicalization { 30 | 31 | public static Canonicalization SIMPLE = new Canonicalization() { 32 | 33 | public String getType() { 34 | 35 | return "simple"; 36 | } 37 | 38 | public String canonicalizeHeader(String name, String value) { 39 | 40 | return name+":"+value; 41 | } 42 | 43 | public String canonicalizeBody(String body) throws IOException { 44 | 45 | if (body == null || "".equals(body) ) { 46 | return "\r\n"; 47 | } 48 | 49 | // The body must end with \r\n 50 | if (!"\r\n".equals(body.substring(body.length()-2, body.length()))) { 51 | return body+"\r\n"; 52 | } 53 | 54 | // Remove trailing empty lines ... 55 | while ("\r\n\r\n".equals(body.substring(body.length()-4, body.length()))) { 56 | body = body.substring(0, body.length()-2); 57 | if (body.length() < 4) { 58 | break; 59 | } 60 | } 61 | 62 | return body; 63 | } 64 | }; 65 | 66 | public static Canonicalization RELAXED = new Canonicalization() { 67 | 68 | public String getType() { 69 | 70 | return "relaxed"; 71 | } 72 | 73 | public String canonicalizeHeader(String name, String value) { 74 | 75 | name = name.trim().toLowerCase(); 76 | value = value.replaceAll("\\s+", " ").trim(); 77 | return name+":"+value; 78 | } 79 | 80 | public String canonicalizeBody(String body) throws IOException { 81 | 82 | if (body == null || "".equals(body) ) { 83 | return "\r\n"; 84 | } 85 | 86 | body = body.replaceAll("[ \\t\\x0B\\f]+", " "); 87 | body = body.replaceAll(" \r\n", "\r\n"); 88 | 89 | // The body must end with \r\n 90 | if (!"\r\n".equals(body.substring(body.length()-2, body.length()))) { 91 | return body+"\r\n"; 92 | } 93 | 94 | // Remove trailing empty lines ... 95 | while ("\r\n\r\n".equals(body.substring(body.length()-4, body.length()))) { 96 | body = body.substring(0, body.length()-2); 97 | if (body.length() < 4) { 98 | break; 99 | } 100 | } 101 | 102 | return body; 103 | } 104 | }; 105 | 106 | public Canonicalization() { } 107 | 108 | public String getType() { 109 | return "unknown"; 110 | } 111 | 112 | public String canonicalizeHeader(String name, String value) { 113 | return null; 114 | } 115 | 116 | public String canonicalizeBody(String body) throws IOException { 117 | return null; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/de/agitos/dkim/DKIMSigner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 The Apache Software Foundation or its licensors, as 3 | * applicable. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008 18 | */ 19 | 20 | package de.agitos.dkim; 21 | 22 | import java.io.ByteArrayOutputStream; 23 | import java.io.DataInputStream; 24 | import java.io.File; 25 | import java.io.FileInputStream; 26 | import java.io.IOException; 27 | import java.security.InvalidKeyException; 28 | import java.security.KeyFactory; 29 | import java.security.MessageDigest; 30 | import java.security.NoSuchAlgorithmException; 31 | import java.security.PrivateKey; 32 | import java.security.Signature; 33 | import java.security.SignatureException; 34 | import java.security.interfaces.RSAPrivateKey; 35 | import java.security.spec.PKCS8EncodedKeySpec; 36 | import java.util.ArrayList; 37 | import java.util.Date; 38 | import java.util.Enumeration; 39 | import java.util.Iterator; 40 | import java.util.LinkedHashMap; 41 | import java.util.Map; 42 | import java.util.Set; 43 | import java.util.Map.Entry; 44 | 45 | import javax.mail.MessagingException; 46 | 47 | import com.sun.mail.util.CRLFOutputStream; 48 | 49 | 50 | /* 51 | * Main class providing a signature according to DKIM RFC 4871. 52 | * 53 | * @author Florian Sager, http://www.agitos.de, 15.10.2008 54 | */ 55 | 56 | public class DKIMSigner { 57 | 58 | private static String DKIMSIGNATUREHEADER = "DKIM-Signature"; 59 | private static int MAXHEADERLENGTH = 67; 60 | 61 | private static ArrayList minimumHeadersToSign = new ArrayList(); 62 | static { 63 | minimumHeadersToSign.add("From"); 64 | } 65 | 66 | private String[] defaultHeadersToSign = new String[]{ 67 | "Content-Description","Content-ID","Content-Type","Content-Transfer-Encoding","Cc", 68 | "Date","From","In-Reply-To","List-Subscribe","List-Post","List-Owner","List-Id", 69 | "List-Archive","List-Help","List-Unsubscribe","MIME-Version","Message-ID","Resent-Sender", 70 | "Resent-Cc","Resent-Date","Resent-To","Reply-To","References","Resent-Message-ID", 71 | "Resent-From","Sender","Subject","To"}; 72 | 73 | private SigningAlgorithm signingAlgorithm = SigningAlgorithm.SHA256withRSA; // use rsa-sha256 by default, see RFC 4871 74 | private Signature signatureService; 75 | private MessageDigest messageDigest; 76 | private String signingDomain; 77 | private String selector; 78 | private String identity = null; 79 | private boolean lengthParam = false; 80 | private boolean zParam = false; 81 | private Canonicalization headerCanonicalization = Canonicalization.RELAXED; 82 | private Canonicalization bodyCanonicalization = Canonicalization.SIMPLE; 83 | private PrivateKey privkey; 84 | 85 | public DKIMSigner(String signingDomain, String selector, PrivateKey privkey) throws Exception { 86 | initDKIMSigner(signingDomain, selector, privkey); 87 | } 88 | 89 | public DKIMSigner(String signingDomain, String selector, String privkeyFilename) throws Exception { 90 | 91 | File privKeyFile = new File(privkeyFilename); 92 | 93 | // read private key DER file 94 | DataInputStream dis = new DataInputStream(new FileInputStream(privKeyFile)); 95 | byte[] privKeyBytes = new byte[(int) privKeyFile.length()]; 96 | dis.read(privKeyBytes); 97 | dis.close(); 98 | 99 | KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 100 | 101 | // decode private key 102 | PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privKeyBytes); 103 | RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(privSpec); 104 | 105 | initDKIMSigner(signingDomain, selector, privKey); 106 | } 107 | 108 | private void initDKIMSigner(String signingDomain, String selector, PrivateKey privkey) throws DKIMSignerException { 109 | 110 | if (!DKIMUtil.isValidDomain(signingDomain)) { 111 | throw new DKIMSignerException(signingDomain+" is an invalid signing domain"); 112 | } 113 | 114 | this.signingDomain = signingDomain; 115 | this.selector = selector.trim(); 116 | this.privkey = privkey; 117 | this.setSigningAlgorithm(this.signingAlgorithm); 118 | } 119 | 120 | public String getIdentity() { 121 | return identity; 122 | } 123 | 124 | public void setIdentity(String identity) throws DKIMSignerException { 125 | 126 | if (identity!=null) { 127 | identity = identity.trim(); 128 | if (!identity.endsWith("@"+signingDomain) && !identity.endsWith("."+signingDomain)) { 129 | throw new DKIMSignerException("The domain part of "+identity+" has to be "+signingDomain+" or its subdomain"); 130 | } 131 | } 132 | 133 | this.identity = identity; 134 | } 135 | 136 | public Canonicalization getBodyCanonicalization() { 137 | return bodyCanonicalization; 138 | } 139 | 140 | public void setBodyCanonicalization(Canonicalization bodyCanonicalization) throws DKIMSignerException { 141 | this.bodyCanonicalization = bodyCanonicalization; 142 | } 143 | 144 | public Canonicalization getHeaderCanonicalization() { 145 | return headerCanonicalization; 146 | } 147 | 148 | public void setHeaderCanonicalization(Canonicalization headerCanonicalization) throws DKIMSignerException { 149 | this.headerCanonicalization = headerCanonicalization; 150 | } 151 | 152 | public String[] getDefaultHeadersToSign() { 153 | return defaultHeadersToSign; 154 | } 155 | 156 | public void addHeaderToSign(String header) { 157 | 158 | if (header==null || "".equals(header)) return; 159 | 160 | int len = this.defaultHeadersToSign.length; 161 | String[] headersToSign = new String[len+1]; 162 | for (int i=0; i dkimSignature) { 239 | 240 | Set> entries = dkimSignature.entrySet(); 241 | StringBuffer buf = new StringBuffer(), fbuf; 242 | int pos = 0; 243 | 244 | Iterator> iter = entries.iterator(); 245 | while (iter.hasNext()) { 246 | Entry entry = iter.next(); 247 | 248 | // buf.append(entry.getKey()).append("=").append(entry.getValue()).append(";\t"); 249 | 250 | fbuf = new StringBuffer(); 251 | fbuf.append(entry.getKey()).append("=").append(entry.getValue()).append(";"); 252 | 253 | if (pos + fbuf.length() + 1 > MAXHEADERLENGTH) { 254 | 255 | pos = fbuf.length(); 256 | 257 | // line folding : this doesn't work "sometimes" --> maybe someone likes to debug this 258 | /* int i = 0; 259 | while (iMAXHEADERLENGTH) { 261 | buf.append("\r\n\t").append(fbuf.substring(i, i+MAXHEADERLENGTH)); 262 | i += MAXHEADERLENGTH; 263 | } else { 264 | buf.append("\r\n\t").append(fbuf.substring(i)); 265 | pos -= i; 266 | break; 267 | } 268 | } */ 269 | 270 | buf.append("\r\n\t").append(fbuf); 271 | 272 | } else { 273 | buf.append(" ").append(fbuf); 274 | pos += fbuf.length() + 1; 275 | } 276 | } 277 | 278 | buf.append("\r\n\tb="); 279 | 280 | return buf.toString().trim(); 281 | } 282 | 283 | private String foldSignedSignature(String s, int offset) { 284 | 285 | int i = 0; 286 | StringBuffer buf = new StringBuffer(); 287 | 288 | while (true) { 289 | if (offset > 0 && s.substring(i).length()>MAXHEADERLENGTH - offset) { 290 | buf.append(s.substring(i, i + MAXHEADERLENGTH - offset)); 291 | i += MAXHEADERLENGTH - offset; 292 | offset = 0; 293 | } else if (s.substring(i).length()>MAXHEADERLENGTH) { 294 | buf.append("\r\n\t").append(s.substring(i, i + MAXHEADERLENGTH)); 295 | i += MAXHEADERLENGTH; 296 | } else { 297 | buf.append("\r\n\t").append(s.substring(i)); 298 | break; 299 | } 300 | } 301 | 302 | return buf.toString(); 303 | } 304 | 305 | public String sign(SMTPDKIMMessage message) throws DKIMSignerException, MessagingException { 306 | 307 | Map dkimSignature = new LinkedHashMap(); 308 | dkimSignature.put("v", "1"); 309 | dkimSignature.put("a", this.signingAlgorithm.getRfc4871Notation()); 310 | dkimSignature.put("q", "dns/txt"); 311 | dkimSignature.put("c", getHeaderCanonicalization().getType()+"/"+getBodyCanonicalization().getType()); 312 | dkimSignature.put("t", ((long) new Date().getTime() / 1000)+""); 313 | dkimSignature.put("s", this.selector); 314 | dkimSignature.put("d", this.signingDomain); 315 | 316 | // set identity inside signature 317 | if (identity!=null) { 318 | dkimSignature.put("i", DKIMUtil.QuotedPrintable(identity)); 319 | } 320 | 321 | // process header 322 | ArrayList assureHeaders = (ArrayList) minimumHeadersToSign.clone(); 323 | 324 | // intersect defaultHeadersToSign with available headers 325 | StringBuffer headerList = new StringBuffer(); 326 | StringBuffer headerContent = new StringBuffer(); 327 | StringBuffer zParamString = new StringBuffer(); 328 | 329 | Enumeration headerLines = message.getMatchingHeaderLines(defaultHeadersToSign); 330 | while (headerLines.hasMoreElements()) { 331 | String header = (String) headerLines.nextElement(); 332 | String[] headerParts = DKIMUtil.splitHeader(header); 333 | headerList.append(headerParts[0]).append(":"); 334 | headerContent.append(this.headerCanonicalization.canonicalizeHeader(headerParts[0], headerParts[1])).append("\r\n"); 335 | assureHeaders.remove(headerParts[0]); 336 | 337 | // add optional z= header list, DKIM-Quoted-Printable 338 | if (this.zParam) { 339 | zParamString.append(headerParts[0]).append(":").append(DKIMUtil.QuotedPrintable(headerParts[1].trim()).replace("|", "=7C")).append("|"); 340 | } 341 | } 342 | 343 | if (!assureHeaders.isEmpty()) { 344 | throw new DKIMSignerException("Could not find the header fields "+DKIMUtil.concatArray(assureHeaders, ", ")+" for signing"); 345 | } 346 | 347 | dkimSignature.put("h", headerList.substring(0, headerList.length()-1)); 348 | 349 | if (this.zParam) { 350 | String zParamTemp = zParamString.toString(); 351 | dkimSignature.put("z", zParamTemp.substring(0, zParamTemp.length()-1)); 352 | } 353 | 354 | // process body 355 | String body = message.getEncodedBody(); 356 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 357 | CRLFOutputStream crlfos = new CRLFOutputStream(baos); 358 | try { 359 | crlfos.write(body.getBytes()); 360 | } catch (IOException e) { 361 | throw new DKIMSignerException("The body conversion to MIME canonical CRLF line terminator failed", e); 362 | } 363 | body = baos.toString(); 364 | 365 | try { 366 | body = this.bodyCanonicalization.canonicalizeBody(body); 367 | } catch (IOException ioe) { 368 | throw new DKIMSignerException("The body canonicalization failed", ioe); 369 | } 370 | 371 | if (this.lengthParam) { 372 | dkimSignature.put("l", body.length()+""); 373 | } 374 | 375 | // calculate and encode body hash 376 | dkimSignature.put("bh", DKIMUtil.base64Encode(this.messageDigest.digest(body.getBytes()))); 377 | 378 | // create signature 379 | String serializedSignature = serializeDKIMSignature(dkimSignature); 380 | 381 | byte[] signedSignature; 382 | try { 383 | signatureService.update(headerContent.append(this.headerCanonicalization.canonicalizeHeader(DKIMSIGNATUREHEADER, " "+serializedSignature)).toString().getBytes()); 384 | signedSignature = signatureService.sign(); 385 | } catch (SignatureException se) { 386 | throw new DKIMSignerException("The signing operation by Java security failed", se); 387 | } 388 | 389 | return DKIMSIGNATUREHEADER + ": " + serializedSignature+foldSignedSignature(DKIMUtil.base64Encode(signedSignature), 3); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /src/de/agitos/dkim/DKIMSignerException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 The Apache Software Foundation or its licensors, as 3 | * applicable. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008 18 | */ 19 | 20 | package de.agitos.dkim; 21 | 22 | /* 23 | * @author Florian Sager, http://www.agitos.de, 15.11.2008 24 | */ 25 | 26 | public class DKIMSignerException extends Exception { 27 | 28 | public DKIMSignerException(String message) { 29 | super(message); 30 | } 31 | 32 | public DKIMSignerException(String message, Exception e) { 33 | super(message, e); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/de/agitos/dkim/DKIMUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 The Apache Software Foundation or its licensors, as 3 | * applicable. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008 18 | */ 19 | 20 | package de.agitos.dkim; 21 | 22 | import java.io.ByteArrayOutputStream; 23 | import java.io.IOException; 24 | import java.security.KeyFactory; 25 | import java.security.NoSuchAlgorithmException; 26 | import java.security.interfaces.RSAPrivateKey; 27 | import java.security.spec.InvalidKeySpecException; 28 | import java.security.spec.PKCS8EncodedKeySpec; 29 | import java.util.ArrayList; 30 | import java.util.Hashtable; 31 | import java.util.Iterator; 32 | import java.util.regex.Matcher; 33 | import java.util.regex.Pattern; 34 | 35 | import javax.naming.NamingException; 36 | import javax.naming.directory.DirContext; 37 | import javax.naming.directory.InitialDirContext; 38 | 39 | import sun.misc.BASE64Encoder; 40 | 41 | import com.sun.mail.util.QPEncoderStream; 42 | 43 | /* 44 | * @author Florian Sager, http://www.agitos.de, 22.11.2008 45 | */ 46 | 47 | public class DKIMUtil { 48 | 49 | protected static String[] splitHeader(String header) throws DKIMSignerException { 50 | int colonPos = header.indexOf(':'); 51 | if (colonPos==-1) { 52 | throw new DKIMSignerException("The header string "+header+" is no valid RFC 822 header-line"); 53 | } 54 | return new String[]{header.substring(0, colonPos), header.substring(colonPos+1)}; 55 | } 56 | 57 | protected static String concatArray(ArrayList l, String separator) { 58 | StringBuffer buf = new StringBuffer(); 59 | Iterator iter = l.iterator(); 60 | while (iter.hasNext()) { 61 | buf.append(iter.next()).append(separator); 62 | } 63 | 64 | return buf.substring(0, buf.length() - separator.length()); 65 | } 66 | 67 | protected static boolean isValidDomain(String domainname) { 68 | Pattern pattern = Pattern.compile("(.+)\\.(.+)"); 69 | Matcher matcher = pattern.matcher(domainname); 70 | return matcher.matches(); 71 | } 72 | 73 | // FSTODO: converts to "platforms default encoding" might be wrong ? 74 | protected static String QuotedPrintable(String s) { 75 | 76 | try { 77 | ByteArrayOutputStream boas = new ByteArrayOutputStream(); 78 | QPEncoderStream encodeStream = new QPEncoderStream(boas); 79 | encodeStream.write(s.getBytes()); 80 | 81 | String encoded = boas.toString(); 82 | encoded = encoded.replaceAll(";", "=3B"); 83 | encoded = encoded.replaceAll(" ", "=20"); 84 | 85 | return encoded; 86 | 87 | } catch (IOException ioe) {} 88 | 89 | return null; 90 | } 91 | 92 | protected static String base64Encode(byte[] b) { 93 | BASE64Encoder base64Enc = new BASE64Encoder(); 94 | String encoded = base64Enc.encode(b); 95 | // remove unnecessary linefeeds after 76 characters 96 | encoded = encoded.replace("\n", ""); // Linux+Win 97 | return encoded.replace("\r", ""); // Win --> FSTODO: select Encoder without line termination 98 | } 99 | 100 | public boolean checkDNSForPublickey(String signingDomain, String selector) throws DKIMSignerException { 101 | 102 | Hashtable env = new Hashtable(); 103 | env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); 104 | String recordname = selector+"._domainkey."+signingDomain; 105 | String value = null; 106 | 107 | try { 108 | DirContext dnsContext = new InitialDirContext(env); 109 | 110 | javax.naming.directory.Attributes attribs = dnsContext.getAttributes(recordname, new String[] {"TXT"}); 111 | javax.naming.directory.Attribute txtrecord = attribs.get("txt"); 112 | 113 | if (txtrecord == null) { 114 | throw new DKIMSignerException("There is no TXT record available for "+recordname); 115 | } 116 | 117 | // "v=DKIM1; g=*; k=rsa; p=MIGfMA0G ..." 118 | value = (String) txtrecord.get(); 119 | 120 | } catch (NamingException ne) { 121 | throw new DKIMSignerException("Selector lookup failed", ne); 122 | } 123 | 124 | if (value == null) { 125 | throw new DKIMSignerException("Value of RR "+recordname+" couldn't be retrieved"); 126 | } 127 | 128 | // try to read public key from RR 129 | String[] tags = value.split(";"); 130 | for (String tag : tags) { 131 | tag = tag.trim(); 132 | if (tag.startsWith("p=")) { 133 | 134 | try { 135 | KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 136 | 137 | // decode public key, FSTODO: convert to DER format 138 | PKCS8EncodedKeySpec pubSpec = new PKCS8EncodedKeySpec(tag.substring(2).getBytes()); 139 | RSAPrivateKey pubKey = (RSAPrivateKey) keyFactory.generatePublic(pubSpec); 140 | } catch (NoSuchAlgorithmException nsae) { 141 | throw new DKIMSignerException("RSA algorithm not found by JVM"); 142 | } catch (InvalidKeySpecException ikse) { 143 | throw new DKIMSignerException("The public key "+tag+" in RR "+recordname+" couldn't be decoded."); 144 | } 145 | 146 | // FSTODO: create test signature with privKey and test validation with pubKey to check on a valid key pair 147 | 148 | return true; 149 | } 150 | } 151 | 152 | throw new DKIMSignerException("No public key available in "+recordname); 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/de/agitos/dkim/SMTPDKIMMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 The Apache Software Foundation or its licensors, as 3 | * applicable. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008 18 | */ 19 | 20 | package de.agitos.dkim; 21 | 22 | import java.io.ByteArrayOutputStream; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.OutputStream; 26 | import java.util.Enumeration; 27 | 28 | import javax.mail.MessagingException; 29 | import javax.mail.Session; 30 | import javax.mail.internet.MimeMessage; 31 | import javax.mail.internet.MimeUtility; 32 | 33 | import com.sun.mail.smtp.SMTPMessage; 34 | import com.sun.mail.util.LineOutputStream; 35 | 36 | /* 37 | * Extension of SMTPMessage for the inclusion of a DKIM signature. 38 | * 39 | * @author Florian Sager, http://www.agitos.de, 22.11.2008 40 | */ 41 | 42 | public class SMTPDKIMMessage extends SMTPMessage { 43 | 44 | private DKIMSigner signer; 45 | private String encodedBody; 46 | 47 | public SMTPDKIMMessage(Session session, DKIMSigner signer) { 48 | super(session); 49 | this.signer = signer; 50 | } 51 | 52 | public SMTPDKIMMessage(MimeMessage message, DKIMSigner signer) throws MessagingException { 53 | super(message); 54 | this.signer = signer; 55 | } 56 | 57 | public SMTPDKIMMessage(Session session, InputStream is, DKIMSigner signer) throws MessagingException { 58 | super(session, is); 59 | this.signer = signer; 60 | } 61 | 62 | /** 63 | * Output the message as an RFC 822 format stream, without 64 | * specified headers. If the saved flag is not set, 65 | * the saveChanges method is called. 66 | * If the modified flag is not 67 | * set and the content array is not null, the 68 | * content array is written directly, after 69 | * writing the appropriate message headers. 70 | * 71 | * @exception javax.mail.MessagingException 72 | * @exception IOException if an error occurs writing to the stream 73 | * or if an error is generated by the 74 | * javax.activation layer. 75 | * @see javax.activation.DataHandler#writeTo 76 | * 77 | * This method enhances the JavaMail method MimeMessage.writeTo(OutputStream os String[] ignoreList); 78 | * See the according Sun Licence, this contribution is CDDL. 79 | */ 80 | public void writeTo(OutputStream os, String[] ignoreList) throws IOException, MessagingException { 81 | 82 | ByteArrayOutputStream osBody = new ByteArrayOutputStream(); 83 | 84 | // Inside saveChanges() it is assured that content encodings are set in all parts of the body 85 | if (!saved) { 86 | saveChanges(); 87 | } 88 | 89 | // First, write out the body to the body buffer 90 | if (modified) { 91 | // Finally, the content. Encode if required. 92 | // XXX: May need to account for ESMTP ? 93 | OutputStream osEncoding = MimeUtility.encode(osBody, this.getEncoding()); 94 | this.getDataHandler().writeTo(osEncoding); 95 | osEncoding.flush(); // Needed to complete encoding 96 | } else { 97 | // Else, the content is untouched, so we can just output it 98 | // Finally, the content. 99 | if (content == null) { 100 | // call getContentStream to give subclass a chance to 101 | // provide the data on demand 102 | InputStream is = getContentStream(); 103 | // now copy the data to the output stream 104 | byte[] buf = new byte[8192]; 105 | int len; 106 | while ((len = is.read(buf)) > 0) 107 | osBody.write(buf, 0, len); 108 | is.close(); 109 | buf = null; 110 | } else { 111 | osBody.write(content); 112 | } 113 | osBody.flush(); 114 | } 115 | encodedBody = osBody.toString(); 116 | 117 | // Second, sign the message 118 | String signatureHeaderLine; 119 | try { 120 | signatureHeaderLine = signer.sign(this); 121 | } catch (Exception e) { 122 | throw new MessagingException(e.getLocalizedMessage(), e); 123 | } 124 | 125 | // Third, write out the header to the header buffer 126 | LineOutputStream los = new LineOutputStream(os); 127 | 128 | // set generated signature to the top 129 | los.writeln(signatureHeaderLine); 130 | 131 | Enumeration hdrLines = getNonMatchingHeaderLines(ignoreList); 132 | while (hdrLines.hasMoreElements()) { 133 | los.writeln((String) hdrLines.nextElement()); 134 | } 135 | 136 | // The CRLF separator between header and content 137 | los.writeln(); 138 | 139 | // Send signed mail to waiting DATA command 140 | os.write(osBody.toByteArray()); 141 | os.flush(); 142 | } 143 | 144 | public String getEncodedBody() { 145 | return encodedBody; 146 | } 147 | 148 | public void setEncodedBody(String encodedBody) { 149 | this.encodedBody = encodedBody; 150 | } 151 | 152 | // Don't allow to switch to 8-bit MIME, instead 7-bit ascii should be kept 153 | // 'cause in forwarding scenarios a change to Content-Transfer-Encoding 154 | // to 7-bit ascii breaks DKIM signatures 155 | public void setAllow8bitMIME(boolean allow) { 156 | // super.setAllow8bitMIME(false); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/de/agitos/dkim/SigningAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 The Apache Software Foundation or its licensors, as 3 | * applicable. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008 18 | */ 19 | 20 | package de.agitos.dkim; 21 | 22 | /* 23 | * Allowed signing algorithms by DKIM RFC 4871 with translation to different Java notations 24 | * 25 | * @author Florian Sager, http://www.agitos.de, 22.11.2008 26 | */ 27 | 28 | public class SigningAlgorithm { 29 | 30 | public static SigningAlgorithm SHA256withRSA = new SigningAlgorithm("rsa-sha256", "SHA256withRSA", "sha-256"); 31 | public static SigningAlgorithm SHA1withRSA = new SigningAlgorithm("rsa-sha1", "SHA1withRSA", "sha-1"); 32 | 33 | private String rfc4871Notation; 34 | private String javaSecNotation; 35 | private String javaHashNotation; 36 | 37 | // 1. argument: RFC 4871 format, 2. argument: java representation, 3. argument: java hashing digest 38 | public SigningAlgorithm(String rfc4871Notation, String javaSecNotation, String javaHashNotation) { 39 | this.rfc4871Notation = rfc4871Notation; 40 | this.javaSecNotation = javaSecNotation; 41 | this.javaHashNotation = javaHashNotation; 42 | } 43 | 44 | public String getJavaHashNotation() { 45 | return javaHashNotation; 46 | } 47 | 48 | public String getJavaSecNotation() { 49 | return javaSecNotation; 50 | } 51 | 52 | public String getRfc4871Notation() { 53 | return rfc4871Notation; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test.properties.template: -------------------------------------------------------------------------------- 1 | # your mail server 2 | mail.smtp.host=mail.myisp.com 3 | 4 | # set auth to true if SMTP-AUTH is needed 5 | mail.smtp.auth=true 6 | 7 | # your username for SMTP-AUTH 8 | mail.smtp.auth.user=myuser@mydomain.com 9 | 10 | # your password for SMTP-AUTH 11 | mail.smtp.auth.password=mypassword 12 | 13 | # the string to use in the From header of your test mails 14 | mail.smtp.from=myuser@mydomain.com 15 | 16 | # the address to which your test mails should be sent. not required if you have a CC address 17 | # mail.smtp.to=sa-test@sendmail.net --> get check results by this autoresponder address 18 | mail.smtp.to=myself@mydomain.com 19 | 20 | # the address to which your test mails should be CC'd, if desired. not requred if you have a To addess 21 | # mail.smtp.cc=sa-test@sendmail.net --> get check results by this autoresponder address 22 | #mail.smtp.cc=myself@mydomain.com 23 | 24 | # a file that can be sent in the attachment in FullExample 25 | mail.smtp.attachment=lib/dsn.jar 26 | 27 | # the signing domain whose zone contains the public key record 28 | mail.smtp.dkim.signingdomain=mydomain.com 29 | 30 | # the selector in the DNS zone, e.g. default._domainkey ... 31 | mail.smtp.dkim.selector=default 32 | 33 | # your private key 34 | mail.smtp.dkim.privatekey=keys/default.private.der 35 | -------------------------------------------------------------------------------- /test/de/agitos/dkim/FullExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 The Apache Software Foundation or its licensors, as 3 | * applicable. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008 18 | */ 19 | 20 | package de.agitos.dkim; 21 | 22 | import java.util.Properties; 23 | 24 | import javax.mail.Message; 25 | import javax.mail.Multipart; 26 | import javax.mail.Session; 27 | import javax.mail.Transport; 28 | import javax.mail.internet.InternetAddress; 29 | import javax.mail.internet.MimeBodyPart; 30 | import javax.mail.internet.MimeMultipart; 31 | 32 | /* 33 | * This example sends a DKIM signed email with standard signature configuration. 34 | * This version of DKIM for JavaMail was tested with JavaMail 1.4.1, downward compatibility with 1.3 is expected. 35 | * 36 | * @author Florian Sager, http://www.agitos.de, 22.11.2008 37 | */ 38 | 39 | public class FullExample { 40 | 41 | public static void main(String args[]) throws Exception { 42 | 43 | // read test configuration from test.properties in your classpath 44 | Properties testProps = TestUtil.readProperties(); 45 | 46 | // get a JavaMail Session object 47 | Session session = Session.getDefaultInstance(testProps, null); 48 | 49 | 50 | 51 | ///////// beginning of DKIM FOR JAVAMAIL stuff 52 | 53 | // get DKIMSigner object 54 | DKIMSigner dkimSigner = new DKIMSigner( 55 | testProps.getProperty("mail.smtp.dkim.signingdomain"), 56 | testProps.getProperty("mail.smtp.dkim.selector"), 57 | testProps.getProperty("mail.smtp.dkim.privatekey")); 58 | 59 | /* set an address or user-id of the user on behalf this message was signed; 60 | * this identity is up to you, except the domain part must be the signing domain 61 | * or a subdomain of the signing domain. 62 | */ 63 | dkimSigner.setIdentity("fullexample@"+testProps.getProperty("mail.smtp.dkim.signingdomain")); 64 | 65 | // get default 66 | System.out.println("Default headers getting signed if available:"); 67 | TestUtil.printArray(dkimSigner.getDefaultHeadersToSign()); 68 | 69 | // the following header will be signed as well if available 70 | dkimSigner.addHeaderToSign("ASpecialHeader"); 71 | 72 | // the following header won't be signed 73 | dkimSigner.removeHeaderToSign("Content-Type"); 74 | 75 | // change default canonicalizations 76 | dkimSigner.setHeaderCanonicalization(Canonicalization.SIMPLE); 77 | dkimSigner.setBodyCanonicalization(Canonicalization.RELAXED); 78 | 79 | // add length param to the signature, see RFC 4871 80 | dkimSigner.setLengthParam(true); 81 | 82 | // change default signing algorithm 83 | dkimSigner.setSigningAlgorithm(SigningAlgorithm.SHA1withRSA); 84 | 85 | // add a list of header=value pairs to the signature for debugging reasons 86 | dkimSigner.setZParam(true); 87 | 88 | ///////// end of DKIM FOR JAVAMAIL stuff 89 | 90 | 91 | 92 | 93 | // construct the JavaMail message using the DKIM message type from DKIM for JavaMail 94 | Message msg = new SMTPDKIMMessage(session, dkimSigner); 95 | Multipart mp = new MimeMultipart(); 96 | msg.setFrom(new InternetAddress(testProps.getProperty("mail.smtp.from"))); 97 | if (testProps.getProperty("mail.smtp.to") != null) { 98 | msg.setRecipients(Message.RecipientType.TO, 99 | InternetAddress.parse(testProps.getProperty("mail.smtp.to"), false)); 100 | } 101 | if (testProps.getProperty("mail.smtp.cc") != null) { 102 | msg.setRecipients(Message.RecipientType.CC, 103 | InternetAddress.parse(testProps.getProperty("mail.smtp.cc"), false)); 104 | } 105 | 106 | msg.setSubject("DKIM for JavaMail: FullExample Testmessage"); 107 | 108 | MimeBodyPart mbp_msgtext = new MimeBodyPart(); 109 | mbp_msgtext.setText(TestUtil.bodyText); 110 | mp.addBodyPart(mbp_msgtext); 111 | 112 | TestUtil.addFileAttachment(mp, testProps.get("mail.smtp.attachment")); 113 | 114 | msg.setContent(mp); 115 | 116 | // send the message by JavaMail 117 | Transport transport = session.getTransport("smtp"); // or smtps ( = TLS) 118 | transport.connect(testProps.getProperty("mail.smtp.host"), 119 | testProps.getProperty("mail.smtp.auth.user"), 120 | testProps.getProperty("mail.smtp.auth.password")); 121 | transport.sendMessage(msg, msg.getAllRecipients()); 122 | transport.close(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /test/de/agitos/dkim/MimeMailExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 The Apache Software Foundation or its licensors, as 3 | * applicable. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008 18 | */ 19 | 20 | package de.agitos.dkim; 21 | 22 | import java.io.ByteArrayInputStream; 23 | import java.util.Date; 24 | import java.util.Properties; 25 | 26 | import javax.mail.Message; 27 | import javax.mail.Session; 28 | import javax.mail.Transport; 29 | import javax.mail.internet.MailDateFormat; 30 | 31 | /* 32 | * This example signs and sends an existing email with standard DKIM signature configuration. 33 | * This version of DKIM for JavaMail was tested with JavaMail 1.4.1, downward compatibility with 1.3 is expected. 34 | * 35 | * @author Florian Sager, http://www.agitos.de, 10.05.2009 36 | */ 37 | 38 | public class MimeMailExample { 39 | 40 | public static void main(String args[]) throws Exception { 41 | 42 | // read test configuration from test.properties in your classpath 43 | Properties testProps = TestUtil.readProperties(); 44 | 45 | // generate string buffered test mail 46 | StringBuffer mimeMail = new StringBuffer(); 47 | mimeMail.append("Date: ").append(new MailDateFormat().format(new Date())).append("\r\n"); 48 | mimeMail.append("From: ").append(testProps.getProperty("mail.smtp.from")).append("\r\n"); 49 | if (testProps.getProperty("mail.smtp.to") != null) { 50 | mimeMail.append("To: ").append(testProps.getProperty("mail.smtp.to")).append("\r\n"); 51 | } 52 | if (testProps.getProperty("mail.smtp.cc") != null) { 53 | mimeMail.append("Cc: ").append(testProps.getProperty("mail.smtp.cc")).append("\r\n"); 54 | } 55 | mimeMail.append("Subject: ").append("DKIM for JavaMail: MimeMailExample Testmessage").append("\r\n"); 56 | mimeMail.append("\r\n"); 57 | mimeMail.append(TestUtil.bodyText); 58 | 59 | // get a JavaMail Session object 60 | Session session = Session.getDefaultInstance(testProps, null); 61 | 62 | 63 | 64 | ///////// beginning of DKIM FOR JAVAMAIL stuff 65 | 66 | // get DKIMSigner object 67 | DKIMSigner dkimSigner = new DKIMSigner( 68 | testProps.getProperty("mail.smtp.dkim.signingdomain"), 69 | testProps.getProperty("mail.smtp.dkim.selector"), 70 | testProps.getProperty("mail.smtp.dkim.privatekey")); 71 | 72 | /* set an address or user-id of the user on behalf this message was signed; 73 | * this identity is up to you, except the domain part must be the signing domain 74 | * or a subdomain of the signing domain. 75 | */ 76 | dkimSigner.setIdentity("mimemailexample@"+testProps.getProperty("mail.smtp.dkim.signingdomain")); 77 | 78 | // construct the JavaMail message using the DKIM message type from DKIM for JavaMail 79 | Message msg = new SMTPDKIMMessage(session, new ByteArrayInputStream(mimeMail.toString().getBytes()), dkimSigner); 80 | 81 | ///////// end of DKIM FOR JAVAMAIL stuff 82 | 83 | // send the message by JavaMail 84 | Transport transport = session.getTransport("smtp"); 85 | transport.connect(testProps.getProperty("mail.smtp.host"), 86 | testProps.getProperty("mail.smtp.auth.user"), 87 | testProps.getProperty("mail.smtp.auth.password")); 88 | transport.sendMessage(msg, msg.getAllRecipients()); 89 | transport.close(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /test/de/agitos/dkim/MultipleMailExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 The Apache Software Foundation or its licensors, as 3 | * applicable. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008 18 | */ 19 | 20 | package de.agitos.dkim; 21 | 22 | import java.util.Properties; 23 | 24 | import javax.mail.Message; 25 | import javax.mail.Session; 26 | import javax.mail.Transport; 27 | import javax.mail.internet.InternetAddress; 28 | 29 | /* 30 | * This example sends multiple DKIM signed emails with standard signature configuration. 31 | * This version of DKIM for JavaMail was tested with JavaMail 1.4.1, downward compatibility with 1.3 is expected. 32 | * 33 | * @author Florian Sager, http://www.agitos.de, 22.11.2008 34 | */ 35 | 36 | public class MultipleMailExample { 37 | 38 | public static void main(String args[]) throws Exception { 39 | 40 | // read test configuration from test.properties in your classpath 41 | Properties testProps = TestUtil.readProperties(); 42 | 43 | // get a JavaMail Session object 44 | Session session = Session.getDefaultInstance(testProps, null); 45 | 46 | Transport transport = session.getTransport("smtp"); 47 | transport.connect(testProps.getProperty("mail.smtp.host"), 48 | testProps.getProperty("mail.smtp.auth.user"), 49 | testProps.getProperty("mail.smtp.auth.password")); 50 | 51 | 52 | 53 | ///////// beginning of DKIM FOR JAVAMAIL stuff 54 | 55 | // get DKIMSigner object 56 | DKIMSigner dkimSigner = new DKIMSigner( 57 | testProps.getProperty("mail.smtp.dkim.signingdomain"), 58 | testProps.getProperty("mail.smtp.dkim.selector"), 59 | testProps.getProperty("mail.smtp.dkim.privatekey")); 60 | 61 | for (int i=0; i<3; i++) { 62 | 63 | /* set an address or user-id of the user on behalf this message was signed; 64 | * this identity is up to you, except the domain part must be the signing domain 65 | * or a subdomain of the signing domain. 66 | */ 67 | dkimSigner.setIdentity("multipleexample"+i+"@"+testProps.getProperty("mail.smtp.dkim.signingdomain")); 68 | 69 | // construct the JavaMail message using the DKIM message type from DKIM for JavaMail 70 | Message msg = new SMTPDKIMMessage(session, dkimSigner); 71 | 72 | ///////// end of DKIM FOR JAVAMAIL stuff 73 | 74 | 75 | 76 | msg.setFrom(new InternetAddress(testProps.getProperty("mail.smtp.from"))); 77 | if (testProps.getProperty("mail.smtp.to") != null) { 78 | msg.setRecipients(Message.RecipientType.TO, 79 | InternetAddress.parse(testProps.getProperty("mail.smtp.to"), false)); 80 | } 81 | if (testProps.getProperty("mail.smtp.cc") != null) { 82 | msg.setRecipients(Message.RecipientType.CC, 83 | InternetAddress.parse(testProps.getProperty("mail.smtp.cc"), false)); 84 | } 85 | 86 | msg.setSubject("DKIM for JavaMail: MultipleExample Testmessage "+i); 87 | msg.setText(TestUtil.bodyText); 88 | 89 | // send the message by JavaMail 90 | transport.sendMessage(msg, msg.getAllRecipients()); 91 | } 92 | 93 | transport.close(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test/de/agitos/dkim/SimpleExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 The Apache Software Foundation or its licensors, as 3 | * applicable. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008 18 | */ 19 | 20 | package de.agitos.dkim; 21 | 22 | import java.util.Properties; 23 | 24 | import javax.mail.Message; 25 | import javax.mail.Session; 26 | import javax.mail.Transport; 27 | import javax.mail.internet.InternetAddress; 28 | 29 | /* 30 | * This example sends a DKIM signed email with standard signature configuration. 31 | * This version of DKIM for JavaMail was tested with JavaMail 1.4.1, downward compatibility with 1.3 is expected. 32 | * 33 | * @author Florian Sager, http://www.agitos.de, 22.11.2008 34 | */ 35 | 36 | public class SimpleExample { 37 | 38 | public static void main(String args[]) throws Exception { 39 | 40 | // read test configuration from test.properties in your classpath 41 | Properties testProps = TestUtil.readProperties(); 42 | 43 | // get a JavaMail Session object 44 | Session session = Session.getDefaultInstance(testProps, null); 45 | 46 | 47 | 48 | ///////// beginning of DKIM FOR JAVAMAIL stuff 49 | 50 | // get DKIMSigner object 51 | DKIMSigner dkimSigner = new DKIMSigner( 52 | testProps.getProperty("mail.smtp.dkim.signingdomain"), 53 | testProps.getProperty("mail.smtp.dkim.selector"), 54 | testProps.getProperty("mail.smtp.dkim.privatekey")); 55 | 56 | /* set an address or user-id of the user on behalf this message was signed; 57 | * this identity is up to you, except the domain part must be the signing domain 58 | * or a subdomain of the signing domain. 59 | */ 60 | dkimSigner.setIdentity("simpleexample@"+testProps.getProperty("mail.smtp.dkim.signingdomain")); 61 | 62 | // construct the JavaMail message using the DKIM message type from DKIM for JavaMail 63 | Message msg = new SMTPDKIMMessage(session, dkimSigner); 64 | 65 | ///////// end of DKIM FOR JAVAMAIL stuff 66 | 67 | 68 | 69 | msg.setFrom(new InternetAddress(testProps.getProperty("mail.smtp.from"))); 70 | if (testProps.getProperty("mail.smtp.to") != null) { 71 | msg.setRecipients(Message.RecipientType.TO, 72 | InternetAddress.parse(testProps.getProperty("mail.smtp.to"), false)); 73 | } 74 | if (testProps.getProperty("mail.smtp.cc") != null) { 75 | msg.setRecipients(Message.RecipientType.CC, 76 | InternetAddress.parse(testProps.getProperty("mail.smtp.cc"), false)); 77 | } 78 | 79 | msg.setSubject("DKIM for JavaMail: SimpleExample Testmessage"); 80 | msg.setText(TestUtil.bodyText); 81 | 82 | // send the message by JavaMail 83 | Transport transport = session.getTransport("smtp"); 84 | transport.connect(testProps.getProperty("mail.smtp.host"), 85 | testProps.getProperty("mail.smtp.auth.user"), 86 | testProps.getProperty("mail.smtp.auth.password")); 87 | transport.sendMessage(msg, msg.getAllRecipients()); 88 | transport.close(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/de/agitos/dkim/TestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 The Apache Software Foundation or its licensors, as 3 | * applicable. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008 18 | */ 19 | 20 | package de.agitos.dkim; 21 | 22 | import java.io.File; 23 | import java.io.FileInputStream; 24 | import java.util.Properties; 25 | 26 | import javax.activation.DataHandler; 27 | import javax.activation.FileDataSource; 28 | import javax.mail.MessagingException; 29 | import javax.mail.Multipart; 30 | import javax.mail.internet.MimeBodyPart; 31 | 32 | /* 33 | * @author Florian Sager, http://www.agitos.de, 22.11.2008 34 | */ 35 | 36 | public class TestUtil { 37 | 38 | public static String bodyText = "Hello,\r\n\r\nthis email was signed by the DKIM for JavaMail library.\r\n\r\nTo check the validity of DKIM signatures, send the generated emails directly to a DKIM test address like sa-test@sendmail.net with your personal email address in the From header.\r\n\r\nPromotional stuff: see www.dkim-reputation.org and consider using DKIM reputation for an improved spam filtering, especially for the reduction of false positives.\r\n\r\nRegards,\r\nFlorian Sager, www.agitos.de\r\n\r\n"; 39 | 40 | public static Properties readProperties() { 41 | 42 | Properties props = new Properties(); 43 | try { 44 | props.load(new FileInputStream("test.properties")); 45 | } catch (Exception e) { 46 | msgAndExit("Check if the test configuration file 'test.properties' is in your classpath and if it's readable"); 47 | } 48 | return props; 49 | } 50 | 51 | public static void printArray(String[] a) { 52 | 53 | for (int i=0; i