/* * Copyright (c) 2011 ICM Uniwersytet Warszawski All rights reserved. * See LICENCE file for licensing information. */ package eu.emi.security.authn.x509.helpers.pkipath; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.cert.CertPath; import java.security.cert.CertPathValidatorException; import java.security.cert.CertStore; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.PKIXParameters; import java.security.cert.TrustAnchor; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.x500.AttributeTypeAndValue; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.i18n.ErrorBundle; import org.bouncycastle.jcajce.PKIXExtendedParameters; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.x509.CertPathReviewerException; import org.bouncycastle.x509.PKIXCertPathReviewer; import eu.emi.security.authn.x509.RevocationParameters; import eu.emi.security.authn.x509.ValidationError; import eu.emi.security.authn.x509.ValidationErrorCode; import eu.emi.security.authn.x509.ValidationResult; import eu.emi.security.authn.x509.helpers.CertificateHelpers; import eu.emi.security.authn.x509.helpers.JavaAndBCStyle; import eu.emi.security.authn.x509.helpers.ObserversHandler; import eu.emi.security.authn.x509.helpers.pkipath.bc.FixedBCPKIXCertPathReviewer; import eu.emi.security.authn.x509.helpers.proxy.ExtendedProxyType; import eu.emi.security.authn.x509.helpers.proxy.ProxyHelper; import eu.emi.security.authn.x509.impl.CertificateUtils; import eu.emi.security.authn.x509.impl.FormatMode; import eu.emi.security.authn.x509.impl.X500NameUtils; import eu.emi.security.authn.x509.proxy.ProxyUtils; /** * Low-level certificate validator based on the BC {@link PKIXCertPathReviewer} * with additional support for proxy certificates. * @author K. Benedyczak */ public class BCCertPathValidator { public static final long PROXY_VALIDATION_GRACE_PERIOD = 5*60000; /** * Performs validation. Expects correctly set up parameters. * <p> * If the proxy support is turned off or the chain has no proxy certificate then * normal X.509 path validation is performed (see below). * <p> * If the proxy support is turned on and the chain has at least one proxy then the * following checks are performed: * <ul> * <li> The chain is split into two chains A and B, where B ends with the * first element of A and it is the first not proxy certificate in the original chain * (i.e. the EEC which is the split point). * <li> The chain A is validated using normal X.509 path validation. * <li> The chain B is also validated with the X.509 path validation * but PROXY extension OIDs are recognized, the only trust anchor is the EEC, the * CRLs are ignored, the CA constraint is not required on any issuing certificate * and the certificate sign bit is also not required. * <li> The chain B is iterated over and on each pair additional checks from the * RFC 3820 are verified, along with the proxy path limit. * </ul> * <p> * The normal path validation is performed as follows: * <ul> * <li> First all basically correct (i.e. fulfilling name chaining rules) * certificate paths are tried to be constructed from the input chain. This step * produces from zero to many paths (in 99%: 0 or 1). * Those paths can differ from the input e.g. by having self-signed intermediary * CA certificate removed. * <li> If there were no path constructed, the input chain is used as-is, as the only * possible path. At this step we already know it is invalid, but we anyway continue to * establish complete and detailed list of errors. * <li> All constructed paths are validated using PKIX rules, and errors found are * recorded. If at least one path validates successfully the algorithm ends. * <li> If all paths were invalid, the one with the least number of errors is selected * and those errors are reported as the validation result. * </ul> * * @param toCheck chain to check * @param proxySupport proxy support * @param trustAnchors trust anchors * @param crlStore crl store * @param revocationParams revocation params * @param observersHandler observers handler * @return validation result * @throws CertificateException if some of the certificates in the chain can not * be parsed */ public ValidationResult validate(X509Certificate[] toCheck, boolean proxySupport, Set<TrustAnchor> trustAnchors, CertStore crlStore, RevocationParameters revocationParams, ObserversHandler observersHandler) throws CertificateException { if (toCheck == null || toCheck.length == 0) throw new IllegalArgumentException("Chain to be validated must be non-empty"); List<ValidationError> errors = new ArrayList<ValidationError>(); Set<String> unresolvedExtensions = new HashSet<String>(); if (trustAnchors.isEmpty()) { //Empty trust anchors set is fine for ExtPKIXParameters but not for the plain PKIXParamters. //As we can possibly use them when checking proxy chains //make a proper error and return it, instead of ugly exception. errors.add(new ValidationError(toCheck, -1, ValidationErrorCode.noTrustAnchorFound)); errors.add(new ValidationError(toCheck, 0, ValidationErrorCode.noIssuerPublicKey)); return new ValidationResult(false, errors, unresolvedExtensions, null); } if (!proxySupport || !ProxyUtils.isProxy(toCheck)) { ExtPKIXParameters2 params = createPKIXParameters(toCheck, proxySupport, trustAnchors, crlStore, revocationParams, observersHandler); List<X509Certificate> chain = checkNonProxyChain(toCheck, params, errors, unresolvedExtensions, 0, toCheck); return new ValidationResult(errors.size() == 0, errors, unresolvedExtensions, chain); } //now we know that we have proxies in the chain and proxy support is turned on int split = getFirstProxy(toCheck); if (split == toCheck.length-1) { errors.add(new ValidationError(toCheck, -1, ValidationErrorCode.proxyNoIssuer)); return new ValidationResult(false, errors, unresolvedExtensions, null); } X509Certificate[] baseChain = new X509Certificate[toCheck.length-split-1]; X509Certificate[] proxyChain = new X509Certificate[split+2]; for (int i=split+1; i<toCheck.length; i++) baseChain[i-split-1] = toCheck[i]; for (int i=0; i<split+2; i++) proxyChain[i] = toCheck[i]; ExtPKIXParameters2 params = createPKIXParameters(baseChain, proxySupport, trustAnchors, crlStore, revocationParams, observersHandler); List<X509Certificate> validatedChain = checkNonProxyChain(baseChain, params, errors, unresolvedExtensions, split+1, toCheck); Set<TrustAnchor> trustForProxyChain; if (baseChain.length > 1) trustForProxyChain = Collections.singleton(new TrustAnchor(baseChain[1], null)); else trustForProxyChain = trustAnchors; checkProxyChainWithBC(proxyChain, trustForProxyChain, errors, unresolvedExtensions); checkProxyChainMain(proxyChain, errors, unresolvedExtensions, params.getBaseParameters().getDate()); if (errors.size() == 0 && validatedChain != null) { for (int j=proxyChain.length-2; j>=0; j--) validatedChain.add(0, proxyChain[j]); } return new ValidationResult(errors.size() == 0, errors, unresolvedExtensions, validatedChain); } protected ExtPKIXParameters2 createPKIXParameters(X509Certificate[] toCheck, boolean proxySupport, Set<TrustAnchor> trustAnchors, CertStore crlStore, RevocationParameters revocationParams, ObserversHandler observersHandler) { X509CertSelector endSelector = new X509CertSelector(); endSelector.setCertificate(toCheck[0]); PKIXParameters baseOfBase; try { baseOfBase = new PKIXParameters(trustAnchors); } catch (InvalidAlgorithmParameterException e) { throw new IllegalStateException("Can't create PKIXParameters, shouldn't happen", e); } baseOfBase.setTargetCertConstraints(endSelector); baseOfBase.setDate(new Date()); baseOfBase.addCertStore(crlStore); CertStore certStore; try { certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(Arrays.asList(toCheck)), BouncyCastleProvider.PROVIDER_NAME); } catch (Exception e1) { throw new RuntimeException("Can't create an instance of a " + "simple Collection certificate store, using the BC provider, BUG?", e1); } baseOfBase.addCertStore(certStore); PKIXExtendedParameters.Builder baseBuilder = new PKIXExtendedParameters.Builder(baseOfBase); ExtPKIXParameters2.Builder paramsBuilder = new ExtPKIXParameters2.Builder( baseBuilder, baseOfBase, trustAnchors, observersHandler); paramsBuilder.setRevocationParams(revocationParams); paramsBuilder.setProxySupport(proxySupport); return paramsBuilder.build(); } protected int getFirstProxy(X509Certificate[] toCheck) { int j; for (j=toCheck.length-1; j>=0; j--) if (ProxyUtils.isProxy(toCheck[j])) return j; //can't happen as we call this method with at least one proxy throw new RuntimeException("No proxy found, while it should be in chain?? BUG"); } /* * Performs checking of the chain which has no proxies (or at least should not have proxies), * using JCA CertPathBuilder, from BC provider. This is not used in production, * rather is an alternative implementation useful in testing and debugging. * @param baseChain * @param params * @param errors * @param unresolvedExtensions * @throws CertificateException */ /* protected void checkNonProxyChain2(X509Certificate[] baseChain, ExtendedPKIXBuilderParameters params, List<ValidationError> errors, Set<String> unresolvedExtensions, int posDelta, X509Certificate[] cc) throws CertificateException { CertPathBuilder builder; try { builder = CertPathBuilder.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME); } catch (Exception e1) { throw new RuntimeException("Can't instantiate PKIX CertPathBuilder " + "using the BC provider, really shouldn't happen", e1); } try { CertPathBuilderResult res = builder.build(params); res.getCertPath(); } catch (CertPathBuilderException e) { e.printStackTrace(); Throwable cause = e.getCause(); if (cause != null && cause instanceof ExtCertPathValidatorException) { errors.add(new ValidationError(cc, -1, ValidationErrorCode.unknownMsg, cause.toString())); } else errors.add(new ValidationError(cc, -1, ValidationErrorCode.unknownMsg, e.toString())); } catch (InvalidAlgorithmParameterException e) { throw new RuntimeException("BUG, shouldn't happen, parameters " + "for the BC CertPathBuilder were prepared correctly.", e); } } */ /** * Performs checking of the chain which has no proxies (or at least should not have proxies), * using {@link FixedBCPKIXCertPathReviewer}. In future, when BC implementation is fixed * it should use {@link PKIXCertPathReviewer} instead. * @param baseChain base chain * @param params parameters * @param errors errors * @param unresolvedExtensions unresolved extensions * @param posDelta position delta * @param cc certificate chain * @return validated chain or null * @throws CertificateException certificate exception */ protected List<X509Certificate> checkNonProxyChain(X509Certificate[] baseChain, ExtPKIXParameters2 params, List<ValidationError> errors, Set<String> unresolvedExtensions, int posDelta, X509Certificate[] cc) throws CertificateException { NonValidatingCertPathBuilder builder = new NonValidatingCertPathBuilder(); List<CertPath> certPaths; List<ValidationError> buildPathErrors = null; try { certPaths = builder.buildPath(params.getBaseBuildParameters(), baseChain[0], cc); } catch (ValidationErrorException e1) { buildPathErrors = e1.getErrors(); certPaths = Collections.singletonList(CertificateHelpers.toCertPath(baseChain)); } // PKIXCertPathReviewer baseReviewer; FixedBCPKIXCertPathReviewer baseReviewer; List<ValidationError> validationErrors = null; List<?>[] rawErrors = null; for (int i=0; i<certPaths.size(); i++) { try { baseReviewer = new FixedBCPKIXCertPathReviewer(certPaths.get(i), params); // baseReviewer = new PKIXCertPathReviewer(certPaths.get(i), params); } catch (CertPathReviewerException e) { //really shoudn't happen - we have checked the arguments throw new IllegalStateException("Can't init PKIXCertPathReviewer, bug?", e); } if (buildPathErrors != null && baseReviewer.isValidCertPath()) { //ups!!! bad! PKIXCertPAthReviewer validated while the path was not even build throw new IllegalStateException("PKIXCertPAthReviewer validated while the path was not even " + "build correctly. Build path error: " + buildPathErrors.get(0)); } List<ValidationError> processedErrors = convertErrors(baseReviewer.getErrors(), false, posDelta, cc); if (processedErrors.size() == 0) { X509Certificate ta = baseReviewer.getTrustAnchor().getTrustedCert(); if (ta == null) return null; List<? extends Certificate> path = certPaths.get(i).getCertificates(); List<X509Certificate> ret = new ArrayList<X509Certificate>(path.size()+1); for (int j=0; j<path.size(); j++) ret.add((X509Certificate) path.get(j)); ret.add(ta); return ret; } if (validationErrors == null || validationErrors.size() > processedErrors.size()) { validationErrors = processedErrors; rawErrors = baseReviewer.getErrors(); } } if (validationErrors != null) { //let's report errors from the validation which had a smallest number of them errors.addAll(validationErrors); if (rawErrors != null) unresolvedExtensions.addAll(getUnresolvedExtensionons(rawErrors)); } else { throw new IllegalStateException("PKIXCertPAthReviewer BUG: validationErrors is null, " + "tested chain: " + CertificateUtils.format(baseChain, FormatMode.FULL)); } return null; } /** * Checks chain with proxies, starting with the EEC using X.509 path validation. * EEC issuer is used as the only trust anchor. CRLs are ignored, proxy extension OIDs * are marked as handled. The error resulting from the missing CA extension is * ignored as well as validity time errors. The latter are checked manually later on. * @param proxyChain proxy chain * @param trustAnchor trust anchor * @param errors errors * @param unresolvedExtensions unresolved extensions * @throws CertificateException certificate exception */ protected void checkProxyChainWithBC(X509Certificate[] proxyChain, Set<TrustAnchor> trustAnchor, List<ValidationError> errors, Set<String> unresolvedExtensions) throws CertificateException { CertPath proxyCertPath = CertificateHelpers.toCertPath(proxyChain); PKIXCertPathReviewer proxyReviewer; try { PKIXParameters proxyParams = new PKIXParameters(trustAnchor); proxyParams.addCertPathChecker(new PKIXProxyCertificateChecker()); proxyParams.setRevocationEnabled(false); proxyReviewer = new PKIXCertPathReviewer(proxyCertPath, proxyParams); } catch (InvalidAlgorithmParameterException e1) { //really shoudn't happen - we have checked the arguments throw new RuntimeException("Can't init PKIXParameters, bug?", e1); } catch (CertPathReviewerException e) { //really shoudn't happen - we have checked the arguments throw new RuntimeException("Can't init PKIXCertPathReviewer, bug?", e); } errors.addAll(convertErrors(proxyReviewer.getErrors(), true, 0, proxyChain)); unresolvedExtensions.addAll(getUnresolvedExtensionons(proxyReviewer.getErrors())); } /** * Performs a validation loop of the proxy chain checking each pair in chain * for the rules not otherwise verified by the base check. Additionally chain length * restriction is verified. * @param proxyChain proxy chain * @param errors errors * @param unresolvedExtensions unresolved extensions * @param validDate valid date * @throws CertificateException certificate exception */ protected void checkProxyChainMain(X509Certificate[] proxyChain, List<ValidationError> errors, Set<String> unresolvedExtensions, Date validDate) throws CertificateException { int remainingLen = Integer.MAX_VALUE; int last = proxyChain.length-1; for (int i=last; i>0; i--) { try { checkPairWithProxy(proxyChain[i], proxyChain[i-1], errors, i-1, proxyChain, validDate); if (i != last && remainingLen != Integer.MIN_VALUE) { int lenRestriction = ProxyHelper.getProxyPathLimit(proxyChain[i]); if (lenRestriction < remainingLen) remainingLen = lenRestriction-1; else { if (remainingLen != Integer.MAX_VALUE) remainingLen--; } if (remainingLen < 0) { remainingLen = Integer.MIN_VALUE; errors.add(new ValidationError(proxyChain, i-1, ValidationErrorCode.proxyLength)); } } } catch (CertPathValidatorException e) { break; } catch (IOException e) { throw new CertificateException("Can't parse the proxy path limit information", e); } } } /** * Checks if the certificate passed as the 2nd argument is a correct proxy * certificate including checks w.r.t. chain rules with the certificate passed * as the 1st argument being its issuing certificate. The checks are: * <ul> * <li> proxyCert is a real proxy cert of any type * <li> issuer may not be a CA (3.1) * <li> issuer must have subject set (3.1) * <li> proxy must have issuer equal to issuerCert subject (3.1) * <li> If the Proxy Issuer certificate has the KeyUsage extension, the * Digital Signature bit MUST be asserted. (3.1) * <li> no issuer alternative name extension (3.2) * <li> proxy subject must be the issuerCert subject with appended one CN component (3.4) * <li> no subject alternative name extension (3.5) * <li> no cA basic constraint (3.7) * <li> time constraints for the proxy are checked here (as we allow for a grace time to work around clock skews) * <li> proxy certificate type (RFC, draft RFC or legacy) must be the same for both certificates * <li> if the issuerCert is restricted then proxyCert must be restricted too. * </ul> * The numbers in brackets refer to the RFC 3820 sections. The last two rules were added in the version 1.1.0 of * the library. * <p> * * @param issuerCert certificate of the issuer * @param proxyCert certificate to be checked * @param errors out arg - list of errors found * @param position position in original chain to be used in error reporting * @param proxyChain proxy chain * @param validationTime validation time * @throws CertPathValidatorException certificate path validator exception * @throws CertificateParsingException certificate parsing exception */ protected void checkPairWithProxy(X509Certificate issuerCert, X509Certificate proxyCert, List<ValidationError> errors, int position, X509Certificate[] proxyChain, Date validationTime) throws CertPathValidatorException, CertificateParsingException { if (!ProxyUtils.isProxy(proxyCert)) { errors.add(new ValidationError(proxyChain, position, ValidationErrorCode.proxyEECInChain)); throw new CertPathValidatorException(); } if (proxyCert.getBasicConstraints() >= 0) errors.add(new ValidationError(proxyChain, position, ValidationErrorCode.proxyCASet)); if (proxyCert.getIssuerAlternativeNames() != null) errors.add(new ValidationError(proxyChain, position, ValidationErrorCode.proxyIssuerAltNameSet)); if (proxyCert.getSubjectAlternativeNames() != null) errors.add(new ValidationError(proxyChain, position, ValidationErrorCode.proxySubjectAltNameSet)); if (issuerCert.getBasicConstraints() >= 0) errors.add(new ValidationError(proxyChain, position+1, ValidationErrorCode.proxyIssuedByCa)); X500Principal issuerDN = issuerCert.getSubjectX500Principal(); if ("".equals(issuerDN.getName())) { errors.add(new ValidationError(proxyChain, position+1, ValidationErrorCode.proxyNoIssuerSubject)); throw new CertPathValidatorException(); } if (!X500NameUtils.rfc3280Equal(issuerDN, proxyCert.getIssuerX500Principal())) errors.add(new ValidationError(proxyChain, position, ValidationErrorCode.proxySubjectInconsistent)); boolean[] keyUsage = issuerCert.getKeyUsage(); if (keyUsage != null && !keyUsage[0]) errors.add(new ValidationError(proxyChain, position+1, ValidationErrorCode.proxyIssuerNoDsig)); checkLastCNNameRule(proxyCert.getSubjectX500Principal(), issuerDN, errors, position, proxyChain); checkProxyTime(proxyCert, validationTime, proxyChain, errors, position); if (position+2 != proxyChain.length) //we won't check it for the first pair as it contains an EEC { ExtendedProxyType issuerType = ProxyHelper.getProxyType(issuerCert); ExtendedProxyType proxyType = ProxyHelper.getProxyType(proxyCert); if (issuerType != proxyType) errors.add(new ValidationError(proxyChain, position, ValidationErrorCode.proxyTypeInconsistent)); try { if (ProxyHelper.isLimited(issuerCert) && !ProxyHelper.isLimited(proxyCert)) errors.add(new ValidationError(proxyChain, position, ValidationErrorCode.proxyInconsistentlyLimited)); } catch (IOException e) { throw new CertificateParsingException("Can't establish whether the proxy is limited", e); } } } protected void checkProxyTime(X509Certificate proxyCert, Date validationTime, X509Certificate[] proxyChain, List<ValidationError> errors, int position) { if (validationTime.getTime() > proxyCert.getNotAfter().getTime() + PROXY_VALIDATION_GRACE_PERIOD) { errors.add(new ValidationError(proxyChain, position, ValidationErrorCode.certificateExpired, proxyCert.getNotAfter())); } if (validationTime.getTime() < proxyCert.getNotBefore().getTime()-PROXY_VALIDATION_GRACE_PERIOD) { errors.add(new ValidationError(proxyChain, position, ValidationErrorCode.certificateNotYetValid, proxyCert.getNotBefore())); } } protected void checkLastCNNameRule(X500Principal srcP, X500Principal issuerP, List<ValidationError> errors, int position, X509Certificate[] proxyChain) throws CertPathValidatorException { X500Name src = CertificateHelpers.toX500Name(srcP); X500Name issuer = CertificateHelpers.toX500Name(issuerP); RDN[] srcRDNs = src.getRDNs(); if (srcRDNs.length < 2) { errors.add(new ValidationError(proxyChain, position+1, ValidationErrorCode.proxySubjectOneRDN)); throw new CertPathValidatorException(); } if (srcRDNs[srcRDNs.length-1].isMultiValued()) { errors.add(new ValidationError(proxyChain, position+1, ValidationErrorCode.proxySubjectMultiLastRDN)); throw new CertPathValidatorException(); } AttributeTypeAndValue lastAVA = srcRDNs[srcRDNs.length-1].getFirst(); if (!lastAVA.getType().equals(BCStyle.CN)) { errors.add(new ValidationError(proxyChain, position+1, ValidationErrorCode.proxySubjectLastRDNNotCN)); throw new CertPathValidatorException(); } RDN[] finalRDNs = Arrays.copyOf(srcRDNs, srcRDNs.length-1); JavaAndBCStyle style = new JavaAndBCStyle(); X500Name truncatedName = new X500Name(style, finalRDNs); if (!style.areEqual(issuer, truncatedName)) errors.add(new ValidationError(proxyChain, position+1, ValidationErrorCode.proxySubjectBaseWrong)); } protected List<ValidationError> convertErrors(List<?>[] bcErrorsA, boolean ignoreProxyErrors, int positionDelta, X509Certificate[] cc) { List<ValidationError> ret = new ArrayList<ValidationError>(); for (int i=0; i<bcErrorsA.length; i++) { List<?> bcErrors = bcErrorsA[i]; for (Object bcError: bcErrors) { if (bcError instanceof ErrorBundle) { ErrorBundle error = (ErrorBundle) bcError; if (ignoreProxyErrors) { String id = error.getId(); if (id.equals("CertPathReviewer.noBasicConstraints")) continue; if (id.equals("CertPathReviewer.noCACert")) continue; if (id.equals("CertPathReviewer.noCertSign")) continue; if (id.equals("CertPathReviewer.certificateNotYetValid")) continue; if (id.equals("CertPathReviewer.certificateExpired")) continue; } ret.add(BCErrorMapper.map(error, i-1+positionDelta, cc)); } else { SimpleValidationErrorException error = (SimpleValidationErrorException) bcError; if (ignoreProxyErrors) { ValidationErrorCode id = error.getCode(); if (id.equals(ValidationErrorCode.noBasicConstraints)) continue; if (id.equals(ValidationErrorCode.noCACert)) continue; if (id.equals(ValidationErrorCode.noCertSign)) continue; if (id.equals(ValidationErrorCode.certificateExpired)) continue; if (id.equals(ValidationErrorCode.certificateNotYetValid)) continue; } ret.add(new ValidationError(cc, i-1+positionDelta, error.getCode(), error.getArguments())); } } } return ret; } protected Set<String> getUnresolvedExtensionons(List<?>[] bcErrorsA) { Set<String> ret = new HashSet<String>(); for (int i=0; i<bcErrorsA.length; i++) { List<?> bcErrors = bcErrorsA[i]; for (Object bcError: bcErrors) { if (bcError instanceof ErrorBundle) { ErrorBundle error = (ErrorBundle) bcError; if (error.getId().equals("CertPathReviewer.unknownCriticalExt")) { ASN1ObjectIdentifier extId = (ASN1ObjectIdentifier) error.getArguments()[0]; ret.add(extId.getId()); } } else { SimpleValidationErrorException error = (SimpleValidationErrorException) bcError; if (error.getCode().equals(ValidationErrorCode.unknownCriticalExt)) { ASN1ObjectIdentifier extId = (ASN1ObjectIdentifier) error.getArguments()[0]; ret.add(extId.getId()); } } } } return ret; } }