/*
 * Embedded Linux library
 * Copyright (C) 2018  Intel Corporation
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>

#include "private.h"
#include "useful.h"
#include "key.h"
#include "queue.h"
#include "asn1-private.h"
#include "cipher.h"
#include "pem-private.h"
#include "time.h"
#include "time-private.h"
#include "utf8.h"
#include "cert.h"
#include "cert-private.h"
#include "tls.h"
#include "tls-private.h"
#include "missing.h"

#define X509_CERTIFICATE_POS			0
#define   X509_TBSCERTIFICATE_POS		  0
#define     X509_TBSCERT_VERSION_POS		    ASN1_CONTEXT_EXPLICIT(0)
#define     X509_TBSCERT_SERIAL_POS		    0
#define     X509_TBSCERT_SIGNATURE_POS		    1
#define       X509_ALGORITHM_ID_ALGORITHM_POS	      0
#define       X509_ALGORITHM_ID_PARAMS_POS	      1
#define     X509_TBSCERT_ISSUER_DN_POS		    2
#define     X509_TBSCERT_VALIDITY_POS		    3
#define     X509_TBSCERT_SUBJECT_DN_POS		    4
#define     X509_TBSCERT_SUBJECT_KEY_POS	    5
#define       X509_SUBJECT_KEY_ALGORITHM_POS	      0
#define       X509_SUBJECT_KEY_VALUE_POS	      1
#define     X509_TBSCERT_ISSUER_UID_POS		    ASN1_CONTEXT_IMPLICIT(1)
#define     X509_TBSCERT_SUBJECT_UID_POS	    ASN1_CONTEXT_IMPLICIT(2)
#define     X509_TBSCERT_EXTENSIONS_POS		    ASN1_CONTEXT_EXPLICIT(3)
#define   X509_SIGNATURE_ALGORITHM_POS		  1
#define   X509_SIGNATURE_VALUE_POS		  2

struct l_cert {
	enum l_cert_key_type pubkey_type;
	struct l_cert *issuer;
	struct l_cert *issued;
	size_t asn1_len;
	uint8_t asn1[];
};

struct l_certchain {
	struct l_cert *leaf;	/* Bottom of the doubly-linked list */
	struct l_cert *ca;	/* Top of the doubly-linked list */
};

static const struct pkcs1_encryption_oid {
	enum l_cert_key_type key_type;
	struct asn1_oid oid;
} pkcs1_encryption_oids[] = {
	{ /* rsaEncryption */
		L_CERT_KEY_RSA,
		{ .asn1_len = 9, .asn1 = {
			0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 }
		},
	},
	{ /* ecPublicKey */
		L_CERT_KEY_ECC,
		{ .asn1_len = 7, .asn1 = {
			0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 }
		},
	},
};

static bool cert_set_pubkey_type(struct l_cert *cert)
{
	const uint8_t *key_type;
	size_t key_type_len;
	int i;

	key_type = asn1_der_find_elem_by_path(cert->asn1, cert->asn1_len,
						ASN1_ID_OID, &key_type_len,
						X509_CERTIFICATE_POS,
						X509_TBSCERTIFICATE_POS,
						X509_TBSCERT_SUBJECT_KEY_POS,
						X509_SUBJECT_KEY_ALGORITHM_POS,
						X509_ALGORITHM_ID_ALGORITHM_POS,
						-1);
	if (!key_type)
		return false;

	for (i = 0; i < (int) L_ARRAY_SIZE(pkcs1_encryption_oids); i++)
		if (asn1_oid_eq(&pkcs1_encryption_oids[i].oid,
					key_type_len, key_type))
			break;

	if (i == L_ARRAY_SIZE(pkcs1_encryption_oids))
		cert->pubkey_type = L_CERT_KEY_UNKNOWN;
	else
		cert->pubkey_type = pkcs1_encryption_oids[i].key_type;

	return true;
}

LIB_EXPORT struct l_cert *l_cert_new_from_der(const uint8_t *buf,
						size_t buf_len)
{
	const uint8_t *seq = buf;
	size_t seq_len = buf_len;
	size_t content_len;
	struct l_cert *cert;

	/* Sanity check: outer element is a SEQUENCE */
	if (seq_len-- < 1 || *seq++ != ASN1_ID_SEQUENCE)
		return NULL;

	/* Sanity check: the SEQUENCE spans the whole buffer */
	content_len = asn1_parse_definite_length(&seq, &seq_len);
	if (content_len < 64 || content_len != seq_len)
		return NULL;

	/*
	 * We could require the signature algorithm and the key algorithm
	 * to be one of our supported types here but instead we only
	 * require that when the user wants to verify this certificate or
	 * get the public key respectively.
	 */

	cert = l_malloc(sizeof(struct l_cert) + buf_len);
	cert->issuer = NULL;
	cert->issued = NULL;
	cert->asn1_len = buf_len;
	memcpy(cert->asn1, buf, buf_len);

	/* Sanity check: structure is correct up to the Public Key Algorithm */
	if (!cert_set_pubkey_type(cert)) {
		l_free(cert);
		return NULL;
	}

	return cert;
}

LIB_EXPORT void l_cert_free(struct l_cert *cert)
{
	l_free(cert);
}

LIB_EXPORT const uint8_t *l_cert_get_der_data(struct l_cert *cert,
						size_t *out_len)
{
	if (unlikely(!cert))
		return NULL;

	*out_len = cert->asn1_len;
	return cert->asn1;
}

LIB_EXPORT const uint8_t *l_cert_get_dn(struct l_cert *cert, size_t *out_len)
{
	if (unlikely(!cert))
		return NULL;

	return asn1_der_find_elem_by_path(cert->asn1, cert->asn1_len,
						ASN1_ID_SEQUENCE, out_len,
						X509_CERTIFICATE_POS,
						X509_TBSCERTIFICATE_POS,
						X509_TBSCERT_SUBJECT_DN_POS,
						-1);
}

static uint64_t cert_parse_asn1_time(const uint8_t *data, size_t len,
					uint8_t tag)
{
	struct tm tm = {};
	int tz_hours;
	int tz_mins;
	int century;
	int msecs = 0;
	time_t tt;
	unsigned int i;

	for (i = 0; i < len && i < 15; i++)
		if (unlikely(!l_ascii_isdigit(data[i])))
			break;

	if (tag == ASN1_ID_UTCTIME) {
		if (unlikely(!L_IN_SET(i, 10, 12)))
			return L_TIME_INVALID;

		century = 19;
	} else if (tag == ASN1_ID_GENERALIZEDTIME) {
		if (unlikely(!L_IN_SET(i, 10, 12, 14)))
			return L_TIME_INVALID;

		century = (data[0] - '0') * 10 + (data[1] - '0');
		if (century < 19)
				return L_TIME_INVALID;

		if (len >= i + 4 && data[i] == '.') {
			if (unlikely(!l_ascii_isdigit(data[i + 1]) ||
						!l_ascii_isdigit(data[i + 2]) ||
						!l_ascii_isdigit(data[i + 3])))
				return L_TIME_INVALID;

			i++;
			msecs += (data[i++] - '0') * 100;
			msecs += (data[i++] - '0') * 10;
			msecs += (data[i++] - '0');
		}

		data += 2;
		len -= 2;
		i -= 2;
	} else
		return L_TIME_INVALID;

	if (unlikely((len != i + 1 || data[i] != 'Z') &&
			(len != i + 5 || (data[i] != '+' && data[i] != '-'))))
		return L_TIME_INVALID;

	tm.tm_year = (data[0] - '0') * 10 + (data[1] - '0');
	tm.tm_mon = (data[2] - '0') * 10 + (data[3] - '0');
	tm.tm_mday = (data[4] - '0') * 10 + (data[5] - '0');
	tm.tm_hour = (data[6] - '0') * 10 + (data[7] - '0');

	if (unlikely(tm.tm_mon < 1 || tm.tm_mon > 12 ||
				tm.tm_mday < 1 || tm.tm_mday > 31 ||
				tm.tm_hour > 23))
		return L_TIME_INVALID;

	if (i >= 10) {
		tm.tm_min = (data[8] - '0') * 10 + (data[9] - '0');
		if (unlikely(tm.tm_min > 59))
			return L_TIME_INVALID;
	}

	if (i >= 12) {
		tm.tm_sec = (data[10] - '0') * 10 + (data[11] - '0');
		if (unlikely(tm.tm_sec > 59))
			return L_TIME_INVALID;
	}

	/* RFC5280 Section 4.1.2.5.1 */
	if (tag == ASN1_ID_UTCTIME && tm.tm_year < 50)
		century = 20;

	tm.tm_year += (century - 19) * 100;

	/* Month number is 1-based in UTCTime and 0-based in struct tm */
	tm.tm_mon -= 1;

	tt = timegm(&tm);
	if (unlikely(tt == (time_t) -1))
		return L_TIME_INVALID;

	if (len == i + 5) {
		data += i;

		for (i = 1; i < 5; i++)
			if (unlikely(!l_ascii_isdigit(data[i])))
				return L_TIME_INVALID;

		tz_hours = (data[1] - '0') * 10 + (data[2] - '0');
		tz_mins = (data[3] - '0') * 10 + (data[4] - '0');

		if (unlikely(tz_hours > 14 || tz_mins > 59))
			return L_TIME_INVALID;

		/* The sign converts UTC to local so invert it */
		if (data[0] == '+')
			tt -= tz_hours * 3600 + tz_mins * 60;
		else
			tt += tz_hours * 3600 + tz_mins * 60;
	}

	return (uint64_t) tt * L_USEC_PER_SEC + msecs * L_USEC_PER_MSEC;
}

LIB_EXPORT bool l_cert_get_valid_times(struct l_cert *cert,
					uint64_t *out_not_before_time,
					uint64_t *out_not_after_time)
{
	const uint8_t *validity;
	const uint8_t *not_before;
	const uint8_t *not_after;
	size_t seq_size;
	size_t not_before_size;
	size_t not_after_size;
	uint8_t not_before_tag;
	uint8_t not_after_tag;
	uint64_t not_before_time = 0;
	uint64_t not_after_time = 0;

	if (unlikely(!cert))
		return false;

	validity = asn1_der_find_elem_by_path(cert->asn1, cert->asn1_len,
						ASN1_ID_SEQUENCE, &seq_size,
						X509_CERTIFICATE_POS,
						X509_TBSCERTIFICATE_POS,
						X509_TBSCERT_VALIDITY_POS,
						-1);
	if (unlikely(!validity))
		return false;

	not_before = asn1_der_find_elem(validity, seq_size, 0, &not_before_tag,
					&not_before_size);
	if (!not_before)
		return false;

	seq_size -= not_before_size + (not_before - validity);
	validity = not_before + not_before_size;
	not_after = asn1_der_find_elem(validity, seq_size, 0, &not_after_tag,
					&not_after_size);
	if (!not_after)
		return false;

	if (out_not_before_time) {
		not_before_time = cert_parse_asn1_time(not_before,
							not_before_size,
							not_before_tag);
		if (not_before_time == L_TIME_INVALID)
			return false;
	}

	if (out_not_after_time) {
		/*
		 * RFC5280 Section 4.1.2.5: "To indicate that a certificate
		 * has no well-defined expiration date, the notAfter SHOULD
		 * be assigned the GeneralizedTime value of 99991231235959Z."
		 */
		if (not_after_size == 15 &&
				!memcmp(not_after, "99991231235959Z", 15))
			not_after_time = 0;
		else {
			not_after_time = cert_parse_asn1_time(not_after,
								not_after_size,
								not_after_tag);
			if (not_after_time == L_TIME_INVALID)
				return false;
		}
	}

	if (out_not_before_time)
		*out_not_before_time = not_before_time;

	if (out_not_after_time)
		*out_not_after_time = not_after_time;

	return true;
}

const uint8_t *cert_get_extension(struct l_cert *cert,
					const struct asn1_oid *ext_id,
					bool *out_critical, size_t *out_len)
{
	const uint8_t *ext, *end;
	size_t ext_len;

	if (unlikely(!cert))
		return NULL;

	ext = asn1_der_find_elem_by_path(cert->asn1, cert->asn1_len,
						ASN1_ID_SEQUENCE, &ext_len,
						X509_CERTIFICATE_POS,
						X509_TBSCERTIFICATE_POS,
						X509_TBSCERT_EXTENSIONS_POS,
						-1);
	if (unlikely(!ext))
		return NULL;

	end = ext + ext_len;
	while (ext < end) {
		const uint8_t *seq, *oid, *data;
		uint8_t tag;
		size_t len, oid_len, data_len;
		bool critical;

		seq = asn1_der_find_elem(ext, end - ext, 0, &tag, &len);
		if (unlikely(!seq || tag != ASN1_ID_SEQUENCE))
			return NULL;

		ext = seq + len;

		oid = asn1_der_find_elem(seq, len, 0, &tag, &oid_len);
		if (unlikely(!oid || tag != ASN1_ID_OID))
			return NULL;

		if (!asn1_oid_eq(ext_id, oid_len, oid))
			continue;

		data = asn1_der_find_elem(seq, len, 1, &tag, &data_len);
		critical = false;

		if (data && tag == ASN1_ID_BOOLEAN) {
			if (data_len != 1)
				return NULL;

			critical = *data != 0;	/* Tolerate BER booleans */

			data = asn1_der_find_elem(seq, len, 2, &tag, &data_len);
		}

		if (unlikely(!data || tag != ASN1_ID_OCTET_STRING))
			return NULL;

		if (out_critical)
			*out_critical = critical;

		if (out_len)
			*out_len = data_len;

		return data;
	}

	return NULL;
}

LIB_EXPORT enum l_cert_key_type l_cert_get_pubkey_type(struct l_cert *cert)
{
	if (unlikely(!cert))
		return L_CERT_KEY_UNKNOWN;

	return cert->pubkey_type;
}

/*
 * Note: Returns a new l_key object to be freed by the caller.
 */
LIB_EXPORT struct l_key *l_cert_get_pubkey(struct l_cert *cert)
{
	if (unlikely(!cert))
		return NULL;

	/* Use kernel's ASN.1 certificate parser to find the key data for us */
	switch (cert->pubkey_type) {
	case L_CERT_KEY_RSA:
		return l_key_new(L_KEY_RSA, cert->asn1, cert->asn1_len);
	case L_CERT_KEY_ECC:
		return l_key_new(L_KEY_ECC, cert->asn1, cert->asn1_len);
	case L_CERT_KEY_UNKNOWN:
		break;
	}

	return NULL;
}

/*
 * Note: takes ownership of the certificate.  The certificate is
 * assumed to be new and not linked into any certchain object.
 */
struct l_certchain *certchain_new_from_leaf(struct l_cert *leaf)
{
	struct l_certchain *chain;

	chain = l_new(struct l_certchain, 1);
	chain->leaf = leaf;
	chain->ca = leaf;
	return chain;
}

/*
 * Note: takes ownership of the certificate.  The certificate is
 * assumed to be new and not linked into any certchain object.
 */
void certchain_link_issuer(struct l_certchain *chain, struct l_cert *ca)
{
	ca->issued = chain->ca;
	chain->ca->issuer = ca;
	chain->ca = ca;
}

static struct l_cert *certchain_pop_ca(struct l_certchain *chain)
{
	struct l_cert *ca = chain->ca;

	if (!ca)
		return NULL;

	if (ca->issued) {
		chain->ca = ca->issued;
		ca->issued->issuer = NULL;
		ca->issued = NULL;
	} else {
		chain->ca = NULL;
		chain->leaf = NULL;
	}

	return ca;
}

LIB_EXPORT void l_certchain_free(struct l_certchain *chain)
{
	while (chain && chain->ca)
		l_cert_free(certchain_pop_ca(chain));

	l_free(chain);
}

LIB_EXPORT struct l_cert *l_certchain_get_leaf(struct l_certchain *chain)
{
	if (unlikely(!chain))
		return NULL;

	return chain->leaf;
}

/*
 * Call @cb for each certificate in the chain starting from the leaf
 * certificate.  Stop if a call returns @true.
 */
LIB_EXPORT void l_certchain_walk_from_leaf(struct l_certchain *chain,
						l_cert_walk_cb_t cb,
						void *user_data)
{
	struct l_cert *cert;

	if (unlikely(!chain))
		return;

	for (cert = chain->leaf; cert; cert = cert->issuer)
		if (cb(cert, user_data))
			break;
}

/*
 * Call @cb for each certificate in the chain starting from the root
 * certificate.  Stop if a call returns @true.
 */
LIB_EXPORT void l_certchain_walk_from_ca(struct l_certchain *chain,
						l_cert_walk_cb_t cb,
						void *user_data)
{
	struct l_cert *cert;

	if (unlikely(!chain))
		return;

	for (cert = chain->ca; cert; cert = cert->issued)
		if (cb(cert, user_data))
			break;
}

static struct l_keyring *cert_set_to_keyring(struct l_cert **certs, char *error)
{
	struct l_keyring *ring;
	int i = 1;
	int count;

	ring = l_keyring_new();
	if (!ring)
		return NULL;

	for (count = 0; certs[count]; count++);

	for (; *certs; certs++) {
		struct l_cert *cert = *certs;
		struct l_key *key = l_cert_get_pubkey(cert);

		if (!key) {
			sprintf(error, "Can't get public key from certificate "
				"%i / %i in certificate set", i, count);
			goto cleanup;
		}

		if (!l_keyring_link(ring, key)) {
			l_key_free(key);
			sprintf(error, "Can't link the public key from "
				"certificate %i / %i to target keyring",
				i, count);
			goto cleanup;
		}

		l_key_free_norevoke(key);
		i++;
	}

	return ring;

cleanup:
	l_keyring_free(ring);
	return NULL;
}

static bool cert_is_in_set(struct l_cert *cert, struct l_cert **set)
{
	for (; *set; set++) {
		struct l_cert *cert2 = *set;

		if (cert == cert2)
			return true;

		if (cert->asn1_len == cert2->asn1_len &&
				!memcmp(cert->asn1, cert2->asn1,
					cert->asn1_len))
			return true;
	}

	return false;
}

static struct l_cert **cert_set_filter_by_validity(struct l_queue *set,
							uint64_t now,
							int *out_total,
							int *out_valid)
{
	const struct l_queue_entry *entry;
	_auto_(l_free) struct l_cert **valid;

	*out_total = l_queue_length(set);
	*out_valid = 0;
	valid = l_new(struct l_cert *, *out_total + 1);

	for (entry = l_queue_get_entries(set); entry; entry = entry->next) {
		struct l_cert *cert = entry->data;
		uint64_t not_before;
		uint64_t not_after;

		if (!l_cert_get_valid_times(cert, &not_before, &not_after))
			return NULL;

		if (now < not_before || (not_after && now > not_after))
			continue;

		valid[(*out_valid)++] = cert;
	}

	return l_steal_ptr(valid);
}

static struct l_key *cert_try_link(struct l_cert *cert, struct l_keyring *ring)
{
	struct l_key *key;

	key = l_key_new(L_KEY_RSA, cert->asn1, cert->asn1_len);
	if (!key)
		return NULL;

	if (l_keyring_link(ring, key))
		return key;

	l_key_free(key);
	return NULL;
}

#define RETURN_ERROR(msg, args...)	\
	do {	\
		if (error) {	\
			*error = error_buf;	\
			snprintf(error_buf, sizeof(error_buf), msg, ## args); \
		}	\
		return false;	\
	} while (0)

LIB_EXPORT bool l_certchain_verify(struct l_certchain *chain,
					struct l_queue *ca_certs,
					const char **error)
{
	struct l_keyring *ca_ring = NULL;
	_auto_(l_keyring_free) struct l_keyring *verify_ring = NULL;
	struct l_cert *cert;
	struct l_key *prev_key = NULL;
	int verified = 0;
	int ca_match = 0;
	int i;
	static char error_buf[1024];
	int total = 0;
	uint64_t now;
	_auto_(l_free) struct l_cert **ca_certs_valid = NULL;
	int ca_certs_total_count = 0;
	int ca_certs_valid_count = 0;

	if (unlikely(!chain || !chain->leaf))
		RETURN_ERROR("Chain empty");

	for (cert = chain->ca; cert; cert = cert->issued, total++);

	now = time_realtime_now();

	for (cert = chain->ca, i = 0; cert; cert = cert->issued, i++) {
		uint64_t not_before;
		uint64_t not_after;
		char time_str[100];

		if (unlikely(!l_cert_get_valid_times(cert, &not_before,
							&not_after)))
			RETURN_ERROR("Can't parse validity in certificate "
					"%i / %i", i + 1, total);

		if (unlikely(now < not_before)) {
			time_t t = not_before / L_USEC_PER_SEC;
			struct tm *tm = gmtime(&t);

			if (!tm || !strftime(time_str, sizeof(time_str),
						"%a %F %T UTC", tm))
				strcpy(time_str, "<error>");

			RETURN_ERROR("Certificate %i / %i not valid before %s",
					i + 1, total, time_str);
		}

		if (unlikely(not_after && now > not_after)) {
			time_t t = not_after / L_USEC_PER_SEC;
			struct tm *tm = gmtime(&t);

			if (!tm || !strftime(time_str, sizeof(time_str),
						"%a %F %T UTC", tm))
				strcpy(time_str, "<error>");

			RETURN_ERROR("Certificate %i / %i expired on %s",
					i + 1, total, time_str);
		}
	}

	if (ca_certs) {
		if (unlikely(l_queue_isempty(ca_certs)))
			RETURN_ERROR("No trusted CA certificates");

		ca_certs_valid = cert_set_filter_by_validity(ca_certs, now,
							&ca_certs_total_count,
							&ca_certs_valid_count);
		if (unlikely(!ca_certs_valid))
			RETURN_ERROR("Can't parse validity in CA cert(s)");

		if (unlikely(!ca_certs_valid_count))
			RETURN_ERROR("All trusted CA certs are expired or "
					"not-yet-valid");

		for (cert = chain->ca, i = 0; cert; cert = cert->issued, i++)
			if (cert_is_in_set(cert, ca_certs_valid)) {
				ca_match = i + 1;
				break;
			}
	}

	verify_ring = l_keyring_new();
	if (!verify_ring)
		RETURN_ERROR("Can't create verify keyring");

	cert = chain->ca;

	/*
	 * For TLS compatibility the trusted root CA certificate is
	 * optionally present in the chain.
	 *
	 * RFC5246 7.4.2:
	 * "Because certificate validation requires that root keys be
	 * distributed independently, the self-signed certificate that
	 * specifies the root certificate authority MAY be omitted from
	 * the chain, under the assumption that the remote end must
	 * already possess it in order to validate it in any case."
	 *
	 * The following is an optimization to skip verifying the root
	 * cert in the chain if it is bitwise-identical to one of the
	 * trusted CA certificates.  In that case we don't have to load
	 * all of the trusted certificates into the kernel, link them
	 * to @ca_ring or link @ca_ring to @verify_ring, instead we
	 * load the first certificate into @verify_ring before we set
	 * the restrict mode on it, same as when no trusted CAs are
	 * provided.
	 *
	 * Note this happens to work around a kernel issue preventing
	 * self-signed certificates missing the optional AKID extension
	 * from being linked to a restricted keyring.  That issue would
	 * have affected us if the trusted CA set included such
	 * certificate and the same certificate was at the root of
	 * the chain.
	 */
	if (ca_certs && !ca_match) {
		ca_ring = cert_set_to_keyring(ca_certs_valid, error_buf);
		if (!ca_ring) {
			if (error)
				*error = error_buf;
			return false;
		}

		if (!l_keyring_link_nested(verify_ring, ca_ring)) {
			l_keyring_free(ca_ring);
			RETURN_ERROR("Can't link CA ring to verify ring");
		}
	} else
		prev_key = cert_try_link(cert, verify_ring);

	/*
	 * The top, unverified certificate(s) are linked to the keyring and
	 * we can now force verification of any new certificates linked.
	 */
	if (!l_keyring_restrict(verify_ring, L_KEYRING_RESTRICT_ASYM_CHAIN,
				NULL)) {
		l_key_free(prev_key);
		l_keyring_free(ca_ring);
		RETURN_ERROR("Can't restrict verify keyring");
	}

	if (ca_ring) {
		/*
		 * Verify the first certificate outside of the loop, then
		 * revoke the trusted CAs' keys so that only the newly
		 * verified cert's public key remains in the ring.
		 */
		prev_key = cert_try_link(cert, verify_ring);
		l_keyring_free(ca_ring);
	}

	cert = cert->issued;

	/* Verify the rest of the chain */
	while (prev_key && cert) {
		struct l_key *new_key = cert_try_link(cert, verify_ring);

		/*
		 * Free and revoke the issuer's public key again leaving only
		 * new_key in verify_ring to ensure the next certificate linked
		 * is signed by the owner of this key.
		 */
		l_key_free(prev_key);
		prev_key = new_key;
		cert = cert->issued;
		verified++;
	}

	if (!prev_key) {
		char str1[100];
		char str2[100] = "";

		if (ca_match)
			snprintf(str1, sizeof(str1), "%i / %i matched a trusted"
					" certificate, root not verified",
					ca_match, total);
		else
			snprintf(str1, sizeof(str1), "root %sverified against "
					"trusted CA(s)",
					ca_certs && verified ? "" : "not ");

		if (ca_certs && !ca_match && !verified &&
				ca_certs_valid_count < ca_certs_total_count)
			snprintf(str2, sizeof(str2), ", %i out of %i trused "
					"CA(s) were expired or not-yet-valid",
					ca_certs_total_count -
					ca_certs_valid_count,
					ca_certs_total_count);

		RETURN_ERROR("Linking certificate %i / %i failed, %s%s",
				verified + 1, total, str1, str2);
	}

	l_key_free(prev_key);
	return true;
}

struct l_key *cert_key_from_pkcs8_private_key_info(const uint8_t *der,
							size_t der_len)
{
	return l_key_new(L_KEY_RSA, der, der_len);
}

/*
 * The passphrase, if given, must have been validated as UTF-8 unless the
 * caller knows that PKCS#12 encryption algorithms are not used.
 * Use l_utf8_validate.
 */
struct l_key *cert_key_from_pkcs8_encrypted_private_key_info(const uint8_t *der,
							size_t der_len,
							const char *passphrase)
{
	const uint8_t *key_info, *alg_id, *data;
	uint8_t tag;
	size_t key_info_len, alg_id_len, data_len, tmp_len;
	struct l_cipher *alg;
	uint8_t *decrypted;
	struct l_key *pkey;
	bool r;
	bool is_block;
	size_t decrypted_len;

	/* Technically this is BER, not limited to DER */
	key_info = asn1_der_find_elem(der, der_len, 0, &tag, &key_info_len);
	if (!key_info || tag != ASN1_ID_SEQUENCE)
		return NULL;

	alg_id = asn1_der_find_elem(key_info, key_info_len, 0, &tag,
					&alg_id_len);
	if (!alg_id || tag != ASN1_ID_SEQUENCE)
		return NULL;

	data = asn1_der_find_elem(key_info, key_info_len, 1, &tag, &data_len);
	if (!data || tag != ASN1_ID_OCTET_STRING || data_len < 8 ||
			(data_len & 7) != 0)
		return NULL;

	if (asn1_der_find_elem(der, der_len, 2, &tag, &tmp_len))
		return NULL;

	alg = cert_cipher_from_pkcs_alg_id(alg_id, alg_id_len, passphrase,
						&is_block);
	if (!alg)
		return NULL;

	decrypted = l_malloc(data_len);

	r = l_cipher_decrypt(alg, data, decrypted, data_len);
	l_cipher_free(alg);

	if (!r) {
		l_free(decrypted);
		return NULL;
	}

	decrypted_len = data_len;

	/*
	 * For block ciphers strip padding as defined in RFC8018
	 * (for PKCS#5 v1) or RFC1423 / RFC5652 (for v2).
	 */
	if (is_block) {
		uint8_t pad = decrypted[data_len - 1];

		pkey = NULL;

		if (pad > data_len || pad > 16 || pad == 0)
			goto cleanup;

		if (!l_secure_memeq(decrypted + data_len - pad, pad - 1U, pad))
			goto cleanup;

		decrypted_len -= pad;
	}

	pkey = cert_key_from_pkcs8_private_key_info(decrypted, decrypted_len);

cleanup:
	explicit_bzero(decrypted, data_len);
	l_free(decrypted);
	return pkey;
}

struct l_key *cert_key_from_pkcs1_rsa_private_key(const uint8_t *der,
							size_t der_len)
{
	const uint8_t *data;
	uint8_t tag;
	size_t data_len;
	const uint8_t *key_data;
	size_t key_data_len;
	int i;
	uint8_t *private_key;
	size_t private_key_len;
	uint8_t *one_asymmetric_key;
	uint8_t *ptr;
	struct l_key *pkey;

	static const uint8_t version0[] = {
		ASN1_ID_INTEGER, 0x01, 0x00
	};
	static const uint8_t pkcs1_rsa_encryption[] = {
		ASN1_ID_SEQUENCE, 0x0d,
		ASN1_ID_OID, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
		0x01, 0x01, 0x01,
		ASN1_ID_NULL, 0x00,
	};

	/*
	 * Sanity check that it's a version 0 or 1 RSAPrivateKey structure
	 * with the 8 integers.
	 */
	key_data = asn1_der_find_elem(der, der_len, 0, &tag, &key_data_len);
	if (!key_data || tag != ASN1_ID_SEQUENCE)
		return NULL;

	data = asn1_der_find_elem(key_data, key_data_len, 0, &tag,
					&data_len);
	if (!data || tag != ASN1_ID_INTEGER || data_len != 1 ||
			(data[0] != 0x00 && data[0] != 0x01))
		return NULL;

	for (i = 1; i < 9; i++) {
		data = asn1_der_find_elem(key_data, key_data_len, i, &tag,
						&data_len);
		if (!data || tag != ASN1_ID_INTEGER || data_len < 1)
			return NULL;
	}

	private_key = l_malloc(10 + der_len);
	ptr = private_key;
	*ptr++ = ASN1_ID_OCTET_STRING;
	asn1_write_definite_length(&ptr, der_len);
	memcpy(ptr, der, der_len);
	ptr += der_len;
	private_key_len = ptr - private_key;

	one_asymmetric_key = l_malloc(32 + private_key_len);
	ptr = one_asymmetric_key;
	*ptr++ = ASN1_ID_SEQUENCE;
	asn1_write_definite_length(&ptr, sizeof(version0) +
					sizeof(pkcs1_rsa_encryption) +
					private_key_len);
	memcpy(ptr, version0, sizeof(version0));
	ptr += sizeof(version0);
	memcpy(ptr, pkcs1_rsa_encryption, sizeof(pkcs1_rsa_encryption));
	ptr += sizeof(pkcs1_rsa_encryption);
	memcpy(ptr, private_key, private_key_len);
	ptr += private_key_len;
	explicit_bzero(private_key, private_key_len);
	l_free(private_key);

	pkey = cert_key_from_pkcs8_private_key_info(one_asymmetric_key,
						ptr - one_asymmetric_key);
	explicit_bzero(one_asymmetric_key, ptr - one_asymmetric_key);
	l_free(one_asymmetric_key);

	return pkey;
}

static const uint8_t *cert_unpack_pkcs7_content_info(const uint8_t *container,
					size_t container_len, int pos,
					const struct asn1_oid *expected_oid,
					struct asn1_oid *out_oid,
					uint8_t *out_tag, size_t *out_len)
{
	const uint8_t *content_info;
	size_t content_info_len;
	const uint8_t *type;
	size_t type_len;
	const uint8_t *ret;
	uint8_t tag;

	if (!(content_info = asn1_der_find_elem(container, container_len, pos,
						&tag, &content_info_len)) ||
			tag != ASN1_ID_SEQUENCE)
		return NULL;

	if (!(type = asn1_der_find_elem(content_info, content_info_len, 0,
					&tag, &type_len)) ||
			tag != ASN1_ID_OID ||
			type_len > sizeof(out_oid->asn1))
		return NULL;

	if (expected_oid && !asn1_oid_eq(expected_oid, type_len, type))
		return NULL;

	if (!(ret = asn1_der_find_elem(content_info, content_info_len,
					ASN1_CONTEXT_EXPLICIT(0),
					out_tag, out_len)) ||
			ret + *out_len != content_info + content_info_len)
		return NULL;

	if (out_oid) {
		out_oid->asn1_len = type_len;
		memcpy(out_oid->asn1, type, type_len);
	}

	return ret;
}

/* RFC5652 Section 8 */
static uint8_t *cert_decrypt_pkcs7_encrypted_data(const uint8_t *data,
						size_t data_len,
						const char *password,
						struct asn1_oid *out_oid,
						size_t *out_len)
{
	const uint8_t *version;
	size_t version_len;
	const uint8_t *encrypted_info;
	size_t encrypted_info_len;
	const uint8_t *type;
	size_t type_len;
	const uint8_t *alg_id;
	size_t alg_id_len;
	const uint8_t *encrypted;
	size_t encrypted_len;
	uint8_t tag;
	struct l_cipher *alg;
	uint8_t *plaintext;
	int i;
	bool ok;
	bool is_block;

	if (!(version = asn1_der_find_elem(data, data_len, 0, &tag,
						&version_len)) ||
			tag != ASN1_ID_INTEGER || version_len != 1 ||
			!L_IN_SET(version[0], 0, 2))
		return NULL;

	if (!(encrypted_info = asn1_der_find_elem(data, data_len, 1, &tag,
							&encrypted_info_len)) ||
			tag != ASN1_ID_SEQUENCE)
		return NULL;

	if (!(type = asn1_der_find_elem(encrypted_info, encrypted_info_len, 0,
					&tag, &type_len)) ||
			tag != ASN1_ID_OID ||
			type_len > sizeof(out_oid->asn1))
		return NULL;

	if (!(alg_id = asn1_der_find_elem(encrypted_info, encrypted_info_len, 1,
					&tag, &alg_id_len)) ||
			tag != ASN1_ID_SEQUENCE)
		return NULL;

	/* Not optional in our case, defined [0] IMPLICIT OCTET STRING */
	if (!(encrypted = asn1_der_find_elem(encrypted_info, encrypted_info_len,
						ASN1_CONTEXT_IMPLICIT(0),
						&tag, &encrypted_len)) ||
			tag != ASN1_ID(ASN1_CLASS_CONTEXT, 0, 0) ||
			encrypted_len < 8)
		return NULL;

	if (!(alg = cert_cipher_from_pkcs_alg_id(alg_id, alg_id_len, password,
							&is_block)))
		return NULL;

	plaintext = l_malloc(encrypted_len);
	ok = l_cipher_decrypt(alg, encrypted, plaintext, encrypted_len);
	l_cipher_free(alg);

	if (!ok) {
		l_free(plaintext);
		return NULL;
	}

	if (is_block) {
		bool ok = true;

		/* Also validate the padding */
		if (encrypted_len < plaintext[encrypted_len - 1] ||
				plaintext[encrypted_len - 1] > 16) {
			plaintext[encrypted_len - 1] = 1;
			ok = false;
		}

		for (i = 1; i < plaintext[encrypted_len - 1]; i++)
			if (plaintext[encrypted_len - 1 - i] !=
					plaintext[encrypted_len - 1])
				ok = false;

		if (!ok) {
			explicit_bzero(plaintext, encrypted_len);
			l_free(plaintext);
			return NULL;
		}

		encrypted_len -= plaintext[encrypted_len - 1];
	}

	if (out_oid) {
		out_oid->asn1_len = type_len;
		memcpy(out_oid->asn1, type, type_len);
	}

	*out_len = encrypted_len;
	return plaintext;
}

/* RFC7292 Appendix A. */
static const struct cert_pkcs12_hash pkcs12_mac_algs[] = {
	{
		L_CHECKSUM_MD5,    16, 16, 64,
		{ 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0f, 0x02, 0x05 } }
	},
	{
		L_CHECKSUM_SHA1,   20, 20, 64,
		{ 5, { 0x2b, 0x0e, 0x03, 0x02, 0x1a } }
	},
	{
		L_CHECKSUM_SHA224, 28, 28, 64,
		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04 } }
	},
	{
		L_CHECKSUM_SHA256, 32, 32, 64,
		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 } }
	},
	{
		L_CHECKSUM_SHA384, 48, 48, 128,
		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 } }
	},
	{
		L_CHECKSUM_SHA512, 64, 64, 128,
		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 } }
	},
	{
		L_CHECKSUM_SHA512, 64, 28, 128,
		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x05 } }
	},
	{
		L_CHECKSUM_SHA512, 64, 32, 128,
		{ 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06 } }
	},
};

static const struct asn1_oid pkcs12_key_bag_oid = {
	11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x01 }
};

static const struct asn1_oid pkcs12_pkcs8_shrouded_key_bag_oid = {
	11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x02 }
};

static const struct asn1_oid pkcs12_cert_bag_oid = {
	11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x03 }
};

static const struct asn1_oid pkcs12_safe_contents_bag_oid = {
	11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x06 }
};

static const struct asn1_oid pkcs9_x509_certificate_oid = {
	10, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x16, 0x01 }
};

/* RFC7292 Section 4.2.3 */
static bool cert_parse_pkcs12_cert_bag(const uint8_t *data, size_t data_len,
					struct l_certchain **out_certchain)
{
	const uint8_t *cert_bag;
	size_t cert_bag_len;
	const uint8_t *cert_id;
	size_t cert_id_len;
	const uint8_t *cert_value;
	size_t cert_value_len;
	uint8_t tag;
	struct l_cert *cert;

	if (!(cert_bag = asn1_der_find_elem(data, data_len, 0,
						&tag, &cert_bag_len)) ||
			tag != ASN1_ID_SEQUENCE)
		return false;

	if (!(cert_id = asn1_der_find_elem(cert_bag, cert_bag_len, 0,
						&tag, &cert_id_len)) ||
			tag != ASN1_ID_OID)
		return false;

	if (!(cert_value = asn1_der_find_elem(cert_bag, cert_bag_len,
						ASN1_CONTEXT_EXPLICIT(0),
						&tag, &cert_value_len)) ||
			tag != ASN1_ID_OCTET_STRING ||
			cert_value + cert_value_len != data + data_len)
		return false;

	/* Skip unsupported certificate types */
	if (!asn1_oid_eq(&pkcs9_x509_certificate_oid, cert_id_len, cert_id))
		return true;

	if (!(cert = l_cert_new_from_der(cert_value, cert_value_len)))
		return false;

	if (!*out_certchain)
		*out_certchain = certchain_new_from_leaf(cert);
	else
		certchain_link_issuer(*out_certchain, cert);

	return true;
}

static bool cert_parse_pkcs12_safe_contents(const uint8_t *data,
					size_t data_len, const char *password,
					struct l_certchain **out_certchain,
					struct l_key **out_privkey)
{
	const uint8_t *safe_contents;
	size_t safe_contents_len;
	uint8_t tag;

	if (!(safe_contents = asn1_der_find_elem(data, data_len, 0, &tag,
							&safe_contents_len)) ||
			tag != ASN1_ID_SEQUENCE ||
			data + data_len != safe_contents + safe_contents_len)
		return false;

	/* RFC7292 Section 4.2 */
	while (safe_contents_len) {
		const uint8_t *safe_bag;
		size_t safe_bag_len;
		const uint8_t *bag_id;
		size_t bag_id_len;
		const uint8_t *bag_value;
		int bag_value_len;

		/* RFC7292 Section 4.2 */
		if (!(safe_bag = asn1_der_find_elem(safe_contents,
							safe_contents_len, 0,
							&tag, &safe_bag_len)) ||
				tag != ASN1_ID_SEQUENCE)
			return false;

		if (!(bag_id = asn1_der_find_elem(safe_bag, safe_bag_len, 0,
							&tag, &bag_id_len)) ||
				tag != ASN1_ID_OID)
			return false;

		/*
		 * The bagValue is EXPLICITly tagged but we don't want to
		 * unpack the inner TLV yet so don't use asn1_der_find_elem.
		 */
		safe_bag_len -= bag_id + bag_id_len - safe_bag;
		safe_bag = bag_id + bag_id_len;

		if (safe_bag_len < 4)
			return false;

		tag = *safe_bag++;
		safe_bag_len--;
		bag_value_len = asn1_parse_definite_length(&safe_bag,
								&safe_bag_len);
		bag_value = safe_bag;

		if (bag_value_len < 0 || bag_value_len > (int) safe_bag_len ||
				tag != ASN1_ID(ASN1_CLASS_CONTEXT, 1, 0))
			return false;

		/* PKCS#9 attributes ignored */

		safe_contents_len -= (safe_bag + safe_bag_len - safe_contents);
		safe_contents = safe_bag + safe_bag_len;

		if (asn1_oid_eq(&pkcs12_key_bag_oid, bag_id_len, bag_id)) {
			if (!out_privkey || *out_privkey)
				continue;

			*out_privkey =
				cert_key_from_pkcs8_private_key_info(bag_value,
								bag_value_len);
			if (!*out_privkey)
				return false;
		} else if (asn1_oid_eq(&pkcs12_pkcs8_shrouded_key_bag_oid,
					bag_id_len, bag_id)) {
			if (!out_privkey || *out_privkey)
				continue;

			*out_privkey =
				cert_key_from_pkcs8_encrypted_private_key_info(
								bag_value,
								bag_value_len,
								password);
			if (!*out_privkey)
				return false;
		} else if (asn1_oid_eq(&pkcs12_cert_bag_oid,
					bag_id_len, bag_id)) {
			if (!out_certchain)
				continue;

			if (!cert_parse_pkcs12_cert_bag(bag_value, bag_value_len,
							out_certchain))
				return false;
		} else if (asn1_oid_eq(&pkcs12_safe_contents_bag_oid,
					bag_id_len, bag_id)) {
			/* TODO: depth check */
			if (!(cert_parse_pkcs12_safe_contents(bag_value,
								bag_value_len,
								password,
								out_certchain,
								out_privkey)))
				return false;
		}
	}

	return true;
}

static bool cert_check_pkcs12_integrity(const uint8_t *mac_data,
					size_t mac_data_len,
					const uint8_t *auth_safe,
					size_t auth_safe_len,
					const char *password)
{
	const uint8_t *mac;
	size_t mac_len;
	const uint8_t *mac_salt;
	size_t mac_salt_len;
	const uint8_t *iterations_data;
	size_t iterations_len;
	unsigned int iterations;
	const uint8_t *digest_alg;
	size_t digest_alg_len;
	const uint8_t *digest;
	size_t digest_len;
	const uint8_t *alg_id;
	size_t alg_id_len;
	const struct cert_pkcs12_hash *mac_hash;
	L_AUTO_FREE_VAR(uint8_t *, key) = NULL;
	struct l_checksum *hmac;
	uint8_t hmac_val[64];
	uint8_t tag;
	bool ok;
	unsigned int i;

	if (!(mac = asn1_der_find_elem(mac_data, mac_data_len, 0, &tag,
					&mac_len)) ||
			tag != ASN1_ID_SEQUENCE)
		return false;

	if (!(mac_salt = asn1_der_find_elem(mac_data, mac_data_len, 1, &tag,
						&mac_salt_len)) ||
			tag != ASN1_ID_OCTET_STRING || mac_salt_len > 1024)
		return false;

	if (!(iterations_data = asn1_der_find_elem(mac_data, mac_data_len, 2,
							&tag,
							&iterations_len)) ||
			tag != ASN1_ID_INTEGER || iterations_len > 4)
		return false;

	for (iterations = 0; iterations_len; iterations_len--)
		iterations = (iterations << 8) | *iterations_data++;

	if (iterations < 1 || iterations > 8192)
		return false;

	/* RFC2315 Section 9.4 */
	if (!(digest_alg = asn1_der_find_elem(mac, mac_len, 0, &tag,
						&digest_alg_len)) ||
			tag != ASN1_ID_SEQUENCE)
		return false;

	if (!(digest = asn1_der_find_elem(mac, mac_len, 1, &tag,
						&digest_len)) ||
			tag != ASN1_ID_OCTET_STRING)
		return false;

	if (!(alg_id = asn1_der_find_elem(digest_alg, digest_alg_len,
						0, &tag, &alg_id_len)) ||
			tag != ASN1_ID_OID)
		return false;

	/* This is going to be used for both the MAC and its key derivation */
	for (i = 0; i < L_ARRAY_SIZE(pkcs12_mac_algs); i++)
		if (asn1_oid_eq(&pkcs12_mac_algs[i].oid, alg_id_len, alg_id)) {
			mac_hash = &pkcs12_mac_algs[i];
			break;
		}

	if (i == L_ARRAY_SIZE(pkcs12_mac_algs) || digest_len != mac_hash->u)
		return false;

	if (!(key = cert_pkcs12_pbkdf(password, mac_hash,
					mac_salt, mac_salt_len,
					iterations, 3, mac_hash->u)))
		return false;

	hmac = l_checksum_new_hmac(mac_hash->alg, key, mac_hash->u);
	explicit_bzero(key, mac_hash->u);

	if (!hmac)
		return false;

	ok = l_checksum_update(hmac, auth_safe, auth_safe_len) &&
		l_checksum_get_digest(hmac, hmac_val, mac_hash->len) > 0;
	l_checksum_free(hmac);

	if (!ok)
		return false;

	/*
	 * SHA-512/224 and SHA-512/256 are not supported.  We can truncate the
	 * output for key derivation but we can't do this inside the HMAC
	 * algorithms based on these hashes.  We skip the MAC verification
	 * if one of these hashes is used (identified by .u != .len)
	 */
	if (mac_hash->u != mac_hash->len)
		return true;

	return l_secure_memcmp(hmac_val, digest, digest_len) == 0;
}

/* RFC5652 Section 4 */
static const struct asn1_oid pkcs7_data_oid = {
	9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01 }
};

/* RFC5652 Section 8 */
static const struct asn1_oid pkcs7_encrypted_data_oid = {
	9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x06 }
};

static bool cert_parse_auth_safe_content(const uint8_t *data, size_t data_len,
					uint8_t tag,
					const struct asn1_oid *data_oid,
					const char *password,
					struct l_certchain **out_certchain,
					struct l_key **out_privkey)
{
	if (asn1_oid_eq(&pkcs7_encrypted_data_oid,
			data_oid->asn1_len, data_oid->asn1)) {
		uint8_t *plaintext;
		size_t plaintext_len;
		struct asn1_oid oid;
		bool ok;

		if (tag != ASN1_ID_SEQUENCE)
			return false;

		/*
		 * This is same as PKCS#7 encryptedData but the ciphers
		 * used are from PKCS#12 (broken but still the default
		 * everywhere) and PKCS#5 (recommended).
		 */
		plaintext = cert_decrypt_pkcs7_encrypted_data(data,
								data_len,
								password, &oid,
								&plaintext_len);
		if (!plaintext)
			return false;

		/*
		 * Since we only support PKCS#7 data and encryptedData
		 * types, and there's no point re-encrypting
		 * encryptedData, the plaintext must be a PKCS#7
		 * "data".
		 */
		ok = asn1_oid_eq(&pkcs7_data_oid,
					oid.asn1_len, oid.asn1) &&
			cert_parse_pkcs12_safe_contents(plaintext,
							plaintext_len,
							password,
							out_certchain,
							out_privkey);
		explicit_bzero(plaintext, plaintext_len);
		l_free(plaintext);

		if (!ok)
			return false;
	} else if (asn1_oid_eq(&pkcs7_data_oid,
				data_oid->asn1_len, data_oid->asn1)) {
		if (tag != ASN1_ID_OCTET_STRING)
			return false;

		if (!cert_parse_pkcs12_safe_contents(data, data_len,
							password,
							out_certchain,
							out_privkey))
			return false;
	}
	/* envelopedData support not needed */

	return true;
}

static bool cert_parse_pkcs12_pfx(const uint8_t *ptr, size_t len,
					const char *password,
					struct l_certchain **out_certchain,
					struct l_key **out_privkey)
{
	const uint8_t *version;
	size_t version_len;
	const uint8_t *auth_safe;
	size_t auth_safe_len;
	const uint8_t *mac_data;
	size_t mac_data_len;
	const uint8_t *auth_safe_seq;
	size_t auth_safe_seq_len;
	uint8_t tag;
	unsigned int i;
	struct l_certchain *certchain = NULL;
	struct l_key *privkey = NULL;

	/* RFC7292 Section 4 */
	if (!(version = asn1_der_find_elem(ptr, len, 0, &tag, &version_len)) ||
			tag != ASN1_ID_INTEGER)
		return false;

	if (version_len != 1 || version[0] != 3)
		return false;

	/*
	 * Since we only support the password-based integrity mode,  the
	 * authSafe must be of PKCS#7 type "data" and not "signedData".
	 */
	if (!(auth_safe = cert_unpack_pkcs7_content_info(ptr, len, 1,
							&pkcs7_data_oid, NULL,
							&tag,
							&auth_safe_len)) ||
			tag != ASN1_ID_OCTET_STRING)
		return false;

	/*
	 * openssl can generate PFX structures without macData not signed
	 * with a public key so handle this case, otherwise the macData
	 * would not be optional.
	 */
	if (auth_safe + auth_safe_len == ptr + len)
		goto integrity_check_done;

	if (!(mac_data = asn1_der_find_elem(ptr, len, 2, &tag,
						&mac_data_len)) ||
			tag != ASN1_ID_SEQUENCE)
		return false;

	if (!cert_check_pkcs12_integrity(mac_data, mac_data_len,
						auth_safe, auth_safe_len,
						password))
		return false;

integrity_check_done:
	if (!(auth_safe_seq = asn1_der_find_elem(auth_safe, auth_safe_len, 0,
						&tag, &auth_safe_seq_len)) ||
			tag != ASN1_ID_SEQUENCE ||
			auth_safe + auth_safe_len !=
			auth_safe_seq + auth_safe_seq_len)
		return false;

	i = 0;
	while (1) {
		struct asn1_oid data_oid;
		const uint8_t *data;
		size_t data_len;

		if (!(data = cert_unpack_pkcs7_content_info(auth_safe_seq,
							auth_safe_seq_len, i++,
							NULL, &data_oid, &tag,
							&data_len)))
			goto error;

		if (!cert_parse_auth_safe_content(data, data_len, tag,
							&data_oid, password,
							out_certchain ?
							&certchain : NULL,
							out_privkey ?
							&privkey : NULL))
			goto error;

		if (data + data_len == auth_safe_seq + auth_safe_seq_len)
			break;
	}

	if (out_certchain)
		*out_certchain = certchain;

	if (out_privkey)
		*out_privkey = privkey;

	return true;

error:
	if (certchain)
		l_certchain_free(certchain);

	if (privkey)
		l_key_free(privkey);

	return false;
}

static int cert_try_load_der_format(const uint8_t *content, size_t content_len,
					const char *password,
					struct l_certchain **out_certchain,
					struct l_key **out_privkey,
					bool *out_encrypted)
{
	const uint8_t *seq;
	size_t seq_len;
	size_t elem_len;
	uint8_t tag;

	if (!(seq = asn1_der_find_elem(content, content_len,
					0, &tag, &seq_len)))
		/* May not have been a DER file after all */
		return -ENOMSG;

	/*
	 * See if the first sub-element is another sequence, then, out of
	 * the formats that we currently support this can only be a raw
	 * certificate.  If integer, it's going to be PKCS#12.  If we wish
	 * to add any more formats we'll probably need to start guessing
	 * from the filename suffix.
	 */
	if (!asn1_der_find_elem(seq, seq_len,0, &tag, &elem_len))
		return -ENOMSG;

	if (tag == ASN1_ID_SEQUENCE) {
		if (out_certchain) {
			struct l_cert *cert;

			if (!(cert = l_cert_new_from_der(content, content_len)))
				return -EINVAL;

			*out_certchain = certchain_new_from_leaf(cert);

			if (out_privkey)
				*out_privkey = NULL;

			if (out_encrypted)
				*out_encrypted = false;

			return 0;
		}

		return -EINVAL;
	}

	if (tag == ASN1_ID_INTEGER) {
		/*
		 * Since we don't support public key-protected PKCS#12
		 * modes, we always require the password at least for the
		 * integrity check.  Strictly speaking encryption may not
		 * actually be in use.  We also don't support files with
		 * different integrity and privacy passwords, they must
		 * be identical if privacy is enabled.
		 */
		if (out_encrypted)
			*out_encrypted = true;

		if (!password) {
			if (!out_encrypted)
				return -EINVAL;

			if (out_certchain)
				*out_certchain = NULL;

			if (out_privkey)
				*out_privkey = NULL;

			return 0;
		}

		if (cert_parse_pkcs12_pfx(seq, seq_len, password,
						out_certchain, out_privkey))
			return 0;
		else
			return -EINVAL;
	}

	return -ENOMSG;
}

static bool cert_try_load_pem_format(const char *content, size_t content_len,
					const char *password,
					struct l_certchain **out_certchain,
					struct l_key **out_privkey,
					bool *out_encrypted)
{
	bool error = false;
	bool done = false;
	struct l_certchain *certchain = NULL;
	struct l_key *privkey = NULL;
	bool encrypted = false;

	while (!done && !error && content_len) {
		uint8_t *der;
		size_t der_len;
		char *type_label;
		char *headers;
		const char *endp;

		if (!(der = pem_load_buffer(content, content_len, &type_label,
						&der_len, &headers, &endp)))
			break;

		content_len -= endp - content;
		content = endp;

		if (out_certchain && L_IN_STRSET(type_label, "CERTIFICATE")) {
			struct l_cert *cert;

			if (!(cert = l_cert_new_from_der(der, der_len))) {
				error = true;
				goto next;
			}

			if (!certchain)
				certchain = certchain_new_from_leaf(cert);
			else
				certchain_link_issuer(certchain, cert);

			goto next;
		}

		/* Only use the first private key found */
		if (out_privkey && !privkey && L_IN_STRSET(type_label,
							"PRIVATE KEY",
							"ENCRYPTED PRIVATE KEY",
							"RSA PRIVATE KEY")) {
			privkey = pem_load_private_key(der, der_len, type_label,
							password, headers,
							&encrypted);
			if (!privkey) {
				if (certchain) {
					l_certchain_free(certchain);
					certchain = NULL;
				}

				if (password)
					error = true;
				else
					error = !encrypted || !out_encrypted;

				done = true;
			}

			continue;
		}

		/* Cisco/gnutls-type PEM-encoded PKCS#12, probably rare */
		if (L_IN_STRSET(type_label, "PKCS12")) {
			encrypted = true;

			if (!password) {
				if (certchain && out_privkey) {
					l_certchain_free(certchain);
					certchain = NULL;
				}

				error = !out_encrypted;
				done = true;
				goto next;
			}

			error = !cert_parse_pkcs12_pfx(der, der_len, password,
							out_certchain ?
							&certchain : NULL,
							out_privkey ?
							&privkey : NULL);
			goto next;
		}

next:
		explicit_bzero(der, der_len);
		l_free(der);
		l_free(type_label);
		l_free(headers);
	}

	if (error) {
		if (certchain)
			l_certchain_free(certchain);

		if (privkey)
			l_key_free(privkey);

		return false;
	}

	if (out_certchain)
		*out_certchain = certchain;

	if (out_privkey)
		*out_privkey = privkey;

	if (out_encrypted)
		*out_encrypted = encrypted;

	return true;
}

/*
 * Look at a file, try to detect which of the few X.509 certificate and/or
 * private key container formats it uses and load any certificates in it as
 * a certificate chain object, and load the first private key as an l_key
 * object.
 *
 * Currently supported are:
 *  PEM X.509 certificates
 *  PEM PKCS#8 encrypted and unencrypted private keys
 *  PEM legacy PKCS#1 encrypted and unencrypted private keys
 *  Raw X.509 certificates (.cer, .der, .crt)
 *  PKCS#12 certificates
 *  PKCS#12 encrypted private keys
 *
 * The raw format contains exactly one certificate, PEM and PKCS#12 files
 * can contain any combination of certificates and private keys.
 *
 * The password must have been validated as UTF-8 (use l_utf8_validate)
 * unless the caller knows that no PKCS#12-defined encryption algorithm
 * or MAC is used.
 *
 * Returns false on "unrecoverable" errors, and *out_certchain,
 * *out_privkey and *out_encrypted (if provided) are not modified.  However
 * when true is returned, *out_certchain and *out_privkey (if provided) may
 * be set to NULL when nothing could be loaded only due to missing password,
 * and *out_encrypted (if provided) will be set accordingly.  It will also
 * be set on success to indicate whether the password was used.
 * *out_certchain and/or *out_privkey will also be NULL if the container
 * was loaded but there were no certificates or private keys in it.
 */
LIB_EXPORT bool l_cert_load_container_file(const char *filename,
					const char *password,
					struct l_certchain **out_certchain,
					struct l_key **out_privkey,
					bool *out_encrypted)
{
	struct pem_file_info file;
	bool error = true;

	if (unlikely(!filename))
		return false;

	if (pem_file_open(&file, filename) < 0)
		return false;

	if (file.st.st_size < 1)
		goto close;

	/* See if we have a DER sequence tag at the start */
	if (file.data[0] == ASN1_ID_SEQUENCE) {
		int err;

		err = cert_try_load_der_format(file.data, file.st.st_size,
						password, out_certchain,
						out_privkey, out_encrypted);
		if (!err) {
			error = false;
			goto close;
		}

		if (err != -ENOMSG)
			goto close;

		/* Try other formats */
	}

	/*
	 * For backwards compatibility try the TLS internal struct Certificate
	 * format as may be captured by PCAP (no future support guaranteed).
	 */
	if (out_certchain && !password && file.st.st_size &&
			tls_parse_certificate_list(file.data, file.st.st_size,
							out_certchain) == 0) {
		error = false;

		if (out_privkey)
			*out_privkey = NULL;

		if (out_encrypted)
			*out_encrypted = false;

		goto close;
	}

	/*
	 * RFC 7486 allows whitespace and possibly other data before the
	 * PEM "encapsulation boundary" so rather than check if the start
	 * of the data looks like PEM, we fall back to this format if the
	 * data didn't look like anything else we knew about.  Note this
	 * succeeds for empty files and files without any PEM markers,
	 * returning NULL chain and privkey.
	 */
	if (cert_try_load_pem_format((const char *) file.data, file.st.st_size,
					password, out_certchain, out_privkey,
					out_encrypted))
		error = false;

close:
	pem_file_close(&file);
	return !error;
}
