/*
 * 'OpenSSL for Ruby' project
 * Copyright (C) 2003  Michal Rokos <m.rokos@sh.cvut.cz>
 * Copyright (C) 2003  GOTOU Yuuzou <gotoyuzo@notwork.org>
 * All rights reserved.
 */
/*
 * This program is licensed under the same licence as Ruby.
 * (See the file 'LICENCE'.)
 */
#include "ossl.h"

#if !defined(OPENSSL_NO_OCSP)

#define NewOCSPReq(klass) \
    TypedData_Wrap_Struct((klass), &ossl_ocsp_request_type, 0)
#define SetOCSPReq(obj, req) do { \
    if(!(req)) ossl_raise(rb_eRuntimeError, "Request wasn't initialized!"); \
    RTYPEDDATA_DATA(obj) = (req); \
} while (0)
#define GetOCSPReq(obj, req) do { \
    TypedData_Get_Struct((obj), OCSP_REQUEST, &ossl_ocsp_request_type, (req)); \
    if(!(req)) ossl_raise(rb_eRuntimeError, "Request wasn't initialized!"); \
} while (0)
#define SafeGetOCSPReq(obj, req) do { \
    OSSL_Check_Kind((obj), cOCSPReq); \
    GetOCSPReq((obj), (req)); \
} while (0)

#define NewOCSPRes(klass) \
    TypedData_Wrap_Struct((klass), &ossl_ocsp_response_type, 0)
#define SetOCSPRes(obj, res) do { \
    if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \
    RTYPEDDATA_DATA(obj) = (res); \
} while (0)
#define GetOCSPRes(obj, res) do { \
    TypedData_Get_Struct((obj), OCSP_RESPONSE, &ossl_ocsp_response_type, (res)); \
    if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \
} while (0)
#define SafeGetOCSPRes(obj, res) do { \
    OSSL_Check_Kind((obj), cOCSPRes); \
    GetOCSPRes((obj), (res)); \
} while (0)

#define NewOCSPBasicRes(klass) \
    TypedData_Wrap_Struct((klass), &ossl_ocsp_basicresp_type, 0)
#define SetOCSPBasicRes(obj, res) do { \
    if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \
    RTYPEDDATA_DATA(obj) = (res); \
} while (0)
#define GetOCSPBasicRes(obj, res) do { \
    TypedData_Get_Struct((obj), OCSP_BASICRESP, &ossl_ocsp_basicresp_type, (res)); \
    if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \
} while (0)
#define SafeGetOCSPBasicRes(obj, res) do { \
    OSSL_Check_Kind((obj), cOCSPBasicRes); \
    GetOCSPBasicRes((obj), (res)); \
} while (0)

#define NewOCSPSingleRes(klass) \
    TypedData_Wrap_Struct((klass), &ossl_ocsp_singleresp_type, 0)
#define SetOCSPSingleRes(obj, res) do { \
    if(!(res)) ossl_raise(rb_eRuntimeError, "SingleResponse wasn't initialized!"); \
    RTYPEDDATA_DATA(obj) = (res); \
} while (0)
#define GetOCSPSingleRes(obj, res) do { \
    TypedData_Get_Struct((obj), OCSP_SINGLERESP, &ossl_ocsp_singleresp_type, (res)); \
    if(!(res)) ossl_raise(rb_eRuntimeError, "SingleResponse wasn't initialized!"); \
} while (0)
#define SafeGetOCSPSingleRes(obj, res) do { \
    OSSL_Check_Kind((obj), cOCSPSingleRes); \
    GetOCSPSingleRes((obj), (res)); \
} while (0)

#define NewOCSPCertId(klass) \
    TypedData_Wrap_Struct((klass), &ossl_ocsp_certid_type, 0)
#define SetOCSPCertId(obj, cid) do { \
    if(!(cid)) ossl_raise(rb_eRuntimeError, "Cert ID wasn't initialized!"); \
    RTYPEDDATA_DATA(obj) = (cid); \
} while (0)
#define GetOCSPCertId(obj, cid) do { \
    TypedData_Get_Struct((obj), OCSP_CERTID, &ossl_ocsp_certid_type, (cid)); \
    if(!(cid)) ossl_raise(rb_eRuntimeError, "Cert ID wasn't initialized!"); \
} while (0)
#define SafeGetOCSPCertId(obj, cid) do { \
    OSSL_Check_Kind((obj), cOCSPCertId); \
    GetOCSPCertId((obj), (cid)); \
} while (0)

VALUE mOCSP;
VALUE eOCSPError;
VALUE cOCSPReq;
VALUE cOCSPRes;
VALUE cOCSPBasicRes;
VALUE cOCSPSingleRes;
VALUE cOCSPCertId;

static void
ossl_ocsp_request_free(void *ptr)
{
    OCSP_REQUEST_free(ptr);
}

static const rb_data_type_t ossl_ocsp_request_type = {
    "OpenSSL/OCSP/REQUEST",
    {
	0, ossl_ocsp_request_free,
    },
    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
};

static void
ossl_ocsp_response_free(void *ptr)
{
    OCSP_RESPONSE_free(ptr);
}

static const rb_data_type_t ossl_ocsp_response_type = {
    "OpenSSL/OCSP/RESPONSE",
    {
	0, ossl_ocsp_response_free,
    },
    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
};

static void
ossl_ocsp_basicresp_free(void *ptr)
{
    OCSP_BASICRESP_free(ptr);
}

static const rb_data_type_t ossl_ocsp_basicresp_type = {
    "OpenSSL/OCSP/BASICRESP",
    {
	0, ossl_ocsp_basicresp_free,
    },
    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
};

static void
ossl_ocsp_singleresp_free(void *ptr)
{
    OCSP_SINGLERESP_free(ptr);
}

static const rb_data_type_t ossl_ocsp_singleresp_type = {
    "OpenSSL/OCSP/SINGLERESP",
    {
	0, ossl_ocsp_singleresp_free,
    },
    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
};

static void
ossl_ocsp_certid_free(void *ptr)
{
    OCSP_CERTID_free(ptr);
}

static const rb_data_type_t ossl_ocsp_certid_type = {
    "OpenSSL/OCSP/CERTID",
    {
	0, ossl_ocsp_certid_free,
    },
    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
};

/*
 * Public
 */
static VALUE
ossl_ocspcertid_new(OCSP_CERTID *cid)
{
    VALUE obj = NewOCSPCertId(cOCSPCertId);
    SetOCSPCertId(obj, cid);
    return obj;
}

/*
 * OCSP::Resquest
 */
static VALUE
ossl_ocspreq_alloc(VALUE klass)
{
    OCSP_REQUEST *req;
    VALUE obj;

    obj = NewOCSPReq(klass);
    if (!(req = OCSP_REQUEST_new()))
	ossl_raise(eOCSPError, NULL);
    SetOCSPReq(obj, req);

    return obj;
}

static VALUE
ossl_ocspreq_initialize_copy(VALUE self, VALUE other)
{
    OCSP_REQUEST *req, *req_old, *req_new;

    rb_check_frozen(self);
    GetOCSPReq(self, req_old);
    SafeGetOCSPReq(other, req);

    req_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_REQUEST), req);
    if (!req_new)
	ossl_raise(eOCSPError, "ASN1_item_dup");

    SetOCSPReq(self, req_new);
    OCSP_REQUEST_free(req_old);

    return self;
}

/*
 * call-seq:
 *   OpenSSL::OCSP::Request.new              -> request
 *   OpenSSL::OCSP::Request.new(request_der) -> request
 *
 * Creates a new OpenSSL::OCSP::Request.  The request may be created empty or
 * from a +request_der+ string.
 */

static VALUE
ossl_ocspreq_initialize(int argc, VALUE *argv, VALUE self)
{
    VALUE arg;
    OCSP_REQUEST *req, *req_new;
    const unsigned char *p;

    rb_scan_args(argc, argv, "01", &arg);
    if(!NIL_P(arg)){
	GetOCSPReq(self, req);
	arg = ossl_to_der_if_possible(arg);
	StringValue(arg);
	p = (unsigned char *)RSTRING_PTR(arg);
	req_new = d2i_OCSP_REQUEST(NULL, &p, RSTRING_LEN(arg));
	if (!req_new)
	    ossl_raise(eOCSPError, "d2i_OCSP_REQUEST");
	SetOCSPReq(self, req_new);
	OCSP_REQUEST_free(req);
    }

    return self;
}

/*
 * call-seq:
 *   request.add_nonce(nonce = nil) -> request
 *
 * Adds a +nonce+ to the OCSP request.  If no nonce is given a random one will
 * be generated.
 *
 * The nonce is used to prevent replay attacks but some servers do not support
 * it.
 */

static VALUE
ossl_ocspreq_add_nonce(int argc, VALUE *argv, VALUE self)
{
    OCSP_REQUEST *req;
    VALUE val;
    int ret;

    rb_scan_args(argc, argv, "01", &val);
    if(NIL_P(val)) {
	GetOCSPReq(self, req);
	ret = OCSP_request_add1_nonce(req, NULL, -1);
    }
    else{
	StringValue(val);
	GetOCSPReq(self, req);
	ret = OCSP_request_add1_nonce(req, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val));
    }
    if(!ret) ossl_raise(eOCSPError, NULL);

    return self;
}

/*
 * call-seq:
 *   request.check_nonce(response) -> result
 *
 * Checks the nonce validity for this request and +response+.
 *
 * The return value is one of the following:
 *
 * -1 :: nonce in request only.
 *  0 :: nonces both present and not equal.
 *  1 :: nonces present and equal.
 *  2 :: nonces both absent.
 *  3 :: nonce present in response only.
 *
 * For most responses, clients can check +result+ > 0.  If a responder doesn't
 * handle nonces <code>result.nonzero?</code> may be necessary.  A result of
 * <code>0</code> is always an error.
 */

static VALUE
ossl_ocspreq_check_nonce(VALUE self, VALUE basic_resp)
{
    OCSP_REQUEST *req;
    OCSP_BASICRESP *bs;
    int res;

    GetOCSPReq(self, req);
    SafeGetOCSPBasicRes(basic_resp, bs);
    res = OCSP_check_nonce(req, bs);

    return INT2NUM(res);
}

/*
 * call-seq:
 *   request.add_certid(certificate_id) -> request
 *
 * Adds +certificate_id+ to the request.
 */

static VALUE
ossl_ocspreq_add_certid(VALUE self, VALUE certid)
{
    OCSP_REQUEST *req;
    OCSP_CERTID *id, *id_new;

    GetOCSPReq(self, req);
    GetOCSPCertId(certid, id);

    if (!(id_new = OCSP_CERTID_dup(id)))
	ossl_raise(eOCSPError, "OCSP_CERTID_dup");
    if (!OCSP_request_add0_id(req, id_new)) {
	OCSP_CERTID_free(id_new);
	ossl_raise(eOCSPError, "OCSP_request_add0_id");
    }

    return self;
}

/*
 * call-seq:
 *   request.certid -> [certificate_id, ...]
 *
 * Returns all certificate IDs in this request.
 */

static VALUE
ossl_ocspreq_get_certid(VALUE self)
{
    OCSP_REQUEST *req;
    OCSP_ONEREQ *one;
    OCSP_CERTID *id;
    VALUE ary, tmp;
    int i, count;

    GetOCSPReq(self, req);
    count = OCSP_request_onereq_count(req);
    ary = (count > 0) ? rb_ary_new() : Qnil;
    for(i = 0; i < count; i++){
	one = OCSP_request_onereq_get0(req, i);
	tmp = NewOCSPCertId(cOCSPCertId);
	if(!(id = OCSP_CERTID_dup(OCSP_onereq_get0_id(one))))
	    ossl_raise(eOCSPError, NULL);
	SetOCSPCertId(tmp, id);
	rb_ary_push(ary, tmp);
    }

    return ary;
}

/*
 * call-seq:
 *   request.sign(cert, key, certs = nil, flags = 0, digest = nil) -> self
 *
 * Signs this OCSP request using +cert+, +key+ and optional +digest+. If
 * +digest+ is not specified, SHA-1 is used. +certs+ is an optional Array of
 * additional certificates which are included in the request in addition to
 * the signer certificate. Note that if +certs+ is nil or not given, flag
 * OpenSSL::OCSP::NOCERTS is enabled. Pass an empty array to include only the
 * signer certificate.
 *
 * +flags+ can be a bitwise OR of the following constants:
 *
 * OpenSSL::OCSP::NOCERTS::
 *   Don't include any certificates in the request. +certs+ will be ignored.
 */
static VALUE
ossl_ocspreq_sign(int argc, VALUE *argv, VALUE self)
{
    VALUE signer_cert, signer_key, certs, flags, digest;
    OCSP_REQUEST *req;
    X509 *signer;
    EVP_PKEY *key;
    STACK_OF(X509) *x509s = NULL;
    unsigned long flg = 0;
    const EVP_MD *md;
    int ret;

    rb_scan_args(argc, argv, "23", &signer_cert, &signer_key, &certs, &flags, &digest);
    GetOCSPReq(self, req);
    signer = GetX509CertPtr(signer_cert);
    key = GetPrivPKeyPtr(signer_key);
    if (!NIL_P(flags))
	flg = NUM2INT(flags);
    if (NIL_P(digest))
	md = EVP_sha1();
    else
	md = GetDigestPtr(digest);
    if (NIL_P(certs))
	flg |= OCSP_NOCERTS;
    else
	x509s = ossl_x509_ary2sk(certs);

    ret = OCSP_request_sign(req, signer, key, md, x509s, flg);
    sk_X509_pop_free(x509s, X509_free);
    if (!ret) ossl_raise(eOCSPError, NULL);

    return self;
}

/*
 * call-seq:
 *   request.verify(certificates, store, flags = 0) -> true or false
 *
 * Verifies this request using the given +certificates+ and +store+.
 * +certificates+ is an array of OpenSSL::X509::Certificate, +store+ is an
 * OpenSSL::X509::Store.
 */

static VALUE
ossl_ocspreq_verify(int argc, VALUE *argv, VALUE self)
{
    VALUE certs, store, flags;
    OCSP_REQUEST *req;
    STACK_OF(X509) *x509s;
    X509_STORE *x509st;
    int flg, result;

    rb_scan_args(argc, argv, "21", &certs, &store, &flags);
    GetOCSPReq(self, req);
    x509st = GetX509StorePtr(store);
    flg = NIL_P(flags) ? 0 : NUM2INT(flags);
    x509s = ossl_x509_ary2sk(certs);
    result = OCSP_request_verify(req, x509s, x509st, flg);
    sk_X509_pop_free(x509s, X509_free);
    if (result <= 0)
	ossl_clear_error();

    return result > 0 ? Qtrue : Qfalse;
}

/*
 * Returns this request as a DER-encoded string
 */

static VALUE
ossl_ocspreq_to_der(VALUE self)
{
    OCSP_REQUEST *req;
    VALUE str;
    unsigned char *p;
    long len;

    GetOCSPReq(self, req);
    if((len = i2d_OCSP_REQUEST(req, NULL)) <= 0)
	ossl_raise(eOCSPError, NULL);
    str = rb_str_new(0, len);
    p = (unsigned char *)RSTRING_PTR(str);
    if(i2d_OCSP_REQUEST(req, &p) <= 0)
	ossl_raise(eOCSPError, NULL);
    ossl_str_adjust(str, p);

    return str;
}

/*
 * OCSP::Response
 */

/* call-seq:
 *   OpenSSL::OCSP::Response.create(status, basic_response = nil) -> response
 *
 * Creates an OpenSSL::OCSP::Response from +status+ and +basic_response+.
 */

static VALUE
ossl_ocspres_s_create(VALUE klass, VALUE status, VALUE basic_resp)
{
    OCSP_BASICRESP *bs;
    OCSP_RESPONSE *res;
    VALUE obj;
    int st = NUM2INT(status);

    if(NIL_P(basic_resp)) bs = NULL;
    else GetOCSPBasicRes(basic_resp, bs); /* NO NEED TO DUP */
    obj = NewOCSPRes(klass);
    if(!(res = OCSP_response_create(st, bs)))
	ossl_raise(eOCSPError, NULL);
    SetOCSPRes(obj, res);

    return obj;
}

static VALUE
ossl_ocspres_alloc(VALUE klass)
{
    OCSP_RESPONSE *res;
    VALUE obj;

    obj = NewOCSPRes(klass);
    if(!(res = OCSP_RESPONSE_new()))
	ossl_raise(eOCSPError, NULL);
    SetOCSPRes(obj, res);

    return obj;
}

static VALUE
ossl_ocspres_initialize_copy(VALUE self, VALUE other)
{
    OCSP_RESPONSE *res, *res_old, *res_new;

    rb_check_frozen(self);
    GetOCSPRes(self, res_old);
    SafeGetOCSPRes(other, res);

    res_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_RESPONSE), res);
    if (!res_new)
	ossl_raise(eOCSPError, "ASN1_item_dup");

    SetOCSPRes(self, res_new);
    OCSP_RESPONSE_free(res_old);

    return self;
}

/*
 * call-seq:
 *   OpenSSL::OCSP::Response.new               -> response
 *   OpenSSL::OCSP::Response.new(response_der) -> response
 *
 * Creates a new OpenSSL::OCSP::Response.  The response may be created empty or
 * from a +response_der+ string.
 */

static VALUE
ossl_ocspres_initialize(int argc, VALUE *argv, VALUE self)
{
    VALUE arg;
    OCSP_RESPONSE *res, *res_new;
    const unsigned char *p;

    rb_scan_args(argc, argv, "01", &arg);
    if(!NIL_P(arg)){
	GetOCSPRes(self, res);
	arg = ossl_to_der_if_possible(arg);
	StringValue(arg);
	p = (unsigned char *)RSTRING_PTR(arg);
	res_new = d2i_OCSP_RESPONSE(NULL, &p, RSTRING_LEN(arg));
	if (!res_new)
	    ossl_raise(eOCSPError, "d2i_OCSP_RESPONSE");
	SetOCSPRes(self, res_new);
	OCSP_RESPONSE_free(res);
    }

    return self;
}

/*
 * call-seq:
 *   response.status -> Integer
 *
 * Returns the status of the response.
 */

static VALUE
ossl_ocspres_status(VALUE self)
{
    OCSP_RESPONSE *res;
    int st;

    GetOCSPRes(self, res);
    st = OCSP_response_status(res);

    return INT2NUM(st);
}

/*
 * call-seq:
 *   response.status_string -> String
 *
 * Returns a status string for the response.
 */

static VALUE
ossl_ocspres_status_string(VALUE self)
{
    OCSP_RESPONSE *res;
    int st;

    GetOCSPRes(self, res);
    st = OCSP_response_status(res);

    return rb_str_new2(OCSP_response_status_str(st));
}

/*
 * call-seq:
 *   response.basic
 *
 * Returns a BasicResponse for this response
 */

static VALUE
ossl_ocspres_get_basic(VALUE self)
{
    OCSP_RESPONSE *res;
    OCSP_BASICRESP *bs;
    VALUE ret;

    GetOCSPRes(self, res);
    ret = NewOCSPBasicRes(cOCSPBasicRes);
    if(!(bs = OCSP_response_get1_basic(res)))
	return Qnil;
    SetOCSPBasicRes(ret, bs);

    return ret;
}

/*
 * call-seq:
 *   response.to_der -> String
 *
 * Returns this response as a DER-encoded string.
 */

static VALUE
ossl_ocspres_to_der(VALUE self)
{
    OCSP_RESPONSE *res;
    VALUE str;
    long len;
    unsigned char *p;

    GetOCSPRes(self, res);
    if((len = i2d_OCSP_RESPONSE(res, NULL)) <= 0)
	ossl_raise(eOCSPError, NULL);
    str = rb_str_new(0, len);
    p = (unsigned char *)RSTRING_PTR(str);
    if(i2d_OCSP_RESPONSE(res, &p) <= 0)
	ossl_raise(eOCSPError, NULL);
    ossl_str_adjust(str, p);

    return str;
}

/*
 * OCSP::BasicResponse
 */
static VALUE
ossl_ocspbres_alloc(VALUE klass)
{
    OCSP_BASICRESP *bs;
    VALUE obj;

    obj = NewOCSPBasicRes(klass);
    if(!(bs = OCSP_BASICRESP_new()))
	ossl_raise(eOCSPError, NULL);
    SetOCSPBasicRes(obj, bs);

    return obj;
}

static VALUE
ossl_ocspbres_initialize_copy(VALUE self, VALUE other)
{
    OCSP_BASICRESP *bs, *bs_old, *bs_new;

    rb_check_frozen(self);
    GetOCSPBasicRes(self, bs_old);
    SafeGetOCSPBasicRes(other, bs);

    bs_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_BASICRESP), bs);
    if (!bs_new)
	ossl_raise(eOCSPError, "ASN1_item_dup");

    SetOCSPBasicRes(self, bs_new);
    OCSP_BASICRESP_free(bs_old);

    return self;
}

/*
 * call-seq:
 *   OpenSSL::OCSP::BasicResponse.new(der_string = nil) -> basic_response
 *
 * Creates a new BasicResponse. If +der_string+ is given, decodes +der_string+
 * as DER.
 */

static VALUE
ossl_ocspbres_initialize(int argc, VALUE *argv, VALUE self)
{
    VALUE arg;
    OCSP_BASICRESP *res, *res_new;
    const unsigned char *p;

    rb_scan_args(argc, argv, "01", &arg);
    if (!NIL_P(arg)) {
	GetOCSPBasicRes(self, res);
	arg = ossl_to_der_if_possible(arg);
	StringValue(arg);
	p = (unsigned char *)RSTRING_PTR(arg);
	res_new = d2i_OCSP_BASICRESP(NULL, &p, RSTRING_LEN(arg));
	if (!res_new)
	    ossl_raise(eOCSPError, "d2i_OCSP_BASICRESP");
	SetOCSPBasicRes(self, res_new);
	OCSP_BASICRESP_free(res);
    }

    return self;
}

/*
 * call-seq:
 *   basic_response.copy_nonce(request) -> Integer
 *
 * Copies the nonce from +request+ into this response.  Returns 1 on success
 * and 0 on failure.
 */

static VALUE
ossl_ocspbres_copy_nonce(VALUE self, VALUE request)
{
    OCSP_BASICRESP *bs;
    OCSP_REQUEST *req;
    int ret;

    GetOCSPBasicRes(self, bs);
    SafeGetOCSPReq(request, req);
    ret = OCSP_copy_nonce(bs, req);

    return INT2NUM(ret);
}

/*
 * call-seq:
 *   basic_response.add_nonce(nonce = nil)
 *
 * Adds +nonce+ to this response.  If no nonce was provided a random nonce
 * will be added.
 */

static VALUE
ossl_ocspbres_add_nonce(int argc, VALUE *argv, VALUE self)
{
    OCSP_BASICRESP *bs;
    VALUE val;
    int ret;

    rb_scan_args(argc, argv, "01", &val);
    if(NIL_P(val)) {
	GetOCSPBasicRes(self, bs);
	ret = OCSP_basic_add1_nonce(bs, NULL, -1);
    }
    else{
	StringValue(val);
	GetOCSPBasicRes(self, bs);
	ret = OCSP_basic_add1_nonce(bs, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val));
    }
    if(!ret) ossl_raise(eOCSPError, NULL);

    return self;
}

static VALUE
add_status_convert_time(VALUE obj)
{
    ASN1_TIME *time;

    if (RB_INTEGER_TYPE_P(obj))
	time = X509_gmtime_adj(NULL, NUM2INT(obj));
    else
	time = ossl_x509_time_adjust(NULL, obj);

    if (!time)
	ossl_raise(eOCSPError, NULL);

    return (VALUE)time;
}

/*
 * call-seq:
 *   basic_response.add_status(certificate_id, status, reason, revocation_time, this_update, next_update, extensions) -> basic_response
 *
 * Adds a certificate status for +certificate_id+. +status+ is the status, and
 * must be one of these:
 *
 * - OpenSSL::OCSP::V_CERTSTATUS_GOOD
 * - OpenSSL::OCSP::V_CERTSTATUS_REVOKED
 * - OpenSSL::OCSP::V_CERTSTATUS_UNKNOWN
 *
 * +reason+ and +revocation_time+ can be given only when +status+ is
 * OpenSSL::OCSP::V_CERTSTATUS_REVOKED. +reason+ describes the reason for the
 * revocation, and must be one of OpenSSL::OCSP::REVOKED_STATUS_* constants.
 * +revocation_time+ is the time when the certificate is revoked.
 *
 * +this_update+ and +next_update+ indicate the time at which ths status is
 * verified to be correct and the time at or before which newer information
 * will be available, respectively. +next_update+ is optional.
 *
 * +extensions+ is an Array of OpenSSL::X509::Extension to be included in the
 * SingleResponse. This is also optional.
 *
 * Note that the times, +revocation_time+, +this_update+ and +next_update+
 * can be specified in either of Integer or Time object. If they are Integer, it
 * is treated as the relative seconds from the current time.
 */
static VALUE
ossl_ocspbres_add_status(VALUE self, VALUE cid, VALUE status,
			 VALUE reason, VALUE revtime,
			 VALUE thisupd, VALUE nextupd, VALUE ext)
{
    OCSP_BASICRESP *bs;
    OCSP_SINGLERESP *single;
    OCSP_CERTID *id;
    ASN1_TIME *ths = NULL, *nxt = NULL, *rev = NULL;
    int st, rsn = 0, error = 0, rstatus = 0;
    long i;
    VALUE tmp;

    GetOCSPBasicRes(self, bs);
    SafeGetOCSPCertId(cid, id);
    st = NUM2INT(status);
    if (!NIL_P(ext)) { /* All ext's members must be X509::Extension */
	ext = rb_check_array_type(ext);
	for (i = 0; i < RARRAY_LEN(ext); i++)
	    OSSL_Check_Kind(RARRAY_AREF(ext, i), cX509Ext);
    }

    if (st == V_OCSP_CERTSTATUS_REVOKED) {
	rsn = NUM2INT(reason);
	tmp = rb_protect(add_status_convert_time, revtime, &rstatus);
	if (rstatus) goto err;
	rev = (ASN1_TIME *)tmp;
    }

    tmp = rb_protect(add_status_convert_time, thisupd, &rstatus);
    if (rstatus) goto err;
    ths = (ASN1_TIME *)tmp;

    if (!NIL_P(nextupd)) {
	tmp = rb_protect(add_status_convert_time, nextupd, &rstatus);
	if (rstatus) goto err;
	nxt = (ASN1_TIME *)tmp;
    }

    if(!(single = OCSP_basic_add1_status(bs, id, st, rsn, rev, ths, nxt))){
	error = 1;
	goto err;
    }

    if(!NIL_P(ext)){
	X509_EXTENSION *x509ext;

	for(i = 0; i < RARRAY_LEN(ext); i++){
	    x509ext = GetX509ExtPtr(RARRAY_AREF(ext, i));
	    if(!OCSP_SINGLERESP_add_ext(single, x509ext, -1)){
		error = 1;
		goto err;
	    }
	}
    }

 err:
    ASN1_TIME_free(ths);
    ASN1_TIME_free(nxt);
    ASN1_TIME_free(rev);
    if(error) ossl_raise(eOCSPError, NULL);
    if(rstatus) rb_jump_tag(rstatus);

    return self;
}

/*
 * call-seq:
 *   basic_response.status -> statuses
 *
 * Returns an Array of statuses for this response.  Each status contains a
 * CertificateId, the status (0 for good, 1 for revoked, 2 for unknown), the
 * reason for the status, the revocation time, the time of this update, the time
 * for the next update and a list of OpenSSL::X509::Extensions.
 *
 * This should be superseded by BasicResponse#responses and #find_response that
 * return SingleResponse.
 */
static VALUE
ossl_ocspbres_get_status(VALUE self)
{
    OCSP_BASICRESP *bs;
    OCSP_SINGLERESP *single;
    OCSP_CERTID *cid;
    ASN1_TIME *revtime, *thisupd, *nextupd;
    int status, reason;
    X509_EXTENSION *x509ext;
    VALUE ret, ary, ext;
    int count, ext_count, i, j;

    GetOCSPBasicRes(self, bs);
    ret = rb_ary_new();
    count = OCSP_resp_count(bs);
    for(i = 0; i < count; i++){
	single = OCSP_resp_get0(bs, i);
	if(!single) continue;

	revtime = thisupd = nextupd = NULL;
	status = OCSP_single_get0_status(single, &reason, &revtime,
					 &thisupd, &nextupd);
	if(status < 0) continue;
	if(!(cid = OCSP_CERTID_dup((OCSP_CERTID *)OCSP_SINGLERESP_get0_id(single)))) /* FIXME */
	    ossl_raise(eOCSPError, NULL);
	ary = rb_ary_new();
	rb_ary_push(ary, ossl_ocspcertid_new(cid));
	rb_ary_push(ary, INT2NUM(status));
	rb_ary_push(ary, INT2NUM(reason));
	rb_ary_push(ary, revtime ? asn1time_to_time(revtime) : Qnil);
	rb_ary_push(ary, thisupd ? asn1time_to_time(thisupd) : Qnil);
	rb_ary_push(ary, nextupd ? asn1time_to_time(nextupd) : Qnil);
	ext = rb_ary_new();
	ext_count = OCSP_SINGLERESP_get_ext_count(single);
	for(j = 0; j < ext_count; j++){
	    x509ext = OCSP_SINGLERESP_get_ext(single, j);
	    rb_ary_push(ext, ossl_x509ext_new(x509ext));
	}
	rb_ary_push(ary, ext);
	rb_ary_push(ret, ary);
    }

    return ret;
}

static VALUE ossl_ocspsres_new(OCSP_SINGLERESP *);

/*
 * call-seq:
 *   basic_response.responses -> Array of SingleResponse
 *
 * Returns an Array of SingleResponse for this BasicResponse.
 */

static VALUE
ossl_ocspbres_get_responses(VALUE self)
{
    OCSP_BASICRESP *bs;
    VALUE ret;
    int count, i;

    GetOCSPBasicRes(self, bs);
    count = OCSP_resp_count(bs);
    ret = rb_ary_new2(count);

    for (i = 0; i < count; i++) {
	OCSP_SINGLERESP *sres, *sres_new;

	sres = OCSP_resp_get0(bs, i);
	sres_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_SINGLERESP), sres);
	if (!sres_new)
	    ossl_raise(eOCSPError, "ASN1_item_dup");

	rb_ary_push(ret, ossl_ocspsres_new(sres_new));
    }

    return ret;
}


/*
 * call-seq:
 *   basic_response.find_response(certificate_id) -> SingleResponse | nil
 *
 * Returns a SingleResponse whose CertId matches with +certificate_id+, or nil
 * if this BasicResponse does not contain it.
 */
static VALUE
ossl_ocspbres_find_response(VALUE self, VALUE target)
{
    OCSP_BASICRESP *bs;
    OCSP_SINGLERESP *sres, *sres_new;
    OCSP_CERTID *id;
    int n;

    SafeGetOCSPCertId(target, id);
    GetOCSPBasicRes(self, bs);

    if ((n = OCSP_resp_find(bs, id, -1)) == -1)
	return Qnil;

    sres = OCSP_resp_get0(bs, n);
    sres_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_SINGLERESP), sres);
    if (!sres_new)
	ossl_raise(eOCSPError, "ASN1_item_dup");

    return ossl_ocspsres_new(sres_new);
}

/*
 * call-seq:
 *   basic_response.sign(cert, key, certs = nil, flags = 0, digest = nil) -> self
 *
 * Signs this OCSP response using the +cert+, +key+ and optional +digest+. This
 * behaves in the similar way as OpenSSL::OCSP::Request#sign.
 *
 * +flags+ can include:
 * OpenSSL::OCSP::NOCERTS::    don't include certificates
 * OpenSSL::OCSP::NOTIME::     don't set producedAt
 * OpenSSL::OCSP::RESPID_KEY:: use signer's public key hash as responderID
 */

static VALUE
ossl_ocspbres_sign(int argc, VALUE *argv, VALUE self)
{
    VALUE signer_cert, signer_key, certs, flags, digest;
    OCSP_BASICRESP *bs;
    X509 *signer;
    EVP_PKEY *key;
    STACK_OF(X509) *x509s = NULL;
    unsigned long flg = 0;
    const EVP_MD *md;
    int ret;

    rb_scan_args(argc, argv, "23", &signer_cert, &signer_key, &certs, &flags, &digest);
    GetOCSPBasicRes(self, bs);
    signer = GetX509CertPtr(signer_cert);
    key = GetPrivPKeyPtr(signer_key);
    if (!NIL_P(flags))
	flg = NUM2INT(flags);
    if (NIL_P(digest))
	md = EVP_sha1();
    else
	md = GetDigestPtr(digest);
    if (NIL_P(certs))
	flg |= OCSP_NOCERTS;
    else
	x509s = ossl_x509_ary2sk(certs);

    ret = OCSP_basic_sign(bs, signer, key, md, x509s, flg);
    sk_X509_pop_free(x509s, X509_free);
    if (!ret) ossl_raise(eOCSPError, NULL);

    return self;
}

/*
 * call-seq:
 *   basic_response.verify(certificates, store, flags = 0) -> true or false
 *
 * Verifies the signature of the response using the given +certificates+ and
 * +store+. This works in the similar way as OpenSSL::OCSP::Request#verify.
 */
static VALUE
ossl_ocspbres_verify(int argc, VALUE *argv, VALUE self)
{
    VALUE certs, store, flags;
    OCSP_BASICRESP *bs;
    STACK_OF(X509) *x509s;
    X509_STORE *x509st;
    int flg, result;

    rb_scan_args(argc, argv, "21", &certs, &store, &flags);
    GetOCSPBasicRes(self, bs);
    x509st = GetX509StorePtr(store);
    flg = NIL_P(flags) ? 0 : NUM2INT(flags);
    x509s = ossl_x509_ary2sk(certs);
#if (OPENSSL_VERSION_NUMBER < 0x1000202fL) || defined(LIBRESSL_VERSION_NUMBER)
    /*
     * OpenSSL had a bug that it doesn't use the certificates in x509s for
     * verifying the chain. This can be a problem when the response is signed by
     * a certificate issued by an intermediate CA.
     *
     *       root_ca
     *         |
     *   intermediate_ca
     *         |-------------|
     *     end_entity    ocsp_signer
     *
     * When the certificate hierarchy is like this, and the response contains
     * only ocsp_signer certificate, the following code wrongly fails.
     *
     *   store = OpenSSL::X509::Store.new; store.add_cert(root_ca)
     *   basic_response.verify([intermediate_ca], store)
     *
     * So add the certificates in x509s to the embedded certificates list first.
     *
     * This is fixed in OpenSSL 0.9.8zg, 1.0.0s, 1.0.1n, 1.0.2b. But it still
     * exists in LibreSSL 2.1.10, 2.2.9, 2.3.6, 2.4.1.
     */
    if (!(flg & (OCSP_NOCHAIN | OCSP_NOVERIFY)) &&
	sk_X509_num(x509s) && sk_X509_num(bs->certs)) {
	int i;

	bs = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_BASICRESP), bs);
	if (!bs) {
	    sk_X509_pop_free(x509s, X509_free);
	    ossl_raise(eOCSPError, "ASN1_item_dup");
	}

	for (i = 0; i < sk_X509_num(x509s); i++) {
	    if (!OCSP_basic_add1_cert(bs, sk_X509_value(x509s, i))) {
		sk_X509_pop_free(x509s, X509_free);
		OCSP_BASICRESP_free(bs);
		ossl_raise(eOCSPError, "OCSP_basic_add1_cert");
	    }
	}
	result = OCSP_basic_verify(bs, x509s, x509st, flg);
	OCSP_BASICRESP_free(bs);
    }
    else {
	result = OCSP_basic_verify(bs, x509s, x509st, flg);
    }
#else
    result = OCSP_basic_verify(bs, x509s, x509st, flg);
#endif
    sk_X509_pop_free(x509s, X509_free);
    if (result <= 0)
	ossl_clear_error();

    return result > 0 ? Qtrue : Qfalse;
}

/*
 * call-seq:
 *   basic_response.to_der -> String
 *
 * Encodes this basic response into a DER-encoded string.
 */
static VALUE
ossl_ocspbres_to_der(VALUE self)
{
    OCSP_BASICRESP *res;
    VALUE str;
    long len;
    unsigned char *p;

    GetOCSPBasicRes(self, res);
    if ((len = i2d_OCSP_BASICRESP(res, NULL)) <= 0)
	ossl_raise(eOCSPError, NULL);
    str = rb_str_new(0, len);
    p = (unsigned char *)RSTRING_PTR(str);
    if (i2d_OCSP_BASICRESP(res, &p) <= 0)
	ossl_raise(eOCSPError, NULL);
    ossl_str_adjust(str, p);

    return str;
}

/*
 * OCSP::SingleResponse
 */
static VALUE
ossl_ocspsres_new(OCSP_SINGLERESP *sres)
{
    VALUE obj;

    obj = NewOCSPSingleRes(cOCSPSingleRes);
    SetOCSPSingleRes(obj, sres);

    return obj;
}

static VALUE
ossl_ocspsres_alloc(VALUE klass)
{
    OCSP_SINGLERESP *sres;
    VALUE obj;

    obj = NewOCSPSingleRes(klass);
    if (!(sres = OCSP_SINGLERESP_new()))
	ossl_raise(eOCSPError, NULL);
    SetOCSPSingleRes(obj, sres);

    return obj;
}

/*
 * call-seq:
 *   OpenSSL::OCSP::SingleResponse.new(der_string) -> SingleResponse
 *
 * Creates a new SingleResponse from +der_string+.
 */
static VALUE
ossl_ocspsres_initialize(VALUE self, VALUE arg)
{
    OCSP_SINGLERESP *res, *res_new;
    const unsigned char *p;

    arg = ossl_to_der_if_possible(arg);
    StringValue(arg);
    GetOCSPSingleRes(self, res);

    p = (unsigned char*)RSTRING_PTR(arg);
    res_new = d2i_OCSP_SINGLERESP(NULL, &p, RSTRING_LEN(arg));
    if (!res_new)
	ossl_raise(eOCSPError, "d2i_OCSP_SINGLERESP");
    SetOCSPSingleRes(self, res_new);
    OCSP_SINGLERESP_free(res);

    return self;
}

static VALUE
ossl_ocspsres_initialize_copy(VALUE self, VALUE other)
{
    OCSP_SINGLERESP *sres, *sres_old, *sres_new;

    rb_check_frozen(self);
    GetOCSPSingleRes(self, sres_old);
    SafeGetOCSPSingleRes(other, sres);

    sres_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_SINGLERESP), sres);
    if (!sres_new)
	ossl_raise(eOCSPError, "ASN1_item_dup");

    SetOCSPSingleRes(self, sres_new);
    OCSP_SINGLERESP_free(sres_old);

    return self;
}

/*
 * call-seq:
 *   single_response.check_validity(nsec = 0, maxsec = -1) -> true | false
 *
 * Checks the validity of thisUpdate and nextUpdate fields of this
 * SingleResponse. This checks the current time is within the range thisUpdate
 * to nextUpdate.
 *
 * It is possible that the OCSP request takes a few seconds or the time is not
 * accurate. To avoid rejecting a valid response, this method allows the times
 * to be within +nsec+ of the current time.
 *
 * Some responders don't set the nextUpdate field. This may cause a very old
 * response to be considered valid. The +maxsec+ parameter can be used to limit
 * the age of responses.
 */
static VALUE
ossl_ocspsres_check_validity(int argc, VALUE *argv, VALUE self)
{
    OCSP_SINGLERESP *sres;
    ASN1_GENERALIZEDTIME *this_update, *next_update;
    VALUE nsec_v, maxsec_v;
    int nsec, maxsec, status, ret;

    rb_scan_args(argc, argv, "02", &nsec_v, &maxsec_v);
    nsec = NIL_P(nsec_v) ? 0 : NUM2INT(nsec_v);
    maxsec = NIL_P(maxsec_v) ? -1 : NUM2INT(maxsec_v);

    GetOCSPSingleRes(self, sres);
    status = OCSP_single_get0_status(sres, NULL, NULL, &this_update, &next_update);
    if (status < 0)
	ossl_raise(eOCSPError, "OCSP_single_get0_status");

    ret = OCSP_check_validity(this_update, next_update, nsec, maxsec);

    if (ret)
	return Qtrue;
    else {
	ossl_clear_error();
	return Qfalse;
    }
}

/*
 * call-seq:
 *   single_response.certid -> CertificateId
 *
 * Returns the CertificateId for which this SingleResponse is.
 */
static VALUE
ossl_ocspsres_get_certid(VALUE self)
{
    OCSP_SINGLERESP *sres;
    OCSP_CERTID *id;

    GetOCSPSingleRes(self, sres);
    id = OCSP_CERTID_dup((OCSP_CERTID *)OCSP_SINGLERESP_get0_id(sres)); /* FIXME */

    return ossl_ocspcertid_new(id);
}

/*
 * call-seq:
 *   single_response.cert_status -> Integer
 *
 * Returns the status of the certificate identified by the certid.
 * The return value may be one of these constant:
 *
 * - V_CERTSTATUS_GOOD
 * - V_CERTSTATUS_REVOKED
 * - V_CERTSTATUS_UNKNOWN
 *
 * When the status is V_CERTSTATUS_REVOKED, the time at which the certificate
 * was revoked can be retrieved by #revocation_time.
 */
static VALUE
ossl_ocspsres_get_cert_status(VALUE self)
{
    OCSP_SINGLERESP *sres;
    int status;

    GetOCSPSingleRes(self, sres);
    status = OCSP_single_get0_status(sres, NULL, NULL, NULL, NULL);
    if (status < 0)
	ossl_raise(eOCSPError, "OCSP_single_get0_status");

    return INT2NUM(status);
}

/*
 * call-seq:
 *   single_response.this_update -> Time
 */
static VALUE
ossl_ocspsres_get_this_update(VALUE self)
{
    OCSP_SINGLERESP *sres;
    int status;
    ASN1_GENERALIZEDTIME *time;

    GetOCSPSingleRes(self, sres);
    status = OCSP_single_get0_status(sres, NULL, NULL, &time, NULL);
    if (status < 0)
	ossl_raise(eOCSPError, "OCSP_single_get0_status");

    return asn1time_to_time(time); /* will handle NULL */
}

/*
 * call-seq:
 *   single_response.next_update -> Time | nil
 */
static VALUE
ossl_ocspsres_get_next_update(VALUE self)
{
    OCSP_SINGLERESP *sres;
    int status;
    ASN1_GENERALIZEDTIME *time;

    GetOCSPSingleRes(self, sres);
    status = OCSP_single_get0_status(sres, NULL, NULL, NULL, &time);
    if (status < 0)
	ossl_raise(eOCSPError, "OCSP_single_get0_status");

    return asn1time_to_time(time);
}

/*
 * call-seq:
 *   single_response.revocation_time -> Time | nil
 */
static VALUE
ossl_ocspsres_get_revocation_time(VALUE self)
{
    OCSP_SINGLERESP *sres;
    int status;
    ASN1_GENERALIZEDTIME *time;

    GetOCSPSingleRes(self, sres);
    status = OCSP_single_get0_status(sres, NULL, &time, NULL, NULL);
    if (status < 0)
	ossl_raise(eOCSPError, "OCSP_single_get0_status");
    if (status != V_OCSP_CERTSTATUS_REVOKED)
	ossl_raise(eOCSPError, "certificate is not revoked");

    return asn1time_to_time(time);
}

/*
 * call-seq:
 *   single_response.revocation_reason -> Integer | nil
 */
static VALUE
ossl_ocspsres_get_revocation_reason(VALUE self)
{
    OCSP_SINGLERESP *sres;
    int status, reason;

    GetOCSPSingleRes(self, sres);
    status = OCSP_single_get0_status(sres, &reason, NULL, NULL, NULL);
    if (status < 0)
	ossl_raise(eOCSPError, "OCSP_single_get0_status");
    if (status != V_OCSP_CERTSTATUS_REVOKED)
	ossl_raise(eOCSPError, "certificate is not revoked");

    return INT2NUM(reason);
}

/*
 * call-seq:
 *   single_response.extensions -> Array of X509::Extension
 */
static VALUE
ossl_ocspsres_get_extensions(VALUE self)
{
    OCSP_SINGLERESP *sres;
    X509_EXTENSION *ext;
    int count, i;
    VALUE ary;

    GetOCSPSingleRes(self, sres);

    count = OCSP_SINGLERESP_get_ext_count(sres);
    ary = rb_ary_new2(count);
    for (i = 0; i < count; i++) {
	ext = OCSP_SINGLERESP_get_ext(sres, i);
	rb_ary_push(ary, ossl_x509ext_new(ext)); /* will dup */
    }

    return ary;
}

/*
 * call-seq:
 *   single_response.to_der -> String
 *
 * Encodes this SingleResponse into a DER-encoded string.
 */
static VALUE
ossl_ocspsres_to_der(VALUE self)
{
    OCSP_SINGLERESP *sres;
    VALUE str;
    long len;
    unsigned char *p;

    GetOCSPSingleRes(self, sres);
    if ((len = i2d_OCSP_SINGLERESP(sres, NULL)) <= 0)
	ossl_raise(eOCSPError, NULL);
    str = rb_str_new(0, len);
    p = (unsigned char *)RSTRING_PTR(str);
    if (i2d_OCSP_SINGLERESP(sres, &p) <= 0)
	ossl_raise(eOCSPError, NULL);
    ossl_str_adjust(str, p);

    return str;
}


/*
 * OCSP::CertificateId
 */
static VALUE
ossl_ocspcid_alloc(VALUE klass)
{
    OCSP_CERTID *id;
    VALUE obj;

    obj = NewOCSPCertId(klass);
    if(!(id = OCSP_CERTID_new()))
	ossl_raise(eOCSPError, NULL);
    SetOCSPCertId(obj, id);

    return obj;
}

static VALUE
ossl_ocspcid_initialize_copy(VALUE self, VALUE other)
{
    OCSP_CERTID *cid, *cid_old, *cid_new;

    rb_check_frozen(self);
    GetOCSPCertId(self, cid_old);
    SafeGetOCSPCertId(other, cid);

    cid_new = OCSP_CERTID_dup(cid);
    if (!cid_new)
	ossl_raise(eOCSPError, "OCSP_CERTID_dup");

    SetOCSPCertId(self, cid_new);
    OCSP_CERTID_free(cid_old);

    return self;
}

/*
 * call-seq:
 *   OpenSSL::OCSP::CertificateId.new(subject, issuer, digest = nil) -> certificate_id
 *   OpenSSL::OCSP::CertificateId.new(der_string)                    -> certificate_id
 *
 * Creates a new OpenSSL::OCSP::CertificateId for the given +subject+ and
 * +issuer+ X509 certificates.  The +digest+ is used to compute the
 * certificate ID and must be an OpenSSL::Digest instance.
 *
 * If only one argument is given, decodes it as DER representation of a
 * certificate ID.
 */

static VALUE
ossl_ocspcid_initialize(int argc, VALUE *argv, VALUE self)
{
    OCSP_CERTID *id, *newid;
    VALUE subject, issuer, digest;

    GetOCSPCertId(self, id);
    if (rb_scan_args(argc, argv, "12", &subject, &issuer, &digest) == 1) {
	VALUE arg;
	const unsigned char *p;

	arg = ossl_to_der_if_possible(subject);
	StringValue(arg);
	p = (unsigned char *)RSTRING_PTR(arg);
	newid = d2i_OCSP_CERTID(NULL, &p, RSTRING_LEN(arg));
	if (!newid)
	    ossl_raise(eOCSPError, "d2i_OCSP_CERTID");
    }
    else {
	X509 *x509s, *x509i;
	const EVP_MD *md;

	x509s = GetX509CertPtr(subject); /* NO NEED TO DUP */
	x509i = GetX509CertPtr(issuer); /* NO NEED TO DUP */
	md = !NIL_P(digest) ? GetDigestPtr(digest) : NULL;

	newid = OCSP_cert_to_id(md, x509s, x509i);
	if (!newid)
	    ossl_raise(eOCSPError, "OCSP_cert_to_id");
    }

    SetOCSPCertId(self, newid);
    OCSP_CERTID_free(id);

    return self;
}

/*
 * call-seq:
 *   certificate_id.cmp(other) -> true or false
 *
 * Compares this certificate id with +other+ and returns true if they are the
 * same.
 */
static VALUE
ossl_ocspcid_cmp(VALUE self, VALUE other)
{
    OCSP_CERTID *id, *id2;
    int result;

    GetOCSPCertId(self, id);
    SafeGetOCSPCertId(other, id2);
    result = OCSP_id_cmp(id, id2);

    return (result == 0) ? Qtrue : Qfalse;
}

/*
 * call-seq:
 *   certificate_id.cmp_issuer(other) -> true or false
 *
 * Compares this certificate id's issuer with +other+ and returns true if
 * they are the same.
 */

static VALUE
ossl_ocspcid_cmp_issuer(VALUE self, VALUE other)
{
    OCSP_CERTID *id, *id2;
    int result;

    GetOCSPCertId(self, id);
    SafeGetOCSPCertId(other, id2);
    result = OCSP_id_issuer_cmp(id, id2);

    return (result == 0) ? Qtrue : Qfalse;
}

/*
 * call-seq:
 *   certificate_id.serial -> Integer
 *
 * Returns the serial number of the certificate for which status is being
 * requested.
 */
static VALUE
ossl_ocspcid_get_serial(VALUE self)
{
    OCSP_CERTID *id;
    ASN1_INTEGER *serial;

    GetOCSPCertId(self, id);
    OCSP_id_get0_info(NULL, NULL, NULL, &serial, id);

    return asn1integer_to_num(serial);
}

/*
 * call-seq:
 *   certificate_id.issuer_name_hash -> String
 *
 * Returns the issuerNameHash of this certificate ID, the hash of the
 * issuer's distinguished name calculated with the hashAlgorithm.
 */
static VALUE
ossl_ocspcid_get_issuer_name_hash(VALUE self)
{
    OCSP_CERTID *id;
    ASN1_OCTET_STRING *name_hash;
    VALUE ret;

    GetOCSPCertId(self, id);
    OCSP_id_get0_info(&name_hash, NULL, NULL, NULL, id);

    ret = rb_str_new(NULL, name_hash->length * 2);
    ossl_bin2hex(name_hash->data, RSTRING_PTR(ret), name_hash->length);

    return ret;
}

/*
 * call-seq:
 *   certificate_id.issuer_key_hash -> String
 *
 * Returns the issuerKeyHash of this certificate ID, the hash of the issuer's
 * public key.
 */
static VALUE
ossl_ocspcid_get_issuer_key_hash(VALUE self)
{
    OCSP_CERTID *id;
    ASN1_OCTET_STRING *key_hash;
    VALUE ret;

    GetOCSPCertId(self, id);
    OCSP_id_get0_info(NULL, NULL, &key_hash, NULL, id);

    ret = rb_str_new(NULL, key_hash->length * 2);
    ossl_bin2hex(key_hash->data, RSTRING_PTR(ret), key_hash->length);

    return ret;
}

/*
 * call-seq:
 *   certificate_id.hash_algorithm -> String
 *
 * Returns the ln (long name) of the hash algorithm used to generate
 * the issuerNameHash and the issuerKeyHash values.
 */
static VALUE
ossl_ocspcid_get_hash_algorithm(VALUE self)
{
    OCSP_CERTID *id;
    ASN1_OBJECT *oid;
    BIO *out;

    GetOCSPCertId(self, id);
    OCSP_id_get0_info(NULL, &oid, NULL, NULL, id);

    if (!(out = BIO_new(BIO_s_mem())))
	ossl_raise(eOCSPError, "BIO_new");

    if (!i2a_ASN1_OBJECT(out, oid)) {
	BIO_free(out);
	ossl_raise(eOCSPError, "i2a_ASN1_OBJECT");
    }
    return ossl_membio2str(out);
}

/*
 * call-seq:
 *   certificate_id.to_der -> String
 *
 * Encodes this certificate identifier into a DER-encoded string.
 */
static VALUE
ossl_ocspcid_to_der(VALUE self)
{
    OCSP_CERTID *id;
    VALUE str;
    long len;
    unsigned char *p;

    GetOCSPCertId(self, id);
    if ((len = i2d_OCSP_CERTID(id, NULL)) <= 0)
	ossl_raise(eOCSPError, NULL);
    str = rb_str_new(0, len);
    p = (unsigned char *)RSTRING_PTR(str);
    if (i2d_OCSP_CERTID(id, &p) <= 0)
	ossl_raise(eOCSPError, NULL);
    ossl_str_adjust(str, p);

    return str;
}

void
Init_ossl_ocsp(void)
{
#if 0
    mOSSL = rb_define_module("OpenSSL");
    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
#endif

    /*
     * OpenSSL::OCSP implements Online Certificate Status Protocol requests
     * and responses.
     *
     * Creating and sending an OCSP request requires a subject certificate
     * that contains an OCSP URL in an authorityInfoAccess extension and the
     * issuer certificate for the subject certificate.  First, load the issuer
     * and subject certificates:
     *
     *   subject = OpenSSL::X509::Certificate.new subject_pem
     *   issuer  = OpenSSL::X509::Certificate.new issuer_pem
     *
     * To create the request we need to create a certificate ID for the
     * subject certificate so the CA knows which certificate we are asking
     * about:
     *
     *   digest = OpenSSL::Digest::SHA1.new
     *   certificate_id =
     *     OpenSSL::OCSP::CertificateId.new subject, issuer, digest
     *
     * Then create a request and add the certificate ID to it:
     *
     *   request = OpenSSL::OCSP::Request.new
     *   request.add_certid certificate_id
     *
     * Adding a nonce to the request protects against replay attacks but not
     * all CA process the nonce.
     *
     *   request.add_nonce
     *
     * To submit the request to the CA for verification we need to extract the
     * OCSP URI from the subject certificate:
     *
     *   authority_info_access = subject.extensions.find do |extension|
     *     extension.oid == 'authorityInfoAccess'
     *   end
     *
     *   descriptions = authority_info_access.value.split "\n"
     *   ocsp = descriptions.find do |description|
     *     description.start_with? 'OCSP'
     *   end
     *
     *   require 'uri'
     *
     *   ocsp_uri = URI ocsp[/URI:(.*)/, 1]
     *
     * To submit the request we'll POST the request to the OCSP URI (per RFC
     * 2560).  Note that we only handle HTTP requests and don't handle any
     * redirects in this example, so this is insufficient for serious use.
     *
     *   require 'net/http'
     *
     *   http_response =
     *     Net::HTTP.start ocsp_uri.hostname, ocsp.port do |http|
     *       http.post ocsp_uri.path, request.to_der,
     *                 'content-type' => 'application/ocsp-request'
     *   end
     *
     *   response = OpenSSL::OCSP::Response.new http_response.body
     *   response_basic = response.basic
     *
     * First we check if the response has a valid signature.  Without a valid
     * signature we cannot trust it.  If you get a failure here you may be
     * missing a system certificate store or may be missing the intermediate
     * certificates.
     *
     *   store = OpenSSL::X509::Store.new
     *   store.set_default_paths
     *
     *   unless response_basic.verify [], store then
     *     raise 'response is not signed by a trusted certificate'
     *   end
     *
     * The response contains the status information (success/fail).  We can
     * display the status as a string:
     *
     *   puts response.status_string #=> successful
     *
     * Next we need to know the response details to determine if the response
     * matches our request.  First we check the nonce.  Again, not all CAs
     * support a nonce.  See Request#check_nonce for the meanings of the
     * return values.
     *
     *   p request.check_nonce basic_response #=> value from -1 to 3
     *
     * Then extract the status information for the certificate from the basic
     * response.
     *
     *   single_response = basic_response.find_response(certificate_id)
     *
     *   unless single_response
     *     raise 'basic_response does not have the status for the certificiate'
     *   end
     *
     * Then check the validity. A status issued in the future must be rejected.
     *
     *   unless single_response.check_validity
     *     raise 'this_update is in the future or next_update time has passed'
     *   end
     *
     *   case single_response.cert_status
     *   when OpenSSL::OCSP::V_CERTSTATUS_GOOD
     *     puts 'certificate is still valid'
     *   when OpenSSL::OCSP::V_CERTSTATUS_REVOKED
     *     puts "certificate has been revoked at #{single_response.revocation_time}"
     *   when OpenSSL::OCSP::V_CERTSTATUS_UNKNOWN
     *     puts 'responder doesn't know about the certificate'
     *   end
     */

    mOCSP = rb_define_module_under(mOSSL, "OCSP");

    /*
     * OCSP error class.
     */

    eOCSPError = rb_define_class_under(mOCSP, "OCSPError", eOSSLError);

    /*
     * An OpenSSL::OCSP::Request contains the certificate information for
     * determining if a certificate has been revoked or not.  A Request can be
     * created for a certificate or from a DER-encoded request created
     * elsewhere.
     */

    cOCSPReq = rb_define_class_under(mOCSP, "Request", rb_cObject);
    rb_define_alloc_func(cOCSPReq, ossl_ocspreq_alloc);
    rb_define_copy_func(cOCSPReq, ossl_ocspreq_initialize_copy);
    rb_define_method(cOCSPReq, "initialize", ossl_ocspreq_initialize, -1);
    rb_define_method(cOCSPReq, "add_nonce", ossl_ocspreq_add_nonce, -1);
    rb_define_method(cOCSPReq, "check_nonce", ossl_ocspreq_check_nonce, 1);
    rb_define_method(cOCSPReq, "add_certid", ossl_ocspreq_add_certid, 1);
    rb_define_method(cOCSPReq, "certid", ossl_ocspreq_get_certid, 0);
    rb_define_method(cOCSPReq, "sign", ossl_ocspreq_sign, -1);
    rb_define_method(cOCSPReq, "verify", ossl_ocspreq_verify, -1);
    rb_define_method(cOCSPReq, "to_der", ossl_ocspreq_to_der, 0);

    /*
     * An OpenSSL::OCSP::Response contains the status of a certificate check
     * which is created from an OpenSSL::OCSP::Request.
     */

    cOCSPRes = rb_define_class_under(mOCSP, "Response", rb_cObject);
    rb_define_singleton_method(cOCSPRes, "create", ossl_ocspres_s_create, 2);
    rb_define_alloc_func(cOCSPRes, ossl_ocspres_alloc);
    rb_define_copy_func(cOCSPRes, ossl_ocspres_initialize_copy);
    rb_define_method(cOCSPRes, "initialize", ossl_ocspres_initialize, -1);
    rb_define_method(cOCSPRes, "status", ossl_ocspres_status, 0);
    rb_define_method(cOCSPRes, "status_string", ossl_ocspres_status_string, 0);
    rb_define_method(cOCSPRes, "basic", ossl_ocspres_get_basic, 0);
    rb_define_method(cOCSPRes, "to_der", ossl_ocspres_to_der, 0);

    /*
     * 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.
     */

    cOCSPBasicRes = rb_define_class_under(mOCSP, "BasicResponse", rb_cObject);
    rb_define_alloc_func(cOCSPBasicRes, ossl_ocspbres_alloc);
    rb_define_copy_func(cOCSPBasicRes, ossl_ocspbres_initialize_copy);
    rb_define_method(cOCSPBasicRes, "initialize", ossl_ocspbres_initialize, -1);
    rb_define_method(cOCSPBasicRes, "copy_nonce", ossl_ocspbres_copy_nonce, 1);
    rb_define_method(cOCSPBasicRes, "add_nonce", ossl_ocspbres_add_nonce, -1);
    rb_define_method(cOCSPBasicRes, "add_status", ossl_ocspbres_add_status, 7);
    rb_define_method(cOCSPBasicRes, "status", ossl_ocspbres_get_status, 0);
    rb_define_method(cOCSPBasicRes, "responses", ossl_ocspbres_get_responses, 0);
    rb_define_method(cOCSPBasicRes, "find_response", ossl_ocspbres_find_response, 1);
    rb_define_method(cOCSPBasicRes, "sign", ossl_ocspbres_sign, -1);
    rb_define_method(cOCSPBasicRes, "verify", ossl_ocspbres_verify, -1);
    rb_define_method(cOCSPBasicRes, "to_der", ossl_ocspbres_to_der, 0);

    /*
     * An OpenSSL::OCSP::SingleResponse represents an OCSP SingleResponse
     * structure, which contains the basic information of the status of the
     * certificate.
     */
    cOCSPSingleRes = rb_define_class_under(mOCSP, "SingleResponse", rb_cObject);
    rb_define_alloc_func(cOCSPSingleRes, ossl_ocspsres_alloc);
    rb_define_copy_func(cOCSPSingleRes, ossl_ocspsres_initialize_copy);
    rb_define_method(cOCSPSingleRes, "initialize", ossl_ocspsres_initialize, 1);
    rb_define_method(cOCSPSingleRes, "check_validity", ossl_ocspsres_check_validity, -1);
    rb_define_method(cOCSPSingleRes, "certid", ossl_ocspsres_get_certid, 0);
    rb_define_method(cOCSPSingleRes, "cert_status", ossl_ocspsres_get_cert_status, 0);
    rb_define_method(cOCSPSingleRes, "this_update", ossl_ocspsres_get_this_update, 0);
    rb_define_method(cOCSPSingleRes, "next_update", ossl_ocspsres_get_next_update, 0);
    rb_define_method(cOCSPSingleRes, "revocation_time", ossl_ocspsres_get_revocation_time, 0);
    rb_define_method(cOCSPSingleRes, "revocation_reason", ossl_ocspsres_get_revocation_reason, 0);
    rb_define_method(cOCSPSingleRes, "extensions", ossl_ocspsres_get_extensions, 0);
    rb_define_method(cOCSPSingleRes, "to_der", ossl_ocspsres_to_der, 0);

    /*
     * An OpenSSL::OCSP::CertificateId identifies a certificate to the CA so
     * that a status check can be performed.
     */

    cOCSPCertId = rb_define_class_under(mOCSP, "CertificateId", rb_cObject);
    rb_define_alloc_func(cOCSPCertId, ossl_ocspcid_alloc);
    rb_define_copy_func(cOCSPCertId, ossl_ocspcid_initialize_copy);
    rb_define_method(cOCSPCertId, "initialize", ossl_ocspcid_initialize, -1);
    rb_define_method(cOCSPCertId, "cmp", ossl_ocspcid_cmp, 1);
    rb_define_method(cOCSPCertId, "cmp_issuer", ossl_ocspcid_cmp_issuer, 1);
    rb_define_method(cOCSPCertId, "serial", ossl_ocspcid_get_serial, 0);
    rb_define_method(cOCSPCertId, "issuer_name_hash", ossl_ocspcid_get_issuer_name_hash, 0);
    rb_define_method(cOCSPCertId, "issuer_key_hash", ossl_ocspcid_get_issuer_key_hash, 0);
    rb_define_method(cOCSPCertId, "hash_algorithm", ossl_ocspcid_get_hash_algorithm, 0);
    rb_define_method(cOCSPCertId, "to_der", ossl_ocspcid_to_der, 0);

    /* Internal error in issuer */
    rb_define_const(mOCSP, "RESPONSE_STATUS_INTERNALERROR", INT2NUM(OCSP_RESPONSE_STATUS_INTERNALERROR));

    /* Illegal confirmation request */
    rb_define_const(mOCSP, "RESPONSE_STATUS_MALFORMEDREQUEST", INT2NUM(OCSP_RESPONSE_STATUS_MALFORMEDREQUEST));

    /* The certificate was revoked for an unknown reason */
    rb_define_const(mOCSP, "REVOKED_STATUS_NOSTATUS", INT2NUM(OCSP_REVOKED_STATUS_NOSTATUS));

    /* You must sign the request and resubmit */
    rb_define_const(mOCSP, "RESPONSE_STATUS_SIGREQUIRED", INT2NUM(OCSP_RESPONSE_STATUS_SIGREQUIRED));

    /* Response has valid confirmations */
    rb_define_const(mOCSP, "RESPONSE_STATUS_SUCCESSFUL", INT2NUM(OCSP_RESPONSE_STATUS_SUCCESSFUL));

    /* Try again later */
    rb_define_const(mOCSP, "RESPONSE_STATUS_TRYLATER", INT2NUM(OCSP_RESPONSE_STATUS_TRYLATER));

    /* The certificate subject's name or other information changed */
    rb_define_const(mOCSP, "REVOKED_STATUS_AFFILIATIONCHANGED", INT2NUM(OCSP_REVOKED_STATUS_AFFILIATIONCHANGED));

    /* This CA certificate was revoked due to a key compromise */
    rb_define_const(mOCSP, "REVOKED_STATUS_CACOMPROMISE", INT2NUM(OCSP_REVOKED_STATUS_CACOMPROMISE));

    /* The certificate is on hold */
    rb_define_const(mOCSP, "REVOKED_STATUS_CERTIFICATEHOLD", INT2NUM(OCSP_REVOKED_STATUS_CERTIFICATEHOLD));

    /* The certificate is no longer needed */
    rb_define_const(mOCSP, "REVOKED_STATUS_CESSATIONOFOPERATION", INT2NUM(OCSP_REVOKED_STATUS_CESSATIONOFOPERATION));

    /* The certificate was revoked due to a key compromise */
    rb_define_const(mOCSP, "REVOKED_STATUS_KEYCOMPROMISE", INT2NUM(OCSP_REVOKED_STATUS_KEYCOMPROMISE));

    /* The certificate was previously on hold and should now be removed from
     * the CRL */
    rb_define_const(mOCSP, "REVOKED_STATUS_REMOVEFROMCRL", INT2NUM(OCSP_REVOKED_STATUS_REMOVEFROMCRL));

    /* The certificate was superseded by a new certificate */
    rb_define_const(mOCSP, "REVOKED_STATUS_SUPERSEDED", INT2NUM(OCSP_REVOKED_STATUS_SUPERSEDED));

    /* Your request is unauthorized. */
    rb_define_const(mOCSP, "RESPONSE_STATUS_UNAUTHORIZED", INT2NUM(OCSP_RESPONSE_STATUS_UNAUTHORIZED));

    /* The certificate was revoked for an unspecified reason */
    rb_define_const(mOCSP, "REVOKED_STATUS_UNSPECIFIED", INT2NUM(OCSP_REVOKED_STATUS_UNSPECIFIED));

    /* Do not include certificates in the response */
    rb_define_const(mOCSP, "NOCERTS", INT2NUM(OCSP_NOCERTS));

    /* Do not search certificates contained in the response for a signer */
    rb_define_const(mOCSP, "NOINTERN", INT2NUM(OCSP_NOINTERN));

    /* Do not check the signature on the response */
    rb_define_const(mOCSP, "NOSIGS", INT2NUM(OCSP_NOSIGS));

    /* Do not verify the certificate chain on the response */
    rb_define_const(mOCSP, "NOCHAIN", INT2NUM(OCSP_NOCHAIN));

    /* Do not verify the response at all */
    rb_define_const(mOCSP, "NOVERIFY", INT2NUM(OCSP_NOVERIFY));

    /* Do not check trust */
    rb_define_const(mOCSP, "NOEXPLICIT", INT2NUM(OCSP_NOEXPLICIT));

    /* (This flag is not used by OpenSSL 1.0.1g) */
    rb_define_const(mOCSP, "NOCASIGN", INT2NUM(OCSP_NOCASIGN));

    /* (This flag is not used by OpenSSL 1.0.1g) */
    rb_define_const(mOCSP, "NODELEGATED", INT2NUM(OCSP_NODELEGATED));

    /* Do not make additional signing certificate checks */
    rb_define_const(mOCSP, "NOCHECKS", INT2NUM(OCSP_NOCHECKS));

    /* Do not verify additional certificates */
    rb_define_const(mOCSP, "TRUSTOTHER", INT2NUM(OCSP_TRUSTOTHER));

    /* Identify the response by signing the certificate key ID */
    rb_define_const(mOCSP, "RESPID_KEY", INT2NUM(OCSP_RESPID_KEY));

    /* Do not include producedAt time in response */
    rb_define_const(mOCSP, "NOTIME", INT2NUM(OCSP_NOTIME));

    /* Indicates the certificate is not revoked but does not necessarily mean
     * the certificate was issued or that this response is within the
     * certificate's validity interval */
    rb_define_const(mOCSP, "V_CERTSTATUS_GOOD", INT2NUM(V_OCSP_CERTSTATUS_GOOD));
    /* Indicates the certificate has been revoked either permanently or
     * temporarily (on hold). */
    rb_define_const(mOCSP, "V_CERTSTATUS_REVOKED", INT2NUM(V_OCSP_CERTSTATUS_REVOKED));

    /* Indicates the responder does not know about the certificate being
     * requested. */
    rb_define_const(mOCSP, "V_CERTSTATUS_UNKNOWN", INT2NUM(V_OCSP_CERTSTATUS_UNKNOWN));

    /* The responder ID is based on the key name. */
    rb_define_const(mOCSP, "V_RESPID_NAME", INT2NUM(V_OCSP_RESPID_NAME));

    /* The responder ID is based on the public key. */
    rb_define_const(mOCSP, "V_RESPID_KEY", INT2NUM(V_OCSP_RESPID_KEY));
}
#else
void
Init_ossl_ocsp(void)
{
}
#endif
