package com.secdec.codedx.security;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* This X509TrustManager implementation allows invalid certificates to possibly
* be accepted by the decision of an {@link InvalidCertificateStrategy} that is
* passed as a constructor argument. Certificates added in this way will be
* added via a {@link ExtraCertManager}, causing the underlying trust manager to
* be reloaded.
*
* Adapted from the implementation at <a href=
* "https://jcalcote.wordpress.com/2010/06/22/managing-a-dynamic-java-trust-store/"
* >"Managing a Dynamic Java Trust Store"</a> (blog post)
*/
public class ReloadableX509TrustManager implements X509TrustManager {
/* package-private */final ExtraCertManager certManager;
private final InvalidCertificateStrategy invalidCertStrat;
private X509TrustManager tmDelegate;
public ReloadableX509TrustManager(ExtraCertManager certManager, InvalidCertificateStrategy invalidCertStrat) throws
GeneralSecurityException {
this.certManager = certManager;
this.invalidCertStrat = invalidCertStrat;
reloadTrustManager();
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
tmDelegate.checkClientTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
tmDelegate.checkServerTrusted(chain, authType);
} catch (CertificateException cx) {
/*
* At this point, we have come across an apparently-invalid
* certificate. We use the `InvalidCertificateStrategy` to decide
* what to do about it; either reject it (rethrow the exception), or
* accept it. If accepting, the certificate can be added
* "temporarily" or "permanently", which is done via the
* `ExtraCertManager`.
*/
Certificate cert = chain[0];
CertificateAcceptance certAcceptance = invalidCertStrat.checkAcceptance(cert, cx);
switch (certAcceptance) {
case REJECT:
throw cx;
case ACCEPT_TEMPORARILY:
try {
certManager.addTemporaryCert(cert);
reloadTrustManager();
} catch (IOException e) {
// wrap errors from the cert manipulation
throw new CertificateException("Error handling temporary acceptance of the certificate", e);
} catch (GeneralSecurityException e) {
throw new CertificateException("Error handling temporary acceptance of the certificate", e);
}
// now retry the trust check
tmDelegate.checkServerTrusted(chain, authType);
break;
case ACCEPT_PERMANENTLY:
try {
certManager.addPermanentCert(cert);
reloadTrustManager();
} catch (IOException e) {
// wrap errors from the cert manipulation
throw new CertificateException("Error handling permanent acceptance of the certificate", e);
} catch (GeneralSecurityException e) {
throw new CertificateException("Error handling permanent acceptance of the certificate", e);
}
// now retry the trust check
tmDelegate.checkServerTrusted(chain, authType);
break;
}
}
}
public X509Certificate[] getAcceptedIssuers() {
return tmDelegate.getAcceptedIssuers();
}
/* package-private */
void reloadTrustManager() throws GeneralSecurityException {
KeyStore ks = certManager.asKeyStore();
// initialize a new TMF with the KeyStore we just created
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
// acquire an X509 trust manager from the TMF
// and update the `tmDelegate` to that value
TrustManager[] tms = tmf.getTrustManagers();
for (TrustManager tm : tms) {
if (tm instanceof X509TrustManager) {
tmDelegate = (X509TrustManager) tm;
return;
}
}
// should have returned in the `for` loop above
throw new NoSuchAlgorithmException("No X509TrustManager in TrustManagerFactory");
}
}