package org.cagrid.security.ssl.proxy.trust;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Set;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/*
* Trying to follow http://www.ietf.org/rfc/rfc3820.txt
*/
public class ProxyTrustManager implements X509TrustManager {
public final static String PROXY_CERT_INFO_OID = "1.3.6.1.5.5.7.1.14";
public final static String BASIC_CONSTRAINTS_OID = "2.5.29.19";
public final static String ISSUER_ALT_NAME_OID = "2.5.29.18";
public final static String SUBJECT_ALT_NAME_OID = "2.5.29.17";
public final static String KEY_USAGE_OID = "2.5.29.15";
public final static String EXTENDED_KEY_USAGE_OID = "2.5.29.37";
public final static int KEY_USAGE_DIGITAL_SIGNATURE = 0;
public final static int ASN1_OCTET_STRING_TAG = 4;
private final static X509Certificate[] EMPTY_ACCEPTED_ISSUERS = new X509Certificate[0];
private final static Logger logger = LoggerFactory
.getLogger(ProxyTrustManager.class);
public static ProxyTrustManager createTrustManager(String trustStorePath,
String trustStorePassword) throws KeyStoreException,
NoSuchAlgorithmException, CertificateException, IOException {
InputStream trustStoreStream = new FileInputStream(trustStorePath);
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(trustStoreStream, trustStorePassword.toCharArray());
trustStoreStream.close();
return createTrustManager(trustStore);
}
public static ProxyTrustManager createTrustManager(KeyStore trustStore)
throws NoSuchAlgorithmException, KeyStoreException {
logger.info("Creating ProxyTrustManager");
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
TrustManager[] plainTrustManagers = trustManagerFactory
.getTrustManagers();
ProxyTrustManager proxyTrustManager = new ProxyTrustManager(
plainTrustManagers);
return proxyTrustManager;
}
private final X509TrustManager[] delegateTrustManagers;
public ProxyTrustManager(TrustManager[] plainTrustManagers) {
int nTrustManagers = 0;
for (TrustManager tm : plainTrustManagers) {
if (tm instanceof X509TrustManager)
nTrustManagers++;
}
delegateTrustManagers = new X509TrustManager[nTrustManagers];
int iTrustManager = 0;
for (TrustManager tm : plainTrustManagers) {
if (tm instanceof X509TrustManager)
delegateTrustManagers[iTrustManager++] = (X509TrustManager) tm;
}
}
/*
* Validate the proxy certificates until the first end-certificate. If OK,
* delegate the end-certificate validation. The RFC says the end-certificate
* must be validated first, but why?
*/
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
logger.info("checkClientTrusted authType=" + authType);
int currentProxyPathLength = -1;
int endCertIX = 0;
for (; endCertIX < chain.length; endCertIX++) {
X509Certificate cert = chain[endCertIX];
// Is this a proxy cert?
byte[] proxyCertInfoBytes = cert
.getExtensionValue(PROXY_CERT_INFO_OID);
if (proxyCertInfoBytes == null)
break;
logger.info("Proxy cert found");
/*
* The extension value is probably wrapped as a primitive octet
* string.
*/
proxyCertInfoBytes = unwrapASN1Octets(proxyCertInfoBytes);
ProxyCertInfo proxyCertInfo = ProxyCertInfo
.getInstance(proxyCertInfoBytes);
currentProxyPathLength++;
/*
* 4.1.3.(a)(2) -- Is the certificate valid now?
*/
cert.checkValidity();
/*
* Must handle all the critical extensions.
*/
Set<String> criticalOIDs = cert.getCriticalExtensionOIDs();
/*
* 3.8. -- The proxyCertInfo must be critical.
*/
if (!criticalOIDs.remove(PROXY_CERT_INFO_OID)) {
throw new CertificateException(
"proxyCertInfo extension must be critical");
}
/*
* 3.2. / 3.5. -- Issuer / subject alternative names may not be
* present.
*/
if ((cert.getIssuerAlternativeNames() != null)
|| (cert.getSubjectAlternativeNames() != null)) {
throw new CertificateException(
"issuerAltName / subjectAltName may not be present");
}
criticalOIDs.remove(ISSUER_ALT_NAME_OID);
criticalOIDs.remove(SUBJECT_ALT_NAME_OID);
/*
* 3.6. -- If key usage is present, digital signature must be
* asserted.
*/
boolean[] keyUsage = cert.getKeyUsage();
if (keyUsage != null) {
if ((keyUsage.length <= KEY_USAGE_DIGITAL_SIGNATURE)
|| !keyUsage[KEY_USAGE_DIGITAL_SIGNATURE]) {
throw new CertificateException(
"keyUsage must assert digitalSignature");
}
}
criticalOIDs.remove(KEY_USAGE_OID);
/*
* 3.7. -- Basic constraints may not assert CA.
*/
int basicConstraints = cert.getBasicConstraints();
if (basicConstraints != -1) {
throw new CertificateException(
"basicConstraints may not assert CA");
}
criticalOIDs.remove(BASIC_CONSTRAINTS_OID);
/*
* 3.8.1. -- The current path length may not exceed the signing
* constraint.
*/
if (currentProxyPathLength > proxyCertInfo.getPathLenConstraint()) {
throw new CertificateException("pCPathLenConstraint exceeded");
}
/*
* 3.8.2 -- Check policy vs. language.
*/
ProxyPolicy proxyPolicy = proxyCertInfo.getProxyPolicy();
DERObjectIdentifier proxyLanguage = proxyPolicy.getPolicyLanguage();
if (ProxyPolicy.IMPERSONATION.equals(proxyLanguage)
|| ProxyPolicy.INDEPENDENT.equals(proxyLanguage)) {
if (proxyPolicy.getPolicy() != null) {
throw new CertificateException(
"proxyLanguage does not allow policy");
}
}
/*
* Not checking extended key usage.
*/
criticalOIDs.remove(EXTENDED_KEY_USAGE_OID);
/*
* Any critical extensions left?
*/
if (!criticalOIDs.isEmpty()) {
throw new CertificateException(
"Unhandled critical extensions: " + criticalOIDs);
}
/*
* 4.1.3.(a)(4) -- Subject must be issuer plus a CN.
*/
X500Principal issuerPrincipal = cert.getIssuerX500Principal();
Set<Attribute> issuerAttributes = getPrincipalAttributes(
issuerPrincipal, "issuer");
X500Principal subjectPrincipal = cert.getSubjectX500Principal();
Set<Attribute> subjectAttributes = getPrincipalAttributes(
subjectPrincipal, "subject");
boolean goodSubject = subjectAttributes
.containsAll(issuerAttributes);
if (goodSubject) {
goodSubject = false;
subjectAttributes.removeAll(issuerAttributes);
if (subjectAttributes.size() == 1) {
Attribute cnAtt = subjectAttributes.iterator().next();
if ("CN".equals(cnAtt.getID()))
goodSubject = true;
}
}
if (!goodSubject) {
throw new CertificateException("subject must be issuer plus CN");
}
/*
* There must be an issuer certificate next in the chain.
*/
int issuerCertIX = endCertIX + 1;
if (issuerCertIX >= chain.length) {
throw new CertificateException(
"no issuing certificate for proxy");
}
X509Certificate issuerCert = chain[issuerCertIX];
/*
* 4.1.3.(a)(3) -- Issuer must match issuing certificate subject.
*/
X500Principal issuerSubjectPrincipal = issuerCert
.getSubjectX500Principal();
Set<Attribute> issuerSubjectAttributes = getPrincipalAttributes(
issuerSubjectPrincipal, "issuer subject");
if (!issuerAttributes.equals(issuerSubjectAttributes)) {
throw new CertificateException(
"issuer must match issuer subject");
}
/*
* Finally, verify signature.
*/
try {
cert.verify(issuerCert.getPublicKey());
} catch (Exception e) {
throw new CertificateException("Invalid signature", e);
}
}
logger.info("Delegating to default TrustManagers...");
X509Certificate[] endChain = new X509Certificate[chain.length
- endCertIX];
for (int i = 0, j = endCertIX; j < chain.length; j++, i++) {
endChain[i] = chain[j];
}
for (X509TrustManager tm : delegateTrustManagers) {
tm.checkClientTrusted(endChain, authType);
}
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// No proxy checking here, really shouldn't be used in this case.
for (X509TrustManager tm : delegateTrustManagers) {
tm.checkServerTrusted(chain, authType);
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
if (delegateTrustManagers.length > 0)
return delegateTrustManagers[0].getAcceptedIssuers();
return EMPTY_ACCEPTED_ISSUERS;
}
private Set<Attribute> getPrincipalAttributes(X500Principal principal,
String type) throws CertificateException {
if (principal == null) {
throw new CertificateException("no " + type + " principal");
}
Set<Attribute> principalAttributes = null;
try {
principalAttributes = DNDissector.dissect(principal);
} catch (NamingException ne) {
throw new CertificateParsingException("Could not dissect " + type
+ " principal", ne);
}
return principalAttributes;
}
private byte[] unwrapASN1Octets(byte[] octets) {
if (octets[0] == ASN1_OCTET_STRING_TAG) {
int offset = 2;
int length1 = octets[1];
if (length1 < 0) {
int nAdditional = length1 & 0x7F;
offset += nAdditional;
}
octets = Arrays.copyOfRange(octets, offset, octets.length);
}
return octets;
}
}