Add ability to parse PKCS#12 files in l_cert_load_container_file(). This
has only been tested with the files produced by openssl so far.
Extend the static buffer in struct asn1_oid (asn1-private.h) so we can
handle some 11-byte OIDs used in PKCS#12 files.
---
ell/asn1-private.h | 2 +-
ell/cert.c | 634 +++++++++++++++++++++++++++++++++++++++++++++
ell/pem.c | 4 +
3 files changed, 639 insertions(+), 1 deletion(-)
diff --git a/ell/asn1-private.h b/ell/asn1-private.h
index 2a31241..079570b 100644
--- a/ell/asn1-private.h
+++ b/ell/asn1-private.h
@@ -37,7 +37,7 @@
struct asn1_oid {
uint8_t asn1_len;
- uint8_t asn1[10];
+ uint8_t asn1[11];
};
#define asn1_oid_eq(oid1, oid2_len, oid2_string) \
diff --git a/ell/cert.c b/ell/cert.c
index b2bf568..fb4e443 100644
--- a/ell/cert.c
+++ b/ell/cert.c
@@ -585,6 +585,11 @@ struct l_key *cert_key_from_pkcs8_private_key_info(const uint8_t
*der,
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)
@@ -739,6 +744,580 @@ struct l_key *cert_key_from_pkcs1_rsa_private_key(const uint8_t
*der,
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)
+ return NULL;
+
+ if (is_block) {
+ bool ok = true;
+
+ /* Also validate the padding */
+ if (encrypted_len < plaintext[encrypted_len - 1] ||
+ plaintext[encrypted_len - 1] > 16)
+ return NULL;
+
+ 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;
+}
+
+/* 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_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 *auth_safe_seq;
+ size_t auth_safe_seq_len;
+ const uint8_t *mac_data;
+ size_t mac_data_len;
+ 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;
+ unsigned int i;
+ bool ok;
+
+ /* 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 (!(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)
+ goto integrity_check_done;
+
+ if (l_secure_memcmp(hmac_val, digest, digest_len))
+ 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)))
+ return false;
+
+ 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;
+
+ 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 */
+
+ if (data + data_len == auth_safe_seq + auth_safe_seq_len)
+ 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
@@ -750,10 +1329,16 @@ struct l_key *cert_key_from_pkcs1_rsa_private_key(const uint8_t
*der,
* PEM PKCS#8 encrypted and unenecrypted private keys
* PEM legacy PKCS#1 encrypted and unenecrypted 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
@@ -823,6 +1408,32 @@ LIB_EXPORT bool l_cert_load_container_file(const char *filename,
goto close;
}
+
+ 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.
+ */
+ encrypted = true;
+
+ if (!password) {
+ error = !out_encrypted;
+ done = true;
+ goto close;
+ }
+
+ error = !cert_parse_pkcs12_pfx(seq_data, len, password,
+ out_certchain ?
+ &certchain : NULL,
+ out_privkey ?
+ &privkey : NULL);
+ goto close;
+ }
}
not_der_after_all:
@@ -890,6 +1501,29 @@ not_der_after_all:
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);
diff --git a/ell/pem.c b/ell/pem.c
index bc2865a..67d2af1 100644
--- a/ell/pem.c
+++ b/ell/pem.c
@@ -791,6 +791,10 @@ LIB_EXPORT struct l_key *l_pem_load_private_key_from_data(const void
*buf,
* success case and on error when NULL is returned. This can be used to
* check if a passphrase is required without prior information.
*
+ * 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.
+ *
* Returns: An l_key object to be freed with an l_key_free* function,
* or NULL.
**/
--
2.27.0