/*
 * Copyright 2016 Analytical Graphics, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * 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.keycloak.common.util;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.CRLReason;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.ocsp.*;
import org.bouncycastle.cert.ocsp.jcajce.JcaCertificateID;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;

import java.io.*;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.cert.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
 * @version $Revision: 1 $
 * @since 10/29/2016
 */

public final class OCSPUtils {


    static {
        BouncyIntegration.init();
    }

    private final static Logger logger = Logger.getLogger(""+OCSPUtils.class);

    private static int OCSP_CONNECT_TIMEOUT = 10000; // 10 sec
    private static final int TIME_SKEW = 900000;

    public enum RevocationStatus {
        GOOD,
        REVOKED,
        UNKNOWN
    }

    public interface OCSPRevocationStatus {
        RevocationStatus getRevocationStatus();
        Date getRevocationTime();
        CRLReason getRevocationReason();
    }

    /**
     * Requests certificate revocation status using OCSP.
     * @param cert the certificate to be checked
     * @param issuerCertificate The issuer certificate
     * @param responderURI an address of OCSP responder. Overrides any OCSP responder URIs stored in certificate's AIA extension
     * @param date
     * @param responderCert a certificate that OCSP responder uses to sign OCSP responses
     * @return revocation status
     */
    public static OCSPRevocationStatus check(X509Certificate cert, X509Certificate issuerCertificate, URI responderURI, X509Certificate responderCert, Date date) throws CertPathValidatorException {
        if (cert == null)
            throw new IllegalArgumentException("cert cannot be null");
        if (issuerCertificate == null)
            throw new IllegalArgumentException("issuerCertificate cannot be null");
        if (responderURI == null)
            throw new IllegalArgumentException("responderURI cannot be null");

        return check(cert, issuerCertificate, Collections.singletonList(responderURI), responderCert, date);
    }
    /**
     * Requests certificate revocation status using OCSP. The OCSP responder URI
     * is obtained from the certificate's AIA extension.
     * @param cert the certificate to be checked
     * @param issuerCertificate The issuer certificate
     * @param date
     * @return revocation status
     */
    public static OCSPRevocationStatus check(X509Certificate cert, X509Certificate issuerCertificate, Date date, X509Certificate responderCert) throws CertPathValidatorException {
        List<String> responderURIs = null;
        try {
            responderURIs = getResponderURIs(cert);
        } catch (CertificateEncodingException e) {
            logger.log(Level.FINE, "CertificateEncodingException: {0}", e.getMessage());
            throw new CertPathValidatorException(e.getMessage(), e);
        }
        if (responderURIs.size() == 0) {
            logger.log(Level.INFO, "No OCSP responders in the specified certificate");
            throw new CertPathValidatorException("No OCSP Responder URI in certificate");
        }

        List<URI> uris = new LinkedList<>();
        for (String value : responderURIs) {
            try {
                URI responderURI = URI.create(value);
                uris.add(responderURI);
            } catch (IllegalArgumentException ex) {
                logger.log(Level.FINE, "Malformed responder URI {0}", value);
            }
        }
        return check(cert, issuerCertificate, Collections.unmodifiableList(uris), responderCert, date);
    }
    /**
     * Requests certificate revocation status using OCSP. The OCSP responder URI
     * is obtained from the certificate's AIA extension.
     * @param cert the certificate to be checked
     * @param issuerCertificate The issuer certificate
     * @return revocation status
     */
    public static OCSPRevocationStatus check(X509Certificate cert, X509Certificate issuerCertificate) throws CertPathValidatorException {
        return check(cert, issuerCertificate, null, null);
    }

    private static OCSPResp getResponse(OCSPReq ocspReq, URI responderUri) throws IOException {
        DataOutputStream dataOut = null;
        InputStream in = null;
        try {
            byte[] array = ocspReq.getEncoded();
            URL urlt = responderUri.toURL();
            HttpURLConnection con = (HttpURLConnection) urlt.openConnection();
            con.setRequestMethod("POST");
            con.setConnectTimeout(OCSP_CONNECT_TIMEOUT);
            con.setReadTimeout(OCSP_CONNECT_TIMEOUT);
            con.setRequestProperty("Content-type", "application/ocsp-request");
            con.setRequestProperty("Content-length", String.valueOf(array.length));
//        con.setRequestProperty("Accept", "application/ocsp-response");

            con.setDoOutput(true);
            con.setDoInput(true);
            OutputStream out = con.getOutputStream();
            dataOut = new DataOutputStream(new BufferedOutputStream(out));
            dataOut.write(array);
            dataOut.flush();

            if (con.getResponseCode() / 100 != 2) {
                String errorMessage = String.format("Connection error, unable to obtain certificate revocation status using OCSP responder \"%s\", code \"%d\"",
                        responderUri.toString(), con.getResponseCode());
                throw new IOException(errorMessage);
            }
            //Get Response
            in = (InputStream) con.getInputStream();
            int contentLen = con.getContentLength();
            if (contentLen == -1) {
                contentLen = Integer.MAX_VALUE;
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bytesRead = 0;
            byte[] buffer = new byte[2048];
            while ((bytesRead = in.read(buffer, 0, buffer.length)) >= 0) {
                baos.write(buffer, 0, bytesRead);
            }
            baos.flush();
            byte[] data = baos.toByteArray();
            return new OCSPResp(data);
        } finally {
            if (dataOut != null) {
                dataOut.close();
            }
            if (in != null) {
                in.close();
            }
        }
    }

    /**
     * Requests certificate revocation status using OCSP.
     * @param cert the certificate to be checked
     * @param issuerCertificate the issuer certificate
     * @param responderURIs the OCSP responder URIs
     * @param responderCert the OCSP responder certificate
     * @param date if null, the current time is used.
     * @return a revocation status
     * @throws CertPathValidatorException
     */
    private static OCSPRevocationStatus check(X509Certificate cert, X509Certificate issuerCertificate, List<URI> responderURIs, X509Certificate responderCert, Date date) throws CertPathValidatorException {
        if (responderURIs == null || responderURIs.size() == 0)
            throw new IllegalArgumentException("Need at least one responder");
        try {
            DigestCalculator digCalc = new BcDigestCalculatorProvider()
                    .get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1));

            JcaCertificateID certificateID = new JcaCertificateID(digCalc, issuerCertificate, cert.getSerialNumber());

            // Create a nounce extension to protect against replay attacks
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            BigInteger nounce = BigInteger.valueOf(Math.abs(random.nextInt()));

            DEROctetString derString = new DEROctetString(nounce.toByteArray());
            Extension nounceExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, derString);
            Extensions extensions = new Extensions(nounceExtension);

            OCSPReq ocspReq = new OCSPReqBuilder().addRequest(certificateID, extensions).build();

            URI responderURI = responderURIs.get(0);
            logger.log(Level.INFO, "OCSP Responder {0}", responderURI);

            try {
                OCSPResp resp = getResponse(ocspReq, responderURI);
                logger.log(Level.FINE, "Received a response from OCSP responder {0}, the response status is {1}", new Object[]{responderURI, resp.getStatus()});
                switch (resp.getStatus()) {
                    case OCSPResp.SUCCESSFUL:
                        if (resp.getResponseObject() instanceof BasicOCSPResp) {
                            return processBasicOCSPResponse(issuerCertificate, responderCert, date, certificateID, nounce, (BasicOCSPResp)resp.getResponseObject());
                        } else {
                            throw new CertPathValidatorException("OCSP responder returned an invalid or unknown OCSP response.");
                        }

                    case OCSPResp.INTERNAL_ERROR:
                    case OCSPResp.TRY_LATER:
                        throw new CertPathValidatorException("Internal error/try later. OCSP response error: " + resp.getStatus(), (Throwable) null, (CertPath) null, -1, CertPathValidatorException.BasicReason.UNDETERMINED_REVOCATION_STATUS);

                    case OCSPResp.SIG_REQUIRED:
                        throw new CertPathValidatorException("Invalid or missing signature. OCSP response error: " + resp.getStatus(), (Throwable) null, (CertPath) null, -1, CertPathValidatorException.BasicReason.INVALID_SIGNATURE);

                    case OCSPResp.UNAUTHORIZED:
                        throw new CertPathValidatorException("Unauthorized request. OCSP response error: " + resp.getStatus(), (Throwable) null, (CertPath) null, -1, CertPathValidatorException.BasicReason.UNSPECIFIED);

                    case OCSPResp.MALFORMED_REQUEST:
                    default:
                        throw new CertPathValidatorException("OCSP request is malformed. OCSP response error: " + resp.getStatus(), (Throwable) null, (CertPath) null, -1, CertPathValidatorException.BasicReason.UNSPECIFIED);
                }
            }
            catch(IOException e) {
                logger.log(Level.FINE, "OCSP Responder \"{0}\" failed to return a valid OCSP response\n{1}",
                        new Object[] {responderURI, e.getMessage()});
                throw new CertPathValidatorException("OCSP check failed", e);
            }
        }
        catch(CertificateNotYetValidException | CertificateExpiredException | OperatorCreationException | OCSPException | CertificateEncodingException | NoSuchAlgorithmException | NoSuchProviderException e) {
            logger.log(Level.FINE, e.getMessage());
            throw new CertPathValidatorException(e.getMessage(), e);
        }
    }

    private static OCSPRevocationStatus processBasicOCSPResponse(X509Certificate issuerCertificate, X509Certificate responderCertificate, Date date, JcaCertificateID certificateID, BigInteger nounce, BasicOCSPResp basicOcspResponse)
            throws OCSPException, NoSuchProviderException, NoSuchAlgorithmException, CertificateNotYetValidException, CertificateExpiredException, CertPathValidatorException {
        SingleResp expectedResponse = null;
        for (SingleResp singleResponse : basicOcspResponse.getResponses()) {
            if (compareCertIDs(certificateID, singleResponse.getCertID())) {
                expectedResponse = singleResponse;
                break;
            }
        }

        if (expectedResponse != null) {
            verifyResponse(basicOcspResponse, issuerCertificate, responderCertificate, nounce.toByteArray(), date);
            return singleResponseToRevocationStatus(expectedResponse);
        } else {
            throw new CertPathValidatorException("OCSP response does not include a response for a certificate supplied in the OCSP request");
        }
    }

    private static boolean compareCertIDs(JcaCertificateID idLeft, CertificateID idRight) {
        if (idLeft == idRight)
            return true;
        if (idLeft == null || idRight == null)
            return false;

        return Arrays.equals(idLeft.getIssuerKeyHash(), idRight.getIssuerKeyHash()) &&
                Arrays.equals(idLeft.getIssuerNameHash(), idRight.getIssuerNameHash()) &&
                idLeft.getSerialNumber().equals(idRight.getSerialNumber());
    }

    private static void verifyResponse(BasicOCSPResp basicOcspResponse, X509Certificate issuerCertificate, X509Certificate responderCertificate, byte[] requestNonce, Date date) throws NoSuchProviderException, NoSuchAlgorithmException, CertificateNotYetValidException, CertificateExpiredException, CertPathValidatorException {

        List<X509CertificateHolder> certs = new ArrayList<>(Arrays.asList(basicOcspResponse.getCerts()));
        X509Certificate signingCert = null;

        try {
            certs.add(new JcaX509CertificateHolder(issuerCertificate));
            if (responderCertificate != null) {
                certs.add(new JcaX509CertificateHolder(responderCertificate));
            }
        } catch (CertificateEncodingException e) {
            e.printStackTrace();
        }
        if (certs.size() > 0) {

            X500Name responderName = basicOcspResponse.getResponderId().toASN1Primitive().getName();
            byte[] responderKey = basicOcspResponse.getResponderId().toASN1Primitive().getKeyHash();

            if (responderName != null) {
                logger.log(Level.INFO, "Responder Name: {0}", responderName.toString());
                for (X509CertificateHolder certHolder : certs) {
                    try {
                        X509Certificate tempCert = new JcaX509CertificateConverter()
                                .setProvider("BC").getCertificate(certHolder);
                        X500Name respName = new X500Name(tempCert.getSubjectX500Principal().getName());
                        if (responderName.equals(respName)) {
                            signingCert = tempCert;
                            logger.log(Level.INFO, "Found a certificate whose principal \"{0}\" matches the responder name \"{1}\"",
                                    new Object[] {tempCert.getSubjectDN().getName(), responderName.toString()});
                            break;
                        }
                    } catch (CertificateException e) {
                        logger.log(Level.FINE, e.getMessage());
                    }
                }
            } else if (responderKey != null) {
                SubjectKeyIdentifier responderSubjectKey = new SubjectKeyIdentifier(responderKey);
                logger.log(Level.INFO, "Responder Key: {0}", Arrays.toString(responderKey));
                for (X509CertificateHolder certHolder : certs) {
                    try {
                        X509Certificate tempCert = new JcaX509CertificateConverter()
                                .setProvider("BC").getCertificate(certHolder);

                        SubjectKeyIdentifier subjectKeyIdentifier = null;
                        if (certHolder.getExtensions() != null) {
                            subjectKeyIdentifier = SubjectKeyIdentifier.fromExtensions(certHolder.getExtensions());
                        }

                        if (subjectKeyIdentifier != null) {
                            logger.log(Level.INFO, "Certificate: {0}\nSubject Key Id: {1}",
                                    new Object[] {tempCert.getSubjectDN().getName(), Arrays.toString(subjectKeyIdentifier.getKeyIdentifier())});
                        }

                        if (subjectKeyIdentifier != null && responderSubjectKey.equals(subjectKeyIdentifier)) {
                            signingCert = tempCert;
                            logger.log(Level.INFO, "Found a signer certificate \"{0}\" with the subject key extension value matching the responder key",
                                    signingCert.getSubjectDN().getName());

                            break;
                        }

                        subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(tempCert.getPublicKey());
                        if (responderSubjectKey.equals(subjectKeyIdentifier)) {
                            signingCert = tempCert;
                            logger.log(Level.INFO, "Found a certificate \"{0}\" with the subject key matching the OCSP responder key", signingCert.getSubjectDN().getName());
                            break;
                        }

                    } catch (CertificateException e) {
                        logger.log(Level.FINE, e.getMessage());
                    }
                }
            }
        }
        if (signingCert != null) {
            if (signingCert.equals(issuerCertificate)) {
                logger.log(Level.INFO, "OCSP response is signed by the target''s Issuing CA");
            } else if (responderCertificate != null && signingCert.equals(responderCertificate)) {
                // https://www.ietf.org/rfc/rfc2560.txt
                // 2.6  OCSP Signature Authority Delegation
                // - The responder certificate is issued to the responder by CA
                logger.log(Level.INFO, "OCSP response is signed by an authorized responder certificate");
            } else {
                // 4.2.2.2  Authorized Responders
                // 3. Includes a value of id-ad-ocspSigning in an ExtendedKeyUsage
                // extension and is issued by the CA that issued the certificate in
                // question."
                if (!signingCert.getIssuerX500Principal().equals(issuerCertificate.getSubjectX500Principal())) {
                    logger.log(Level.INFO, "Signer certificate''s Issuer: {0}\nIssuer certificate''s Subject: {1}",
                            new Object[] {signingCert.getIssuerX500Principal().getName(), issuerCertificate.getSubjectX500Principal().getName()});
                    throw new CertPathValidatorException("Responder\'s certificate is not authorized to sign OCSP responses");
                }
                try {
                    List<String> purposes = signingCert.getExtendedKeyUsage();
                    if (purposes != null && !purposes.contains(KeyPurposeId.id_kp_OCSPSigning.getId())) {
                        logger.log(Level.INFO, "OCSPSigning extended usage is not set");
                        throw new CertPathValidatorException("Responder\'s certificate not valid for signing OCSP responses");
                    }
                } catch (CertificateParsingException e) {
                    logger.log(Level.FINE, "Failed to get certificate''s extended key usage extension\n{0}", e.getMessage());
                }
                if (date == null) {
                    signingCert.checkValidity();
                } else {
                    signingCert.checkValidity(date);
                }
                try {
                    Extension noOCSPCheck = new JcaX509CertificateHolder(signingCert).getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nocheck);
                    // TODO If the extension is present, the OCSP client can trust the
                    // responder's certificate for the lifetime of the certificate.
                    logger.log(Level.INFO, "OCSP no-check extension is {0} present", noOCSPCheck == null ? "not" : "");
                } catch (CertificateEncodingException e) {
                    logger.log(Level.FINE, "Certificate encoding exception: {0}", e.getMessage());
                }

                try {
                    signingCert.verify(issuerCertificate.getPublicKey());
                    logger.log(Level.INFO, "OCSP response is signed by an Authorized Responder");

                } catch (GeneralSecurityException ex) {
                    signingCert = null;
                }
            }
        }
        if (signingCert == null) {
            throw new CertPathValidatorException("Unable to verify OCSP Response\'s signature");
        } else {
            if (!verifySignature(basicOcspResponse, signingCert)) {
                throw new CertPathValidatorException("Error verifying OCSP Response\'s signature");
            } else {
                Extension responseNonce = basicOcspResponse.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
                if (responseNonce != null && requestNonce != null && !Arrays.equals(requestNonce, responseNonce.getExtnValue().getOctets())) {
                    throw new CertPathValidatorException("Nonces do not match.");
                } else {
                    // See Sun's OCSP implementation.
                    // https://www.ietf.org/rfc/rfc2560.txt, if nextUpdate is not set,
                    // the responder is indicating that newer update is avilable all the time
                    long current = date == null ? System.currentTimeMillis() : date.getTime();
                    Date stop = new Date(current + (long) TIME_SKEW);
                    Date start = new Date(current - (long) TIME_SKEW);

                    Iterator<SingleResp> iter = Arrays.asList(basicOcspResponse.getResponses()).iterator();
                    SingleResp singleRes = null;
                    do {
                        if (!iter.hasNext()) {
                            return;
                        }
                        singleRes = iter.next();
                    }
                    while (!stop.before(singleRes.getThisUpdate()) &&
                            !start.after(singleRes.getNextUpdate() != null ? singleRes.getNextUpdate() : singleRes.getThisUpdate()));

                    throw new CertPathValidatorException("Response is unreliable: its validity interval is out-of-date");
                }
            }
        }
    }

    private static boolean verifySignature(BasicOCSPResp basicOcspResponse, X509Certificate cert) {
        try {
            ContentVerifierProvider contentVerifier = new JcaContentVerifierProviderBuilder()
                    .setProvider("BC").build(cert.getPublicKey());
            return basicOcspResponse.isSignatureValid(contentVerifier);
        } catch (OperatorCreationException e) {
            logger.log(Level.FINE, "Unable to construct OCSP content signature verifier\n{0}", e.getMessage());
        } catch (OCSPException e) {
            logger.log(Level.FINE, "Unable to validate OCSP response signature\n{0}", e.getMessage());
        }
        return false;
    }

    private static OCSPRevocationStatus unknownStatus() {
        return new OCSPRevocationStatus() {
            @Override
            public RevocationStatus getRevocationStatus() {
                return RevocationStatus.UNKNOWN;
            }

            @Override
            public Date getRevocationTime() {
                return new Date(System.currentTimeMillis());
            }

            @Override
            public CRLReason getRevocationReason() {
                return CRLReason.lookup(CRLReason.unspecified);
            }
        };
    }

    private static OCSPRevocationStatus singleResponseToRevocationStatus(final SingleResp singleResponse) throws CertPathValidatorException {
        final CertificateStatus certStatus = singleResponse.getCertStatus();

        int revocationReason = CRLReason.unspecified;
        Date revocationTime = null;
        RevocationStatus status = RevocationStatus.UNKNOWN;
        if (certStatus == CertificateStatus.GOOD) {
            status = RevocationStatus.GOOD;
        } else if (certStatus instanceof RevokedStatus) {
            RevokedStatus revoked = (RevokedStatus)certStatus;
            revocationTime = revoked.getRevocationTime();
            status = RevocationStatus.REVOKED;
            if (revoked.hasRevocationReason()) {
                revocationReason = revoked.getRevocationReason();
            }
        } else if (certStatus instanceof UnknownStatus) {
            status = RevocationStatus.UNKNOWN;
        } else {
            throw new CertPathValidatorException("Unrecognized revocation status received from OCSP.");
        }

        final RevocationStatus finalStatus = status;
        final Date finalRevocationTime = revocationTime;
        final int finalRevocationReason = revocationReason;
        return new OCSPRevocationStatus() {
            @Override
            public RevocationStatus getRevocationStatus() {
                return finalStatus;
            }

            @Override
            public Date getRevocationTime() {
                return finalRevocationTime;
            }

            @Override
            public CRLReason getRevocationReason() {
                return CRLReason.lookup(finalRevocationReason);
            }
        };
    }


    /**
     * Extracts OCSP responder URI from X509 AIA v3 extension, if available. There can be
     * multiple responder URIs encoded in the certificate.
     * @param cert
     * @return a list of available responder URIs.
     * @throws CertificateEncodingException
     */
    private static List<String> getResponderURIs(X509Certificate cert) throws CertificateEncodingException {

        LinkedList<String> responderURIs = new LinkedList<>();
        JcaX509CertificateHolder holder = new JcaX509CertificateHolder(cert);
        Extension aia = holder.getExtension(Extension.authorityInfoAccess);
        if (aia != null) {
            try {
                ASN1InputStream in = new ASN1InputStream(aia.getExtnValue().getOctetStream());
                ASN1Sequence seq = (ASN1Sequence)in.readObject();
                AuthorityInformationAccess authorityInfoAccess = AuthorityInformationAccess.getInstance(seq);
                for (AccessDescription ad : authorityInfoAccess.getAccessDescriptions()) {
                    if (ad.getAccessMethod().equals(AccessDescription.id_ad_ocsp)) {
                        // See https://www.ietf.org/rfc/rfc2560.txt, 3.1 Certificate Content
                        if (ad.getAccessLocation().getTagNo() == GeneralName.uniformResourceIdentifier) {
                            DERIA5String value = DERIA5String.getInstance(ad.getAccessLocation().getName());
                            responderURIs.add(value.getString());
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responderURIs;
    }
}
