eap-tls-common: allow embedded PEMs in settings

Refactoring was required to allow for embedded certs. The existing
eap_tls_state object was changed to hold the cert types (l_queue,
l_certchain, l_key) rather than the file path, since there may not
actually be separate PEM files.

Care was taken to properly manage the memory of these objects.
Since the TLS object takes ownership when setting auth data or the
CA certs all error cases must be handled properly to free these
objects after they are loaded and in addition they must be set to
NULL so that the cleanup doesn't double free them.

If everything goes to plan, we load all the PEMs in settings_load,
provide these objects to the TLS APIs, and then NULL out the
pointers (TLS now owns this memory). If anything fails between
settings_load and l_tls_start we must free these objects.

A special format must be used to indicate that a PEM is embedded
inside the settings file. First, the l_settings format should be
followed for the PEM itself, e.g.

[@pem@my_ca_cert]
<CA Cert data>

This PEM can then be referenced by "embed:my_ca_cert", e.g.

EAP-TLS-CACert=embed:my_ca_cert

Any other value not starting with "embed:" will be treated as a file
path.
This commit is contained in:
James Prestwood 2019-10-07 09:11:11 -07:00 committed by Denis Kenzior
parent 3f13c4029a
commit bea1d22a5c
1 changed files with 177 additions and 82 deletions

View File

@ -113,10 +113,9 @@ struct eap_tls_state {
bool expecting_frag_ack:1;
char *ca_cert;
char *client_cert;
char *client_key;
char *passphrase;
struct l_queue *ca_cert;
struct l_certchain *client_cert;
struct l_key *client_key;
char **domain_mask;
const struct eap_tls_variant_ops *variant_ops;
@ -154,6 +153,21 @@ static void __eap_tls_common_state_reset(struct eap_tls_state *eap_tls)
}
}
static void __eap_tls_common_state_free(struct eap_tls_state *eap_tls)
{
if (eap_tls->ca_cert)
l_queue_destroy(eap_tls->ca_cert,
(l_queue_destroy_func_t)l_cert_free);
if (eap_tls->client_cert)
l_certchain_free(eap_tls->client_cert);
if (eap_tls->client_key)
l_key_free(eap_tls->client_key);
l_strv_free(eap_tls->domain_mask);
l_free(eap_tls);
}
bool eap_tls_common_state_reset(struct eap_state *eap)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
@ -177,18 +191,7 @@ void eap_tls_common_state_free(struct eap_state *eap)
if (eap_tls->variant_ops->destroy)
eap_tls->variant_ops->destroy(eap_tls->variant_data);
l_free(eap_tls->ca_cert);
l_free(eap_tls->client_cert);
l_free(eap_tls->client_key);
if (eap_tls->passphrase) {
explicit_bzero(eap_tls->passphrase,
strlen(eap_tls->passphrase));
l_free(eap_tls->passphrase);
}
l_strv_free(eap_tls->domain_mask);
l_free(eap_tls);
__eap_tls_common_state_free(eap_tls);
}
static void eap_tls_tunnel_debug(const char *str, void *user_data)
@ -544,55 +547,34 @@ static bool eap_tls_tunnel_init(struct eap_state *eap)
NULL);
if (eap_tls->client_cert || eap_tls->client_key) {
struct l_certchain *client_cert =
l_pem_load_certificate_chain(eap_tls->client_cert);
struct l_key *client_key;
if (!l_tls_set_auth_data(eap_tls->tunnel, eap_tls->client_cert,
eap_tls->client_key)) {
l_certchain_free(eap_tls->client_cert);
eap_tls->client_cert = NULL;
if (!client_cert) {
l_error("%s: Failed to parse client certificate: %s.",
eap_get_method_name(eap),
eap_tls->client_cert);
return false;
}
l_key_free(eap_tls->client_key);
eap_tls->client_key = NULL;
client_key = l_pem_load_private_key(eap_tls->client_key,
eap_tls->passphrase,
NULL);
if (!client_key) {
l_error("%s: Failed to parse client private key: %s.",
eap_get_method_name(eap),
eap_tls->client_key);
return false;
}
if (!l_tls_set_auth_data(eap_tls->tunnel,
client_cert, client_key)) {
l_certchain_free(client_cert);
l_key_free(client_key);
l_error("%s: Failed to set auth data.",
eap_get_method_name(eap));
return false;
}
eap_tls->client_cert = NULL;
eap_tls->client_key = NULL;
}
if (eap_tls->ca_cert) {
struct l_queue *ca_cert =
l_pem_load_certificate_list(eap_tls->ca_cert);
if (!ca_cert) {
l_error("%s: Error loading CA certificates from %s.",
eap_get_method_name(eap),
eap_tls->ca_cert);
return false;
}
if (!l_tls_set_cacert(eap_tls->tunnel, ca_cert)) {
l_queue_destroy(ca_cert,
if (!l_tls_set_cacert(eap_tls->tunnel, eap_tls->ca_cert)) {
l_queue_destroy(eap_tls->ca_cert,
(l_queue_destroy_func_t)l_cert_free);
eap_tls->ca_cert = NULL;
l_error("%s: Error settings CA certificates.",
eap_get_method_name(eap));
return false;
}
eap_tls->ca_cert = NULL;
}
if (eap_tls->domain_mask)
@ -793,6 +775,83 @@ error:
eap_method_error(eap);
}
static const char *load_embedded_pem(struct l_settings *settings,
const char *name)
{
const char *pem;
const char *type;
pem = l_settings_get_embedded_value(settings, name + 6, &type);
if (!pem)
return NULL;
if (strcmp(type, "pem"))
return NULL;
return pem;
}
static bool is_embedded(const char *str)
{
if (!str)
return false;
if (strlen(str) < 6)
return false;
if (!strncmp("embed:", str, 6))
return true;
return false;
}
static struct l_queue *eap_tls_load_ca_cert(struct l_settings *settings,
const char *value)
{
const char *pem;
if (!is_embedded(value))
return l_pem_load_certificate_list(value);
pem = load_embedded_pem(settings, value);
if (!pem)
return NULL;
return l_pem_load_certificate_list_from_data(pem, strlen(pem));
}
static struct l_certchain *eap_tls_load_client_cert(struct l_settings *settings,
const char *value)
{
const char *pem;
if (!is_embedded(value))
return l_pem_load_certificate_chain(value);
pem = load_embedded_pem(settings, value);
if (!pem)
return NULL;
return l_pem_load_certificate_chain_from_data(pem, strlen(pem));
}
static struct l_key *eap_tls_load_priv_key(struct l_settings *settings,
const char *value, const char *passphrase,
bool *is_encrypted)
{
const char *pem;
if (!is_embedded(value))
return l_pem_load_private_key(value, passphrase, is_encrypted);
pem = load_embedded_pem(settings, value);
if (!pem)
return NULL;
return l_pem_load_private_key_from_data(pem, strlen(pem),
passphrase, is_encrypted);
}
int eap_tls_common_settings_check(struct l_settings *settings,
struct l_queue *secrets,
const char *prefix,
@ -813,16 +872,17 @@ int eap_tls_common_settings_check(struct l_settings *settings,
struct l_key *pub_key;
const char *domain_mask_str;
L_AUTO_FREE_VAR(char *, path);
L_AUTO_FREE_VAR(char *, value);
L_AUTO_FREE_VAR(char *, client_cert) = NULL;
L_AUTO_FREE_VAR(char *, passphrase) = NULL;
snprintf(setting_key, sizeof(setting_key), "%sCACert", prefix);
path = l_settings_get_string(settings, "Security", setting_key);
if (path) {
cacerts = l_pem_load_certificate_list(path);
value = l_settings_get_string(settings, "Security", setting_key);
if (value) {
cacerts = eap_tls_load_ca_cert(settings, value);
if (!cacerts) {
l_error("Failed to load %s", path);
l_error("Failed to load %s", value);
return -EIO;
}
}
@ -832,7 +892,8 @@ int eap_tls_common_settings_check(struct l_settings *settings,
client_cert = l_settings_get_string(settings, "Security",
client_cert_setting);
if (client_cert) {
cert = l_pem_load_certificate_chain(client_cert);
cert = eap_tls_load_client_cert(settings, client_cert);
if (!cert) {
l_error("Failed to load %s", client_cert);
ret = -EIO;
@ -843,7 +904,7 @@ int eap_tls_common_settings_check(struct l_settings *settings,
if (cacerts)
l_error("Certificate chain %s is not trusted "
"by any CA in %s or fails verification"
": %s", client_cert, path, error_str);
": %s", client_cert, value, error_str);
else
l_error("Certificate chain %s fails "
"verification: %s",
@ -854,17 +915,17 @@ int eap_tls_common_settings_check(struct l_settings *settings,
}
}
l_free(path);
l_free(value);
snprintf(setting_key, sizeof(setting_key), "%sClientKey", prefix);
path = l_settings_get_string(settings, "Security", setting_key);
value = l_settings_get_string(settings, "Security", setting_key);
if (path && !client_cert) {
if (value && !client_cert) {
l_error("%s present but no client certificate (%s)",
setting_key, client_cert_setting);
ret = -ENOENT;
goto done;
} else if (!path && client_cert) {
} else if (!value && client_cert) {
l_error("%s present but no client private key (%s)",
client_cert_setting, setting_key);
ret = -ENOENT;
@ -885,10 +946,11 @@ int eap_tls_common_settings_check(struct l_settings *settings,
passphrase = l_strdup(secret->value);
}
if (!path) {
if (!value) {
if (passphrase) {
l_error("%s present but no client private key path set (%s)",
passphrase_setting, setting_key);
l_error("%s present but no client private key"
" value set (%s)", passphrase_setting,
setting_key);
ret = -ENOENT;
goto done;
}
@ -897,17 +959,19 @@ int eap_tls_common_settings_check(struct l_settings *settings,
goto done;
}
priv_key = l_pem_load_private_key(path, passphrase, &is_encrypted);
priv_key = eap_tls_load_priv_key(settings, value, passphrase,
&is_encrypted);
if (!priv_key) {
if (!is_encrypted) {
l_error("Error loading client private key %s", path);
l_error("Error loading client private key %s", value);
ret = -EIO;
goto done;
}
if (passphrase) {
l_error("Error loading encrypted client private key %s",
path);
value);
ret = -EACCES;
goto done;
}
@ -918,7 +982,7 @@ int eap_tls_common_settings_check(struct l_settings *settings,
*/
eap_append_secret(out_missing,
EAP_SECRET_LOCAL_PKEY_PASSPHRASE,
passphrase_setting, NULL, path,
passphrase_setting, NULL, value,
EAP_CACHE_TEMPORARY);
ret = 0;
goto done;
@ -926,7 +990,7 @@ int eap_tls_common_settings_check(struct l_settings *settings,
if (passphrase && !is_encrypted) {
l_error("%s present but client private key %s is not encrypted",
passphrase_setting, path);
passphrase_setting, value);
ret = -ENOENT;
goto done;
}
@ -934,7 +998,7 @@ int eap_tls_common_settings_check(struct l_settings *settings,
if (!l_key_get_info(priv_key, L_KEY_RSA_PKCS1_V1_5, L_CHECKSUM_NONE,
&size, &is_public) || is_public) {
l_error("%s is not a private key or l_key_get_info fails",
path);
value);
ret = -EINVAL;
goto done;
}
@ -964,14 +1028,14 @@ int eap_tls_common_settings_check(struct l_settings *settings,
result = l_key_decrypt(priv_key, L_KEY_RSA_PKCS1_V1_5, L_CHECKSUM_NONE,
encrypted, decrypted, size, size);
if (result < 0) {
l_error("l_key_decrypt fails with %s: %s", path,
l_error("l_key_decrypt fails with %s: %s", value,
strerror(-result));
ret = result;
goto done;
}
if (result != 1 || decrypted[0] != 0) {
l_error("Private key %s does not match certificate %s", path,
l_error("Private key %s does not match certificate %s", value,
client_cert);
ret = -EINVAL;
goto done;
@ -1014,6 +1078,8 @@ bool eap_tls_common_settings_load(struct eap_state *eap,
struct eap_tls_state *eap_tls;
char setting_key[72];
char *domain_mask_str;
L_AUTO_FREE_VAR(char *, value) = NULL;
L_AUTO_FREE_VAR(char *, passphrase) = NULL;
eap_tls = l_new(struct eap_tls_state, 1);
@ -1022,21 +1088,45 @@ bool eap_tls_common_settings_load(struct eap_state *eap,
eap_tls->variant_data = variant_data;
snprintf(setting_key, sizeof(setting_key), "%sCACert", prefix);
eap_tls->ca_cert = l_settings_get_string(settings, "Security",
setting_key);
value = l_settings_get_string(settings, "Security", setting_key);
if (value) {
eap_tls->ca_cert = eap_tls_load_ca_cert(settings, value);
if (!eap_tls->ca_cert) {
l_error("Could not load CACert %s", value);
goto load_error;
}
}
l_free(value);
snprintf(setting_key, sizeof(setting_key), "%sClientCert", prefix);
eap_tls->client_cert = l_settings_get_string(settings, "Security",
setting_key);
value = l_settings_get_string(settings, "Security", setting_key);
if (value) {
eap_tls->client_cert = eap_tls_load_client_cert(settings,
value);
if (!eap_tls->ca_cert) {
l_error("Could not load ClientCert %s", value);
goto load_error;
}
}
snprintf(setting_key, sizeof(setting_key), "%sClientKey", prefix);
eap_tls->client_key = l_settings_get_string(settings, "Security",
setting_key);
l_free(value);
snprintf(setting_key, sizeof(setting_key), "%sClientKeyPassphrase",
prefix);
eap_tls->passphrase = l_settings_get_string(settings, "Security",
setting_key);
passphrase = l_settings_get_string(settings, "Security", setting_key);
snprintf(setting_key, sizeof(setting_key), "%sClientKey", prefix);
value = l_settings_get_string(settings, "Security", setting_key);
if (value) {
eap_tls->client_key = eap_tls_load_priv_key(settings, value,
passphrase,
NULL);
if (!eap_tls->client_key) {
l_error("Could not load ClientKey %s", value);
goto load_error;
}
}
snprintf(setting_key, sizeof(setting_key), "%sServerDomainMask",
prefix);
@ -1051,6 +1141,11 @@ bool eap_tls_common_settings_load(struct eap_state *eap,
eap_set_data(eap, eap_tls);
return true;
load_error:
__eap_tls_common_state_free(eap_tls);
return false;
}
void eap_tls_common_set_completed(struct eap_state *eap)