
org.glite.security.trustmanager.OpensslCertPathValidator Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) Members of the EGEE Collaboration. 2004. See
* http://www.eu-egee.org/partners/ for details on the copyright holders.
*
* 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.glite.security.trustmanager;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.glite.security.util.*;
import org.glite.security.util.CertificateRevokedException;
import org.glite.security.util.namespace.DNCheckerImpl;
import org.glite.security.util.proxy.ProxyCertInfoExtension;
import org.glite.security.util.proxy.ProxyCertificateInfo;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.*;
import java.text.ParseException;
import java.util.Set;
import java.util.Vector;
/**
* OpenSSLCertPathValidator validates certificate paths. A certificate path is an array of certificates where the first
* certificate is signed by the public key of the second, the second certificate is signed by the public key of the
* third and so on. The certificate path might contain a certificate authority (CA) certificate as the last element or
* it may not. If the path ends in CA certificate, the CA certificate is ignored. To validate the last non-CA
* certificate the trust anchors given in the constructor are searched and if a CA that issued the certificate is found
* the non-CA certificate is checked against the CA certificate. The last non-CA certificate is checked against the
* optional certificate revocation lists (CRL) given in the setCRLs method. If all the certificates in the array are
* valid and there is a CA that signed the last non-CA certificate, the path is valid. The certificates have to be
* arranged in correct order. The have to be ordered from index 0 being the actual end certificate, 0 or more
* intermediate certificates. The last item in the array can be the end certificate if it is signed by a CA, an
* intermediate certificate that is signed by a CA or a CA certificate, which is ignored and the previous certificate is
* used as the last of the array. Notice: a certificate path consisting of only a CA certificate is considered invalid
* certificate path. The certificates are also checked for:
*
* - Date (the cert has to be valid for the time of check)
* - The certificate revocation list (CRL)
* - Namespace restrictions to check whether it is inside the accepted namespace of the CA
*
*
* @author Joni Hahkala Created on Mar 6, 2008, 6:23 PM
*/
public class OpensslCertPathValidator {
/** The logging facility. */
private static final Logger LOGGER = Logger.getLogger(OpensslCertPathValidator.class.getName());
/** The underlying trust storage used for the certificate checking. */
private TrustStorage m_storage = null;
/** The underlying certificate factory used for reading certificates. */
private CertificateFactory m_certFact;
/** The flag to show that the CRLs are required for the CAs to be valid and used for cert path checking. */
private boolean m_crlRequired = true;
/** Add the bouncy castle provider unless it's already set */
static {
if (Security.getProvider("BC") == null) {
LOGGER.debug("ContextWrapper: bouncycastle provider set.");
Security.addProvider(new BouncyCastleProvider());
}
}
/**
* Creates a new instance of MyCertPathValidator
*
* @param trustPath A vector or TrustAnchors (Certificate Authority certificates with additional info and wrapping)
* that are considered trusted.
* @param crlRequired true if CRLs are required for each CA for them to be used in the certificate path checking.
* @throws CertificateException in case CA certificate loading fails.
* @throws NoSuchProviderException in case bouncycastle provider is not found.
* @throws IOException in case CA, CRL or namespace file reading fails.
* @throws ParseException in case the reading of namespace files fails.
* @deprecated use OpensslCertPathValidator(String trustPath, boolean crlRequired, CaseInsensitiveProperties props) instead.
*/
public OpensslCertPathValidator(String trustPath, boolean crlRequired) throws CertificateException,
NoSuchProviderException, IOException, ParseException {
m_certFact = CertificateFactory.getInstance("X.509", "BC");
this.m_storage = new TrustStorage(trustPath, null);
this.m_crlRequired = crlRequired;
}
/**
* Creates a new instance of MyCertPathValidator
*
* @param trustPath A vector or TrustAnchors (Certificate Authority certificates with additional info and wrapping)
* that are considered trusted.
* @param crlRequired true if CRLs are required for each CA for them to be used in the certificate path checking.
* @param props properties to pass along for child classes to use.
*
* @throws CertificateException in case CA certificate loading fails.
* @throws NoSuchProviderException in case bouncycastle provider is not found.
* @throws IOException in case CA, CRL or namespace file reading fails.
* @throws ParseException in case the reading of namespace files fails.
*/
public OpensslCertPathValidator(String trustPath, boolean crlRequired, CaseInsensitiveProperties props) throws CertificateException,
NoSuchProviderException, IOException, ParseException {
m_certFact = CertificateFactory.getInstance("X.509", "BC");
this.m_storage = new TrustStorage(trustPath, props);
this.m_crlRequired = crlRequired;
}
/**
* Searches for a parent CA from trustAnchors and add the cert to the cert chain.
*
* @param inpath The input path.
* @return the constructed path.
*/
public boolean findAddParent(Vector inpath) {
X509Certificate firstCert = inpath.lastElement();
// If the cert is self signed, the path can't be extended.
if (DNHandler.getSubject(firstCert).equals(DNHandler.getIssuer(firstCert))) {
return false;
}
String hash = OpensslTrustmanager.getOpenSSLCAHash((X509Name) firstCert.getIssuerDN());
FullTrustAnchor acceptedAnchor = null;
// Get all the anchors that have the hash.
FullTrustAnchor[] anchors = m_storage.getAnchors(hash);
if (anchors != null) {
LOGGER.debug("found " + anchors.length + " CAs that match, cheking which to use");
// Check it any of them match.
for (int n = 0; n < anchors.length; n++) {
if (DNHandler.getSubject(anchors[n].m_caCert).equals(DNHandler.getIssuer(firstCert))) {
acceptedAnchor = anchors[n];
// Remove the CA from the chain.
inpath.add(acceptedAnchor.m_caCert);
return true;
}
}
}
return false;
}
/**
* Constructs the CA path of the given cert chain. If the chain starts with a CA cert, tries to replace it with one
* from trustAnchors. If not starting with CA or if the starting CA is not in trustStore, searches for the CA that
* signed the first cert. If trustanchor is found, tries to build upwards to parent CAs and returns the constructed
* chain.
*
* @param inpath The certificate chain to start with.
* @return The constructed certificate chain using as many CA certs as possible (longest cert chain possible).
* @throws CertPathValidatorException Thrown in case the certificate chain building fails, like if there is no valid
* trustanchor for the chain.
* @throws CertificateException In case certificate handling fails, in case of corrupted certs etc.
*/
public Vector buildPath(X509Certificate[] inpath) throws CertPathValidatorException,
CertificateException {
// convert certs to BC certs if necessary.
Vector pathVect = new Vector();
for (int i = 0; i < inpath.length; i++) {
if (inpath[i] instanceof org.bouncycastle.jce.provider.X509CertificateObject) {
pathVect.add(inpath[i]);
} else {
byte[] bytes = inpath[i].getEncoded();
BufferedInputStream certIS = new BufferedInputStream(new ByteArrayInputStream(bytes));
pathVect.add((X509Certificate) m_certFact.generateCertificate(certIS));
}
}
// CA candidate
X509Certificate caCert = pathVect.lastElement();
boolean found = false;
// if the last cert is a CA, check if it is in the trustAnchors
if (caCert.getBasicConstraints() > -1) {
// Check whether the chain ends with the CA cert or not.
String hash = OpensslTrustmanager.getOpenSSLCAHash((X509Name) caCert.getSubjectDN());
FullTrustAnchor acceptedAnchor = null;
// Get all the anchors that have the hash.
FullTrustAnchor[] anchors = m_storage.getAnchors(hash);
if (anchors != null) {
LOGGER.debug("found " + anchors.length + " CAs that match, cheking which to use");
// Check it any of them match.
for (int n = 0; n < anchors.length; n++) {
if (anchors[n].m_caCert.getPublicKey().equals(caCert.getPublicKey())
&& DNHandler.getSubject(anchors[n].m_caCert).equals(DNHandler.getSubject(caCert))) {
acceptedAnchor = anchors[n];
// Remove the CA from the chain.
found = true;
pathVect.remove(caCert);
pathVect.add(acceptedAnchor.m_caCert);
break;
}
}
}
// Fail if self signed CA cert that is not found.
if (!found && DNHandler.getSubject(caCert).equals(DNHandler.getIssuer(caCert))) {
LOGGER.info("Self-signed CA cert " + DNHandler.getSubject(caCert)
+ " is not trusted, rejecting the certificate chain.");
throw new CertPathValidatorException("Self-signed CA cert " + DNHandler.getSubject(caCert)
+ " is not trusted, rejecting the certificate chain.");
}
}
// If the CA cert in the chain was not found or there was none, search issuer from the trustanchors or fail.
if (!found) {
found = findAddParent(pathVect);
// no trustanchors were found, fail.
if (!found) {
LOGGER.info("The root of the cert chain " + DNHandler.getSubject(caCert)
+ " is not trusted CA nor issued by one, rejecting the certificate chain.");
throw new CertPathValidatorException("The root of the cert chain " + DNHandler.getSubject(caCert)
+ " is not trusted CA nor issued by one, rejecting the certificate chain.");
}
}
// valid anchor was found, try to construct further by finding parent CAs in the trustAnchors.
do {
found = findAddParent(pathVect);
} while (found);
return pathVect;
}
/**
* Checks that a certificate path is valid. Look above the class description for better explanation.
*
* @param inpath The certificate path to check
* @throws CertPathValidatorException Thrown if there was a problem linking two certificates
* @throws CertificateException thrown if there was a problem with a single certificate
*/
public void check(X509Certificate[] inpath) throws CertPathValidatorException, CertificateException {
if (inpath.length == 0) {
LOGGER.error("No certificates given to check");
throw new CertPathValidatorException("No certificates given to check");
}
if (LOGGER.isDebugEnabled()) {
for (int i = 0; i < inpath.length; i++) {
LOGGER.debug("input path cert type: " + inpath[i].getClass().getName() + " DN ["
+ inpath[i].getSubjectDN() + "]");
}
}
// Find trustanchor and any parent CAs, if not found, throw exception.
Vector pathVect = buildPath(inpath);
// now we have trusted CA starting chain.
LOGGER.debug("Given path len is " + inpath.length + " and constructed path lenght " + pathVect.size());
CertPathValidatorState state = new CertPathValidatorState();
state.m_proxyType = ProxyCertificateInfo.CA_CERT;
X509Certificate[] certs = pathVect.toArray(new X509Certificate[] {});
// check pairs, current is the parent, starting with the root of the chain.
int currentIndex = certs.length - 1;
checkValidity(certs[currentIndex]);
// Check the anchors
while (currentIndex > 0) {
X509Certificate current = certs[currentIndex];
X509Certificate next = certs[currentIndex - 1];
// if the current cert is CA, do CA-cert pair checks.
if (current.getBasicConstraints() > -1) {
try {
state = checkAnchorAndCert(next, current, state, (currentIndex == certs.length - 1));
} catch (CRLException e) {
LOGGER.info("Certificate for " + DNHandler.getSubject(next) + " revoked by "
+ DNHandler.getSubject(current) + ", rejecting it");
throw new CertPathValidatorException("Certificate for " + DNHandler.getSubject(next)
+ " revoked by " + DNHandler.getSubject(current) + ", rejecting it");
} catch (Exception e) {
LOGGER.info("Certificate checking for " + DNHandler.getSubject(next)
+ " failed, rejecting it. Error was: " + e.getMessage());
throw new CertPathValidatorException("Certificate checking for " + DNHandler.getSubject(next)
+ " failed, rejecting it. Error was: " + e.getMessage(), e);
}
} else {
// do just cert pair checks.
try {
state = checkCertificatePair(next, current, state);
} catch (CertPathValidatorException e) {
LOGGER.info(e.getMessage());
throw e;
} catch (CertificateException e) {
LOGGER.info(e.getMessage());
throw e;
}
}
currentIndex--;
}
LOGGER.info("certificate path for " + DNHandler.getSubject(inpath[0]) + " is valid");
}
/**
* Checks that the certificate is valid now and throws the corresponding exception in case it isn't.
*
* @param cert
* @throws CertificateExpiredException
* @throws CertificateNotYetValidException
*/
public void checkValidity(X509Certificate cert) throws CertificateExpiredException, CertificateNotYetValidException {
try {
cert.checkValidity();
} catch (CertificateExpiredException e) {
LOGGER.info("the Certificate for " + DNHandler.getSubject(cert) + " expired on " + cert.getNotAfter());
throw new CertificateExpiredException("the Certificate for " + DNHandler.getSubject(cert) + " expired on "
+ cert.getNotAfter());
} catch (CertificateNotYetValidException e) {
LOGGER.info("the Certificate for " + DNHandler.getSubject(cert) + " will only be valid after "
+ cert.getNotBefore());
throw new CertificateNotYetValidException("the Certificate for " + DNHandler.getSubject(cert)
+ " will only be valid after " + cert.getNotBefore());
}
}
/**
* Checks that the sub certificate is signed by the signer.
*
* @param sub The sub certificate, the certificate appearing before signer in the certificate path array.
* @param signer The signer certificate, the certificate appearing after the sub in the certificate path array.
* @throws CertPathValidatorException Thrown if the signature cheking fails
* @throws CertificateException Thrown if a problem occures when accessing either certificate.
*/
public void checkSignature(X509Certificate sub, X509Certificate signer) throws CertPathValidatorException,
CertificateException {
LOGGER.debug("Checking the signature");
// get the public key of the signer
PublicKey signKey = signer.getPublicKey();
LOGGER.debug("Sub cert is " + sub.getClass().getName());
// check that the sub cert was signed by the signer key
try {
sub.verify(signKey);
} catch (java.security.NoSuchAlgorithmException e) {
LOGGER.info("Invalid signature algorithm in \"" + sub.getSubjectDN().toString() + "\" error was "
+ e.getClass().getName() + ":" + e.getMessage());
throw new CertificateException("Invalid signature algorithm in \"" + sub.getSubjectDN().toString()
+ "\" error was " + e.getClass().getName() + ":" + e.getMessage());
} catch (java.security.InvalidKeyException e) {
LOGGER.info("Invalid public key in \"" + signer.getSubjectDN().toString() + "\" error was "
+ e.getClass().getName() + ":" + e.getMessage());
throw new CertificateException("Invalid public key in \"" + signer.getSubjectDN().toString()
+ "\" error was " + e.getClass().getName() + ":" + e.getMessage());
} catch (java.security.NoSuchProviderException e) {
LOGGER.error("Internal error, no crypto provider found. Error was " + e.getClass().getName() + ":"
+ e.getMessage());
throw new CertificateException("Internal error, no crypto provider found. Error was " + e.getMessage());
} catch (java.security.SignatureException e) {
LOGGER.info("invalid signature in " + sub.getSubjectDN().toString());
throw new CertPathValidatorException("invalid signature in " + sub.getSubjectDN().toString());
}
}
/**
* Checks that the sub certificate is signed and issued by signer
*
* @param sub the sub certificate
* @param signer the signer certificate. The certificate for the issuer of the sub certificate.
* @param state the state for this certificate pair checking from the previous round.
* @return state the state for the next certificate pair checking.
* @throws CertPathValidatorException Thrown if the signature in sub is invalid or the certificate is not issued by
* signer.
* @throws CertificateException Thrown if there is a problem accessing data from either of the certificates
*/
public CertPathValidatorState checkCertificatePair(X509Certificate sub, X509Certificate signer,
CertPathValidatorState state) throws CertPathValidatorException, CertificateException {
LOGGER.debug("Checking a cert pair");
// check that the sub is signed by the signer
checkSignature(sub, signer);
// check the validity of sub cert, the first signer is checked before the chain checking.
checkValidity(sub);
CertPathValidatorState newState = new CertPathValidatorState();
// check that the issuer DN of the sub and the subject DN of the signer match
DN subIssuer = DNHandler.getIssuer(sub);
DN signerSubject = DNHandler.getSubject(signer);
DN subSubject = DNHandler.getSubject(sub);
Set criticalOIDs = sub.getCriticalExtensionOIDs();
// check for empty subjects, not supported, in rfc3820 proxies they are nor allowed, reject also elsewhere for
// simplicity.
if (signerSubject.isEmpty() || subSubject.isEmpty()) {
throw new CertPathValidatorException("Subject DN of " + (signerSubject.isEmpty() ? "parent" : "sub")
+ " certificate is empty, invalid certificate.");
}
LOGGER.debug("Checking cert basic constraints extension and proxy type");
// process the basicConstraints and determine the possible proxy type.
int signerPathLen = signer.getBasicConstraints();
int subPathLen = sub.getBasicConstraints();
if (signerPathLen >= 0) { // signer has a CA flag and it is ok as it is either anchor or accepted sub ca from
// previous round. (case ca-ca or ca-end entity cert)
if (subPathLen >= 0) { // sub is claiming to be a subCA
if (state.m_basicConstraintsPathLimit >= 0) { // check that the ca certs path length limit isn't
// reached.
if (subPathLen < state.m_basicConstraintsPathLimit) {// check if the previous CAs limit the path
// shorter than this.
newState.m_basicConstraintsPathLimit = subPathLen - 1;
newState.m_basicConstraintsPathLimiter = subSubject;
} else {
newState.m_basicConstraintsPathLimit = state.m_basicConstraintsPathLimit - 1;
newState.m_basicConstraintsPathLimiter = state.m_basicConstraintsPathLimiter;
}
} else { // ca path limit was reached, this CA cert can't be accepted.
throw new CertPathValidatorException("Certificate " + subSubject
+ " has a CA flag, but path lenght is too long, it was limited by "
+ newState.m_basicConstraintsPathLimiter);
}
newState.m_proxyType = ProxyCertificateInfo.CA_CERT;
} else { // sub is not a CA cert, so it must be a usr/end entity cert.
newState.m_proxyType = ProxyCertificateInfo.USER_CERT;
}
} else { // if the issuer is not a CA, and the cert is not yet defined as a proxy, find out if it is a proxy.
// (case "end entity cert"-proxy or proxy-proxy)
assert (state.m_proxyType == ProxyCertificateInfo.CA_CERT); // this must not happen, find out if it does
if (subPathLen != -1) { // no cert after non-CA cert in path can claim to be a CA.
throw new CertPathValidatorException("A certificate " + subSubject + " after non-CA cert "
+ signerSubject + " has a CA flag, which is not allowed. Rejecting certificate path.");
}
if (criticalOIDs != null) {
if (criticalOIDs.contains(ProxyCertInfoExtension.PROXY_CERT_INFO_EXTENSION_OID)) {
// remove the oid to mark it recognized.
criticalOIDs.remove(ProxyCertInfoExtension.PROXY_CERT_INFO_EXTENSION_OID);
newState.m_proxyType = ProxyCertificateInfo.RFC3820_PROXY;
} else {
if (criticalOIDs.contains(ProxyCertInfoExtension.DRAFT_PROXY_CERT_INFO_EXTENSION_OID)) {
// remove the oid to mark it recognized.
criticalOIDs.remove(ProxyCertInfoExtension.DRAFT_PROXY_CERT_INFO_EXTENSION_OID);
newState.m_proxyType = ProxyCertificateInfo.DRAFT_RFC_PROXY;
} else { // no proxy extension found, but other critical extensions are present.
if (subSubject.getLastCNValue().toLowerCase().equals("proxy")
|| subSubject.getLastCNValue().toLowerCase().equals("limited proxy")) {
newState.m_proxyType = ProxyCertificateInfo.LEGACY_PROXY;
} else { // invalid cert as not a valid proxy and signed by nonCA cert.
throw new CertPathValidatorException(
"Unknown proxy type, no draft or RFC3820 extensions found and subject doesn't follow legacy proxy convention.");
}
}
}
} else { // no proxy extension or other critical extensions found.
if (subSubject.getLastCNValue().toLowerCase().equals("proxy")
|| subSubject.getLastCNValue().toLowerCase().equals("limited proxy")) {
newState.m_proxyType = ProxyCertificateInfo.LEGACY_PROXY;
} else { // invalid cert as not a valid proxy and signed by nonCA cert.
throw new CertPathValidatorException(
"Unknown proxy type, no draft or RFC3820 extensions found and subject doesn't follow legacy proxy convention.");
}
}
}
LOGGER.debug("Checking cert transitions.");
// double check the transitions
if (state.m_proxyType == ProxyCertificateInfo.CA_CERT) { // ca cert to ca ot usercert is ok
if (newState.m_proxyType != ProxyCertificateInfo.CA_CERT
&& newState.m_proxyType != ProxyCertificateInfo.USER_CERT) {
throw new CertPathValidatorException("The CA cert " + signerSubject
+ " can only sign sub CAs or user certs. The cert " + subSubject + " is neither.");
}
} else { // user cert to proxy is ok
if (state.m_proxyType == ProxyCertificateInfo.USER_CERT) {
if (newState.m_proxyType != ProxyCertificateInfo.LEGACY_PROXY
&& newState.m_proxyType != ProxyCertificateInfo.DRAFT_RFC_PROXY
&& newState.m_proxyType != ProxyCertificateInfo.RFC3820_PROXY) {
throw new CertPathValidatorException("The end entity cert " + signerSubject
+ " can only sign proxies. The cert " + subSubject + " wasn't recognized as a proxy.");
}
} else { // proxy cert to same type proxy is ok.
if (state.m_proxyType == ProxyCertificateInfo.LEGACY_PROXY
|| state.m_proxyType == ProxyCertificateInfo.DRAFT_RFC_PROXY
|| state.m_proxyType == ProxyCertificateInfo.RFC3820_PROXY) {
if (state.m_proxyType != newState.m_proxyType) {
throw new CertPathValidatorException("The proxy cert " + signerSubject
+ " and the sub proxy cert " + subSubject + " are of different type.");
}
} else {
throw new CertPathValidatorException("Unknown cert " + signerSubject + " and transition");
}
}
}
// the naming constraint is enabled or the first is the end entity (user) certificate, enforce it, also check
// key usage extension and proxy path limit
if (newState.m_proxyType == ProxyCertificateInfo.LEGACY_PROXY
|| newState.m_proxyType == ProxyCertificateInfo.DRAFT_RFC_PROXY
|| newState.m_proxyType == ProxyCertificateInfo.RFC3820_PROXY) {
LOGGER.debug("Checkin that " + DNHandler.getSubject(signer) + " matches end of "
+ DNHandler.getSubject(sub) + " because proxy constraints");
checkDNRestriction(sub, signer, state.m_proxyType);
if (newState.m_proxyType == ProxyCertificateInfo.RFC3820_PROXY
|| newState.m_proxyType == ProxyCertificateInfo.DRAFT_RFC_PROXY) {
if (state.m_proxyInfoPathLimit < 0) {
throw new CertPathValidatorException("The proxy certificate path of \"" + subSubject
+ "\" is longer than allowed by \"" + state.m_proxyInfoPathLimiter
+ "\" that set the proxy path length limit.");
}
ProxyCertificateInfo info = new ProxyCertificateInfo(sub);
int pathLimit;
try {
pathLimit = info.getProxyPathLimit();
} catch (IOException e) {
throw new CertificateException("Parsing of a proxy certificate \"" + subSubject
+ "\" failed with: " + e.getMessage());
}
if (pathLimit < state.m_proxyInfoPathLimit) {
newState.m_proxyInfoPathLimit = pathLimit - 1;
newState.m_proxyInfoPathLimiter = subSubject;
} else {
if(state.m_proxyInfoPathLimit != ProxyCertInfoExtension.UNLIMITED){
newState.m_proxyInfoPathLimit = state.m_proxyInfoPathLimit - 1;
} else { // do not substract the unlimited value
newState.m_proxyInfoPathLimit = state.m_proxyInfoPathLimit;
}
newState.m_proxyInfoPathLimiter = state.m_proxyInfoPathLimiter;
}
if(LOGGER.isDebugEnabled()){
LOGGER.debug("ProxyInfoPath limit is: " + newState.m_proxyInfoPathLimit);
}
}
// check that the mandatory digital signature bit is set in case the proxy cert has keyUsage extension.
boolean[] keyUsageBits = sub.getKeyUsage();
if (keyUsageBits != null && keyUsageBits[0] != true) {
throw new CertPathValidatorException("The proxy cert " + subSubject
+ " has keyUsage extension, but the digital signature bit is not set as required.");
}
}
// remove key usage extension from the unhandled list as it is recognized
if (criticalOIDs != null && !criticalOIDs.isEmpty()) {
criticalOIDs.remove("2.5.29.15");
}
// remove basic constraints extension from the unhandled list as it is recognized
if (criticalOIDs != null && !criticalOIDs.isEmpty()) {
criticalOIDs.remove("2.5.29.19");
}
if (criticalOIDs != null && !criticalOIDs.isEmpty()) {
throw new CertPathValidatorException("Certificate " + subSubject
+ " contains unsupported critical extensions, e.g. " + criticalOIDs.iterator().next());
}
LOGGER.debug("Checking DN match");
if (!subIssuer.equals(signerSubject)) {
throw new CertPathValidatorException("cert issuer DN (" + subIssuer + ") - Issuer subject DN ("
+ signerSubject + ") mismatch.");
}
// check namespace constraints next round only if the parent is a CA, proxies are restricted to the end entity
// DN anyway.
if (signer.getBasicConstraints() > -1) {
DNCheckerImpl checker = new DNCheckerImpl();
FullTrustAnchor[] parentAnchors = state.m_anchorStack.toArray(new FullTrustAnchor[] {});
// go through anchors from lowest CA towards the root, find first namespace and check the DN against that,
// if none found, no restrictions.
for (int i = parentAnchors.length - 1; i >= 0; i--) {
FullTrustAnchor anchor = parentAnchors[i];
if (anchor.m_namespace != null && !anchor.m_namespace.getPolices().isEmpty()) {
// check against the namespace, failure throws exception.
checker.check(subSubject, signerSubject, anchor.m_namespace.getPolices());
// success, namespace definition found and DN is allowed, no further checks necessary.
break;
}
}
newState.m_anchorStack = state.m_anchorStack;
}
return newState;
}
/**
* Does the same checks as checkCertificatePair and in addition checks that the sub is not listed in the possible
* CRL issued by the CA represented by the anchor.
*
* @param sub The sub certificate to check.
* @param caCert The ca cert to check.
* @param state The state from the possible previous steps.
* @param firstAnchor The flag for first anchor in the chain. The anchor must be found, otherwise checking fails.
* @return the state for the next certificate pair checking.
* @throws CertPathValidatorException Thrown if the sub certificate is not issued by anchor, is revoked or the
* signature in sub is invalid.
* @throws CertificateException Thrown if there is a problem accessing the data from the certificate or the trust
* anchor
* @throws CRLException In case the CRL parsing or usage fails.
*/
public CertPathValidatorState checkAnchorAndCert(X509Certificate sub, X509Certificate caCert,
CertPathValidatorState state, boolean firstAnchor) throws CertPathValidatorException, CertificateException,
CRLException {
LOGGER.debug("Checkin cert and anchor");
String hash = OpensslTrustmanager.getOpenSSLCAHash((X509Name) caCert.getSubjectDN());
FullTrustAnchor currentAnchor = null;
// Get all the anchors that have the hash.
FullTrustAnchor[] anchors = m_storage.getAnchors(hash);
if (anchors != null) {
LOGGER.debug("found " + anchors.length + " CAs that match, cheking which to use");
// Check it any of them match.
for (int n = 0; n < anchors.length; n++) {
if (anchors[n].m_caCert.getPublicKey().equals(caCert.getPublicKey())) {
currentAnchor = anchors[n];
}
}
}
// first anchor must be found, otherwise the chain can't be based on any trusted anchor.
if (currentAnchor == null && firstAnchor) {
throw new CertPathValidatorException("The CA certificate " + DNHandler.getSubject(caCert).getRFCDN()
+ " was not found. Certificate chain isn't based on any trusted CA.");
}
// beware, can be a null!
state.m_anchorStack.add(currentAnchor);
CertPathValidatorState newstate = checkCertificatePair(sub, caCert, state);
String caDN = DNHandler.getSubject(caCert).getRFCDN();
String subDN = DNHandler.getSubject(sub).getRFCDN();
// Check CRL ----------------
// if no anchor found, and crls are, fail.
if (currentAnchor == null) {
if (m_crlRequired) {
LOGGER.info("The certificate " + subDN + " is rejected as no CRL was found for CA " + caDN);
throw new CertPathValidatorException("The certificate " + subDN
+ " is rejected as no CRL was found for CA " + caDN);
}
} else {
// RevocationChecker found
if (currentAnchor.m_revChecker != null) {
try {
currentAnchor.m_revChecker.check(sub);
} catch (CertificateRevokedException e) {
LOGGER.info("The certificate " + subDN + " is revoked by " + caDN + ".");
throw new CertPathValidatorException("The certificate " + subDN + " revocation checking failed: " + e.getMessage());
}catch (Exception e) {
if (m_crlRequired) {
LOGGER.info("The certificate " + subDN + " revocation check failed! Problem was: " + e.getMessage());
throw new CertPathValidatorException("The certificate " + subDN + " revocation check failed! Problem was: " + e.getMessage());
} else {
LOGGER.debug("The certificate " + subDN + " revocation check failed because CRL problem, but CRL checking was not required. Problem was: " + e.getMessage());
}
}
} else {
// if no checkers found, but they are required, fail.
if (m_crlRequired) {
LOGGER.info("The certificate " + subDN + " is not trusted as the revocation checker creation hasn't succeeded for the CA " + caDN + ".");
throw new CertPathValidatorException("The certificate " + subDN + " is not trusted as the revocation cheker creation hasn't succeeded for the CA " + caDN + ".");
}
LOGGER.info("Revocation checker creation hasn't succeeded for CA " + caDN + ", but they're are not required, so accepting the cert " + subDN + ".");
}
}
if (currentAnchor != null && currentAnchor.m_caCert != null) {
// check that the mandatory keyCertSign bit is set in case the CA cert has keyUsage extension.
boolean[] keyUsageBits = currentAnchor.m_caCert.getKeyUsage();
if (keyUsageBits != null && keyUsageBits[5] != true) {
throw new CertPathValidatorException("The CA cert " + caDN
+ " has keyUsage extension, but the keyCertSign bit is not set as required.");
}
/*
* Basic constraint exists and is > -1 as it was checked during anchor loading
*/
int basicConstraints = currentAnchor.m_caCert.getBasicConstraints();
/* Prepare for next round, set the basic constraints */
if (basicConstraints < state.m_basicConstraintsPathLimit - 1) {
newstate.m_basicConstraintsPathLimit = basicConstraints - 1;
newstate.m_basicConstraintsPathLimiter = DNHandler.getSubject(currentAnchor.m_caCert);
} else {
newstate.m_basicConstraintsPathLimit = state.m_basicConstraintsPathLimit - 1;
newstate.m_basicConstraintsPathLimiter = state.m_basicConstraintsPathLimiter;
}
} else {
newstate.m_basicConstraintsPathLimit = state.m_basicConstraintsPathLimit;
newstate.m_basicConstraintsPathLimiter = state.m_basicConstraintsPathLimiter;
}
LOGGER.debug("Certificate for " + subDN + " is validly issued by CA " + caDN);
return newstate;
}
/**
* Returns an array of accepted CA certificates
*
* @return Returns the array of CA certificates
*/
public java.security.cert.X509Certificate[] getCACerts() {
FullTrustAnchor[] anchors = m_storage.getAnchors();
if (anchors == null) {
return null;
}
X509Certificate[] certs = new X509Certificate[anchors.length];
for (int n = 0; n < anchors.length; n++) {
certs[n] = anchors[n].m_caCert;
}
LOGGER.debug("getCACerts: returning " + certs.length + " ca certs");
return certs;
}
/**
* Checks that the subject DN starts with the DN parts of the signer.
*
* @param sub The certificate to check.
* @param signer The signer cert to take the DN from for the checking.
* @param proxyType type of this proxy type.
* @throws CertificateException Thrown in case there is problems in handling the certificates.
* @see ProxyCertificateInfo
*/
public void checkDNRestriction(X509Certificate sub, X509Certificate signer, int proxyType)
throws CertificateException {
LOGGER.debug("Checking dn restriction");
DN subDN = DNHandler.getSubject(sub);
DN signerDN = DNHandler.getSubject(signer);
try {
DN subDNWithoutProxy = subDN.withoutLastCN(false);
if (subDNWithoutProxy.equals(signerDN) != true) {
throw new CertificateException("The DN [" + subDN + "] doesn't end with [" + signerDN
+ "] as required for proxy certs");
}
if (proxyType == ProxyCertificateInfo.LEGACY_PROXY) {
String lastCN = subDN.getLastCNValue().toLowerCase();
if (!lastCN.matches("limited proxy|proxy")) {
throw new CertPathValidatorException("Legacy proxy " + subDN.getCanon()
+ " does not end with \"proxy\" or \"limited proxy\" as required.");
}
}
} catch (Exception e) {
LOGGER.info("Error while cheking naming constrainst between sub [" + subDN + "] and signer [" + signerDN
+ " error: " + e + e.getMessage());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("StacTrace: ", e);
}
if (e instanceof CertificateException) {
throw (CertificateException) e;
}
throw new CertificateException("Error while cheking naming constrainst between sub [" + subDN
+ "] and signer [" + signerDN + "] error: " + e + e.getMessage());
}
}
/**
* Checks whether any trust anchor information has been updated on disk and reloads them if they have.
*
* @throws IOException In case there is unrecoverable trust info reading failure during update.
* @throws CertificateException In case there is unrecoverable certificate parsing or handling problem during
* update.
* @throws ParseException In case there is an unrecoverable CRL or namespace parsing error during update.
*/
public void checkUpdate() throws IOException, CertificateException, ParseException {
if (m_storage != null) {
m_storage.checkUpdate();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy