summaryrefslogtreecommitdiffstats
path: root/crypto/heimdal/lib/hx509/revoke.c
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/heimdal/lib/hx509/revoke.c')
-rw-r--r--crypto/heimdal/lib/hx509/revoke.c1525
1 files changed, 1525 insertions, 0 deletions
diff --git a/crypto/heimdal/lib/hx509/revoke.c b/crypto/heimdal/lib/hx509/revoke.c
new file mode 100644
index 0000000..cfde439
--- /dev/null
+++ b/crypto/heimdal/lib/hx509/revoke.c
@@ -0,0 +1,1525 @@
+/*
+ * Copyright (c) 2006 - 2007 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/**
+ * @page page_revoke Revocation methods
+ *
+ * There are two revocation method for PKIX/X.509: CRL and OCSP.
+ * Revocation is needed if the private key is lost and
+ * stolen. Depending on how picky you are, you might want to make
+ * revocation for destroyed private keys too (smartcard broken), but
+ * that should not be a problem.
+ *
+ * CRL is a list of certifiates that have expired.
+ *
+ * OCSP is an online checking method where the requestor sends a list
+ * of certificates to the OCSP server to return a signed reply if they
+ * are valid or not. Some services sends a OCSP reply as part of the
+ * hand-shake to make the revoktion decision simpler/faster for the
+ * client.
+ */
+
+#include "hx_locl.h"
+RCSID("$Id: revoke.c 22275 2007-12-11 11:02:11Z lha $");
+
+struct revoke_crl {
+ char *path;
+ time_t last_modfied;
+ CRLCertificateList crl;
+ int verified;
+ int failed_verify;
+};
+
+struct revoke_ocsp {
+ char *path;
+ time_t last_modfied;
+ OCSPBasicOCSPResponse ocsp;
+ hx509_certs certs;
+ hx509_cert signer;
+};
+
+
+struct hx509_revoke_ctx_data {
+ unsigned ref;
+ struct {
+ struct revoke_crl *val;
+ size_t len;
+ } crls;
+ struct {
+ struct revoke_ocsp *val;
+ size_t len;
+ } ocsps;
+};
+
+/**
+ * Allocate a revokation context. Free with hx509_revoke_free().
+ *
+ * @param context A hx509 context.
+ * @param ctx returns a newly allocated revokation context.
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_revoke
+ */
+
+int
+hx509_revoke_init(hx509_context context, hx509_revoke_ctx *ctx)
+{
+ *ctx = calloc(1, sizeof(**ctx));
+ if (*ctx == NULL)
+ return ENOMEM;
+
+ (*ctx)->ref = 1;
+ (*ctx)->crls.len = 0;
+ (*ctx)->crls.val = NULL;
+ (*ctx)->ocsps.len = 0;
+ (*ctx)->ocsps.val = NULL;
+
+ return 0;
+}
+
+hx509_revoke_ctx
+_hx509_revoke_ref(hx509_revoke_ctx ctx)
+{
+ if (ctx == NULL)
+ return NULL;
+ if (ctx->ref <= 0)
+ _hx509_abort("revoke ctx refcount <= 0");
+ ctx->ref++;
+ if (ctx->ref == 0)
+ _hx509_abort("revoke ctx refcount == 0");
+ return ctx;
+}
+
+static void
+free_ocsp(struct revoke_ocsp *ocsp)
+{
+ free(ocsp->path);
+ free_OCSPBasicOCSPResponse(&ocsp->ocsp);
+ hx509_certs_free(&ocsp->certs);
+ hx509_cert_free(ocsp->signer);
+}
+
+/**
+ * Free a hx509 revokation context.
+ *
+ * @param ctx context to be freed
+ *
+ * @ingroup hx509_revoke
+ */
+
+void
+hx509_revoke_free(hx509_revoke_ctx *ctx)
+{
+ size_t i ;
+
+ if (ctx == NULL || *ctx == NULL)
+ return;
+
+ if ((*ctx)->ref <= 0)
+ _hx509_abort("revoke ctx refcount <= 0 on free");
+ if (--(*ctx)->ref > 0)
+ return;
+
+ for (i = 0; i < (*ctx)->crls.len; i++) {
+ free((*ctx)->crls.val[i].path);
+ free_CRLCertificateList(&(*ctx)->crls.val[i].crl);
+ }
+
+ for (i = 0; i < (*ctx)->ocsps.len; i++)
+ free_ocsp(&(*ctx)->ocsps.val[i]);
+ free((*ctx)->ocsps.val);
+
+ free((*ctx)->crls.val);
+
+ memset(*ctx, 0, sizeof(**ctx));
+ free(*ctx);
+ *ctx = NULL;
+}
+
+static int
+verify_ocsp(hx509_context context,
+ struct revoke_ocsp *ocsp,
+ time_t time_now,
+ hx509_certs certs,
+ hx509_cert parent)
+{
+ hx509_cert signer = NULL;
+ hx509_query q;
+ int ret;
+
+ _hx509_query_clear(&q);
+
+ /*
+ * Need to match on issuer too in case there are two CA that have
+ * issued the same name to a certificate. One example of this is
+ * the www.openvalidation.org test's ocsp validator.
+ */
+
+ q.match = HX509_QUERY_MATCH_ISSUER_NAME;
+ q.issuer_name = &_hx509_get_cert(parent)->tbsCertificate.issuer;
+
+ switch(ocsp->ocsp.tbsResponseData.responderID.element) {
+ case choice_OCSPResponderID_byName:
+ q.match |= HX509_QUERY_MATCH_SUBJECT_NAME;
+ q.subject_name = &ocsp->ocsp.tbsResponseData.responderID.u.byName;
+ break;
+ case choice_OCSPResponderID_byKey:
+ q.match |= HX509_QUERY_MATCH_KEY_HASH_SHA1;
+ q.keyhash_sha1 = &ocsp->ocsp.tbsResponseData.responderID.u.byKey;
+ break;
+ }
+
+ ret = hx509_certs_find(context, certs, &q, &signer);
+ if (ret && ocsp->certs)
+ ret = hx509_certs_find(context, ocsp->certs, &q, &signer);
+ if (ret)
+ goto out;
+
+ /*
+ * If signer certificate isn't the CA certificate, lets check the
+ * it is the CA that signed the signer certificate and the OCSP EKU
+ * is set.
+ */
+ if (hx509_cert_cmp(signer, parent) != 0) {
+ Certificate *p = _hx509_get_cert(parent);
+ Certificate *s = _hx509_get_cert(signer);
+
+ ret = _hx509_cert_is_parent_cmp(s, p, 0);
+ if (ret != 0) {
+ ret = HX509_PARENT_NOT_CA;
+ hx509_set_error_string(context, 0, ret, "Revoke OSCP signer is "
+ "doesn't have CA as signer certificate");
+ goto out;
+ }
+
+ ret = _hx509_verify_signature_bitstring(context,
+ p,
+ &s->signatureAlgorithm,
+ &s->tbsCertificate._save,
+ &s->signatureValue);
+ if (ret) {
+ hx509_set_error_string(context, HX509_ERROR_APPEND, ret,
+ "OSCP signer signature invalid");
+ goto out;
+ }
+
+ ret = hx509_cert_check_eku(context, signer,
+ oid_id_pkix_kp_OCSPSigning(), 0);
+ if (ret)
+ goto out;
+ }
+
+ ret = _hx509_verify_signature_bitstring(context,
+ _hx509_get_cert(signer),
+ &ocsp->ocsp.signatureAlgorithm,
+ &ocsp->ocsp.tbsResponseData._save,
+ &ocsp->ocsp.signature);
+ if (ret) {
+ hx509_set_error_string(context, HX509_ERROR_APPEND, ret,
+ "OSCP signature invalid");
+ goto out;
+ }
+
+ ocsp->signer = signer;
+ signer = NULL;
+out:
+ if (signer)
+ hx509_cert_free(signer);
+
+ return ret;
+}
+
+/*
+ *
+ */
+
+static int
+parse_ocsp_basic(const void *data, size_t length, OCSPBasicOCSPResponse *basic)
+{
+ OCSPResponse resp;
+ size_t size;
+ int ret;
+
+ memset(basic, 0, sizeof(*basic));
+
+ ret = decode_OCSPResponse(data, length, &resp, &size);
+ if (ret)
+ return ret;
+ if (length != size) {
+ free_OCSPResponse(&resp);
+ return ASN1_EXTRA_DATA;
+ }
+
+ switch (resp.responseStatus) {
+ case successful:
+ break;
+ default:
+ free_OCSPResponse(&resp);
+ return HX509_REVOKE_WRONG_DATA;
+ }
+
+ if (resp.responseBytes == NULL) {
+ free_OCSPResponse(&resp);
+ return EINVAL;
+ }
+
+ ret = der_heim_oid_cmp(&resp.responseBytes->responseType,
+ oid_id_pkix_ocsp_basic());
+ if (ret != 0) {
+ free_OCSPResponse(&resp);
+ return HX509_REVOKE_WRONG_DATA;
+ }
+
+ ret = decode_OCSPBasicOCSPResponse(resp.responseBytes->response.data,
+ resp.responseBytes->response.length,
+ basic,
+ &size);
+ if (ret) {
+ free_OCSPResponse(&resp);
+ return ret;
+ }
+ if (size != resp.responseBytes->response.length) {
+ free_OCSPResponse(&resp);
+ free_OCSPBasicOCSPResponse(basic);
+ return ASN1_EXTRA_DATA;
+ }
+ free_OCSPResponse(&resp);
+
+ return 0;
+}
+
+/*
+ *
+ */
+
+static int
+load_ocsp(hx509_context context, struct revoke_ocsp *ocsp)
+{
+ OCSPBasicOCSPResponse basic;
+ hx509_certs certs = NULL;
+ size_t length;
+ struct stat sb;
+ void *data;
+ int ret;
+
+ ret = _hx509_map_file(ocsp->path, &data, &length, &sb);
+ if (ret)
+ return ret;
+
+ ret = parse_ocsp_basic(data, length, &basic);
+ _hx509_unmap_file(data, length);
+ if (ret) {
+ hx509_set_error_string(context, 0, ret,
+ "Failed to parse OCSP response");
+ return ret;
+ }
+
+ if (basic.certs) {
+ int i;
+
+ ret = hx509_certs_init(context, "MEMORY:ocsp-certs", 0,
+ NULL, &certs);
+ if (ret) {
+ free_OCSPBasicOCSPResponse(&basic);
+ return ret;
+ }
+
+ for (i = 0; i < basic.certs->len; i++) {
+ hx509_cert c;
+
+ ret = hx509_cert_init(context, &basic.certs->val[i], &c);
+ if (ret)
+ continue;
+
+ ret = hx509_certs_add(context, certs, c);
+ hx509_cert_free(c);
+ if (ret)
+ continue;
+ }
+ }
+
+ ocsp->last_modfied = sb.st_mtime;
+
+ free_OCSPBasicOCSPResponse(&ocsp->ocsp);
+ hx509_certs_free(&ocsp->certs);
+ hx509_cert_free(ocsp->signer);
+
+ ocsp->ocsp = basic;
+ ocsp->certs = certs;
+ ocsp->signer = NULL;
+
+ return 0;
+}
+
+/**
+ * Add a OCSP file to the revokation context.
+ *
+ * @param context hx509 context
+ * @param ctx hx509 revokation context
+ * @param path path to file that is going to be added to the context.
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_revoke
+ */
+
+int
+hx509_revoke_add_ocsp(hx509_context context,
+ hx509_revoke_ctx ctx,
+ const char *path)
+{
+ void *data;
+ int ret;
+ size_t i;
+
+ if (strncmp(path, "FILE:", 5) != 0) {
+ hx509_set_error_string(context, 0, HX509_UNSUPPORTED_OPERATION,
+ "unsupport type in %s", path);
+ return HX509_UNSUPPORTED_OPERATION;
+ }
+
+ path += 5;
+
+ for (i = 0; i < ctx->ocsps.len; i++) {
+ if (strcmp(ctx->ocsps.val[0].path, path) == 0)
+ return 0;
+ }
+
+ data = realloc(ctx->ocsps.val,
+ (ctx->ocsps.len + 1) * sizeof(ctx->ocsps.val[0]));
+ if (data == NULL) {
+ hx509_clear_error_string(context);
+ return ENOMEM;
+ }
+
+ ctx->ocsps.val = data;
+
+ memset(&ctx->ocsps.val[ctx->ocsps.len], 0,
+ sizeof(ctx->ocsps.val[0]));
+
+ ctx->ocsps.val[ctx->ocsps.len].path = strdup(path);
+ if (ctx->ocsps.val[ctx->ocsps.len].path == NULL) {
+ hx509_clear_error_string(context);
+ return ENOMEM;
+ }
+
+ ret = load_ocsp(context, &ctx->ocsps.val[ctx->ocsps.len]);
+ if (ret) {
+ free(ctx->ocsps.val[ctx->ocsps.len].path);
+ return ret;
+ }
+ ctx->ocsps.len++;
+
+ return ret;
+}
+
+/*
+ *
+ */
+
+static int
+verify_crl(hx509_context context,
+ hx509_revoke_ctx ctx,
+ CRLCertificateList *crl,
+ time_t time_now,
+ hx509_certs certs,
+ hx509_cert parent)
+{
+ hx509_cert signer;
+ hx509_query q;
+ time_t t;
+ int ret;
+
+ t = _hx509_Time2time_t(&crl->tbsCertList.thisUpdate);
+ if (t > time_now) {
+ hx509_set_error_string(context, 0, HX509_CRL_USED_BEFORE_TIME,
+ "CRL used before time");
+ return HX509_CRL_USED_BEFORE_TIME;
+ }
+
+ if (crl->tbsCertList.nextUpdate == NULL) {
+ hx509_set_error_string(context, 0, HX509_CRL_INVALID_FORMAT,
+ "CRL missing nextUpdate");
+ return HX509_CRL_INVALID_FORMAT;
+ }
+
+ t = _hx509_Time2time_t(crl->tbsCertList.nextUpdate);
+ if (t < time_now) {
+ hx509_set_error_string(context, 0, HX509_CRL_USED_AFTER_TIME,
+ "CRL used after time");
+ return HX509_CRL_USED_AFTER_TIME;
+ }
+
+ _hx509_query_clear(&q);
+
+ /*
+ * If it's the signer have CRLSIGN bit set, use that as the signer
+ * cert for the certificate, otherwise, search for a certificate.
+ */
+ if (_hx509_check_key_usage(context, parent, 1 << 6, FALSE) == 0) {
+ signer = hx509_cert_ref(parent);
+ } else {
+ q.match = HX509_QUERY_MATCH_SUBJECT_NAME;
+ q.match |= HX509_QUERY_KU_CRLSIGN;
+ q.subject_name = &crl->tbsCertList.issuer;
+
+ ret = hx509_certs_find(context, certs, &q, &signer);
+ if (ret) {
+ hx509_set_error_string(context, HX509_ERROR_APPEND, ret,
+ "Failed to find certificate for CRL");
+ return ret;
+ }
+ }
+
+ ret = _hx509_verify_signature_bitstring(context,
+ _hx509_get_cert(signer),
+ &crl->signatureAlgorithm,
+ &crl->tbsCertList._save,
+ &crl->signatureValue);
+ if (ret) {
+ hx509_set_error_string(context, HX509_ERROR_APPEND, ret,
+ "CRL signature invalid");
+ goto out;
+ }
+
+ /*
+ * If signer is not CA cert, need to check revoke status of this
+ * CRL signing cert too, this include all parent CRL signer cert
+ * up to the root *sigh*, assume root at least hve CERTSIGN flag
+ * set.
+ */
+ while (_hx509_check_key_usage(context, signer, 1 << 5, TRUE)) {
+ hx509_cert crl_parent;
+
+ _hx509_query_clear(&q);
+
+ q.match = HX509_QUERY_MATCH_SUBJECT_NAME;
+ q.match |= HX509_QUERY_KU_CRLSIGN;
+ q.subject_name = &_hx509_get_cert(signer)->tbsCertificate.issuer;
+
+ ret = hx509_certs_find(context, certs, &q, &crl_parent);
+ if (ret) {
+ hx509_set_error_string(context, HX509_ERROR_APPEND, ret,
+ "Failed to find parent of CRL signer");
+ goto out;
+ }
+
+ ret = hx509_revoke_verify(context,
+ ctx,
+ certs,
+ time_now,
+ signer,
+ crl_parent);
+ hx509_cert_free(signer);
+ signer = crl_parent;
+ if (ret) {
+ hx509_set_error_string(context, HX509_ERROR_APPEND, ret,
+ "Failed to verify revoke "
+ "status of CRL signer");
+ goto out;
+ }
+ }
+
+out:
+ hx509_cert_free(signer);
+
+ return ret;
+}
+
+static int
+load_crl(const char *path, time_t *t, CRLCertificateList *crl)
+{
+ size_t length, size;
+ struct stat sb;
+ void *data;
+ int ret;
+
+ memset(crl, 0, sizeof(*crl));
+
+ ret = _hx509_map_file(path, &data, &length, &sb);
+ if (ret)
+ return ret;
+
+ *t = sb.st_mtime;
+
+ ret = decode_CRLCertificateList(data, length, crl, &size);
+ _hx509_unmap_file(data, length);
+ if (ret)
+ return ret;
+
+ /* check signature is aligned */
+ if (crl->signatureValue.length & 7) {
+ free_CRLCertificateList(crl);
+ return HX509_CRYPTO_SIG_INVALID_FORMAT;
+ }
+ return 0;
+}
+
+/**
+ * Add a CRL file to the revokation context.
+ *
+ * @param context hx509 context
+ * @param ctx hx509 revokation context
+ * @param path path to file that is going to be added to the context.
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_revoke
+ */
+
+int
+hx509_revoke_add_crl(hx509_context context,
+ hx509_revoke_ctx ctx,
+ const char *path)
+{
+ void *data;
+ size_t i;
+ int ret;
+
+ if (strncmp(path, "FILE:", 5) != 0) {
+ hx509_set_error_string(context, 0, HX509_UNSUPPORTED_OPERATION,
+ "unsupport type in %s", path);
+ return HX509_UNSUPPORTED_OPERATION;
+ }
+
+
+ path += 5;
+
+ for (i = 0; i < ctx->crls.len; i++) {
+ if (strcmp(ctx->crls.val[0].path, path) == 0)
+ return 0;
+ }
+
+ data = realloc(ctx->crls.val,
+ (ctx->crls.len + 1) * sizeof(ctx->crls.val[0]));
+ if (data == NULL) {
+ hx509_clear_error_string(context);
+ return ENOMEM;
+ }
+ ctx->crls.val = data;
+
+ memset(&ctx->crls.val[ctx->crls.len], 0, sizeof(ctx->crls.val[0]));
+
+ ctx->crls.val[ctx->crls.len].path = strdup(path);
+ if (ctx->crls.val[ctx->crls.len].path == NULL) {
+ hx509_clear_error_string(context);
+ return ENOMEM;
+ }
+
+ ret = load_crl(path,
+ &ctx->crls.val[ctx->crls.len].last_modfied,
+ &ctx->crls.val[ctx->crls.len].crl);
+ if (ret) {
+ free(ctx->crls.val[ctx->crls.len].path);
+ return ret;
+ }
+
+ ctx->crls.len++;
+
+ return ret;
+}
+
+/**
+ * Check that a certificate is not expired according to a revokation
+ * context. Also need the parent certificte to the check OCSP
+ * parent identifier.
+ *
+ * @param context hx509 context
+ * @param ctx hx509 revokation context
+ * @param certs
+ * @param now
+ * @param cert
+ * @param parent_cert
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_revoke
+ */
+
+
+int
+hx509_revoke_verify(hx509_context context,
+ hx509_revoke_ctx ctx,
+ hx509_certs certs,
+ time_t now,
+ hx509_cert cert,
+ hx509_cert parent_cert)
+{
+ const Certificate *c = _hx509_get_cert(cert);
+ const Certificate *p = _hx509_get_cert(parent_cert);
+ unsigned long i, j, k;
+ int ret;
+
+ hx509_clear_error_string(context);
+
+ for (i = 0; i < ctx->ocsps.len; i++) {
+ struct revoke_ocsp *ocsp = &ctx->ocsps.val[i];
+ struct stat sb;
+
+ /* check this ocsp apply to this cert */
+
+ /* check if there is a newer version of the file */
+ ret = stat(ocsp->path, &sb);
+ if (ret == 0 && ocsp->last_modfied != sb.st_mtime) {
+ ret = load_ocsp(context, ocsp);
+ if (ret)
+ continue;
+ }
+
+ /* verify signature in ocsp if not already done */
+ if (ocsp->signer == NULL) {
+ ret = verify_ocsp(context, ocsp, now, certs, parent_cert);
+ if (ret)
+ continue;
+ }
+
+ for (j = 0; j < ocsp->ocsp.tbsResponseData.responses.len; j++) {
+ heim_octet_string os;
+
+ ret = der_heim_integer_cmp(&ocsp->ocsp.tbsResponseData.responses.val[j].certID.serialNumber,
+ &c->tbsCertificate.serialNumber);
+ if (ret != 0)
+ continue;
+
+ /* verify issuer hashes hash */
+ ret = _hx509_verify_signature(context,
+ NULL,
+ &ocsp->ocsp.tbsResponseData.responses.val[i].certID.hashAlgorithm,
+ &c->tbsCertificate.issuer._save,
+ &ocsp->ocsp.tbsResponseData.responses.val[i].certID.issuerNameHash);
+ if (ret != 0)
+ continue;
+
+ os.data = p->tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data;
+ os.length = p->tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.length / 8;
+
+ ret = _hx509_verify_signature(context,
+ NULL,
+ &ocsp->ocsp.tbsResponseData.responses.val[j].certID.hashAlgorithm,
+ &os,
+ &ocsp->ocsp.tbsResponseData.responses.val[j].certID.issuerKeyHash);
+ if (ret != 0)
+ continue;
+
+ switch (ocsp->ocsp.tbsResponseData.responses.val[j].certStatus.element) {
+ case choice_OCSPCertStatus_good:
+ break;
+ case choice_OCSPCertStatus_revoked:
+ hx509_set_error_string(context, 0,
+ HX509_CERT_REVOKED,
+ "Certificate revoked by issuer in OCSP");
+ return HX509_CERT_REVOKED;
+ case choice_OCSPCertStatus_unknown:
+ continue;
+ }
+
+ /* don't allow the update to be in the future */
+ if (ocsp->ocsp.tbsResponseData.responses.val[j].thisUpdate >
+ now + context->ocsp_time_diff)
+ continue;
+
+ /* don't allow the next update to be in the past */
+ if (ocsp->ocsp.tbsResponseData.responses.val[j].nextUpdate) {
+ if (*ocsp->ocsp.tbsResponseData.responses.val[j].nextUpdate < now)
+ continue;
+ } else
+ /* Should force a refetch, but can we ? */;
+
+ return 0;
+ }
+ }
+
+ for (i = 0; i < ctx->crls.len; i++) {
+ struct revoke_crl *crl = &ctx->crls.val[i];
+ struct stat sb;
+
+ /* check if cert.issuer == crls.val[i].crl.issuer */
+ ret = _hx509_name_cmp(&c->tbsCertificate.issuer,
+ &crl->crl.tbsCertList.issuer);
+ if (ret)
+ continue;
+
+ ret = stat(crl->path, &sb);
+ if (ret == 0 && crl->last_modfied != sb.st_mtime) {
+ CRLCertificateList cl;
+
+ ret = load_crl(crl->path, &crl->last_modfied, &cl);
+ if (ret == 0) {
+ free_CRLCertificateList(&crl->crl);
+ crl->crl = cl;
+ crl->verified = 0;
+ crl->failed_verify = 0;
+ }
+ }
+ if (crl->failed_verify)
+ continue;
+
+ /* verify signature in crl if not already done */
+ if (crl->verified == 0) {
+ ret = verify_crl(context, ctx, &crl->crl, now, certs, parent_cert);
+ if (ret) {
+ crl->failed_verify = 1;
+ continue;
+ }
+ crl->verified = 1;
+ }
+
+ if (crl->crl.tbsCertList.crlExtensions) {
+ for (j = 0; j < crl->crl.tbsCertList.crlExtensions->len; j++) {
+ if (crl->crl.tbsCertList.crlExtensions->val[j].critical) {
+ hx509_set_error_string(context, 0,
+ HX509_CRL_UNKNOWN_EXTENSION,
+ "Unknown CRL extension");
+ return HX509_CRL_UNKNOWN_EXTENSION;
+ }
+ }
+ }
+
+ if (crl->crl.tbsCertList.revokedCertificates == NULL)
+ return 0;
+
+ /* check if cert is in crl */
+ for (j = 0; j < crl->crl.tbsCertList.revokedCertificates->len; j++) {
+ time_t t;
+
+ ret = der_heim_integer_cmp(&crl->crl.tbsCertList.revokedCertificates->val[j].userCertificate,
+ &c->tbsCertificate.serialNumber);
+ if (ret != 0)
+ continue;
+
+ t = _hx509_Time2time_t(&crl->crl.tbsCertList.revokedCertificates->val[j].revocationDate);
+ if (t > now)
+ continue;
+
+ if (crl->crl.tbsCertList.revokedCertificates->val[j].crlEntryExtensions)
+ for (k = 0; k < crl->crl.tbsCertList.revokedCertificates->val[j].crlEntryExtensions->len; k++)
+ if (crl->crl.tbsCertList.revokedCertificates->val[j].crlEntryExtensions->val[k].critical)
+ return HX509_CRL_UNKNOWN_EXTENSION;
+
+ hx509_set_error_string(context, 0,
+ HX509_CERT_REVOKED,
+ "Certificate revoked by issuer in CRL");
+ return HX509_CERT_REVOKED;
+ }
+
+ return 0;
+ }
+
+
+ if (context->flags & HX509_CTX_VERIFY_MISSING_OK)
+ return 0;
+ hx509_set_error_string(context, HX509_ERROR_APPEND,
+ HX509_REVOKE_STATUS_MISSING,
+ "No revoke status found for "
+ "certificates");
+ return HX509_REVOKE_STATUS_MISSING;
+}
+
+struct ocsp_add_ctx {
+ OCSPTBSRequest *req;
+ hx509_certs certs;
+ const AlgorithmIdentifier *digest;
+ hx509_cert parent;
+};
+
+static int
+add_to_req(hx509_context context, void *ptr, hx509_cert cert)
+{
+ struct ocsp_add_ctx *ctx = ptr;
+ OCSPInnerRequest *one;
+ hx509_cert parent = NULL;
+ Certificate *p, *c = _hx509_get_cert(cert);
+ heim_octet_string os;
+ int ret;
+ hx509_query q;
+ void *d;
+
+ d = realloc(ctx->req->requestList.val,
+ sizeof(ctx->req->requestList.val[0]) *
+ (ctx->req->requestList.len + 1));
+ if (d == NULL)
+ return ENOMEM;
+ ctx->req->requestList.val = d;
+
+ one = &ctx->req->requestList.val[ctx->req->requestList.len];
+ memset(one, 0, sizeof(*one));
+
+ _hx509_query_clear(&q);
+
+ q.match |= HX509_QUERY_FIND_ISSUER_CERT;
+ q.subject = c;
+
+ ret = hx509_certs_find(context, ctx->certs, &q, &parent);
+ if (ret)
+ goto out;
+
+ if (ctx->parent) {
+ if (hx509_cert_cmp(ctx->parent, parent) != 0) {
+ ret = HX509_REVOKE_NOT_SAME_PARENT;
+ hx509_set_error_string(context, 0, ret,
+ "Not same parent certifate as "
+ "last certificate in request");
+ goto out;
+ }
+ } else
+ ctx->parent = hx509_cert_ref(parent);
+
+ p = _hx509_get_cert(parent);
+
+ ret = copy_AlgorithmIdentifier(ctx->digest, &one->reqCert.hashAlgorithm);
+ if (ret)
+ goto out;
+
+ ret = _hx509_create_signature(context,
+ NULL,
+ &one->reqCert.hashAlgorithm,
+ &c->tbsCertificate.issuer._save,
+ NULL,
+ &one->reqCert.issuerNameHash);
+ if (ret)
+ goto out;
+
+ os.data = p->tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data;
+ os.length =
+ p->tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.length / 8;
+
+ ret = _hx509_create_signature(context,
+ NULL,
+ &one->reqCert.hashAlgorithm,
+ &os,
+ NULL,
+ &one->reqCert.issuerKeyHash);
+ if (ret)
+ goto out;
+
+ ret = copy_CertificateSerialNumber(&c->tbsCertificate.serialNumber,
+ &one->reqCert.serialNumber);
+ if (ret)
+ goto out;
+
+ ctx->req->requestList.len++;
+out:
+ hx509_cert_free(parent);
+ if (ret) {
+ free_OCSPInnerRequest(one);
+ memset(one, 0, sizeof(*one));
+ }
+
+ return ret;
+}
+
+/**
+ * Create an OCSP request for a set of certificates.
+ *
+ * @param context a hx509 context
+ * @param reqcerts list of certificates to request ocsp data for
+ * @param pool certificate pool to use when signing
+ * @param signer certificate to use to sign the request
+ * @param digest the signing algorithm in the request, if NULL use the
+ * default signature algorithm,
+ * @param request the encoded request, free with free_heim_octet_string().
+ * @param nonce nonce in the request, free with free_heim_octet_string().
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_revoke
+ */
+
+int
+hx509_ocsp_request(hx509_context context,
+ hx509_certs reqcerts,
+ hx509_certs pool,
+ hx509_cert signer,
+ const AlgorithmIdentifier *digest,
+ heim_octet_string *request,
+ heim_octet_string *nonce)
+{
+ OCSPRequest req;
+ size_t size;
+ int ret;
+ struct ocsp_add_ctx ctx;
+ Extensions *es;
+
+ memset(&req, 0, sizeof(req));
+
+ if (digest == NULL)
+ digest = _hx509_crypto_default_digest_alg;
+
+ ctx.req = &req.tbsRequest;
+ ctx.certs = pool;
+ ctx.digest = digest;
+ ctx.parent = NULL;
+
+ ret = hx509_certs_iter(context, reqcerts, add_to_req, &ctx);
+ hx509_cert_free(ctx.parent);
+ if (ret)
+ goto out;
+
+ if (nonce) {
+ req.tbsRequest.requestExtensions =
+ calloc(1, sizeof(*req.tbsRequest.requestExtensions));
+ if (req.tbsRequest.requestExtensions == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ es = req.tbsRequest.requestExtensions;
+
+ es->val = calloc(es->len, sizeof(es->val[0]));
+ if (es->val == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ es->len = 1;
+
+ ret = der_copy_oid(oid_id_pkix_ocsp_nonce(), &es->val[0].extnID);
+ if (ret) {
+ free_OCSPRequest(&req);
+ return ret;
+ }
+
+ es->val[0].extnValue.data = malloc(10);
+ if (es->val[0].extnValue.data == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ es->val[0].extnValue.length = 10;
+
+ ret = RAND_bytes(es->val[0].extnValue.data,
+ es->val[0].extnValue.length);
+ if (ret != 1) {
+ ret = HX509_CRYPTO_INTERNAL_ERROR;
+ goto out;
+ }
+ ret = der_copy_octet_string(nonce, &es->val[0].extnValue);
+ if (ret) {
+ ret = ENOMEM;
+ goto out;
+ }
+ }
+
+ ASN1_MALLOC_ENCODE(OCSPRequest, request->data, request->length,
+ &req, &size, ret);
+ free_OCSPRequest(&req);
+ if (ret)
+ goto out;
+ if (size != request->length)
+ _hx509_abort("internal ASN.1 encoder error");
+
+ return 0;
+
+out:
+ free_OCSPRequest(&req);
+ return ret;
+}
+
+static char *
+printable_time(time_t t)
+{
+ static char s[128];
+ strlcpy(s, ctime(&t)+ 4, sizeof(s));
+ s[20] = 0;
+ return s;
+}
+
+/**
+ * Print the OCSP reply stored in a file.
+ *
+ * @param context a hx509 context
+ * @param path path to a file with a OCSP reply
+ * @param out the out FILE descriptor to print the reply on
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_revoke
+ */
+
+int
+hx509_revoke_ocsp_print(hx509_context context, const char *path, FILE *out)
+{
+ struct revoke_ocsp ocsp;
+ int ret, i;
+
+ if (out == NULL)
+ out = stdout;
+
+ memset(&ocsp, 0, sizeof(ocsp));
+
+ ocsp.path = strdup(path);
+ if (ocsp.path == NULL)
+ return ENOMEM;
+
+ ret = load_ocsp(context, &ocsp);
+ if (ret) {
+ free_ocsp(&ocsp);
+ return ret;
+ }
+
+ fprintf(out, "signer: ");
+
+ switch(ocsp.ocsp.tbsResponseData.responderID.element) {
+ case choice_OCSPResponderID_byName: {
+ hx509_name n;
+ char *s;
+ _hx509_name_from_Name(&ocsp.ocsp.tbsResponseData.responderID.u.byName, &n);
+ hx509_name_to_string(n, &s);
+ hx509_name_free(&n);
+ fprintf(out, " byName: %s\n", s);
+ free(s);
+ break;
+ }
+ case choice_OCSPResponderID_byKey: {
+ char *s;
+ hex_encode(ocsp.ocsp.tbsResponseData.responderID.u.byKey.data,
+ ocsp.ocsp.tbsResponseData.responderID.u.byKey.length,
+ &s);
+ fprintf(out, " byKey: %s\n", s);
+ free(s);
+ break;
+ }
+ default:
+ _hx509_abort("choice_OCSPResponderID unknown");
+ break;
+ }
+
+ fprintf(out, "producedAt: %s\n",
+ printable_time(ocsp.ocsp.tbsResponseData.producedAt));
+
+ fprintf(out, "replies: %d\n", ocsp.ocsp.tbsResponseData.responses.len);
+
+ for (i = 0; i < ocsp.ocsp.tbsResponseData.responses.len; i++) {
+ const char *status;
+ switch (ocsp.ocsp.tbsResponseData.responses.val[i].certStatus.element) {
+ case choice_OCSPCertStatus_good:
+ status = "good";
+ break;
+ case choice_OCSPCertStatus_revoked:
+ status = "revoked";
+ break;
+ case choice_OCSPCertStatus_unknown:
+ status = "unknown";
+ break;
+ default:
+ status = "element unknown";
+ }
+
+ fprintf(out, "\t%d. status: %s\n", i, status);
+
+ fprintf(out, "\tthisUpdate: %s\n",
+ printable_time(ocsp.ocsp.tbsResponseData.responses.val[i].thisUpdate));
+ if (ocsp.ocsp.tbsResponseData.responses.val[i].nextUpdate)
+ fprintf(out, "\tproducedAt: %s\n",
+ printable_time(ocsp.ocsp.tbsResponseData.responses.val[i].thisUpdate));
+
+ }
+
+ fprintf(out, "appended certs:\n");
+ if (ocsp.certs)
+ ret = hx509_certs_iter(context, ocsp.certs, hx509_ci_print_names, out);
+
+ free_ocsp(&ocsp);
+ return ret;
+}
+
+/**
+ * Verify that the certificate is part of the OCSP reply and it's not
+ * expired. Doesn't verify signature the OCSP reply or it's done by a
+ * authorized sender, that is assumed to be already done.
+ *
+ * @param context a hx509 context
+ * @param now the time right now, if 0, use the current time.
+ * @param cert the certificate to verify
+ * @param flags flags control the behavior
+ * @param data pointer to the encode ocsp reply
+ * @param length the length of the encode ocsp reply
+ * @param expiration return the time the OCSP will expire and need to
+ * be rechecked.
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_verify
+ */
+
+int
+hx509_ocsp_verify(hx509_context context,
+ time_t now,
+ hx509_cert cert,
+ int flags,
+ const void *data, size_t length,
+ time_t *expiration)
+{
+ const Certificate *c = _hx509_get_cert(cert);
+ OCSPBasicOCSPResponse basic;
+ int ret, i;
+
+ if (now == 0)
+ now = time(NULL);
+
+ *expiration = 0;
+
+ ret = parse_ocsp_basic(data, length, &basic);
+ if (ret) {
+ hx509_set_error_string(context, 0, ret,
+ "Failed to parse OCSP response");
+ return ret;
+ }
+
+ for (i = 0; i < basic.tbsResponseData.responses.len; i++) {
+
+ ret = der_heim_integer_cmp(&basic.tbsResponseData.responses.val[i].certID.serialNumber,
+ &c->tbsCertificate.serialNumber);
+ if (ret != 0)
+ continue;
+
+ /* verify issuer hashes hash */
+ ret = _hx509_verify_signature(context,
+ NULL,
+ &basic.tbsResponseData.responses.val[i].certID.hashAlgorithm,
+ &c->tbsCertificate.issuer._save,
+ &basic.tbsResponseData.responses.val[i].certID.issuerNameHash);
+ if (ret != 0)
+ continue;
+
+ switch (basic.tbsResponseData.responses.val[i].certStatus.element) {
+ case choice_OCSPCertStatus_good:
+ break;
+ case choice_OCSPCertStatus_revoked:
+ case choice_OCSPCertStatus_unknown:
+ continue;
+ }
+
+ /* don't allow the update to be in the future */
+ if (basic.tbsResponseData.responses.val[i].thisUpdate >
+ now + context->ocsp_time_diff)
+ continue;
+
+ /* don't allow the next update to be in the past */
+ if (basic.tbsResponseData.responses.val[i].nextUpdate) {
+ if (*basic.tbsResponseData.responses.val[i].nextUpdate < now)
+ continue;
+ *expiration = *basic.tbsResponseData.responses.val[i].nextUpdate;
+ } else
+ *expiration = now;
+
+ free_OCSPBasicOCSPResponse(&basic);
+ return 0;
+ }
+
+ free_OCSPBasicOCSPResponse(&basic);
+
+ {
+ hx509_name name;
+ char *subject;
+
+ ret = hx509_cert_get_subject(cert, &name);
+ if (ret) {
+ hx509_clear_error_string(context);
+ goto out;
+ }
+ ret = hx509_name_to_string(name, &subject);
+ hx509_name_free(&name);
+ if (ret) {
+ hx509_clear_error_string(context);
+ goto out;
+ }
+ hx509_set_error_string(context, 0, HX509_CERT_NOT_IN_OCSP,
+ "Certificate %s not in OCSP response "
+ "or not good",
+ subject);
+ free(subject);
+ }
+out:
+ return HX509_CERT_NOT_IN_OCSP;
+}
+
+struct hx509_crl {
+ hx509_certs revoked;
+ time_t expire;
+};
+
+/**
+ * Create a CRL context. Use hx509_crl_free() to free the CRL context.
+ *
+ * @param context a hx509 context.
+ * @param crl return pointer to a newly allocated CRL context.
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_verify
+ */
+
+int
+hx509_crl_alloc(hx509_context context, hx509_crl *crl)
+{
+ int ret;
+
+ *crl = calloc(1, sizeof(**crl));
+ if (*crl == NULL) {
+ hx509_set_error_string(context, 0, ENOMEM, "out of memory");
+ return ENOMEM;
+ }
+
+ ret = hx509_certs_init(context, "MEMORY:crl", 0, NULL, &(*crl)->revoked);
+ if (ret) {
+ free(*crl);
+ *crl = NULL;
+ return ret;
+ }
+ (*crl)->expire = 0;
+ return ret;
+}
+
+/**
+ * Add revoked certificate to an CRL context.
+ *
+ * @param context a hx509 context.
+ * @param crl the CRL to add the revoked certificate to.
+ * @param certs keyset of certificate to revoke.
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_verify
+ */
+
+int
+hx509_crl_add_revoked_certs(hx509_context context,
+ hx509_crl crl,
+ hx509_certs certs)
+{
+ return hx509_certs_merge(context, crl->revoked, certs);
+}
+
+/**
+ * Set the lifetime of a CRL context.
+ *
+ * @param context a hx509 context.
+ * @param crl a CRL context
+ * @param delta delta time the certificate is valid, library adds the
+ * current time to this.
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_verify
+ */
+
+int
+hx509_crl_lifetime(hx509_context context, hx509_crl crl, int delta)
+{
+ crl->expire = time(NULL) + delta;
+ return 0;
+}
+
+/**
+ * Free a CRL context.
+ *
+ * @param context a hx509 context.
+ * @param crl a CRL context to free.
+ *
+ * @ingroup hx509_verify
+ */
+
+void
+hx509_crl_free(hx509_context context, hx509_crl *crl)
+{
+ if (*crl == NULL)
+ return;
+ hx509_certs_free(&(*crl)->revoked);
+ memset(*crl, 0, sizeof(**crl));
+ free(*crl);
+ *crl = NULL;
+}
+
+static int
+add_revoked(hx509_context context, void *ctx, hx509_cert cert)
+{
+ TBSCRLCertList *c = ctx;
+ unsigned int num;
+ void *ptr;
+ int ret;
+
+ num = c->revokedCertificates->len;
+ ptr = realloc(c->revokedCertificates->val,
+ (num + 1) * sizeof(c->revokedCertificates->val[0]));
+ if (ptr == NULL) {
+ hx509_clear_error_string(context);
+ return ENOMEM;
+ }
+ c->revokedCertificates->val = ptr;
+
+ ret = hx509_cert_get_serialnumber(cert,
+ &c->revokedCertificates->val[num].userCertificate);
+ if (ret) {
+ hx509_clear_error_string(context);
+ return ret;
+ }
+ c->revokedCertificates->val[num].revocationDate.element =
+ choice_Time_generalTime;
+ c->revokedCertificates->val[num].revocationDate.u.generalTime =
+ time(NULL) - 3600 * 24;
+ c->revokedCertificates->val[num].crlEntryExtensions = NULL;
+
+ c->revokedCertificates->len++;
+
+ return 0;
+}
+
+/**
+ * Sign a CRL and return an encode certificate.
+ *
+ * @param context a hx509 context.
+ * @param signer certificate to sign the CRL with
+ * @param crl the CRL to sign
+ * @param os return the signed and encoded CRL, free with
+ * free_heim_octet_string()
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_verify
+ */
+
+int
+hx509_crl_sign(hx509_context context,
+ hx509_cert signer,
+ hx509_crl crl,
+ heim_octet_string *os)
+{
+ const AlgorithmIdentifier *sigalg = _hx509_crypto_default_sig_alg;
+ CRLCertificateList c;
+ size_t size;
+ int ret;
+ hx509_private_key signerkey;
+
+ memset(&c, 0, sizeof(c));
+
+ signerkey = _hx509_cert_private_key(signer);
+ if (signerkey == NULL) {
+ ret = HX509_PRIVATE_KEY_MISSING;
+ hx509_set_error_string(context, 0, ret,
+ "Private key missing for CRL signing");
+ return ret;
+ }
+
+ c.tbsCertList.version = malloc(sizeof(*c.tbsCertList.version));
+ if (c.tbsCertList.version == NULL) {
+ hx509_set_error_string(context, 0, ENOMEM, "out of memory");
+ return ENOMEM;
+ }
+
+ *c.tbsCertList.version = 1;
+
+ ret = copy_AlgorithmIdentifier(sigalg, &c.tbsCertList.signature);
+ if (ret) {
+ hx509_clear_error_string(context);
+ goto out;
+ }
+
+ ret = copy_Name(&_hx509_get_cert(signer)->tbsCertificate.issuer,
+ &c.tbsCertList.issuer);
+ if (ret) {
+ hx509_clear_error_string(context);
+ goto out;
+ }
+
+ c.tbsCertList.thisUpdate.element = choice_Time_generalTime;
+ c.tbsCertList.thisUpdate.u.generalTime = time(NULL) - 24 * 3600;
+
+ c.tbsCertList.nextUpdate = malloc(sizeof(*c.tbsCertList.nextUpdate));
+ if (c.tbsCertList.nextUpdate == NULL) {
+ hx509_set_error_string(context, 0, ENOMEM, "out of memory");
+ ret = ENOMEM;
+ goto out;
+ }
+
+ {
+ time_t next = crl->expire;
+ if (next == 0)
+ next = time(NULL) + 24 * 3600 * 365;
+
+ c.tbsCertList.nextUpdate->element = choice_Time_generalTime;
+ c.tbsCertList.nextUpdate->u.generalTime = next;
+ }
+
+ c.tbsCertList.revokedCertificates =
+ calloc(1, sizeof(*c.tbsCertList.revokedCertificates));
+ if (c.tbsCertList.revokedCertificates == NULL) {
+ hx509_set_error_string(context, 0, ENOMEM, "out of memory");
+ ret = ENOMEM;
+ goto out;
+ }
+ c.tbsCertList.crlExtensions = NULL;
+
+ ret = hx509_certs_iter(context, crl->revoked, add_revoked, &c.tbsCertList);
+ if (ret)
+ goto out;
+
+ /* if not revoked certs, remove OPTIONAL entry */
+ if (c.tbsCertList.revokedCertificates->len == 0) {
+ free(c.tbsCertList.revokedCertificates);
+ c.tbsCertList.revokedCertificates = NULL;
+ }
+
+ ASN1_MALLOC_ENCODE(TBSCRLCertList, os->data, os->length,
+ &c.tbsCertList, &size, ret);
+ if (ret) {
+ hx509_set_error_string(context, 0, ret, "failed to encode tbsCRL");
+ goto out;
+ }
+ if (size != os->length)
+ _hx509_abort("internal ASN.1 encoder error");
+
+
+ ret = _hx509_create_signature_bitstring(context,
+ signerkey,
+ sigalg,
+ os,
+ &c.signatureAlgorithm,
+ &c.signatureValue);
+ free(os->data);
+
+ ASN1_MALLOC_ENCODE(CRLCertificateList, os->data, os->length,
+ &c, &size, ret);
+ free_CRLCertificateList(&c);
+ if (ret) {
+ hx509_set_error_string(context, 0, ret, "failed to encode CRL");
+ goto out;
+ }
+ if (size != os->length)
+ _hx509_abort("internal ASN.1 encoder error");
+
+ return 0;
+
+out:
+ free_CRLCertificateList(&c);
+ return ret;
+}
OpenPOWER on IntegriCloud