Java tutorial
/* * Copyright 1999-2010 University of Chicago * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. * * See the License for the specific language governing permissions and limitations under the License. */ package org.globus.security.trustmanager; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.KeyStore; import java.security.cert.CertPath; import java.security.cert.CertPathParameters; import java.security.cert.CertPathValidatorException; import java.security.cert.CertPathValidatorResult; import java.security.cert.CertPathValidatorSpi; import java.security.cert.CertStore; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Map; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.TBSCertificateStructure; import org.bouncycastle.asn1.x509.X509Extension; import org.bouncycastle.asn1.x509.X509Extensions; import org.globus.security.Constants; import org.globus.security.X509ProxyCertPathParameters; import org.globus.security.X509ProxyCertPathValidatorResult; import org.globus.security.provider.SigningPolicyStore; import org.globus.security.proxyExtension.ProxyCertInfo; import org.globus.security.proxyExtension.ProxyPolicy; import org.globus.security.proxyExtension.ProxyPolicyHandler; import org.globus.security.util.CertificateUtil; import org.globus.security.util.ProxyCertificateUtil; /** * Implementation of the CertPathValidatorSpi and the logic for X.509 Proxy Path Validation. * * @version ${version} * @since 1.0 */ public class X509ProxyCertPathValidator extends CertPathValidatorSpi { public static final String BASIC_CONSTRAINT_OID = "2.5.29.19"; public static final String KEY_USAGE_OID = "2.5.29.15"; protected KeyStore keyStore; protected CertStore certStore; protected SigningPolicyStore policyStore; private X509Certificate identityCert; private boolean limited; private boolean rejectLimitedProxy; private Map<String, ProxyPolicyHandler> policyHandlers; /** * Validates the specified certification path using the specified algorithm parameter set. * <p/> * The <code>CertPath</code> specified must be of a type that is supported by the validation algorithm, otherwise * an <code>InvalidAlgorithmParameterException</code> will be thrown. For example, a <code>CertPathValidator</code> * that implements the PKIX algorithm validates <code>CertPath</code> objects of type X.509. * * @param certPath the <code>CertPath</code> to be validated * @param params the algorithm parameters * @return the result of the validation algorithm * @throws java.security.cert.CertPathValidatorException * if the <code>CertPath</code> does not validate * @throws java.security.InvalidAlgorithmParameterException * if the specified parameters or the type of the * specified <code>CertPath</code> are inappropriate for this <code>CertPathValidator</code> */ @SuppressWarnings("unchecked") public CertPathValidatorResult engineValidate(CertPath certPath, CertPathParameters params) throws CertPathValidatorException, InvalidAlgorithmParameterException { if (certPath == null) { throw new IllegalArgumentException("Certificate path cannot be null"); } List list = certPath.getCertificates(); if (list.size() < 1) { throw new IllegalArgumentException("Certificate path cannot be empty"); } parseParameters(params); // find the root trust anchor. Validate signatures and see if the // chain ends in one of the trust root certificates CertPath trustedCertPath = TrustedCertPathFinder.findTrustedCertPath(this.keyStore, certPath); // rest of the validation return validate(trustedCertPath); } /** * Dispose of the current validation state. */ public void clear() { this.identityCert = null; this.limited = false; } protected void parseParameters(CertPathParameters params) throws InvalidAlgorithmParameterException { if (!(params instanceof X509ProxyCertPathParameters)) { throw new IllegalArgumentException( "Parameter of type " + X509ProxyCertPathParameters.class.getName() + " required"); } X509ProxyCertPathParameters parameters = (X509ProxyCertPathParameters) params; this.keyStore = parameters.getTrustStore(); this.certStore = parameters.getCrlStore(); this.policyStore = parameters.getSigningPolicyStore(); this.rejectLimitedProxy = parameters.isRejectLimitedProxy(); this.policyHandlers = parameters.getPolicyHandlers(); } /** * Validates the certificate path and does the following for each certificate in the chain: method * checkCertificate() In addition: a) Validates if the issuer type of each certificate is correct b) CA path * constraints c) Proxy path constraints * <p/> * If it is of type proxy, check following: a) proxy constraints b) restricted proxy else if certificate, check the * following: a) keyisage * * @param certPath The CertPath to validate. * @return The results of the validation. * @throws CertPathValidatorException If the CertPath is invalid. */ protected CertPathValidatorResult validate(CertPath certPath) throws CertPathValidatorException { List<? extends Certificate> certificates = certPath.getCertificates(); if (certificates.size() == 0) { return null; } X509Certificate cert; TBSCertificateStructure tbsCert; Constants.CertificateType certType; X509Certificate issuerCert; TBSCertificateStructure issuerTbsCert; Constants.CertificateType issuerCertType; int proxyDepth = 0; cert = (X509Certificate) certificates.get(0); tbsCert = getTBSCertificateStructure(cert); certType = getCertificateType(tbsCert); // validate the first certificate in chain checkCertificate(cert, certType); boolean isProxy = ProxyCertificateUtil.isProxy(certType); if (isProxy) { proxyDepth++; } for (int i = 1; i < certificates.size(); i++) { boolean certIsProxy = ProxyCertificateUtil.isProxy(certType); issuerCert = (X509Certificate) certificates.get(i); issuerTbsCert = getTBSCertificateStructure(issuerCert); issuerCertType = getCertificateType(issuerTbsCert); proxyDepth = validateCert(cert, certType, issuerCert, issuerTbsCert, issuerCertType, proxyDepth, i, certIsProxy); if (certIsProxy) { checkProxyConstraints(certPath, cert, tbsCert, certType, issuerTbsCert, i); } else { try { checkKeyUsage(issuerTbsCert); } catch (IOException e) { throw new CertPathValidatorException("Key usage check failed on " + issuerCert.getSubjectDN(), e); } } checkCertificate(issuerCert, issuerCertType); cert = issuerCert; certType = issuerCertType; tbsCert = issuerTbsCert; } return new X509ProxyCertPathValidatorResult(this.identityCert, this.limited); } private Constants.CertificateType getCertificateType(TBSCertificateStructure issuerTbsCert) throws CertPathValidatorException { Constants.CertificateType issuerCertType; try { issuerCertType = CertificateUtil.getCertificateType(issuerTbsCert); } catch (CertificateException e) { throw new CertPathValidatorException("Error obtaining certificate type", e); } catch (IOException e) { throw new CertPathValidatorException("Error obtaining certificate type", e); } return issuerCertType; } private TBSCertificateStructure getTBSCertificateStructure(X509Certificate issuerCert) throws CertPathValidatorException { TBSCertificateStructure issuerTbsCert; try { issuerTbsCert = CertificateUtil.getTBSCertificateStructure(issuerCert); } catch (CertificateException e) { throw new CertPathValidatorException("Error converting certificate", e); } catch (IOException e) { throw new CertPathValidatorException("Error converting certificate", e); } return issuerTbsCert; } private int validateCert(X509Certificate cert, Constants.CertificateType certType, X509Certificate issuerCert, TBSCertificateStructure issuerTbsCert, Constants.CertificateType issuerCertType, int proxyDepth, int i, boolean certIsProxy) throws CertPathValidatorException { if (issuerCertType == Constants.CertificateType.CA) { validateCACert(cert, issuerCert, issuerTbsCert, proxyDepth, i, certIsProxy); } else if (ProxyCertificateUtil.isGsi3Proxy(issuerCertType) || ProxyCertificateUtil.isGsi4Proxy(issuerCertType)) { return validateGsiProxyCert(cert, certType, issuerCert, issuerTbsCert, issuerCertType, proxyDepth); } else if (ProxyCertificateUtil.isGsi2Proxy(issuerCertType)) { return validateGsi2ProxyCert(cert, certType, issuerCert, proxyDepth); } else if (issuerCertType == Constants.CertificateType.EEC) { validateEECCert(cert, certType, issuerCert); } else { // this should never happen? throw new CertPathValidatorException( "UNknown issuer type " + issuerCertType + " for certificate " + issuerCert.getSubjectDN()); } return proxyDepth; } private void checkProxyConstraints(CertPath certPath, X509Certificate cert, TBSCertificateStructure tbsCert, Constants.CertificateType certType, TBSCertificateStructure issuerTbsCert, int i) throws CertPathValidatorException { // check all the proxy & issuer constraints if (ProxyCertificateUtil.isGsi3Proxy(certType) || ProxyCertificateUtil.isGsi4Proxy(certType)) { try { checkProxyConstraints(tbsCert, issuerTbsCert, cert); } catch (IOException e) { throw new CertPathValidatorException("Proxy constraint check failed on " + cert.getSubjectDN(), e); } if ((certType == Constants.CertificateType.GSI_3_RESTRICTED_PROXY) || (certType == Constants.CertificateType.GSI_4_RESTRICTED_PROXY)) { try { checkRestrictedProxy(tbsCert, certPath, i); } catch (IOException e) { throw new CertPathValidatorException("Restricted proxy check failed on " + cert.getSubjectDN(), e); } } } } private void validateEECCert(X509Certificate cert, Constants.CertificateType certType, X509Certificate issuerCert) throws CertPathValidatorException { if (!ProxyCertificateUtil.isProxy(certType)) { throw new CertPathValidatorException("EEC can only sign another proxy certificate. Violated by " + issuerCert.getSubjectDN() + " issuing " + cert.getSubjectDN()); } } private int validateGsi2ProxyCert(X509Certificate cert, Constants.CertificateType certType, X509Certificate issuerCert, int proxyDepth) throws CertPathValidatorException { // PC can sign EEC or another PC only if (!ProxyCertificateUtil.isGsi2Proxy(certType)) { throw new CertPathValidatorException( "Proxy certificate can only sign another proxy certificate of same type. Violated by " + issuerCert.getSubjectDN() + " issuing " + cert.getSubjectDN()); } return proxyDepth + 1; } private int validateGsiProxyCert(X509Certificate cert, Constants.CertificateType certType, X509Certificate issuerCert, TBSCertificateStructure issuerTbsCert, Constants.CertificateType issuerCertType, int proxyDepth) throws CertPathValidatorException { if (ProxyCertificateUtil.isGsi3Proxy(issuerCertType)) { if (!ProxyCertificateUtil.isGsi3Proxy(certType)) { throw new CertPathValidatorException( "Proxy certificate can only sign another proxy certificate of same type. Violated by " + issuerCert.getSubjectDN() + " issuing " + cert.getSubjectDN()); } } else if (ProxyCertificateUtil.isGsi4Proxy(issuerCertType) && !ProxyCertificateUtil.isGsi4Proxy(certType)) { throw new CertPathValidatorException( "Proxy certificate can only sign another proxy certificate of same type. Violated by " + issuerCert.getSubjectDN() + " issuing " + cert.getSubjectDN()); } int pathLen; try { pathLen = ProxyCertificateUtil.getProxyPathConstraint(issuerTbsCert); } catch (IOException e) { throw new CertPathValidatorException("Error obtaining proxy path constraint", e); } if (pathLen == 0) { throw new CertPathValidatorException( "Proxy path length constraint violated of certificate " + issuerCert.getSubjectDN()); } if (pathLen < Integer.MAX_VALUE && proxyDepth > pathLen) { throw new CertPathValidatorException( "Proxy path length constraint violated of certificate " + issuerCert.getSubjectDN()); } return proxyDepth + 1; } private void validateCACert(X509Certificate cert, X509Certificate issuerCert, TBSCertificateStructure issuerTbsCert, int proxyDepth, int i, boolean certIsProxy) throws CertPathValidatorException { // PC can only be signed by EEC or PC if (certIsProxy) { throw new CertPathValidatorException("Proxy certificate can be signed only by EEC or Proxy " + "Certificate. Certificate " + cert.getSubjectDN() + " violates this."); } try { int pathLen = CertificateUtil.getCAPathConstraint(issuerTbsCert); if (pathLen < Integer.MAX_VALUE && (i - proxyDepth - 1) > pathLen) { throw new CertPathValidatorException( "Path length constraint of certificate " + issuerCert.getSubjectDN() + " violated"); } } catch (IOException e) { throw new CertPathValidatorException("Error obtaining CA Path constraint", e); } } // private X509Certificate checkCertificate(List<X509Certificate> trustedCertPath, X509Certificate x509Certificate, // Certificate issuerCertificate) throws CertPathValidatorException { // X509Certificate x509IssuerCertificate = (X509Certificate) issuerCertificate; // // // check that the next one is indeed issuer // Principal issuerDN = x509Certificate.getIssuerDN(); // Principal issuerCertDN = x509IssuerCertificate.getSubjectDN(); // if (!(issuerDN.equals(issuerCertDN))) { // throw new IllegalArgumentException("Incorrect certificate path, certificate in chain can only " // + "be issuer of previous certificate"); // } // // // validate integrity of signature // PublicKey publicKey = x509IssuerCertificate.getPublicKey(); // try { // x509Certificate.verify(publicKey); // } catch (CertificateException e) { // throw new CertPathValidatorException( // "Signature validation on the certificate " + x509Certificate.getSubjectDN(), e); // } catch (NoSuchAlgorithmException e) { // throw new CertPathValidatorException( // "Signature validation on the certificate " + x509Certificate.getSubjectDN(), e); // } catch (InvalidKeyException e) { // throw new CertPathValidatorException( // "Signature validation on the certificate " + x509Certificate.getSubjectDN(), e); // } catch (NoSuchProviderException e) { // throw new CertPathValidatorException( // "Signature validation on the certificate " + x509Certificate.getSubjectDN(), e); // } catch (SignatureException e) { // throw new CertPathValidatorException( // "Signature validation on the certificate " + x509Certificate.getSubjectDN(), e); // } // // trustedCertPath.add(x509Certificate); // return x509IssuerCertificate; // } protected void checkRestrictedProxy(TBSCertificateStructure proxy, CertPath certPath, int index) throws CertPathValidatorException, IOException { ProxyCertInfo info = ProxyCertificateUtil.getProxyCertInfo(proxy); ProxyPolicy policy = info.getProxyPolicy(); String pl = policy.getPolicyLanguage().getId(); ProxyPolicyHandler handler = null; if (this.policyHandlers != null) { handler = this.policyHandlers.get(pl); } if (handler == null) { throw new CertPathValidatorException("Unknown policy, no handler registered to validate policy " + pl); } handler.validate(info, certPath, index); } protected void checkKeyUsage(TBSCertificateStructure issuer) throws CertPathValidatorException, IOException { boolean[] issuerKeyUsage = CertificateUtil.getKeyUsage(issuer); if (issuerKeyUsage != null && issuerKeyUsage.length > 0 && !issuerKeyUsage[CertificateUtil.KEY_CERTSIGN]) { throw new CertPathValidatorException( "Certificate " + issuer.getSubject() + " violated key usage policy."); } } protected List<CertificateChecker> getCertificateCheckers() { List<CertificateChecker> checkers = new ArrayList<CertificateChecker>(); checkers.add(new DateValidityChecker()); checkers.add(new UnsupportedCriticalExtensionChecker()); checkers.add(new IdentityChecker(this)); checkers.add(new CRLChecker(this.certStore, this.keyStore, true)); checkers.add(new SigningPolicyChecker(this.policyStore)); return checkers; } /* * Method to check following for any given certificate * * a) Date validity, is it valid for the curent time (see DateValidityChecker) * b) Any unsupported critical extensions (see UnsupportedCriticalExtensionChecker) * c) Identity of certificate (see IdentityChecker) * d) Revocation (see CRLChecker) * e) Signing policy (see SigningPolicyChecker) * */ private void checkCertificate(X509Certificate cert, Constants.CertificateType certType) throws CertPathValidatorException { for (CertificateChecker checker : getCertificateCheckers()) { checker.invoke(cert, certType); } } @SuppressWarnings("unused") protected void checkProxyConstraints(TBSCertificateStructure proxy, TBSCertificateStructure issuer, X509Certificate checkedProxy) throws CertPathValidatorException, IOException { X509Extensions extensions; DERObjectIdentifier oid; X509Extension proxyExtension; X509Extension proxyKeyUsage = null; extensions = proxy.getExtensions(); if (extensions != null) { Enumeration e = extensions.oids(); while (e.hasMoreElements()) { oid = (DERObjectIdentifier) e.nextElement(); proxyExtension = extensions.getExtension(oid); if (oid.equals(X509Extensions.SubjectAlternativeName) || oid.equals(X509Extensions.IssuerAlternativeName)) { // No Alt name extensions - 3.2 & 3.5 throw new CertPathValidatorException("Proxy violation: no Subject or Issuer Alternative Name"); } else if (oid.equals(X509Extensions.BasicConstraints)) { // Basic Constraint must not be true - 3.8 BasicConstraints basicExt = CertificateUtil.getBasicConstraints(proxyExtension); if (basicExt.isCA()) { throw new CertPathValidatorException("Proxy violation: Basic Constraint CA is set to true"); } } else if (oid.equals(X509Extensions.KeyUsage)) { proxyKeyUsage = proxyExtension; checkKeyUsage(issuer, proxyExtension); } } } extensions = issuer.getExtensions(); if (extensions != null) { Enumeration e = extensions.oids(); while (e.hasMoreElements()) { oid = (DERObjectIdentifier) e.nextElement(); proxyExtension = extensions.getExtension(oid); checkExtension(oid, proxyExtension, proxyKeyUsage); } } } private void checkKeyUsage(TBSCertificateStructure issuer, X509Extension proxyExtension) throws IOException, CertPathValidatorException { boolean[] keyUsage = CertificateUtil.getKeyUsage(proxyExtension); // these must not be asserted if (keyUsage[CertificateUtil.NON_REPUDIATION] || keyUsage[CertificateUtil.KEY_CERTSIGN]) { throw new CertPathValidatorException("Proxy violation: Key usage is asserted."); } boolean[] issuerKeyUsage = CertificateUtil.getKeyUsage(issuer); if (issuerKeyUsage.length > 0) { for (int i = 0; i < CertificateUtil.DEFAULT_USAGE_LENGTH; i++) { if (i == CertificateUtil.NON_REPUDIATION || i == CertificateUtil.KEY_CERTSIGN) { continue; } if (!issuerKeyUsage[i] && keyUsage[i]) { throw new CertPathValidatorException("Proxy violation: Issuer key usage is incorrect"); } } } } private void checkExtension(DERObjectIdentifier oid, X509Extension proxyExtension, X509Extension proxyKeyUsage) throws CertPathValidatorException { if (oid.equals(X509Extensions.KeyUsage)) { // If issuer has it then proxy must have it also if (proxyKeyUsage == null) { throw new CertPathValidatorException("Proxy violation: Issuer has key usage, but proxy does not"); } // If issuer has it as critical so does the proxy if (proxyExtension.isCritical() && !proxyKeyUsage.isCritical()) { throw new CertPathValidatorException( "Proxy voilation: issuer key usage is critical, but proxy certificate's is not"); } } } public X509Certificate getIdentityCertificate() { return this.identityCert; } public void setLimited(boolean limited) { this.limited = limited; } public void setIdentityCert(X509Certificate identityCert) { this.identityCert = identityCert; } public boolean isRejectLimitedProxy() { return this.rejectLimitedProxy; } }