SecureBlackbox 16: Diagnosing certificate chain validation errors when validating a certificate or signature with *AdES components

Note: This article applies only to SecureBlackbox Legacy. For future development please consider using the latest version.

Note: This article primarily addresses the components that perform complete chain validation out of the box. In particular, these include the TElX509CertificateValidator and *AdES components. You are getting certificate chain validation errors when validating a certificate or signature with *AdES components. The certificate is apparently correct. What is going wrong?

A number of SecureBlackbox components perform deep, thorough validation of the certificate chains. This process involves the construction of certificate tree(s) and the establishment of the correctness and effectiveness of each and every certificate in this tree. In certain cases, the number of certificates in the constructed tree reaches as many as 10, and the overall number of validation elements (such as CRLs and OCSP responses) up to three times more. This, together with other factors - such as a dependency on third-party offline and online services and a lack of standard compliance by some CAs - can affect the flow of the validation process and pose a risk for validation failures.

So, you have come across a chain validation error ("chain validation failed" or "collected validation information is not complete" - the exact message may vary).

First, the good news: In most cases when you get this error, the certificates themselves are OK, and you can tune the component to perform the validation correctly with little work as described below. But before you start, read the article on how TElX509CertificateValidator works. This article explains some internals of the TElX509CertificateValidator class and gives you some tips for problem diagnostics.

  1. The first thing to try is to make the validation procedure more tolerant to incorrectly composed certificates and validation elements. This can be done by adjusting the following properties:

    validator.MandatoryCRLCheck= false;
    validator.MandatoryOCSPCheck=false;
    validator.MandatoryRevocationCheck=true;
    

    If the above doesn't help, let's do some further adjustments:

    validator.IgnoreCAKeyUsage = true;
    
  2. If the validation still fails after the above adjustments, let's proceed to the second step, which includes some detective work. It is important to track down the exact element that fails; the solution will depend on the specifics of the element and the particular problem it causes. To do this, handle the events of the TElX509CertificateValidator object (note that some components, such as TElCAdESProcessor, pass the validator object they create back to the user code via the OnCertValidatorPrepared event). Inside the handlers, log the details of the elements being checked:

     processor.OnCertValidatorPrepared += new TSBPDFCertValidatorPreparedEvent(handler_OnCertValidatorPrepared);
    processor.OnCertValidatorFinished += new TSBPDFCertValidatorFinishedEvent(handler_OnCertValidatorFinished);
    
    ...
    
    void handler_OnCertValidatorPrepared(object Sender, ref SBCertValidator.TElX509CertificateValidator CertValidator, TElX509Certificate Cert)
    {
      Log("Starting validation of the certificate: " + Cert.SubjectRDN.SaveToDNString() + " / " + Cert.IssuerRDN.SaveToDNString());
      CertValidator.OnBeforeCRLRetrieverUse += new SBCertValidator.TSBBeforeCRLRetrieverUseEvent(CertValidator_OnBeforeCRLRetrieverUse);
      CertValidator.OnBeforeOCSPClientUse += new SBCertValidator.TSBBeforeOCSPClientUseEvent(CertValidator_OnBeforeOCSPClientUse);
      CertValidator.OnCRLError += new SBCertValidator.TSBCertificateValidatorCRLErrorEvent(CertValidator_OnCRLError);
      CertValidator.OnCRLNeeded += new SBCertValidator.TSBCRLNeededEvent(CertValidator_OnCRLNeeded);
      CertValidator.OnCRLRetrieved += new SBCertValidator.TSBCRLRetrievedEvent(CertValidator_OnCRLRetrieved);
      CertValidator.OnOCSPError += new SBCertValidator.TSBCertificateValidatorOCSPErrorEvent(CertValidator_OnOCSPError);
      CertValidator.OnAfterCRLUse += new SBCertValidator.TSBAfterCRLUseEvent(CertValidator_OnAfterCRLUse);
      CertValidator.OnAfterOCSPResponseUse += new SBCertValidator.TSBAfterOCSPResponseUseEvent(CertValidator_OnAfterOCSPResponseUse);
      CertValidator.OnBeforeCertificateValidation += new TSBBeforeCertificateValidationEvent(CertValidator_OnBeforeCertificateValidation);
      CertValidator.OnAfterCertificateValidation += new TSBAfterCertificateValidationEvent(CertValidator_OnAfterCertificateValidation);
    
    }
    
    void handler_OnCertValidatorFinished(object Sender, SBCertValidator.TElX509CertificateValidator CertValidator, TElX509Certificate Cert, TSBCertificateValidity Validity, int Reason)
    {
      Log("Finished validation of the certificate: " + Cert.SubjectRDN.SaveToDNString() + " / " + Cert.IssuerRDN.SaveToDNString() + ", validity: " + Validity.ToString() + ", reason: " + Reason.ToString());
    }
    
    void CertValidator_OnAfterOCSPResponseUse(object Sender, TElX509Certificate Certificate, TElX509Certificate CACertificate, SBOCSPClient.TElOCSPResponse Response)
    {
      Log("Successfully used OCSP response for certificate: " + Certificate.SubjectRDN.SaveToDNString() + " / " + Certificate.IssuerRDN.SaveToDNString());
    }
    
    void CertValidator_OnAfterCRLUse(object Sender, TElX509Certificate Certificate, TElX509Certificate CACertificate, SBCRL.TElCertificateRevocationList CRL)
    {
      Log("Successfully used CRL for certificate: " + Certificate.SubjectRDN.SaveToDNString() + " / " + Certificate.IssuerRDN.SaveToDNString());
    }
    
    void CertValidator_OnOCSPError(object Sender, TElX509Certificate Certificate, string Location, SBOCSPClient.TElOCSPClient Client, int ErrorCode)
    {
      Log("Encountered OCSP error when validating certificate: " + Certificate.SubjectRDN.SaveToDNString() + " / " + Certificate.IssuerRDN.SaveToDNString() + ", location: " + Location + ", error: " + ErrorCode.ToString());
    }
    
    void CertValidator_OnCRLRetrieved(object Sender, TElX509Certificate Certificate, TElX509Certificate CACertificate, SBX509Ext.TSBGeneralName NameType, string Location, SBCRL.TElCertificateRevocationList CRL)
    {
      Log("Retrieved CRL for certificate: " + Certificate.SubjectRDN.SaveToDNString() + " / " + Certificate.IssuerRDN.SaveToDNString() + ", location: " + Location);
    }
    
    void CertValidator_OnCRLNeeded(object Sender, TElX509Certificate Certificate, TElX509Certificate CACertificate, ref SBCRLStorage.TElCustomCRLStorage CRLs)
    {
      Log("CRL needed for certificate: " + Certificate.SubjectRDN.SaveToDNString() + " / " + Certificate.IssuerRDN.SaveToDNString());
    }
    
    void CertValidator_OnCRLError(object Sender, TElX509Certificate Certificate, string Location, SBCRLStorage.TElCustomCRLRetriever Retriever, int ErrorCode)
    {
      Log("Encountered CRL error when validating certificate: " + Certificate.SubjectRDN.SaveToDNString() + " / " + Certificate.IssuerRDN.SaveToDNString() + ", location: " + Location + ", error: " + ErrorCode.ToString());
    }
    
    void CertValidator_OnBeforeOCSPClientUse(object Sender, TElX509Certificate Certificate, TElX509Certificate CACertificate, string OCSPLocation, ref SBOCSPClient.TElOCSPClient OCSPClient)
    {
      Log("Will be retrieving OCSP response for certificate: " + Certificate.SubjectRDN.SaveToDNString() + " / " + Certificate.IssuerRDN.SaveToDNString() + ", location: " + OCSPLocation);
    }
    
    void CertValidator_OnBeforeCRLRetrieverUse(object Sender, TElX509Certificate Certificate, TElX509Certificate CACertificate, SBX509Ext.TSBGeneralName NameType, string Location, ref SBCRLStorage.TElCustomCRLRetriever Retriever)
    {
      Log("Will be retrieving CRL response for certificate: " + Certificate.SubjectRDN.SaveToDNString() + " / " + Certificate.IssuerRDN.SaveToDNString() + ", location: " + Location);
    }
    
    void CertValidator_OnAfterCertificateValidation(object Sender, TElX509Certificate Certificate, TElX509Certificate CACertificate, ref TSBCertificateValidity Validity, ref int Reason, ref bool DoContinue)
    {
      Log("Certificate validation completed for certificate: " + Certificate.SubjectRDN.SaveToDNString() + " / " + Certificate.IssuerRDN.SaveToDNString() + ". Validity: " + Validity.ToString() + ", Reason: " + Reason.ToString());
    }
    
    void CertValidator_OnBeforeCertificateValidation(object Sender, TElX509Certificate Certificate)
    {
      Log("Starting certificate validation: " + Certificate.SubjectRDN.SaveToDNString() + " / " + Certificate.IssuerRDN.SaveToDNString());
    }
    

    Now run the code and reproduce the problem. Once you have, have a look into the log to establish the element that fails to pass the validation. The further steps to take depend on the element and the validation problem it exposes. Below are just a few examples of possible validation problems:

    • The root certificate of either of the chains (e.g. the “main” chain or TSA chain) is not trusted.
    • Some CRL or OCSP server cannot be reached due to a network-related issue.
    • A firewall blocks connections to exotic (e.g., LDAP) CRL sources.
    • One or more certificates are expired.

We appreciate your feedback.  If you have any questions, comments, or suggestions about this article please contact our support team at kb@nsoftware.com.