jakartaee / mail-api

Jakarta Mail Specification project
https://jakartaee.github.io/mail-api
Other
247 stars 101 forks source link

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

Open dincciftci opened 5 years ago

dincciftci commented 5 years ago

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, 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 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:

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)

<redacted>
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: <redacted hostname>
        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)
<redacted>
Caused by: java.io.IOException: Can't verify identity of server: <redacted hostname>
        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)
<redacted>
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)
<redacted>
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
bshannon commented 5 years ago

We could probably do better, but there's a limit to what we can do since JavaMail is largely at the mercy of the JDK and which exceptions it throws for different SSL errors. Since the JDK hasn't standardized exceptions for all the different cases, I don't want to try to interpret the JDK exceptions and turn them into JavaMail exceptions. Many of the details of what went wrong at the SSL/TLS level are going to need to defer to the JDK exceptions.

But there are definitely cases where JavaMail knows, at a high level, what went wrong, and those could probably be reflected in more specific JavaMail exceptions. I would probably try to do this as an implementation-specific feature before proposing new standard exception classes.

dincciftci commented 5 years ago

Thanks for taking a look! I think your evaluation is fair.

Regarding the work to make this JavaMail implementation throw specific errors, is it sufficient for me to follow this item for updates? Also, do you have any guesses on which release this request could be targeted for? (even if it's far out)

bshannon commented 5 years ago

Yes, following this issue is sufficient.

I may try to look into this for the next release, but I fear that it's going to be more work than expected. The implementation is probably not too difficult, but writing the tests that force all the different failures may be the hard part.

There's no plan or schedule for the next release, but typically releases are 6 - 12 months apart.