freebsd-dev/crypto/heimdal/lib/hx509/revoke.c
Stanislav Sedov ae77177087 - Update FreeBSD Heimdal distribution to version 1.5.1. This also brings
several new kerberos related libraries and applications to FreeBSD:
  o kgetcred(1) allows one to manually get a ticket for a particular service.
  o kf(1) securily forwards ticket to another host through an authenticated
    and encrypted stream.
  o kcc(1) is an umbrella program around klist(1), kswitch(1), kgetcred(1)
    and other user kerberos operations. klist and kswitch are just symlinks
    to kcc(1) now.
  o kswitch(1) allows you to easily switch between kerberos credentials if
    you're running KCM.
  o hxtool(1) is a certificate management tool to use with PKINIT.
  o string2key(1) maps a password into key.
  o kdigest(8) is a userland tool to access the KDC's digest interface.
  o kimpersonate(8) creates a "fake" ticket for a service.

  We also now install manpages for some lirbaries that were not installed
  before, libheimntlm and libhx509.

- The new HEIMDAL version no longer supports Kerberos 4.  All users are
  recommended to switch to Kerberos 5.

- Weak ciphers are now disabled by default.  To enable DES support (used
  by telnet(8)), use "allow_weak_crypto" option in krb5.conf.

- libtelnet, pam_ksu and pam_krb5 are now compiled with error on warnings
  disabled due to the function they use (krb5_get_err_text(3)) being
  deprecated.  I plan to work on this next.

- Heimdal's KDC now require sqlite to operate.  We use the bundled version
  and install it as libheimsqlite.  If some other FreeBSD components will
  require it in the future we can rename it to libbsdsqlite and use for these
  components as well.

- This is not a latest Heimdal version, the new one was released while I was
  working on the update.  I will update it to 1.5.2 soon, as it fixes some
  important bugs and security issues.
2012-03-22 08:48:42 +00:00

1544 lines
37 KiB
C

/*
* 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"
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 int 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 on ref");
ctx->ref++;
if (ctx->ref == UINT_MAX)
_hx509_abort("revoke ctx refcount == UINT_MAX on ref");
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 OCSP signer is "
"doesn't have CA as signer certificate");
goto out;
}
ret = _hx509_verify_signature_bitstring(context,
parent,
&s->signatureAlgorithm,
&s->tbsCertificate._save,
&s->signatureValue);
if (ret) {
hx509_set_error_string(context, HX509_ERROR_APPEND, ret,
"OCSP signer signature invalid");
goto out;
}
ret = hx509_cert_check_eku(context, signer,
&asn1_oid_id_pkix_kp_OCSPSigning, 0);
if (ret)
goto out;
}
ret = _hx509_verify_signature_bitstring(context,
signer,
&ocsp->ocsp.signatureAlgorithm,
&ocsp->ocsp.tbsResponseData._save,
&ocsp->ocsp.signature);
if (ret) {
hx509_set_error_string(context, HX509_ERROR_APPEND, ret,
"OCSP 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,
&asn1_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 = rk_undumpdata(ocsp->path, &data, &length);
if (ret)
return ret;
ret = stat(ocsp->path, &sb);
if (ret)
return errno;
ret = parse_ocsp_basic(data, length, &basic);
rk_xfree(data);
if (ret) {
hx509_set_error_string(context, 0, ret,
"Failed to parse OCSP response");
return ret;
}
if (basic.certs) {
size_t 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,
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 = rk_undumpdata(path, &data, &length);
if (ret)
return ret;
ret = stat(path, &sb);
if (ret)
return errno;
*t = sb.st_mtime;
ret = decode_CRLCertificateList(data, length, crl, &size);
rk_xfree(data);
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;
int diff;
/* check if cert.issuer == crls.val[i].crl.issuer */
ret = _hx509_name_cmp(&c->tbsCertificate.issuer,
&crl->crl.tbsCertList.issuer, &diff);
if (ret || diff)
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_f(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(&asn1_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];
char *p;
if ((p = ctime(&t)) == NULL)
strlcpy(s, "?", sizeof(s));
else {
strlcpy(s, p + 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;
size_t 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%zu. 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_f(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;
size_t 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_f(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);
if (ret) {
hx509_set_error_string(context, 0, ret, "Failed to sign CRL");
goto out;
}
ASN1_MALLOC_ENCODE(CRLCertificateList, os->data, os->length,
&c, &size, ret);
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");
free_CRLCertificateList(&c);
return 0;
out:
free_CRLCertificateList(&c);
return ret;
}