k-9/k9mail-library/src/main/java/com/fsck/k9/mail/CertificateValidationExcept...

130 lines
4.7 KiB
Java

package com.fsck.k9.mail;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLHandshakeException;
import android.security.KeyChainException;
public class CertificateValidationException extends MessagingException {
public static final long serialVersionUID = -1;
private final Reason mReason;
private X509Certificate[] mCertChain;
private boolean mNeedsUserAttention = false;
private String mAlias;
public enum Reason {
Unknown, UseMessage, Expired, MissingCapability, RetrievalFailure
}
public CertificateValidationException(String message) {
this(message, Reason.UseMessage, null);
}
public CertificateValidationException(Reason reason) {
this(null, reason, null);
}
public CertificateValidationException(String message, Reason reason, String alias) {
super(message);
/*
* Instances created without a Throwable parameter as a cause are
* presumed to need user attention.
*/
mNeedsUserAttention = true;
mReason = reason;
mAlias = alias;
}
public CertificateValidationException(final String message, Throwable throwable) {
super(message, throwable);
mReason = Reason.Unknown;
scanForCause();
}
public String getAlias() {
return mAlias;
}
public Reason getReason() {
return mReason;
}
private void scanForCause() {
Throwable throwable = getCause();
/*
* User attention is required if the server certificate was deemed
* invalid or if there was a problem with a client certificate.
*
* A CertificateException is known to be thrown by the default
* X509TrustManager.checkServerTrusted() if the server certificate
* doesn't validate. The cause of the CertificateException will be a
* CertPathValidatorException. However, it's unlikely those exceptions
* will be encountered here, because they are caught in
* SecureX509TrustManager.checkServerTrusted(), which throws a
* CertificateChainException instead (an extension of
* CertificateException).
*
* A CertificateChainException will likely result in (or, be the cause
* of) an SSLHandshakeException (an extension of SSLException).
*
* The various mail protocol handlers (IMAP, POP3, ...) will catch an
* SSLException and throw a CertificateValidationException (this class)
* with the SSLException as the cause. (They may also throw a
* CertificateValidationException when STARTTLS is not available, just
* for the purpose of triggering a user notification.)
*
* SSLHandshakeException is also known to occur if the *client*
* certificate was not accepted by the server (unknown CA, certificate
* expired, etc.). In this case, the SSLHandshakeException will not have
* a CertificateChainException as a cause.
*
* KeyChainException is known to occur if the device has no client
* certificate that's associated with the alias stored in the server
* settings.
*/
while (throwable != null
&& !(throwable instanceof CertPathValidatorException)
&& !(throwable instanceof CertificateException)
&& !(throwable instanceof KeyChainException)
&& !(throwable instanceof SSLHandshakeException)) {
throwable = throwable.getCause();
}
if (throwable != null) {
mNeedsUserAttention = true;
// See if there is a server certificate chain attached to the SSLHandshakeException
if (throwable instanceof SSLHandshakeException) {
while (throwable != null && !(throwable instanceof CertificateChainException)) {
throwable = throwable.getCause();
}
}
if (throwable != null && throwable instanceof CertificateChainException) {
mCertChain = ((CertificateChainException) throwable).getCertChain();
}
}
}
public boolean needsUserAttention() {
return mNeedsUserAttention;
}
/**
* If the cause of this {@link CertificateValidationException} was a
* {@link CertificateChainException}, then the offending chain is available
* for return.
*
* @return An {@link X509Certificate X509Certificate[]} containing the Cert.
* chain, or else null.
*/
public X509Certificate[] getCertChain() {
return mCertChain;
}
}