eap-tls-common: More complete certificate validation

In the methods' check_settings do a more complete early check for
possible certificate / private key misconfiguration, including check
that the certificate and the private key are always present or absent
together and that they actually match each other.  Do this by encrypting
and decrypting a small buffer because we have no better API for that.
This commit is contained in:
Andrew Zaborowski 2019-02-08 18:32:13 +01:00 committed by Denis Kenzior
parent d9f0cc47d0
commit dbd619c231
1 changed files with 123 additions and 46 deletions

View File

@ -731,6 +731,16 @@ int eap_tls_common_settings_check(struct l_settings *settings,
char setting_key[72]; char setting_key[72];
char client_cert_setting[72]; char client_cert_setting[72];
char passphrase_setting[72]; char passphrase_setting[72];
struct l_queue *cacerts = NULL;
struct l_certchain *cert = NULL;
struct l_key *priv_key = NULL;
bool is_encrypted, is_public;
int ret;
const char *error_str;
size_t size;
ssize_t result;
uint8_t *encrypted, *decrypted;
struct l_key *pub_key;
L_AUTO_FREE_VAR(char *, path); L_AUTO_FREE_VAR(char *, path);
L_AUTO_FREE_VAR(char *, client_cert) = NULL; L_AUTO_FREE_VAR(char *, client_cert) = NULL;
@ -739,16 +749,11 @@ int eap_tls_common_settings_check(struct l_settings *settings,
snprintf(setting_key, sizeof(setting_key), "%sCACert", prefix); snprintf(setting_key, sizeof(setting_key), "%sCACert", prefix);
path = l_settings_get_string(settings, "Security", setting_key); path = l_settings_get_string(settings, "Security", setting_key);
if (path) { if (path) {
struct l_queue *cacerts;
cacerts = l_pem_load_certificate_list(path); cacerts = l_pem_load_certificate_list(path);
if (!cacerts) { if (!cacerts) {
l_error("Failed to load %s", path); l_error("Failed to load %s", path);
return -EIO; return -EIO;
} }
l_queue_destroy(cacerts,
(l_queue_destroy_func_t) l_cert_free);
} }
snprintf(client_cert_setting, sizeof(client_cert_setting), snprintf(client_cert_setting, sizeof(client_cert_setting),
@ -756,15 +761,26 @@ int eap_tls_common_settings_check(struct l_settings *settings,
client_cert = l_settings_get_string(settings, "Security", client_cert = l_settings_get_string(settings, "Security",
client_cert_setting); client_cert_setting);
if (client_cert) { if (client_cert) {
struct l_certchain *cert;
cert = l_pem_load_certificate_chain(client_cert); cert = l_pem_load_certificate_chain(client_cert);
if (!cert) { if (!cert) {
l_error("Failed to load %s", client_cert); l_error("Failed to load %s", client_cert);
return -EIO; ret = -EIO;
goto done;
} }
l_certchain_free(cert); if (!l_certchain_verify(cert, cacerts, &error_str)) {
if (cacerts)
l_error("Certificate chain %s is not trusted "
"by any CA in %s or fails verification"
": %s", client_cert, path, error_str);
else
l_error("Certificate chain %s fails "
"verification: %s",
client_cert, error_str);
ret = -EINVAL;
goto done;
}
} }
l_free(path); l_free(path);
@ -775,7 +791,13 @@ int eap_tls_common_settings_check(struct l_settings *settings,
if (path && !client_cert) { if (path && !client_cert) {
l_error("%s present but no client certificate (%s)", l_error("%s present but no client certificate (%s)",
setting_key, client_cert_setting); setting_key, client_cert_setting);
return -ENOENT; ret = -ENOENT;
goto done;
} else if (!path && client_cert) {
l_error("%s present but no client private key (%s)",
client_cert_setting, setting_key);
ret = -ENOENT;
goto done;
} }
snprintf(passphrase_setting, sizeof(passphrase_setting), snprintf(passphrase_setting, sizeof(passphrase_setting),
@ -792,50 +814,105 @@ int eap_tls_common_settings_check(struct l_settings *settings,
passphrase = l_strdup(secret->value); passphrase = l_strdup(secret->value);
} }
if (path) { if (!path) {
struct l_key *priv_key; if (passphrase) {
bool encrypted; l_error("%s present but no client private key path set (%s)",
passphrase_setting, setting_key);
ret = -ENOENT;
goto done;
}
priv_key = l_pem_load_private_key(path, passphrase, &encrypted); ret = 0;
if (!priv_key) { goto done;
if (!encrypted) { }
l_error("Error loading client private key %s",
path);
return -EIO;
}
if (passphrase) { priv_key = l_pem_load_private_key(path, passphrase, &is_encrypted);
l_error("Error loading encrypted client " if (!priv_key) {
"private key %s", path); if (!is_encrypted) {
return -EACCES; l_error("Error loading client private key %s", path);
} ret = -EIO;
goto done;
}
/* if (passphrase) {
* We've got an encrypted key and passphrase was not l_error("Error loading encrypted client private key %s",
* saved in the network settings, need to request path);
* the passphrase. ret = -EACCES;
*/ goto done;
eap_append_secret(out_missing, }
/*
* We've got an encrypted key and passphrase was not saved
* in the network settings, need to request the passphrase.
*/
eap_append_secret(out_missing,
EAP_SECRET_LOCAL_PKEY_PASSPHRASE, EAP_SECRET_LOCAL_PKEY_PASSPHRASE,
passphrase_setting, NULL, path, passphrase_setting, NULL, path,
EAP_CACHE_TEMPORARY); EAP_CACHE_TEMPORARY);
} else { ret = 0;
l_key_free(priv_key); goto done;
if (passphrase && !encrypted) {
l_error("%s present but client private "
"key %s is not encrypted",
passphrase_setting, path);
return -ENOENT;
}
}
} else if (passphrase) {
l_error("%s present but no client private key path set (%s)",
passphrase_setting, setting_key);
return -ENOENT;
} }
return 0; if (passphrase && !is_encrypted) {
l_error("%s present but client private key %s is not encrypted",
passphrase_setting, path);
ret = -ENOENT;
goto done;
}
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);
ret = -EINVAL;
goto done;
}
size /= 8;
encrypted = alloca(size);
decrypted = alloca(size);
pub_key = l_cert_get_pubkey(l_certchain_get_leaf(cert));
if (!pub_key) {
l_error("l_cert_get_pubkey fails for %s", client_cert);
ret = -EIO;
goto done;
}
result = l_key_encrypt(pub_key, L_KEY_RSA_PKCS1_V1_5, L_CHECKSUM_NONE,
"", encrypted, 1, size);
l_key_free(pub_key);
if (result != (ssize_t) size) {
l_error("l_key_encrypt fails with %s: %s", client_cert,
strerror(-result));
ret = result;
goto done;
}
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,
strerror(-result));
ret = result;
goto done;
}
if (result != 1 || decrypted[0] != 0) {
l_error("Private key %s does not match certificate %s", path,
client_cert);
ret = -EINVAL;
goto done;
}
ret = 0;
done:
l_queue_destroy(cacerts,
(l_queue_destroy_func_t) l_cert_free);
l_certchain_free(cert);
l_key_free(priv_key);
return ret;
} }
bool eap_tls_common_settings_load(struct eap_state *eap, bool eap_tls_common_settings_load(struct eap_state *eap,