jakartaee / jakartaee/mail-api

Define a contract for various types of failures encountered when creating a connection with STARTTLS

Open
#392 3 comments 1 reaction 0 assignees View on GitHub
Dominant language
Java
Stars
285
Forks
109
Avg merge
15h 19m
Merged PRs (30d)
1

Description

**Is your feature request related to a problem? Please describe.**
JavaMail can be used to create an encrypted connection to IMAP and SMTP servers using STARTTLS.

There are various errors that can be encountered when such a connection is attempted, based on
the properties passed in when creating a `javax.mail.Session` object and the configuration of the email server that is being connected to. For example, the mail transport can be configured to require STARTTLS, such that the connection attempt (using an implementation of [Session::connect](https://eclipse-ee4j.github.io/javamail/docs/api/javax/mail/Service.html#connect-java.lang.String-int-java.lang.String-java.lang.String-), such as `SMTPTransport::connect`) will fail if the server being talked to does not support TLS.

Currently, the API for the various overloaded `connect` methods defines two main errors:
```
AuthenticationFailedException - for authentication failures
MessagingException - for non-authentication failures
```

Based on the `connect` methods' current contracts, it is not possible for us to programmatically determine the root causes of many of the errors related to TLS. Based on the exception thrown by JavaMail, we can make an educated guess-- but this guess is not based on a contract in the interface, and can easily break through future changes in the relevant codepath in JavaMail.

We would like a contract in the interface (e.g. `TLSUnavailableException` is thrown when the server doesn't support TLS) because this will let the consumers of JavaMail triage errors more accurately in a programmatic way. For our software, this will translate to user-visible errors in the part of the product that uses JavaMail.

For background, our software lets the user configure some of the TLS-related parameters when the user wants our software to talk to an SMTP server. It is important to communicate errors back to the user when the error is actionable for them e.g. when they have whitelisted a single cipher for use with TLS, but the server they are talking to does not support it.

Right now, we can programmatically look for a `javax.mail.MessagingException` whose first-level nested exception is a `javax.net.ssl.SSLHandshakeException` whose exception message mentions the phrase `cipher suites` in order to determine that this is the problem. This approach is more of a heuristic based on the current behavior of JavaMail and can break in the future, because it's not part of the interface definition.

Ideally, the `connect` method would define an Exception type in its interface that is guaranteed to be thrown in this scenario.

**Describe the solution you'd like**
The various [Session::connect](https://eclipse-ee4j.github.io/javamail/docs/api/javax/mail/Service.html#connect-java.lang.String-int-java.lang.String-java.lang.String-) method overloads should define specific Exception types that they will throw when encountering specific TLS-related problems. A shortlist of scenarios that should be considered:

* STARTTLS is configured to be required, but the mail server doesn't support it.
* STARTTLS is not requried (e.g. either not enabled at all, or enabled but not required) but the mail server requires it.
* The mail server fails the identity check (as configured through the `ssl.checkserveridentity` setting)
* The mail server's certificate can not be validated. If we could get more detail here, that'd be amazing. (e.g. certificate's chain of trust can not be validated)
* The mail server does not support any of the cipher suites that the client is configured with (through the `ssl.ciphersuites` setting)
* The mail server does not support any of the TLS protocols that the client is configured with (through the `ssl.protocols` setting)

**Describe alternatives you've considered**
Documenting the current behavior when such errors are encountered, adding automated tests for them in JavaMail them to ensure the behavior doesn't change, and capturing the behavior in the documentation.

**Additional context**
Example stack traces from specific scenarios:
TLS is required, but the mail server does not support it:
```
javax.mail.MessagingException: STARTTLS is required but host does not support STARTTLS
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:734)
at javax.mail.Service.connect(Service.java:366)
```
The certificate presented by the mail server can not be trusted:
```
javax.mail.MessagingException: Could not convert socket to TLS;
nested exception is:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provid
er.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:2064)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:724)
at javax.mail.Service.connect(Service.java:366)

Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.pro
vider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1964)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:328)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:322)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1614)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1052)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:987)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1072)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397)
at com.sun.mail.util.SocketFetcher.configureSSLSocket(SocketFetcher.java:598)
at com.sun.mail.util.SocketFetcher.startTLS(SocketFetcher.java:525)
at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:2059)
... 13 more
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderExce
ption: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:397)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:302)
at sun.security.validator.Validator.validate(Validator.java:262)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1596)
... 23 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:392)
... 29 more
```

The mail server fails identity check:
```
javax.mail.MessagingException: Could not convert socket to TLS;
nested exception is:
java.io.IOException: Can't verify identity of server:
at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:2064)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:724)
at javax.mail.Service.connect(Service.java:366)

Caused by: java.io.IOException: Can't verify identity of server:
at com.sun.mail.util.SocketFetcher.checkServerIdentity(SocketFetcher.java:648)
at com.sun.mail.util.SocketFetcher.configureSSLSocket(SocketFetcher.java:606)
at com.sun.mail.util.SocketFetcher.startTLS(SocketFetcher.java:525)
at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:2059)
... 13 more
```

The client and the server don't have any supported TLS versions in common:
```
javax.mail.MessagingException: Could not convert socket to TLS;
nested exception is:
javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:2064)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:724)
at javax.mail.Service.connect(Service.java:366)

Caused by: javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1002)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397)
at com.sun.mail.util.SocketFetcher.configureSSLSocket(SocketFetcher.java:598)
at com.sun.mail.util.SocketFetcher.startTLS(SocketFetcher.java:525)
at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:2059)
... 13 more
Caused by: java.io.EOFException: SSL peer shut down incorrectly
at sun.security.ssl.InputRecord.read(InputRecord.java:505)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:983)
... 19 more
```
The client and the server don't have any supported ciphersuites in common:
```
javax.mail.MessagingException: Could not convert socket to TLS;
nested exception is:
javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:2064)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:724)
at javax.mail.Service.connect(Service.java:366)

Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
at com.sun.mail.util.SocketFetcher.configureSSLSocket(SocketFetcher.java:598)
at com.sun.mail.util.SocketFetcher.startTLS(SocketFetcher.java:525)
at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:2059)
... 10 more
```

Contributor guide

Open the contributing guide

Research direction

Start with the Session::connect interface contract and the SMTPTransport and SocketFetcher paths shown in the examples. Review how the listed STARTTLS and TLS failures are currently surfaced, then inspect the documented alternative of tests and behavior documentation. Done means the contract and coverage clearly distinguish the requested failure scenarios.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.