blob: 8653684814eeb3d3df2e40be260da13eb44f65f2 [file] [log] [blame]
/*
* Copyright 2016-2021 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#ifdef OPENSSL_NO_CT
# error "CT is disabled"
#endif
#include <stddef.h>
#include <string.h>
#include <openssl/err.h>
#include <openssl/obj_mac.h>
#include <openssl/x509.h>
#include "ct_local.h"
SCT_CTX *SCT_CTX_new(OSSL_LIB_CTX *libctx, const char *propq)
{
SCT_CTX *sctx = OPENSSL_zalloc(sizeof(*sctx));
if (sctx == NULL) {
ERR_raise(ERR_LIB_CT, ERR_R_MALLOC_FAILURE);
return NULL;
}
sctx->libctx = libctx;
if (propq != NULL) {
sctx->propq = OPENSSL_strdup(propq);
if (sctx->propq == NULL) {
ERR_raise(ERR_LIB_CT, ERR_R_MALLOC_FAILURE);
OPENSSL_free(sctx);
return NULL;
}
}
return sctx;
}
void SCT_CTX_free(SCT_CTX *sctx)
{
if (sctx == NULL)
return;
EVP_PKEY_free(sctx->pkey);
OPENSSL_free(sctx->pkeyhash);
OPENSSL_free(sctx->ihash);
OPENSSL_free(sctx->certder);
OPENSSL_free(sctx->preder);
OPENSSL_free(sctx->propq);
OPENSSL_free(sctx);
}
/*
* Finds the index of the first extension with the given NID in cert.
* If there is more than one extension with that NID, *is_duplicated is set to
* 1, otherwise 0 (unless it is NULL).
*/
static int ct_x509_get_ext(X509 *cert, int nid, int *is_duplicated)
{
int ret = X509_get_ext_by_NID(cert, nid, -1);
if (is_duplicated != NULL)
*is_duplicated = ret >= 0 && X509_get_ext_by_NID(cert, nid, ret) >= 0;
return ret;
}
/*
* Modifies a certificate by deleting extensions and copying the issuer and
* AKID from the presigner certificate, if necessary.
* Returns 1 on success, 0 otherwise.
*/
__owur static int ct_x509_cert_fixup(X509 *cert, X509 *presigner)
{
int preidx, certidx;
int pre_akid_ext_is_dup, cert_akid_ext_is_dup;
if (presigner == NULL)
return 1;
preidx = ct_x509_get_ext(presigner, NID_authority_key_identifier,
&pre_akid_ext_is_dup);
certidx = ct_x509_get_ext(cert, NID_authority_key_identifier,
&cert_akid_ext_is_dup);
/* An error occurred whilst searching for the extension */
if (preidx < -1 || certidx < -1)
return 0;
/* Invalid certificate if they contain duplicate extensions */
if (pre_akid_ext_is_dup || cert_akid_ext_is_dup)
return 0;
/* AKID must be present in both certificate or absent in both */
if (preidx >= 0 && certidx == -1)
return 0;
if (preidx == -1 && certidx >= 0)
return 0;
/* Copy issuer name */
if (!X509_set_issuer_name(cert, X509_get_issuer_name(presigner)))
return 0;
if (preidx != -1) {
/* Retrieve and copy AKID encoding */
X509_EXTENSION *preext = X509_get_ext(presigner, preidx);
X509_EXTENSION *certext = X509_get_ext(cert, certidx);
ASN1_OCTET_STRING *preextdata;
/* Should never happen */
if (preext == NULL || certext == NULL)
return 0;
preextdata = X509_EXTENSION_get_data(preext);
if (preextdata == NULL ||
!X509_EXTENSION_set_data(certext, preextdata))
return 0;
}
return 1;
}
int SCT_CTX_set1_cert(SCT_CTX *sctx, X509 *cert, X509 *presigner)
{
unsigned char *certder = NULL, *preder = NULL;
X509 *pretmp = NULL;
int certderlen = 0, prederlen = 0;
int idx = -1;
int poison_ext_is_dup, sct_ext_is_dup;
int poison_idx = ct_x509_get_ext(cert, NID_ct_precert_poison, &poison_ext_is_dup);
/* Duplicate poison extensions are present - error */
if (poison_ext_is_dup)
goto err;
/* If *cert doesn't have a poison extension, it isn't a precert */
if (poison_idx == -1) {
/* cert isn't a precert, so we shouldn't have a presigner */
if (presigner != NULL)
goto err;
certderlen = i2d_X509(cert, &certder);
if (certderlen < 0)
goto err;
}
/* See if cert has a precert SCTs extension */
idx = ct_x509_get_ext(cert, NID_ct_precert_scts, &sct_ext_is_dup);
/* Duplicate SCT extensions are present - error */
if (sct_ext_is_dup)
goto err;
if (idx >= 0 && poison_idx >= 0) {
/*
* cert can't both contain SCTs (i.e. have an SCT extension) and be a
* precert (i.e. have a poison extension).
*/
goto err;
}
if (idx == -1) {
idx = poison_idx;
}
/*
* If either a poison or SCT extension is present, remove it before encoding
* cert. This, along with ct_x509_cert_fixup(), gets a TBSCertificate (see
* RFC5280) from cert, which is what the CT log signed when it produced the
* SCT.
*/
if (idx >= 0) {
/* Take a copy of certificate so we don't modify passed version */
pretmp = X509_dup(cert);
if (pretmp == NULL)
goto err;
X509_EXTENSION_free(X509_delete_ext(pretmp, idx));
if (!ct_x509_cert_fixup(pretmp, presigner))
goto err;
prederlen = i2d_re_X509_tbs(pretmp, &preder);
if (prederlen <= 0)
goto err;
}
X509_free(pretmp);
OPENSSL_free(sctx->certder);
sctx->certder = certder;
sctx->certderlen = certderlen;
OPENSSL_free(sctx->preder);
sctx->preder = preder;
sctx->prederlen = prederlen;
return 1;
err:
OPENSSL_free(certder);
OPENSSL_free(preder);
X509_free(pretmp);
return 0;
}
__owur static int ct_public_key_hash(SCT_CTX *sctx, X509_PUBKEY *pkey,
unsigned char **hash, size_t *hash_len)
{
int ret = 0;
unsigned char *md = NULL, *der = NULL;
int der_len;
unsigned int md_len;
EVP_MD *sha256 = EVP_MD_fetch(sctx->libctx, "SHA2-256", sctx->propq);
if (sha256 == NULL)
goto err;
/* Reuse buffer if possible */
if (*hash != NULL && *hash_len >= SHA256_DIGEST_LENGTH) {
md = *hash;
} else {
md = OPENSSL_malloc(SHA256_DIGEST_LENGTH);
if (md == NULL)
goto err;
}
/* Calculate key hash */
der_len = i2d_X509_PUBKEY(pkey, &der);
if (der_len <= 0)
goto err;
if (!EVP_Digest(der, der_len, md, &md_len, sha256, NULL))
goto err;
if (md != *hash) {
OPENSSL_free(*hash);
*hash = md;
*hash_len = SHA256_DIGEST_LENGTH;
}
md = NULL;
ret = 1;
err:
EVP_MD_free(sha256);
OPENSSL_free(md);
OPENSSL_free(der);
return ret;
}
int SCT_CTX_set1_issuer(SCT_CTX *sctx, const X509 *issuer)
{
return SCT_CTX_set1_issuer_pubkey(sctx, X509_get_X509_PUBKEY(issuer));
}
int SCT_CTX_set1_issuer_pubkey(SCT_CTX *sctx, X509_PUBKEY *pubkey)
{
return ct_public_key_hash(sctx, pubkey, &sctx->ihash, &sctx->ihashlen);
}
int SCT_CTX_set1_pubkey(SCT_CTX *sctx, X509_PUBKEY *pubkey)
{
EVP_PKEY *pkey = X509_PUBKEY_get(pubkey);
if (pkey == NULL)
return 0;
if (!ct_public_key_hash(sctx, pubkey, &sctx->pkeyhash, &sctx->pkeyhashlen)) {
EVP_PKEY_free(pkey);
return 0;
}
EVP_PKEY_free(sctx->pkey);
sctx->pkey = pkey;
return 1;
}
void SCT_CTX_set_time(SCT_CTX *sctx, uint64_t time_in_ms)
{
sctx->epoch_time_in_ms = time_in_ms;
}