hx509: private key exclusion options

Add two ways to exclude private keys when dealing with an hx509
certificate store.  One as a load option (load no private keys, never
add private keys), one as a store option (store no private keys).

This is useful for CA code so it can have a single store with the
issuer's credentials _and_ the chain for it, and copy those to a store
with the issued certificate and _not_ accidentally include the issuer's
private key.

It would be much safer still to flip the default for this flag, but that
could break out-of-tree libhx509 dependents.
This commit is contained in:
Nicolas Williams
2019-12-06 17:11:01 -06:00
parent 3c0d1258ce
commit e515745996
9 changed files with 177 additions and 61 deletions

View File

@@ -300,6 +300,26 @@ hx509_cert_init(hx509_context context, const Certificate *c, heim_error_t *error
return cert;
}
/**
* Copy a certificate object, but drop any private key assignment.
*
* @param context A hx509 context.
* @param src Certificate object
* @param error
*
* @return Returns an hx509 certificate
*
* @ingroup hx509_cert
*/
HX509_LIB_FUNCTION hx509_cert HX509_LIB_CALL
hx509_cert_copy_no_private_key(hx509_context context,
hx509_cert src,
heim_error_t *error)
{
return hx509_cert_init(context, src->data, error);
}
/**
* Allocate and init an hx509 certificate object containing only a private key
* (but no Certificate).

View File

@@ -154,6 +154,11 @@ typedef enum {
/* flags to hx509_certs_init */
#define HX509_CERTS_CREATE 0x01
#define HX509_CERTS_UNPROTECT_ALL 0x02
#define HX509_CERTS_NO_PRIVATE_KEYS 0x04
/* flags to hx509_certs_store */
#define HX509_CERTS_STORE_NO_PRIVATE_KEYS 0x04
/* flags to hx509_set_error_string */
#define HX509_ERROR_APPEND 0x01

View File

@@ -996,6 +996,16 @@ command = {
argument = "datetime"
help = "check that the certificate's notBefore is after the given time"
}
option = {
long = "has-private-key"
type = "flag"
help = "check that the certificate has a private key"
}
option = {
long = "lacks-private-key"
type = "flag"
help = "check that the certificate does not have a private key"
}
name = "acert"
min_args = "1"
max_args = "1"

View File

@@ -2677,6 +2677,7 @@ acert1_validity(struct acert_options *opt, hx509_cert cert)
time_t not_after_eq = 0;
time_t not_after_lt = 0;
time_t not_after_gt = 0;
int ret = 0;
if (opt->valid_now_flag) {
time_t now = time(NULL);
@@ -2684,12 +2685,12 @@ acert1_validity(struct acert_options *opt, hx509_cert cert)
if (hx509_cert_get_notBefore(cert) > now) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate not valid yet\n");
return -1;
ret = -1;
}
if (hx509_cert_get_notAfter(cert) < now) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate currently expired\n");
return -1;
ret = -1;
}
}
if (opt->valid_at_string) {
@@ -2699,13 +2700,13 @@ acert1_validity(struct acert_options *opt, hx509_cert cert)
if (opt->verbose_flag)
fprintf(stderr, "Certificate not valid yet at %s\n",
opt->valid_at_string);
return -1;
ret = -1;
}
if (hx509_cert_get_notAfter(cert) < at) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate expired before %s\n",
opt->valid_at_string);
return -1;
ret = -1;
}
}
@@ -2727,17 +2728,29 @@ acert1_validity(struct acert_options *opt, hx509_cert cert)
(not_before_gt && hx509_cert_get_notBefore(cert) <= not_before_gt)) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate notBefore not as requested\n");
return -1;
ret = -1;
}
if ((not_after_eq && hx509_cert_get_notAfter(cert) != not_after_eq) ||
(not_after_lt && hx509_cert_get_notAfter(cert) >= not_after_lt) ||
(not_after_gt && hx509_cert_get_notAfter(cert) <= not_after_gt)) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate notAfter not as requested\n");
return -1;
ret = -1;
}
return 0;
if (opt->has_private_key_flag && !hx509_cert_have_private_key(cert)) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate does not have a private key\n");
ret = -1;
}
if (opt->lacks_private_key_flag && hx509_cert_have_private_key(cert)) {
if (opt->verbose_flag)
fprintf(stderr, "Certificate does not have a private key\n");
ret = -1;
}
return ret;
}
static int
@@ -2810,7 +2823,7 @@ acert1(struct acert_options *opt, size_t cert_num, hx509_cert cert, int *matched
if (e == NULL) {
if (wanted)
return -1;
return acert1_validity(opt, cert);;
return acert1_validity(opt, cert);
}
for (i = 0; i < e->len; i++) {

View File

@@ -63,6 +63,7 @@ struct hx509_certs_data {
unsigned int ref;
struct hx509_keyset_ops *ops;
void *ops_data;
int flags;
};
static struct hx509_keyset_ops *
@@ -103,6 +104,7 @@ _hx509_ks_register(hx509_context context, struct hx509_keyset_ops *ops)
* @param flags list of flags:
* - HX509_CERTS_CREATE create a new keystore of the specific TYPE.
* - HX509_CERTS_UNPROTECT_ALL fails if any private key failed to be extracted.
* - HX509_CERTS_NO_PRIVATE_KEYS does not load or permit adding private keys
* @param lock a lock that unlocks the certificates store, use NULL to
* select no password/certifictes/prompt lock (see @ref page_lock).
* @param certs return pointer, free with hx509_certs_free().
@@ -158,6 +160,7 @@ hx509_certs_init(hx509_context context,
hx509_clear_error_string(context);
return ENOMEM;
}
c->flags = flags;
c->ops = ops;
c->ref = 1;
@@ -201,9 +204,12 @@ hx509_certs_destroy(hx509_context context,
/**
* Write the certificate store to stable storage.
*
* Use the HX509_CERTS_STORE_NO_PRIVATE_KEYS flag to ensure that no private
* keys are stored, even if added.
*
* @param context A hx509 context.
* @param certs a certificate store to store.
* @param flags currently unused, use 0.
* @param flags currently one flag is defined: HX509_CERTS_STORE_NO_PRIVATE_KEYS
* @param lock a lock that unlocks the certificates store, use NULL to
* select no password/certifictes/prompt lock (see @ref page_lock).
*
@@ -485,6 +491,9 @@ hx509_ci_print_names(hx509_context context, void *ctx, hx509_cert c)
HX509_LIB_FUNCTION int HX509_LIB_CALL
hx509_certs_add(hx509_context context, hx509_certs certs, hx509_cert cert)
{
hx509_cert copy = NULL;
int ret;
if (certs->ops->add == NULL) {
hx509_set_error_string(context, 0, ENOENT,
"Keyset type %s doesn't support add operation",
@@ -492,7 +501,20 @@ hx509_certs_add(hx509_context context, hx509_certs certs, hx509_cert cert)
return ENOENT;
}
return (*certs->ops->add)(context, certs, certs->ops_data, cert);
if ((certs->flags & HX509_CERTS_NO_PRIVATE_KEYS) &&
hx509_cert_have_private_key(cert)) {
if ((copy = hx509_cert_copy_no_private_key(context, cert,
NULL)) == NULL) {
hx509_set_error_string(context, 0, ENOMEM,
"Could not add certificate to store");
return ENOMEM;
}
cert = copy;
}
ret = (*certs->ops->add)(context, certs, certs->ops_data, cert);
hx509_cert_free(copy);
return ret;
}
/**
@@ -637,8 +659,7 @@ certs_merge_func(hx509_context context, void *ctx, hx509_cert c)
}
/**
* Merge a certificate store into another. The from store is keep
* intact.
* Merge one certificate store into another. The from store is kept intact.
*
* @param context a hx509 context.
* @param to the store to merge into.

View File

@@ -49,7 +49,7 @@ struct ks_file {
*/
static int
parse_certificate(hx509_context context, const char *fn,
parse_certificate(hx509_context context, const char *fn, int flags,
struct hx509_collector *c,
const hx509_pem_header *headers,
const void *data, size_t len,
@@ -74,6 +74,7 @@ parse_certificate(hx509_context context, const char *fn,
static int
try_decrypt(hx509_context context,
struct hx509_collector *collector,
int flags,
const AlgorithmIdentifier *alg,
const EVP_CIPHER *c,
const void *ivdata,
@@ -122,12 +123,9 @@ try_decrypt(hx509_context context,
EVP_CIPHER_CTX_cleanup(&ctx);
}
ret = _hx509_collector_private_key_add(context,
collector,
alg,
NULL,
&clear,
NULL);
if (!(flags & HX509_CERTS_NO_PRIVATE_KEYS))
ret = _hx509_collector_private_key_add(context, collector, alg, NULL,
&clear, NULL);
memset_s(clear.data, clear.length, 0, clear.length);
free(clear.data);
@@ -138,7 +136,7 @@ out:
}
static int
parse_pkcs8_private_key(hx509_context context, const char *fn,
parse_pkcs8_private_key(hx509_context context, const char *fn, int flags,
struct hx509_collector *c,
const hx509_pem_header *headers,
const void *data, size_t length,
@@ -146,28 +144,28 @@ parse_pkcs8_private_key(hx509_context context, const char *fn,
{
PKCS8PrivateKeyInfo ki;
heim_octet_string keydata;
int ret;
ret = decode_PKCS8PrivateKeyInfo(data, length, &ki, NULL);
if (ret)
return ret;
keydata.data = rk_UNCONST(data);
keydata.length = length;
ret = _hx509_collector_private_key_add(context,
c,
&ki.privateKeyAlgorithm,
NULL,
&ki.privateKey,
&keydata);
if (!(flags & HX509_CERTS_NO_PRIVATE_KEYS)) {
keydata.data = rk_UNCONST(data);
keydata.length = length;
ret = _hx509_collector_private_key_add(context,
c,
&ki.privateKeyAlgorithm,
NULL,
&ki.privateKey,
&keydata);
}
free_PKCS8PrivateKeyInfo(&ki);
return ret;
}
static int
parse_pem_private_key(hx509_context context, const char *fn,
parse_pem_private_key(hx509_context context, const char *fn, int flags,
struct hx509_collector *c,
const hx509_pem_header *headers,
const void *data, size_t len,
@@ -271,7 +269,7 @@ parse_pem_private_key(hx509_context context, const char *fn,
password = pw->val[i];
passwordlen = strlen(password);
ret = try_decrypt(context, c, ai, cipher, ivdata,
ret = try_decrypt(context, c, flags, ai, cipher, ivdata,
password, passwordlen, data, len);
if (ret == 0) {
decrypted = 1;
@@ -292,21 +290,21 @@ parse_pem_private_key(hx509_context context, const char *fn,
ret = hx509_lock_prompt(lock, &prompt);
if (ret == 0)
ret = try_decrypt(context, c, ai, cipher, ivdata, password,
strlen(password), data, len);
ret = try_decrypt(context, c, flags, ai, cipher, ivdata,
password, strlen(password), data, len);
/* XXX add password to lock password collection ? */
memset_s(password, sizeof(password), 0, sizeof(password));
}
free(ivdata);
} else {
} else if (!(flags & HX509_CERTS_NO_PRIVATE_KEYS)) {
heim_octet_string keydata;
keydata.data = rk_UNCONST(data);
keydata.length = len;
ret = _hx509_collector_private_key_add(context, c, ai, NULL,
&keydata, NULL);
ret = _hx509_collector_private_key_add(context, c, ai, NULL,
&keydata, NULL);
}
return ret;
@@ -315,7 +313,7 @@ parse_pem_private_key(hx509_context context, const char *fn,
struct pem_formats {
const char *name;
int (*func)(hx509_context, const char *, struct hx509_collector *,
int (*func)(hx509_context, const char *, int, struct hx509_collector *,
const hx509_pem_header *, const void *, size_t,
const AlgorithmIdentifier *);
const AlgorithmIdentifier *(*ai)(void);
@@ -347,11 +345,12 @@ pem_func(hx509_context context, const char *type,
const char *q = formats[j].name;
if (strcasecmp(type, q) == 0) {
const AlgorithmIdentifier *ai = NULL;
if (formats[j].ai != NULL)
ai = (*formats[j].ai)();
ret = (*formats[j].func)(context, NULL, pem_ctx->c,
header, data, len, ai);
ret = (*formats[j].func)(context, NULL, pem_ctx->flags, pem_ctx->c,
header, data, len, ai);
if (ret && (pem_ctx->flags & HX509_CERTS_UNPROTECT_ALL)) {
hx509_set_error_string(context, HX509_ERROR_APPEND, ret,
"Failed parseing PEM format %s", type);
@@ -468,10 +467,12 @@ file_init_common(hx509_context context,
for (i = 0; i < sizeof(formats)/sizeof(formats[0]); i++) {
const AlgorithmIdentifier *ai = NULL;
if (formats[i].ai != NULL)
ai = (*formats[i].ai)();
ret = (*formats[i].func)(context, p, pem_ctx.c, NULL, ptr, length, ai);
ret = (*formats[i].func)(context, p, pem_ctx.flags, pem_ctx.c,
NULL, ptr, length, ai);
if (ret == 0)
break;
}
@@ -539,6 +540,7 @@ file_free(hx509_certs certs, void *data)
struct store_ctx {
FILE *f;
outformat format;
int store_flags;
};
static int HX509_LIB_CALL
@@ -563,7 +565,8 @@ store_func(hx509_context context, void *ctx, hx509_cert c)
if (data.data) {
fwrite(data.data, data.length, 1, sc->f);
free(data.data);
} else if (_hx509_cert_private_key_exportable(c)) {
} else if (_hx509_cert_private_key_exportable(c) &&
!(sc->store_flags & HX509_CERTS_STORE_NO_PRIVATE_KEYS)) {
hx509_private_key key = _hx509_cert_private_key(c);
ret = _hx509_private_key_export(context, key,
@@ -573,7 +576,8 @@ store_func(hx509_context context, void *ctx, hx509_cert c)
}
break;
case USE_PEM:
if (_hx509_cert_private_key_exportable(c)) {
if (_hx509_cert_private_key_exportable(c) &&
!(sc->store_flags & HX509_CERTS_STORE_NO_PRIVATE_KEYS)) {
heim_octet_string priv_key;
hx509_private_key key = _hx509_cert_private_key(c);
@@ -658,6 +662,7 @@ file_store(hx509_context context,
return ret;
}
rk_cloexec_file(sc.f);
sc.store_flags = flags;
sc.format = ksf->format;
ret = hx509_certs_iter_f(context, ksf->certs, store_func, &sc);

View File

@@ -328,6 +328,13 @@ keychain_init(hx509_context context,
{
struct ks_keychain *ctx;
if (flags & HX509_CERTS_NO_PRIVATE_KEYS) {
hx509_set_error_string(context, 0, ENOTSUP,
"KEYCHAIN store does not support not reading "
"private keys");
return ENOTSUP;
}
ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL) {
hx509_clear_error_string(context);

View File

@@ -820,6 +820,13 @@ p11_init(hx509_context context,
*data = NULL;
if (flags & HX509_CERTS_NO_PRIVATE_KEYS) {
hx509_set_error_string(context, 0, ENOTSUP,
"PKCS#11 store does not support "
"HX509_CERTS_NO_PRIVATE_KEYS flag");
return ENOTSUP;
}
if (residue == NULL || residue[0] == '\0') {
hx509_set_error_string(context, 0, EINVAL,
"PKCS#11 store not specified");

View File

@@ -36,10 +36,12 @@
struct ks_pkcs12 {
hx509_certs certs;
char *fn;
unsigned int store_no_priv_keys;
};
typedef int (*collector_func)(hx509_context,
struct hx509_collector *,
int,
const void *, size_t,
const PKCS12_Attributes *);
@@ -49,8 +51,9 @@ struct type {
};
static void
parse_pkcs12_type(hx509_context, struct hx509_collector *, const heim_oid *,
const void *, size_t, const PKCS12_Attributes *);
parse_pkcs12_type(hx509_context, struct hx509_collector *, int,
const heim_oid *, const void *, size_t,
const PKCS12_Attributes *);
static const PKCS12_Attribute *
@@ -68,6 +71,7 @@ find_attribute(const PKCS12_Attributes *attrs, const heim_oid *oid)
static int
keyBag_parser(hx509_context context,
struct hx509_collector *c,
int flags,
const void *data, size_t length,
const PKCS12_Attributes *attrs)
{
@@ -76,6 +80,9 @@ keyBag_parser(hx509_context context,
const heim_octet_string *os = NULL;
int ret;
if (flags & HX509_CERTS_NO_PRIVATE_KEYS)
return 0;
attr = find_attribute(attrs, &asn1_oid_id_pkcs_9_at_localKeyId);
if (attr)
os = &attr->attrValues;
@@ -97,6 +104,7 @@ keyBag_parser(hx509_context context,
static int
ShroudedKeyBag_parser(hx509_context context,
struct hx509_collector *c,
int flags,
const void *data, size_t length,
const PKCS12_Attributes *attrs)
{
@@ -119,7 +127,8 @@ ShroudedKeyBag_parser(hx509_context context,
if (ret)
return ret;
ret = keyBag_parser(context, c, content.data, content.length, attrs);
ret = keyBag_parser(context, c, flags, content.data, content.length,
attrs);
der_free_octet_string(&content);
return ret;
}
@@ -127,6 +136,7 @@ ShroudedKeyBag_parser(hx509_context context,
static int
certBag_parser(hx509_context context,
struct hx509_collector *c,
int flags,
const void *data, size_t length,
const PKCS12_Attributes *attrs)
{
@@ -191,6 +201,7 @@ certBag_parser(hx509_context context,
static int
parse_safe_content(hx509_context context,
struct hx509_collector *c,
int flags,
const unsigned char *p, size_t len)
{
PKCS12_SafeContents sc;
@@ -206,6 +217,7 @@ parse_safe_content(hx509_context context,
for (i = 0; i < sc.len ; i++)
parse_pkcs12_type(context,
c,
flags,
&sc.val[i].bagId,
sc.val[i].bagValue.data,
sc.val[i].bagValue.length,
@@ -218,6 +230,7 @@ parse_safe_content(hx509_context context,
static int
safeContent_parser(hx509_context context,
struct hx509_collector *c,
int flags,
const void *data, size_t length,
const PKCS12_Attributes *attrs)
{
@@ -227,7 +240,7 @@ safeContent_parser(hx509_context context,
ret = decode_PKCS12_OctetString(data, length, &os, NULL);
if (ret)
return ret;
ret = parse_safe_content(context, c, os.data, os.length);
ret = parse_safe_content(context, c, flags, os.data, os.length);
der_free_octet_string(&os);
return ret;
}
@@ -235,6 +248,7 @@ safeContent_parser(hx509_context context,
static int
encryptedData_parser(hx509_context context,
struct hx509_collector *c,
int flags,
const void *data, size_t length,
const PKCS12_Attributes *attrs)
{
@@ -253,7 +267,8 @@ encryptedData_parser(hx509_context context,
return ret;
if (der_heim_oid_cmp(&contentType, &asn1_oid_id_pkcs7_data) == 0)
ret = parse_safe_content(context, c, content.data, content.length);
ret = parse_safe_content(context, c, flags,
content.data, content.length);
der_free_octet_string(&content);
der_free_oid(&contentType);
@@ -263,6 +278,7 @@ encryptedData_parser(hx509_context context,
static int
envelopedData_parser(hx509_context context,
struct hx509_collector *c,
int flags,
const void *data, size_t length,
const PKCS12_Attributes *attrs)
{
@@ -290,7 +306,8 @@ envelopedData_parser(hx509_context context,
}
if (der_heim_oid_cmp(&contentType, &asn1_oid_id_pkcs7_data) == 0)
ret = parse_safe_content(context, c, content.data, content.length);
ret = parse_safe_content(context, c, flags,
content.data, content.length);
der_free_octet_string(&content);
der_free_oid(&contentType);
@@ -311,6 +328,7 @@ struct type bagtypes[] = {
static void
parse_pkcs12_type(hx509_context context,
struct hx509_collector *c,
int flags,
const heim_oid *oid,
const void *data, size_t length,
const PKCS12_Attributes *attrs)
@@ -319,7 +337,7 @@ parse_pkcs12_type(hx509_context context,
for (i = 0; i < sizeof(bagtypes)/sizeof(bagtypes[0]); i++)
if (der_heim_oid_cmp(bagtypes[i].oid, oid) == 0)
(*bagtypes[i].func)(context, c, data, length, attrs);
(*bagtypes[i].func)(context, c, flags, data, length, attrs);
}
static int
@@ -429,6 +447,7 @@ p12_init(hx509_context context,
for (i = 0; i < as.len; i++)
parse_pkcs12_type(context,
c,
flags,
&as.val[i].contentType,
as.val[i].content->data,
as.val[i].content->length,
@@ -492,10 +511,15 @@ addBag(hx509_context context,
return 0;
}
struct store_func_ctx {
PKCS12_AuthenticatedSafe as;
int store_flags;
};
static int HX509_LIB_CALL
store_func(hx509_context context, void *ctx, hx509_cert c)
store_func(hx509_context context, void *d, hx509_cert c)
{
PKCS12_AuthenticatedSafe *as = ctx;
struct store_func_ctx *ctx = d;
PKCS12_OctetString os;
PKCS12_CertBag cb;
size_t size;
@@ -528,9 +552,11 @@ store_func(hx509_context context, void *ctx, hx509_cert c)
if (ret)
goto out;
ret = addBag(context, as, &asn1_oid_id_pkcs12_certBag, os.data, os.length);
ret = addBag(context, &ctx->as, &asn1_oid_id_pkcs12_certBag, os.data,
os.length);
if (_hx509_cert_private_key_exportable(c)) {
if (_hx509_cert_private_key_exportable(c) &&
!(ctx->store_flags & HX509_CERTS_STORE_NO_PRIVATE_KEYS)) {
hx509_private_key key = _hx509_cert_private_key(c);
PKCS8PrivateKeyInfo pki;
@@ -561,7 +587,8 @@ store_func(hx509_context context, void *ctx, hx509_cert c)
if (ret)
return ret;
ret = addBag(context, as, &asn1_oid_id_pkcs12_keyBag, os.data, os.length);
ret = addBag(context, &ctx->as, &asn1_oid_id_pkcs12_keyBag, os.data,
os.length);
if (ret)
return ret;
}
@@ -576,21 +603,22 @@ p12_store(hx509_context context,
{
struct ks_pkcs12 *p12 = data;
PKCS12_PFX pfx;
PKCS12_AuthenticatedSafe as;
struct store_func_ctx ctx;
PKCS12_OctetString asdata;
size_t size;
int ret;
memset(&as, 0, sizeof(as));
memset(&ctx, 0, sizeof(ctx));
memset(&pfx, 0, sizeof(pfx));
ctx.store_flags = flags;
ret = hx509_certs_iter_f(context, p12->certs, store_func, &as);
ret = hx509_certs_iter_f(context, p12->certs, store_func, &ctx);
if (ret)
goto out;
ASN1_MALLOC_ENCODE(PKCS12_AuthenticatedSafe, asdata.data, asdata.length,
&as, &size, ret);
free_PKCS12_AuthenticatedSafe(&as);
&ctx.as, &size, ret);
free_PKCS12_AuthenticatedSafe(&ctx.as);
if (ret)
return ret;
@@ -642,7 +670,7 @@ p12_store(hx509_context context,
free(asdata.data);
out:
free_PKCS12_AuthenticatedSafe(&as);
free_PKCS12_AuthenticatedSafe(&ctx.as);
free_PKCS12_PFX(&pfx);
return ret;