/*
* The contents of this file are subject to the Common Public License Version 1.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.eclipse.org/legal/cpl-v10.html
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR APARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*  Copyright (C) 2017 Donovan Lampa <donovan.lampa@gmail.com>
*  Copyright (C) 2009-2017 The JRuby Team
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
*
*
* JRuby-OpenSSL includes software by The Legion of the Bouncy Castle Inc.
* Please, visit (http://bouncycastle.org/license.html) for licensing details.
*/
package org.jruby.ext.openssl;

import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
import org.bouncycastle.asn1.ocsp.CertID;
import org.bouncycastle.asn1.ocsp.CertStatus;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.ocsp.ResponderID;
import org.bouncycastle.asn1.ocsp.RevokedInfo;
import org.bouncycastle.asn1.ocsp.SingleResponse;
import org.bouncycastle.asn1.x509.CRLReason;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.RespID;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyInteger;
import org.jruby.RubyModule;
import org.jruby.RubyObject;
import org.jruby.RubyString;
import org.jruby.RubyTime;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.ext.openssl.impl.ASN1Registry;
import org.jruby.ext.openssl.x509store.X509AuxCertificate;
import org.jruby.ext.openssl.x509store.X509Utils;
import org.jruby.runtime.Arity;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

import static org.jruby.ext.openssl.Digest._Digest;
import static org.jruby.ext.openssl.OCSP._OCSP;
import static org.jruby.ext.openssl.OCSP.newOCSPError;
import static org.jruby.ext.openssl.X509._X509;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateParsingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

/*
 * An OpenSSL::OCSP::BasicResponse contains the status of a certificate
 * check which is created from an OpenSSL::OCSP::Request. 
 * A BasicResponse is more detailed than a Response.
 * 
 * @author lampad
 */
public class OCSPBasicResponse extends RubyObject {
    private static final long serialVersionUID = 8755480816625884227L;

    private static final String OCSP_NOCERTS = "NOCERTS";
    private static final String OCSP_NOCHAIN = "NOCHAIN";
    private static final String OCSP_NOCHECKS = "NOCHECKS";
    private static final String OCSP_NOTIME = "NOTIME";
    private static final String OCSP_NOSIGS = "NOSIGS";
    private static final String OCSP_NOVERIFY = "NOVERIFY";
    private static final String OCSP_NOINTERN = "NOINTERN";
    private static final String OCSP_RESPID_KEY = "RESPID_KEY";
    private static final String OCSP_TRUSTOTHER = "TRUSTOTHER";
    
    private static ObjectAllocator BASICRESPONSE_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new OCSPBasicResponse(runtime, klass);
        }
    };
    
    public static void createBasicResponse(final Ruby runtime, final RubyModule OCSP) {
        RubyClass BasicResponse = OCSP.defineClassUnder("BasicResponse", runtime.getObject(), BASICRESPONSE_ALLOCATOR);
        BasicResponse.defineAnnotatedMethods(OCSPBasicResponse.class);
    }
    
    private byte[] nonce;
    private List<OCSPSingleResponse> singleResponses = new ArrayList<OCSPSingleResponse>();
    private BasicOCSPResponse asn1BCBasicOCSPResp;
    private List<Extension> extensions = new ArrayList<Extension>();

    public OCSPBasicResponse(Ruby runtime, RubyClass metaClass) {
        super(runtime, metaClass);
    }
    
    public OCSPBasicResponse(Ruby runtime) {
        this(runtime, (RubyClass) _OCSP(runtime).getConstantAt("BasicResponse"));
    }
    
    @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE)
    public IRubyObject initialize(final ThreadContext context, IRubyObject der) {        
        if (der == null || der.isNil()) return this;
        
        asn1BCBasicOCSPResp = BasicOCSPResponse.getInstance(StringHelper.readPossibleDERInput(context, der).getBytes());
        
        return this;
    }
    
    @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE)
    public IRubyObject initialize(final ThreadContext context) {
        return this;
    }
    
    @Override
    @JRubyMethod(name = "initialize_copy", visibility = Visibility.PRIVATE)
    public IRubyObject initialize_copy(IRubyObject obj) {
        if ( this == obj ) return this;

        checkFrozen();
        this.asn1BCBasicOCSPResp = ((OCSPBasicResponse)obj).getASN1BCOCSPResp();
        return this;
    }
    
    @JRubyMethod(name = "add_nonce", rest = true)
    public OCSPBasicResponse add_nonce(IRubyObject[] args) {
        Ruby runtime = getRuntime();
                
        byte[] tmpNonce;
        if ( Arity.checkArgumentCount(runtime, args, 0, 1) == 0 ) {
            tmpNonce = generateNonce();
        }
        else {
            RubyString input = (RubyString)args[0];
            tmpNonce = input.getBytes();
        }
                
        extensions.add(new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, tmpNonce));
        nonce = tmpNonce;
        
        return this;
    }
    
    @JRubyMethod(name = "add_status", rest = true)
    public OCSPBasicResponse add_status(final ThreadContext context, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        Arity.checkArgumentCount(runtime, args, 7, 7);
                
        IRubyObject certificateId = args[0];
        IRubyObject status = args[1];
        IRubyObject reason = args[2];
        IRubyObject revocation_time = args[3];
        IRubyObject this_update = args[4];
        IRubyObject next_update = args[5];
        IRubyObject extensions = args[6];
        
        CertStatus certStatus = null;
        switch (RubyFixnum.fix2int((RubyFixnum)status)) {
            case 0 : 
                certStatus = new CertStatus();
                break;
            case 1 :
                ASN1GeneralizedTime revTime = rubyIntOrTimeToGenTime(revocation_time);
                RevokedInfo revokedInfo = new RevokedInfo(revTime,
                        CRLReason.lookup(RubyFixnum.fix2int((RubyFixnum)reason)));
                certStatus = new CertStatus(revokedInfo);
                break;
            case 2 :
                certStatus = new CertStatus(2, DERNull.INSTANCE);
                break;
            default :
                break;
        }
        
        ASN1GeneralizedTime thisUpdate = rubyIntOrTimeToGenTime(this_update);
        ASN1GeneralizedTime nextUpdate = rubyIntOrTimeToGenTime(next_update);
        Extensions singleExtensions = convertRubyExtensions(extensions);
        CertID certID = ((OCSPCertificateId)certificateId).getCertID();
        
        SingleResponse ocspSingleResp = new SingleResponse(certID, certStatus, thisUpdate, nextUpdate, singleExtensions);
        OCSPSingleResponse rubySingleResp = new OCSPSingleResponse(runtime);
        try {
            rubySingleResp.initialize(context, RubyString.newString(runtime, ocspSingleResp.getEncoded()));
            singleResponses.add(rubySingleResp);
        }
        catch (IOException e) {
            throw newOCSPError(runtime, e);
        }
        
        return this;
    }
    
    @JRubyMethod(name = "copy_nonce")
    public IRubyObject copy_nonce(final ThreadContext context, IRubyObject request) {
        add_nonce(new IRubyObject[] {RubyString.newString(getRuntime(), ((OCSPRequest)request).getNonce())});
        return RubyFixnum.one(context.getRuntime());
    }
    
    @JRubyMethod(name = "find_response")
    public IRubyObject find_response(final ThreadContext context, IRubyObject certId) {
        if (certId.isNil()) return context.nil;
        OCSPCertificateId rubyCertId = (OCSPCertificateId)certId;
        IRubyObject retResp = context.nil;
        for (OCSPSingleResponse singleResp : singleResponses) {
            CertID thisId = rubyCertId.getCertID();
            CertID thatId = singleResp.getBCSingleResp().getCertID();
            if (thisId.equals(thatId)) {
                retResp = singleResp;
                break;
            }
        }
        
        return retResp;
    }
    
    @JRubyMethod(name = "responses")
    public IRubyObject responses() {
        return RubyArray.newArray(getRuntime(), singleResponses);
    }
    
    @JRubyMethod(name = "sign", rest = true)
    public IRubyObject sign(final ThreadContext context, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        
        int flag = 0;
        IRubyObject additionalCerts = context.nil;
        IRubyObject flags = context.nil;
        IRubyObject digest = context.nil;
        Digest digestInstance = new Digest(runtime, _Digest(runtime));
        List<X509CertificateHolder> addlCerts = new ArrayList<X509CertificateHolder>();
        
        switch (Arity.checkArgumentCount(runtime, args, 2, 5)) {
            case 3 :
                additionalCerts = args[2];
                break;
            case 4 :
                additionalCerts = args[2];
                flags = args[3];
                break;
            case 5 :
                additionalCerts = args[2];
                flags = args[3];
                digest = args[4];
                break;
            default :
                break;       
        }
                        
        if (digest.isNil()) digest = digestInstance.initialize(context, new IRubyObject[] { RubyString.newString(runtime, "SHA1") });
        if (!flags.isNil()) flag = RubyFixnum.fix2int(flags);
        if (additionalCerts.isNil()) flag |= RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_NOCERTS));
                        
        X509Cert signer = (X509Cert) args[0];
        PKey signerKey = (PKey) args[1];
        
        String keyAlg = signerKey.getAlgorithm();
        String digAlg = ((Digest) digest).getShortAlgorithm();

        JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(digAlg + "with" + keyAlg);
        signerBuilder.setProvider("BC");
        ContentSigner contentSigner = null;
        try {
            contentSigner = signerBuilder.build(signerKey.getPrivateKey());
        }
        catch (OperatorCreationException e) {
            throw newOCSPError(runtime, e);
        }
        
        BasicOCSPRespBuilder respBuilder = null;
        try {
            if ((flag & RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_RESPID_KEY))) != 0) {
                JcaDigestCalculatorProviderBuilder dcpb = new JcaDigestCalculatorProviderBuilder();
                dcpb.setProvider("BC");
                DigestCalculatorProvider dcp = dcpb.build();
                DigestCalculator calculator = dcp.get(contentSigner.getAlgorithmIdentifier());
                respBuilder = new BasicOCSPRespBuilder(SubjectPublicKeyInfo.getInstance(signerKey.getPublicKey().getEncoded()), calculator);
            }
            else {
                respBuilder = new BasicOCSPRespBuilder(new RespID(signer.getSubject().getX500Name()));
            }
        }
        catch (Exception e) {
            throw newOCSPError(runtime, e);
        }
        
        X509CertificateHolder[] chain = null;
        try {
            if ((flag & RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_NOCERTS))) == 0) {
                addlCerts.add(new X509CertificateHolder(signer.getAuxCert().getEncoded()));
                if (!additionalCerts.isNil()) {
                    Iterator<java.security.cert.Certificate> rubyAddlCerts = ((RubyArray)additionalCerts).iterator();
                    while (rubyAddlCerts.hasNext()) {
                        java.security.cert.Certificate cert = rubyAddlCerts.next();
                        addlCerts.add(new X509CertificateHolder(cert.getEncoded()));
                    }
                }
                
                chain = addlCerts.toArray(new X509CertificateHolder[addlCerts.size()]);
            }
        }
        catch (Exception e) {
            throw newOCSPError(runtime, e);
        }
        
        Date producedAt = null;
        if ((flag & RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_NOTIME))) == 0) {
            producedAt = new Date();
        }
        
        for (OCSPSingleResponse resp : singleResponses) {
            SingleResp singleResp = new SingleResp(resp.getBCSingleResp());
            respBuilder.addResponse(singleResp.getCertID(), 
                    singleResp.getCertStatus(), 
                    singleResp.getThisUpdate(), 
                    singleResp.getNextUpdate(), 
                    resp.getBCSingleResp().getSingleExtensions());
        }
        
        try {
            Extension[] respExtAry = new Extension[extensions.size()];
            Extensions respExtensions = new Extensions(extensions.toArray(respExtAry));
            BasicOCSPResp bcBasicOCSPResp = respBuilder.setResponseExtensions(respExtensions).build(contentSigner, chain, producedAt);
            asn1BCBasicOCSPResp = BasicOCSPResponse.getInstance(bcBasicOCSPResp.getEncoded());
        }
        catch (Exception e) {
            throw newOCSPError(runtime, e);
        }
        return this;
    }
    
    @JRubyMethod(name = "verify", rest = true)
    public IRubyObject verify(final ThreadContext context, IRubyObject[] args) {
        Ruby runtime = context.runtime;
        int flags = 0;
        IRubyObject certificates = args[0];
        IRubyObject store = args[1];
        boolean ret = false;
        
        if (Arity.checkArgumentCount(runtime, args, 2, 3) == 3) {
            flags = RubyFixnum.fix2int(args[2]);
        }
        
        JcaContentVerifierProviderBuilder jcacvpb = new JcaContentVerifierProviderBuilder();
        jcacvpb.setProvider("BC");
        BasicOCSPResp basicOCSPResp = getBasicOCSPResp();
        
        java.security.cert.Certificate signer = findSignerCert(context, asn1BCBasicOCSPResp, convertRubyCerts(certificates), flags);
        if ( signer == null ) return RubyBoolean.newBoolean(runtime, false);
        if ( (flags & RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_NOINTERN))) == 0 && 
                (flags & RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_TRUSTOTHER))) != 0 ) {
            flags |= RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_NOVERIFY));
        }
        if ( (flags & RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_NOSIGS))) == 0 ) {
            PublicKey sPKey = signer.getPublicKey();
            if ( sPKey == null ) return RubyBoolean.newBoolean(runtime, false);
            try {
                ContentVerifierProvider cvp = jcacvpb.build(sPKey);
                ret = basicOCSPResp.isSignatureValid(cvp);
            }
            catch (Exception e) {
                throw newOCSPError(runtime, e);
            }
        }
        if ((flags & RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_NOVERIFY))) == 0) {
            List<X509Cert> untrustedCerts = null;
            if ((flags & RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_NOCHAIN))) != 0) {                
            }
            else if (basicOCSPResp.getCerts() != null && (certificates != null && !((RubyArray)certificates).isEmpty())) {
                untrustedCerts = getCertsFromResp();
                
                Iterator<java.security.cert.Certificate> certIt = ((RubyArray)certificates).iterator();
                while (certIt.hasNext()) {
                    try {
                        untrustedCerts.add(X509Cert.wrap(context, certIt.next().getEncoded()));
                    }
                    catch (CertificateEncodingException e) {
                        throw newOCSPError(runtime, e);
                    }
                }
            }
            else {
                untrustedCerts = getCertsFromResp();
            }
            
            RubyArray rUntrustedCerts = RubyArray.newEmptyArray(runtime);
            if (untrustedCerts != null) {
                X509Cert[] rubyCerts = new X509Cert[untrustedCerts.size()];
                rUntrustedCerts = RubyArray.newArray(runtime, untrustedCerts.toArray(rubyCerts));
            }
            X509StoreContext ctx;
            try {
                ctx = X509StoreContext.newStoreContext(context, (X509Store)store, X509Cert.wrap(runtime, signer), rUntrustedCerts);
            }
            catch (CertificateEncodingException e) {
                throw newOCSPError(runtime, e);
            }
            
            ctx.set_purpose(context, _X509(runtime).getConstant("PURPOSE_OCSP_HELPER"));
            ret = ctx.verify(context).isTrue();
            IRubyObject chain = ctx.chain(context);
            
            if ((flags & RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_NOCHECKS))) > 0) {
                ret = true;
            }
            
            try {
                if (checkIssuer(getBasicOCSPResp(), chain)) return RubyBoolean.newBoolean(runtime, true);
            }
            catch (IOException e) {
                throw newOCSPError(runtime, e);
            }
            
            if ((flags & RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_NOCHAIN))) != 0) {
                return RubyBoolean.newBoolean(runtime, ret);
            }
            else {
                X509Cert rootCA = (X509Cert)((RubyArray)chain).last();
                PublicKey rootKey = rootCA.getAuxCert().getPublicKey();
                try {
                    // check if self-signed and valid (trusts itself)
                    rootCA.getAuxCert().verify(rootKey);
                    ret = true;
                }
                catch (Exception e) {
                    ret = false;
                }
            }
        }
        
        return RubyBoolean.newBoolean(runtime, ret);
    }
    
    @JRubyMethod(name = "status")
    public IRubyObject status(ThreadContext context) {
        final Ruby runtime = context.runtime;
        RubyArray ret = RubyArray.newArray(runtime, singleResponses.size());
        
        for (OCSPSingleResponse resp : singleResponses) {
            RubyArray respAry = RubyArray.newArray(runtime, 7);
            
            respAry.append(resp.certid(context));
            respAry.append(resp.cert_status());
            respAry.append(resp.revocation_reason());
            respAry.append(resp.revocation_time());
            respAry.append(resp.this_update());
            respAry.append(resp.next_update());
            respAry.append(resp.extensions());

            ret.add(respAry);
        }
        
        return ret;
    }
    
    @JRubyMethod(name = "to_der")
    public IRubyObject to_der() {
        Ruby runtime = getRuntime();
        IRubyObject ret;
        try {
           ret = RubyString.newString(runtime, asn1BCBasicOCSPResp.getEncoded());
        }
        catch (IOException e) {
            throw newOCSPError(runtime, e);
        }
        
        return ret;
    }
    
    private boolean checkIssuer(BasicOCSPResp basicOCSPResp, IRubyObject chain) throws IOException {
        boolean ret = false;
        if ( ((RubyArray)chain).size() <= 0 ) return false;
        List<SingleResp> singleResponses = Arrays.asList(basicOCSPResp.getResponses());
        CertificateID certId = checkCertIds(singleResponses);
        
        X509Cert signer = (X509Cert)((RubyArray)chain).first();
        if (((RubyArray)chain).size() > 1) {
            X509Cert signerCA = (X509Cert)((RubyArray)chain).entry(1);
            if(matchIssuerId(signerCA, certId, singleResponses)) {
                return checkDelegated(signerCA);
            }
        }
        else {
            ret = matchIssuerId(signer, certId, singleResponses);
        }
        
        return ret;
    }

    private boolean checkDelegated(X509Cert signerCA) {
        try {
            return (signerCA.getAuxCert().getExFlags() & X509Utils.EXFLAG_XKUSAGE) != 0 &&
                    (signerCA.getAuxCert().getExtendedKeyUsage().contains(ASN1Registry.OBJ_OCSP_sign));
        }
        catch (CertificateParsingException e) {
            throw newOCSPError(getRuntime(), e);
        }
    }

    private boolean matchIssuerId(X509Cert signerCA, CertificateID certId, List<SingleResp> singleResponses) throws IOException {
        Ruby runtime = getRuntime();
        if (certId == null) {
            //gotta check em all
            for(SingleResp resp : singleResponses) {
                CertificateID tempId = resp.getCertID();
                if(!matchIssuerId(signerCA, tempId, null)) return false;
            }
            return true;
        }
        else {
            // we have a matching cid
            ASN1ObjectIdentifier alg = certId.getHashAlgOID();
            String sym = ASN1.oid2Sym(runtime, alg);
            MessageDigest md = Digest.getDigest(runtime, sym);
            byte[] issuerNameDigest = md.digest(signerCA.getIssuer().getX500Name().getEncoded());
            byte[] issuerKeyDigest = md.digest(signerCA.getAuxCert().getPublicKey().getEncoded());
            if(!issuerNameDigest.equals(certId.getIssuerNameHash())) return false;
            if(!issuerKeyDigest.equals(certId.getIssuerKeyHash())) return false;
            return true;
        }
    }

    private CertificateID checkCertIds(List<SingleResp> singleResponses) {
        ArrayList<SingleResp> ary = new ArrayList<SingleResp>(singleResponses);
        CertificateID cid = ary.remove(0).getCertID();
        
        for (SingleResp singleResp : ary) {
            if (!cid.equals(singleResp.getCertID())) return null;
        }
        
        return cid;
    }

    public BasicOCSPResponse getASN1BCOCSPResp() {
        return this.asn1BCBasicOCSPResp;
    }
    
    public byte[] getNonce() {
        return this.nonce;
    }
        
    private byte[] generateNonce() {
        // OSSL currently generates 16 byte nonce by default
        return generateNonce(new byte[16]);
    }
    
    private byte[] generateNonce(byte[] bytes) {
        OpenSSL.getSecureRandom(getRuntime()).nextBytes(bytes);
        return bytes;
    }
    
    private ASN1GeneralizedTime rubyIntOrTimeToGenTime(IRubyObject intOrTime) {
        if (intOrTime.isNil()) return null;
        Date retTime = new Date();
        if (intOrTime instanceof RubyInteger) {
            retTime.setTime(retTime.getTime() + RubyFixnum.fix2int((RubyFixnum)intOrTime)*1000);
        }
        else if (intOrTime instanceof RubyTime) {
            retTime = ((RubyTime)intOrTime).getJavaDate();
        }
        else {
            throw Utils.newArgumentError(
                    getRuntime(), new IllegalArgumentException("Unknown Revocation Time class: " + intOrTime.getClass().getName())
                    );
        }
        
        return new ASN1GeneralizedTime(retTime);
    }
    
    private Extensions convertRubyExtensions(IRubyObject extensions) {
        if (extensions.isNil()) return null;
        List<Extension> retExtensions = new ArrayList<Extension>();
        Iterator<IRubyObject> rubyExtensions = ((RubyArray)extensions).iterator();
        while (rubyExtensions.hasNext()) {
            X509Extension rubyExt = (X509Extension)rubyExtensions.next();
            Extension ext = Extension.getInstance(((RubyString)rubyExt.to_der()).getBytes());
            retExtensions.add(ext);
        }
        Extension[] exts = new Extension[retExtensions.size()];
        retExtensions.toArray(exts);
        return new Extensions(exts);
    }
    
    private List<java.security.cert.Certificate> convertRubyCerts(IRubyObject certificates) {
        Iterator<java.security.cert.Certificate> it = ((RubyArray)certificates).iterator();
        List<java.security.cert.Certificate> ret = new ArrayList<java.security.cert.Certificate>();
        while (it.hasNext()) {
            ret.add(it.next());
        }
        
        return ret;
    }
    
    private java.security.cert.Certificate findSignerCert(final ThreadContext context,
        BasicOCSPResponse basicResp, List<java.security.cert.Certificate> certificates, int flags) {
        final Ruby runtime = context.runtime;
        ResponderID respID = basicResp.getTbsResponseData().getResponderID();
        java.security.cert.Certificate ret;
        ret = findSignerByRespId(context, certificates, respID);
        
        if (ret == null && (flags & RubyFixnum.fix2int((RubyFixnum)_OCSP(runtime).getConstant(OCSP_NOINTERN))) == 0) {
            List<X509AuxCertificate> javaCerts = new ArrayList<X509AuxCertificate>();
            for (X509CertificateHolder cert : getBasicOCSPResp().getCerts()) {
                try {
                    javaCerts.add(X509Cert.wrap(context, cert.getEncoded()).getAuxCert());
                }
                catch (IOException e) {
                    throw newOCSPError(runtime, e);
                }
            }
            ret = findSignerByRespId(context, javaCerts, respID);
        }
        
        return ret;
    }
    
    private java.security.cert.Certificate findSignerByRespId(final ThreadContext context, List<? extends java.security.cert.Certificate> certificates, ResponderID respID) {
        if (respID.getName() != null) {
            for (java.security.cert.Certificate cert : certificates) {
                try {
                    X509Cert rubyCert = X509Cert.wrap(context, cert);
                    if (rubyCert.getSubject().getX500Name().equals(respID.getName())) return cert;
                }
                catch (CertificateEncodingException e) {
                    throw newOCSPError(context.runtime, e);
                }
            }
        }
        else {
            // Ignore anything that's not SHA1 (weirdly) SHA_DIGEST_LENGTH == 20
            if (respID.getKeyHash().length != 20) return null;
            for (java.security.cert.Certificate cert : certificates) {
                byte[] pubKeyDigest = Digest.digest(
                        context,
                        this,
                        RubyString.newString(context.runtime, "SHA1"),
                        RubyString.newString(context.runtime, cert.getPublicKey().getEncoded())
                        ).getBytes();
                if (respID.getKeyHash().equals(pubKeyDigest)) return cert;
            }
        }
        return null;
    }

    private List<X509Cert> getCertsFromResp() {
        Ruby runtime = getRuntime();
        ThreadContext context = runtime.getCurrentContext();
        List<X509Cert> retCerts = new ArrayList<X509Cert>();
        List<X509CertificateHolder> respCerts = Arrays.asList(getBasicOCSPResp().getCerts());
        for (X509CertificateHolder cert : respCerts) {        
            try {
                retCerts.add(X509Cert.wrap(context, cert.getEncoded()));
            }
            catch (IOException e) {
                throw newOCSPError(runtime, e);
            }                    
        }
        
        return retCerts;
    }

    
    private BasicOCSPResp getBasicOCSPResp() {
        return new BasicOCSPResp(asn1BCBasicOCSPResp);
    }
}
