Files
heimdal/lib/hx509/ks_file.c
Nicolas Williams ddbc36d86b hx509: Store priv keys first in PEM stores
Most consumers of PEM files don't care about the order in which private
keys and certificates are stored.  However, Postfix does care when
multiple EE certs (and chains) are stored in a file, in which case it
requires that private keys come before their certificates.
2019-11-02 18:49:42 -05:00

797 lines
19 KiB
C

/*
* Copyright (c) 2005 - 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.
*/
#include "hx_locl.h"
#ifndef WIN32
#include <libgen.h>
#endif
typedef enum { USE_PEM, USE_DER } outformat;
struct ks_file {
hx509_certs certs;
char *fn;
outformat format;
};
/*
*
*/
static int
parse_certificate(hx509_context context, const char *fn,
struct hx509_collector *c,
const hx509_pem_header *headers,
const void *data, size_t len,
const AlgorithmIdentifier *ai)
{
heim_error_t error = NULL;
hx509_cert cert;
int ret;
cert = hx509_cert_init_data(context, data, len, &error);
if (cert == NULL) {
ret = heim_error_get_code(error);
heim_release(error);
return ret;
}
ret = _hx509_collector_certs_add(context, c, cert);
hx509_cert_free(cert);
return ret;
}
static int
try_decrypt(hx509_context context,
struct hx509_collector *collector,
const AlgorithmIdentifier *alg,
const EVP_CIPHER *c,
const void *ivdata,
const void *password,
size_t passwordlen,
const void *cipher,
size_t len)
{
heim_octet_string clear;
size_t keylen;
void *key;
int ret;
keylen = EVP_CIPHER_key_length(c);
key = malloc(keylen);
if (key == NULL) {
hx509_clear_error_string(context);
return ENOMEM;
}
ret = EVP_BytesToKey(c, EVP_md5(), ivdata,
password, passwordlen,
1, key, NULL);
if (ret <= 0) {
ret = HX509_CRYPTO_INTERNAL_ERROR;
hx509_set_error_string(context, 0, ret,
"Failed to do string2key for private key");
goto out;
}
clear.data = malloc(len);
if (clear.data == NULL) {
hx509_set_error_string(context, 0, ENOMEM,
"Out of memory to decrypt for private key");
ret = ENOMEM;
goto out;
}
clear.length = len;
{
EVP_CIPHER_CTX ctx;
EVP_CIPHER_CTX_init(&ctx);
EVP_CipherInit_ex(&ctx, c, NULL, key, ivdata, 0);
EVP_Cipher(&ctx, clear.data, cipher, len);
EVP_CIPHER_CTX_cleanup(&ctx);
}
ret = _hx509_collector_private_key_add(context,
collector,
alg,
NULL,
&clear,
NULL);
memset_s(clear.data, clear.length, 0, clear.length);
free(clear.data);
out:
memset_s(key, keylen, 0, keylen);
free(key);
return ret;
}
static int
parse_pkcs8_private_key(hx509_context context, const char *fn,
struct hx509_collector *c,
const hx509_pem_header *headers,
const void *data, size_t length,
const AlgorithmIdentifier *ai)
{
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);
free_PKCS8PrivateKeyInfo(&ki);
return ret;
}
static int
parse_pem_private_key(hx509_context context, const char *fn,
struct hx509_collector *c,
const hx509_pem_header *headers,
const void *data, size_t len,
const AlgorithmIdentifier *ai)
{
int ret = 0;
const char *enc;
enc = hx509_pem_find_header(headers, "Proc-Type");
if (enc) {
const char *dek;
char *type, *iv;
ssize_t ssize, size;
void *ivdata;
const EVP_CIPHER *cipher;
const struct _hx509_password *pw;
hx509_lock lock;
int decrypted = 0;
size_t i;
lock = _hx509_collector_get_lock(c);
if (lock == NULL) {
hx509_set_error_string(context, 0, HX509_ALG_NOT_SUPP,
"Failed to get password for "
"password protected file %s", fn);
return HX509_ALG_NOT_SUPP;
}
if (strcmp(enc, "4,ENCRYPTED") != 0) {
hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
"Private key encrypted in unknown method %s "
"in file",
enc, fn);
hx509_clear_error_string(context);
return HX509_PARSING_KEY_FAILED;
}
dek = hx509_pem_find_header(headers, "DEK-Info");
if (dek == NULL) {
hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
"Encrypted private key missing DEK-Info");
return HX509_PARSING_KEY_FAILED;
}
type = strdup(dek);
if (type == NULL) {
hx509_clear_error_string(context);
return ENOMEM;
}
iv = strchr(type, ',');
if (iv == NULL) {
free(type);
hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
"IV missing");
return HX509_PARSING_KEY_FAILED;
}
*iv++ = '\0';
size = strlen(iv);
ivdata = malloc(size);
if (ivdata == NULL) {
hx509_clear_error_string(context);
free(type);
return ENOMEM;
}
cipher = EVP_get_cipherbyname(type);
if (cipher == NULL) {
free(ivdata);
hx509_set_error_string(context, 0, HX509_ALG_NOT_SUPP,
"Private key encrypted with "
"unsupported cipher: %s",
type);
free(type);
return HX509_ALG_NOT_SUPP;
}
#define PKCS5_SALT_LEN 8
ssize = hex_decode(iv, ivdata, size);
free(type);
type = NULL;
iv = NULL;
if (ssize < 0 || ssize < PKCS5_SALT_LEN || ssize < EVP_CIPHER_iv_length(cipher)) {
free(ivdata);
hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
"Salt have wrong length in "
"private key file");
return HX509_PARSING_KEY_FAILED;
}
pw = _hx509_lock_get_passwords(lock);
if (pw != NULL) {
const void *password;
size_t passwordlen;
for (i = 0; i < pw->len; i++) {
password = pw->val[i];
passwordlen = strlen(password);
ret = try_decrypt(context, c, ai, cipher, ivdata,
password, passwordlen, data, len);
if (ret == 0) {
decrypted = 1;
break;
}
}
}
if (!decrypted) {
hx509_prompt prompt;
char password[128];
memset(&prompt, 0, sizeof(prompt));
prompt.prompt = "Password for keyfile: ";
prompt.type = HX509_PROMPT_TYPE_PASSWORD;
prompt.reply.data = password;
prompt.reply.length = sizeof(password);
ret = hx509_lock_prompt(lock, &prompt);
if (ret == 0)
ret = try_decrypt(context, c, 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 {
heim_octet_string keydata;
keydata.data = rk_UNCONST(data);
keydata.length = len;
ret = _hx509_collector_private_key_add(context, c, ai, NULL,
&keydata, NULL);
}
return ret;
}
struct pem_formats {
const char *name;
int (*func)(hx509_context, const char *, struct hx509_collector *,
const hx509_pem_header *, const void *, size_t,
const AlgorithmIdentifier *);
const AlgorithmIdentifier *(*ai)(void);
} formats[] = {
{ "CERTIFICATE", parse_certificate, NULL },
{ "PRIVATE KEY", parse_pkcs8_private_key, NULL },
{ "RSA PRIVATE KEY", parse_pem_private_key, hx509_signature_rsa },
#ifdef HAVE_HCRYPTO_W_OPENSSL
{ "EC PRIVATE KEY", parse_pem_private_key, hx509_signature_ecPublicKey }
#endif
};
struct pem_ctx {
int flags;
struct hx509_collector *c;
};
static int
pem_func(hx509_context context, const char *type,
const hx509_pem_header *header,
const void *data, size_t len, void *ctx)
{
struct pem_ctx *pem_ctx = (struct pem_ctx*)ctx;
int ret = 0;
size_t j;
for (j = 0; j < sizeof(formats)/sizeof(formats[0]); j++) {
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);
if (ret && (pem_ctx->flags & HX509_CERTS_UNPROTECT_ALL)) {
hx509_set_error_string(context, HX509_ERROR_APPEND, ret,
"Failed parseing PEM format %s", type);
return ret;
}
break;
}
}
if (j == sizeof(formats)/sizeof(formats[0])) {
ret = HX509_UNSUPPORTED_OPERATION;
hx509_set_error_string(context, 0, ret,
"Found no matching PEM format for %s", type);
return ret;
}
return 0;
}
/*
*
*/
static int
file_init_common(hx509_context context,
hx509_certs certs, void **data, int flags,
const char *residue, hx509_lock lock, outformat format)
{
char *p, *pnext;
struct ks_file *ksf = NULL;
hx509_private_key *keys = NULL;
int ret;
struct pem_ctx pem_ctx;
pem_ctx.flags = flags;
pem_ctx.c = NULL;
if (residue == NULL || residue[0] == '\0') {
hx509_set_error_string(context, 0, EINVAL,
"PEM file name not specified");
return EINVAL;
}
*data = NULL;
if (lock == NULL)
lock = _hx509_empty_lock;
ksf = calloc(1, sizeof(*ksf));
if (ksf == NULL) {
hx509_clear_error_string(context);
return ENOMEM;
}
ksf->format = format;
ksf->fn = strdup(residue);
if (ksf->fn == NULL) {
hx509_clear_error_string(context);
ret = ENOMEM;
goto out;
}
/*
* XXX this is broken, the function should parse the file before
* overwriting it
*/
if (flags & HX509_CERTS_CREATE) {
/*
* Note that the file creation is deferred until file_store() is
* called.
*/
ret = hx509_certs_init(context, "MEMORY:ks-file-create",
0, lock, &ksf->certs);
if (ret)
goto out;
*data = ksf;
return 0;
}
ret = _hx509_collector_alloc(context, lock, &pem_ctx.c);
if (ret)
goto out;
for (p = ksf->fn; p != NULL; p = pnext) {
FILE *f;
pnext = strchr(p, ',');
if (pnext)
*pnext++ = '\0';
if ((f = fopen(p, "r")) == NULL) {
ret = ENOENT;
hx509_set_error_string(context, 0, ret,
"Failed to open PEM file \"%s\": %s",
p, strerror(errno));
goto out;
}
rk_cloexec_file(f);
ret = hx509_pem_read(context, f, pem_func, &pem_ctx);
fclose(f);
if (ret != 0 && ret != HX509_PARSING_KEY_FAILED)
goto out;
else if (ret == HX509_PARSING_KEY_FAILED) {
size_t length;
void *ptr;
size_t i;
ret = rk_undumpdata(p, &ptr, &length);
if (ret) {
hx509_clear_error_string(context);
goto out;
}
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);
if (ret == 0)
break;
}
rk_xfree(ptr);
if (ret) {
hx509_clear_error_string(context);
goto out;
}
}
}
ret = _hx509_collector_collect_certs(context, pem_ctx.c, &ksf->certs);
if (ret)
goto out;
ret = _hx509_collector_collect_private_keys(context, pem_ctx.c, &keys);
if (ret == 0) {
int i;
for (i = 0; keys[i]; i++)
_hx509_certs_keys_add(context, ksf->certs, keys[i]);
_hx509_certs_keys_free(context, keys);
}
out:
if (ret == 0)
*data = ksf;
else {
if (ksf->fn)
free(ksf->fn);
free(ksf);
}
if (pem_ctx.c)
_hx509_collector_free(pem_ctx.c);
return ret;
}
static int
file_init_pem(hx509_context context,
hx509_certs certs, void **data, int flags,
const char *residue, hx509_lock lock)
{
return file_init_common(context, certs, data, flags, residue, lock, USE_PEM);
}
static int
file_init_der(hx509_context context,
hx509_certs certs, void **data, int flags,
const char *residue, hx509_lock lock)
{
return file_init_common(context, certs, data, flags, residue, lock, USE_DER);
}
static int
file_free(hx509_certs certs, void *data)
{
struct ks_file *ksf = data;
hx509_certs_free(&ksf->certs);
free(ksf->fn);
free(ksf);
return 0;
}
struct store_ctx {
FILE *f;
outformat format;
};
static int HX509_LIB_CALL
store_func(hx509_context context, void *ctx, hx509_cert c)
{
struct store_ctx *sc = ctx;
heim_octet_string data;
int ret;
if (hx509_cert_have_private_key_only(c)) {
data.length = 0;
data.data = NULL;
} else {
ret = hx509_cert_binary(context, c, &data);
if (ret)
return ret;
}
switch (sc->format) {
case USE_DER:
/* Can't store both. Well, we could, but nothing will support it */
if (data.data) {
fwrite(data.data, data.length, 1, sc->f);
free(data.data);
} else if (_hx509_cert_private_key_exportable(c)) {
hx509_private_key key = _hx509_cert_private_key(c);
ret = _hx509_private_key_export(context, key,
HX509_KEY_FORMAT_DER, &data);
fwrite(data.data, data.length, 1, sc->f);
free(data.data);
}
break;
case USE_PEM:
if (_hx509_cert_private_key_exportable(c)) {
heim_octet_string priv_key;
hx509_private_key key = _hx509_cert_private_key(c);
ret = _hx509_private_key_export(context, key,
HX509_KEY_FORMAT_DER, &priv_key);
if (ret) {
free(data.data);
break;
}
hx509_pem_write(context, _hx509_private_pem_name(key), NULL, sc->f,
priv_key.data, priv_key.length);
free(priv_key.data);
}
if (data.data) {
hx509_pem_write(context, "CERTIFICATE", NULL, sc->f,
data.data, data.length);
free(data.data);
}
break;
}
return 0;
}
static int
mk_temp(const char *fn, char **tfn)
{
char *ds;
int ret = -1;
#ifdef WIN32
char buf[PATH_MAX];
char *p;
*tfn = NULL;
if ((ds = _fullpath(buf, fn, sizeof(buf))) == NULL) {
errno = errno ? errno : ENAMETOOLONG;
return -1;
}
if (p = strrchr(ds, '\\') == NULL) {
ret = asprintf(tfn, ".%s-XXXXXX", ds); /* XXX can't happen */
} else {
*(p++) = '\0';
ret = asprintf(tfn, "%s/.%s-XXXXXX", ds, p);
}
#else
*tfn = NULL;
if ((ds = strdup(fn)))
ret = asprintf(tfn, "%s/.%s-XXXXXX", dirname(ds), basename(ds));
free(ds);
#endif
/*
* Using mkostemp() risks leaving garbage files lying around. To do better
* without resorting to file locks (which have their own problems) we need
* O_TMPFILE and linkat(2), which only Linux has.
*/
return (ret == -1 || *tfn == NULL) ? -1 : mkostemp(*tfn, O_CLOEXEC);
}
static int
file_store(hx509_context context,
hx509_certs certs, void *data, int flags, hx509_lock lock)
{
struct ks_file *ksf = data;
struct store_ctx sc;
char *tfn;
int ret;
int fd;
sc.f = NULL;
fd = mk_temp(ksf->fn, &tfn);
if (fd > -1)
sc.f = fdopen(fd, "w");
if (sc.f == NULL) {
hx509_set_error_string(context, 0, ret = errno,
"Failed to open file %s for writing", ksf->fn);
if (fd > -1)
(void) close(fd);
return ret;
}
rk_cloexec_file(sc.f);
sc.format = ksf->format;
ret = hx509_certs_iter_f(context, ksf->certs, store_func, &sc);
if (ret == 0)
ret = fclose(sc.f);
else
(void) fclose(sc.f);
if (ret)
(void) unlink(tfn);
else
(void) rename(tfn, ksf->fn);
free(tfn);
return ret;
}
static int
file_add(hx509_context context, hx509_certs certs, void *data, hx509_cert c)
{
struct ks_file *ksf = data;
return hx509_certs_add(context, ksf->certs, c);
}
static int
file_iter_start(hx509_context context,
hx509_certs certs, void *data, void **cursor)
{
struct ks_file *ksf = data;
return hx509_certs_start_seq(context, ksf->certs, cursor);
}
static int
file_iter(hx509_context context,
hx509_certs certs, void *data, void *iter, hx509_cert *cert)
{
struct ks_file *ksf = data;
return hx509_certs_next_cert(context, ksf->certs, iter, cert);
}
static int
file_iter_end(hx509_context context,
hx509_certs certs,
void *data,
void *cursor)
{
struct ks_file *ksf = data;
return hx509_certs_end_seq(context, ksf->certs, cursor);
}
static int
file_getkeys(hx509_context context,
hx509_certs certs,
void *data,
hx509_private_key **keys)
{
struct ks_file *ksf = data;
return _hx509_certs_keys_get(context, ksf->certs, keys);
}
static int
file_addkey(hx509_context context,
hx509_certs certs,
void *data,
hx509_private_key key)
{
struct ks_file *ksf = data;
return _hx509_certs_keys_add(context, ksf->certs, key);
}
static int
file_destroy(hx509_context context,
hx509_certs certs,
void *data)
{
struct ks_file *ksf = data;
return _hx509_erase_file(context, ksf->fn);
}
static struct hx509_keyset_ops keyset_file = {
"FILE",
0,
file_init_pem,
file_store,
file_free,
file_add,
NULL,
file_iter_start,
file_iter,
file_iter_end,
NULL,
file_getkeys,
file_addkey,
file_destroy
};
static struct hx509_keyset_ops keyset_pemfile = {
"PEM-FILE",
0,
file_init_pem,
file_store,
file_free,
file_add,
NULL,
file_iter_start,
file_iter,
file_iter_end,
NULL,
file_getkeys,
file_addkey,
file_destroy
};
static struct hx509_keyset_ops keyset_derfile = {
"DER-FILE",
0,
file_init_der,
file_store,
file_free,
file_add,
NULL,
file_iter_start,
file_iter,
file_iter_end,
NULL,
file_getkeys,
file_addkey,
file_destroy
};
HX509_LIB_FUNCTION void HX509_LIB_CALL
_hx509_ks_file_register(hx509_context context)
{
_hx509_ks_register(context, &keyset_file);
_hx509_ks_register(context, &keyset_pemfile);
_hx509_ks_register(context, &keyset_derfile);
}