mirror of https://gitee.com/openkylin/bluez.git
1678 lines
41 KiB
C
1678 lines
41 KiB
C
/*
|
|
* 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;
|
|
}
|