blob: 141ea1cec038dfd84c8c7b320bb9d0f853aaf645 [file] [log] [blame]
/*
* Embedded Linux library
*
* Copyright (C) 2018 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdio.h>
#include <errno.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 "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[0];
};
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,
{ 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 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);
}
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 false;
ext = seq + len;
oid = asn1_der_find_elem(seq, len, 0, &tag, &oid_len);
if (unlikely(!oid || tag != ASN1_ID_OID))
return false;
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 false;
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 false;
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 */
if (cert->pubkey_type == L_CERT_KEY_RSA)
return l_key_new(L_KEY_RSA, cert->asn1, cert->asn1_len);
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_queue *certs, char *error)
{
struct l_keyring *ring;
const struct l_queue_entry *entry;
int i = 1;
ring = l_keyring_new();
if (!ring)
return NULL;
for (entry = l_queue_get_entries(certs); entry; entry = entry->next) {
struct l_cert *cert = entry->data;
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,
l_queue_length(certs));
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, l_queue_length(certs));
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_queue *set)
{
const struct l_queue_entry *entry;
for (entry = l_queue_get_entries(set); entry; entry = entry->next) {
struct l_cert *cert2 = entry->data;
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_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 = 0;
static char error_buf[200];
if (unlikely(!chain || !chain->leaf))
RETURN_ERROR("Chain empty");
verify_ring = l_keyring_new();
if (!verify_ring)
RETURN_ERROR("Can't create verify keyring");
for (cert = chain->ca; cert; cert = cert->issued, i++)
if (cert_is_in_set(cert, ca_certs)) {
ca_match = i + 1;
break;
}
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 restric 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, 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) {
int total = 0;
char str[100];
for (cert = chain->ca; cert; cert = cert->issued, total++);
if (ca_match)
snprintf(str, sizeof(str), "%i / %i matched a trusted "
"certificate, root not verified",
ca_match, total);
else
snprintf(str, sizeof(str), "root %sverified against "
"trusted CA(s)",
ca_certs && !ca_match && verified ? "" :
"not ");
RETURN_ERROR("Linking certificate %i / %i failed, %s",
verified + 1, total, str);
}
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;
const uint8_t *elem_data;
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 (!(elem_data = 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;
}