Skip to content

Commit 633aa4d

Browse files
committed
Misc. TLS security fixes and improvements
- The SMTP_PLAIN transport strategy now attempts an (insecure) STARTTLS upgrade where possible, but will always permit plaintext fallback to preserve backwards-compatibility with unencrypted SMTP. The opportunistic STARTTLS handshake in SMTP_PLAIN does not validate the server certificate's issuer or identity; therefore, it does not protect against active network attackers. The STARTTLS handshake, in this transport strategy, is merely a best-effort encryption mechanism to defend against passive network eavesdroppers. - The SMTP_SSL and SMTP_TLS transport strategies now validate certificates by setting JavaMail's `mail.<protocol>.ssl.checkserveridentity` property to true. Previously, no identity validation was performed, leaving SMTPS and STARTTLS connections vulnerable to man-in-the-middle attacks. Without identity validation, JavaMail accepts _any_ certificate issued by a JVM-trusted CA, regardless of the identity encoded in the certificate. - The SMTP_TLS transport strategy now requires STARTTLS support by setting JavaMail's `mail.smtp.starttls.required` property to true. Previously, STARTTLS support was not required, enabling a man-in-the-middle attack whereby an attacker could strip the STARTTLS request from an SMTP connection, causing JavaMail to fall back to plaintext SMTP for authentication and email transport.
1 parent b87402d commit 633aa4d

File tree

2 files changed

+66
-12
lines changed

2 files changed

+66
-12
lines changed

src/main/java/org/simplejavamail/mailer/config/TransportStrategy.java

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,48 @@
1010
* <code>TransportStrategy</code> implementation.
1111
*
1212
* @author Benny Bottema
13+
* @author Christian Barcenas
1314
*/
1415
public enum TransportStrategy {
1516

1617
/**
17-
* Simplest possible form: only vanilla ".smtp." property names and no extra properties. Additionally the transport protocol is explicitly set to
18-
* smtp.
18+
* Vanilla SMTP with an insecure STARTTLS upgrade (if supported).
19+
* <p>
20+
* This {@code TransportStrategy} falls back to plaintext when a mail server does not indicate support for
21+
* STARTTLS. Additionally, even if a TLS session is negotiated, <strong>server certificates are not validated in
22+
* any way</strong>.
23+
* <p>
24+
* This {@code TransportStrategy} only offers protection against passive network eavesdroppers when the mail server
25+
* indicates support for STARTTLS. Active network attackers can trivially bypass the encryption 1) by tampering with
26+
* the STARTTLS indicator, 2) by presenting a self-signed certificate, 3) by presenting a certificate issued by an
27+
* untrusted certificate authority; or 4) by presenting a certificate that was issued by a valid certificate
28+
* authority to a domain other than the mail server's.
29+
* <p>
30+
* For proper mail transport encryption, see {@link TransportStrategy#SMTP_SSL} or
31+
* {@link TransportStrategy#SMTP_TLS}.
32+
* <p>
33+
* Implementation notes:
34+
* <ul>
35+
* <li>The transport protocol is explicitly set to {@code smtp}.</li>
36+
* <li>Only {@code mail.smtp} properties are set.</li>
37+
* <li>STARTTLS is enabled by setting {@code mail.smtp.starttls.enable} to {@code true}.</li>
38+
* <li>STARTTLS plaintext fallback is enabled by setting {@code mail.smtp.starttls.required} to {@code false}.</li>
39+
* <li>Certificate issuer checks are disabled by setting {@code mail.smtp.ssl.trust} to {@code "*"}.</li>
40+
* <li>Certificate identity checks are disabled by setting {@code mail.smtp.ssl.checkserveridentity} to {@code false}.</li>
41+
* </ul>
1942
*/
2043
SMTP_PLAIN {
2144
/**
22-
* Here protocol "mail.transport.protocol" is set to "smtp".
23-
*
2445
* @see TransportStrategy#SMTP_PLAIN
2546
*/
2647
@Override
2748
public Properties generateProperties() {
2849
final Properties props = super.generateProperties();
2950
props.put("mail.transport.protocol", "smtp");
51+
props.put("mail.smtp.starttls.enable", true);
52+
props.put("mail.smtp.starttls.required", false);
53+
props.put("mail.smtp.ssl.trust", "*");
54+
props.put("mail.smtp.ssl.checkserveridentity", false);
3055
return props;
3156
}
3257

@@ -63,9 +88,18 @@ public String propertyNameAuthenticate() {
6388
}
6489
},
6590
/**
66-
* SMTPS / SSL transport strategy, that returns the ".smtps." variation of the SMTP_PLAIN version. Additionally the transport protocol is
67-
* explicitly set to smtps. Finally, he property "mail.smtps.quitwait" is set to false, to get rid of a strange SSL exception:<br>
91+
* SMTP entirely encapsulated by TLS. Commonly known as SMTPS.
6892
* <p>
93+
* Strict validation of server certificates is enabled. Server certificates must be issued 1) by a certificate
94+
* authority in the system trust store; and 2) to a subject matching the identity of the remote SMTP server.
95+
* <p>
96+
* Implementation notes:
97+
* <ul>
98+
* <li>The transport protocol is explicitly set to {@code smtps}.</li>
99+
* <li>Only {@code mail.smtps} properties are set.</li>
100+
* <li>Certificate identity checks are enabled by setting {@code mail.smtp.ssl.checkserveridentity} to {@code true}.</li>
101+
* <li>
102+
* {@code mail.smtps.quitwait} is set to {@code false} to get rid of a strange SSLException:
69103
* <pre>
70104
* javax.mail.MessagingException: Exception reading response;
71105
* nested exception is:
@@ -76,17 +110,18 @@ public String propertyNameAuthenticate() {
76110
* <blockquote>The mail is sent but the exception is unwanted. The property <em>quitwait</em> means If set to false, the QUIT command is sent and
77111
* the connection is immediately closed. If set to true (the default), causes the transport to wait for the response to the QUIT
78112
* command</blockquote><br> <strong>- <a href="http://www.rgagnon.com/javadetails/java-0570.html">source</a></strong>
113+
* </li>
114+
* </ul>
79115
*/
80116
SMTP_SSL {
81117
/**
82-
* Here protocol "mail.transport.protocol" is set to "smtps" and the quitwait property "mail.smtps.quitwait" is set to "false".
83-
*
84118
* @see TransportStrategy#SMTP_SSL
85119
*/
86120
@Override
87121
public Properties generateProperties() {
88122
final Properties properties = super.generateProperties();
89123
properties.put("mail.transport.protocol", "smtps");
124+
properties.put("mail.smtps.ssl.checkserveridentity", "true");
90125
properties.put("mail.smtps.quitwait", "false");
91126
return properties;
92127
}
@@ -124,22 +159,33 @@ public String propertyNameAuthenticate() {
124159
}
125160
},
126161
/**
162+
* Plaintext SMTP with a mandatory, authenticated STARTTLS upgrade.
163+
* <p>
127164
* <strong>NOTE: this code is in untested beta state</strong>
128165
* <p>
129-
* Uses standard ".smtp." property names (like {@link TransportStrategy#SMTP_PLAIN}). Additionally the transport protocol is explicitly set to
130-
* smtp. Finally, the property "mail.smtp.starttls.enable" is being set to true.
166+
* Strict validation of server certificates is enabled. Server certificates must be issued 1) by a certificate
167+
* authority in the system trust store; and 2) to a subject matching the identity of the remote SMTP server.
168+
* <p>
169+
* Implementation notes:
170+
* <ul>
171+
* <li>The transport protocol is explicitly set to {@code smtp}.</li>
172+
* <li>Only {@code mail.smtp} properties are set.</li>
173+
* <li>STARTTLS is enabled by setting {@code mail.smtp.starttls.enable} to {@code true}.</li>
174+
* <li>STARTTLS plaintext fallback is disabled by setting {@code mail.smtp.starttls.required} to {@code true}.</li>
175+
* <li>Certificate identity checks are enabled by setting {@code mail.smtp.ssl.checkserveridentity} to {@code true}.</li>
176+
* </ul>
131177
*/
132178
SMTP_TLS {
133179
/**
134-
* Here protocol "mail.transport.protocol" is set to "smtp" and the tls property "mail.smtp.starttls.enable" is set to "true".
135-
*
136180
* @see TransportStrategy#SMTP_TLS
137181
*/
138182
@Override
139183
public Properties generateProperties() {
140184
final Properties props = super.generateProperties();
141185
props.put("mail.transport.protocol", "smtp");
142186
props.put("mail.smtp.starttls.enable", "true");
187+
props.put("mail.smtp.starttls.required", "true");
188+
props.put("mail.smtp.ssl.checkserveridentity", "true");
143189
return props;
144190
}
145191

src/test/java/org/simplejavamail/mailer/MailerTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ public void createMailSession_AnonymousProxyConstructor_WithoutConfig()
9090
assertThat(session.getProperty("mail.smtp.port")).isEqualTo("25");
9191
assertThat(session.getProperty("mail.transport.protocol")).isEqualTo("smtp");
9292
assertThat(session.getProperty("mail.smtp.starttls.enable")).isEqualTo("true");
93+
assertThat(session.getProperty("mail.smtp.starttls.required")).isEqualTo("true");
94+
assertThat(session.getProperty("mail.smtp.ssl.checkserveridentity")).isEqualTo("true");
9395
assertThat(session.getProperty("mail.smtp.username")).isEqualTo("username smtp");
9496
assertThat(session.getProperty("mail.smtp.auth")).isEqualTo("true");
9597
assertThat(session.getProperty("mail.smtp.socks.host")).isEqualTo("proxy host");
@@ -112,6 +114,8 @@ public void createMailSession_MaximumConstructor_WithoutConfig()
112114
assertThat(session.getProperty("mail.smtp.port")).isEqualTo("25");
113115
assertThat(session.getProperty("mail.transport.protocol")).isEqualTo("smtp");
114116
assertThat(session.getProperty("mail.smtp.starttls.enable")).isEqualTo("true");
117+
assertThat(session.getProperty("mail.smtp.starttls.required")).isEqualTo("true");
118+
assertThat(session.getProperty("mail.smtp.ssl.checkserveridentity")).isEqualTo("true");
115119
assertThat(session.getProperty("mail.smtp.username")).isEqualTo("username smtp");
116120
assertThat(session.getProperty("mail.smtp.auth")).isEqualTo("true");
117121
// the following two are because authentication is needed, otherwise proxy would be straightworward
@@ -131,6 +135,8 @@ public void createMailSession_MinimalConstructor_WithConfig() {
131135
assertThat(session.getProperty("mail.smtp.port")).isEqualTo("25");
132136
assertThat(session.getProperty("mail.transport.protocol")).isEqualTo("smtp");
133137
assertThat(session.getProperty("mail.smtp.starttls.enable")).isEqualTo("true");
138+
assertThat(session.getProperty("mail.smtp.starttls.required")).isEqualTo("true");
139+
assertThat(session.getProperty("mail.smtp.ssl.checkserveridentity")).isEqualTo("true");
134140
assertThat(session.getProperty("mail.smtp.username")).isEqualTo("username smtp");
135141
assertThat(session.getProperty("mail.smtp.auth")).isEqualTo("true");
136142
// the following two are because authentication is needed, otherwise proxy would be straightworward
@@ -150,6 +156,8 @@ public void createMailSession_MaximumConstructor_WithConfig()
150156
assertThat(session.getProperty("mail.smtp.port")).isEqualTo("25");
151157
assertThat(session.getProperty("mail.transport.protocol")).isEqualTo("smtp");
152158
assertThat(session.getProperty("mail.smtp.starttls.enable")).isEqualTo("true");
159+
assertThat(session.getProperty("mail.smtp.starttls.required")).isEqualTo("true");
160+
assertThat(session.getProperty("mail.smtp.ssl.checkserveridentity")).isEqualTo("true");
153161
assertThat(session.getProperty("mail.smtp.username")).isEqualTo("overridden username smtp");
154162
assertThat(session.getProperty("mail.smtp.auth")).isEqualTo("true");
155163
// the following two are because authentication is needed, otherwise proxy would be straightworward

0 commit comments

Comments
 (0)