diff options
author | Dan Williams <dcbw@redhat.com> | 2008-11-13 21:19:08 +0000 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2008-11-13 21:19:08 +0000 |
commit | e2f65ce12ae4fe2f2b0c537eaf59d6c8f8c8a002 (patch) | |
tree | d1ca74254c9c7bf8e8911480185aa59bf42b8b17 | |
parent | e4ae149b37f2a25e3b4e6e545884d504921ea817 (diff) | |
download | NetworkManager-e2f65ce12ae4fe2f2b0c537eaf59d6c8f8c8a002.tar.gz |
2008-11-13 Dan Williams <dcbw@redhat.com>
Add support for PKCS#12 private keys (bgo #558982)
* libnm-util/crypto.c
libnm-util/crypto.h
- (parse_old_openssl_key_file): rename from parse_key_file(); adapt to
take a GByteArray instead of a filename
- (file_to_g_byte_array): handle private key files too
- (decrypt_key): take a GByteArray rather than data + len
- (crypto_get_private_key_data): refactor crypto_get_private_key() into
one function that takes a filename, and one that takes raw data;
detect pkcs#12 files as well
- (crypto_load_and_verify_certificate): detect file type
- (crypto_is_pkcs12_data, crypto_is_pkcs12_file): add pkcs#12 detection
functions
* libnm-util/crypto_gnutls.c
- (crypto_decrypt): take GByteArray rather than data + len; fix a bug
whereby tail padding was incorrectly handled, leading to erroneous
successes when trying to decrypt the data
- (crypto_verify_cert): rework somewhat
- (crypto_verify_pkcs12): validate pkcs#12 keys
* libnm-util/crypto_nss.c
- (crypto_init): enable various pkcs#12 ciphers
- (crypto_decrypt): take a GByteArray rather than data + len
- (crypto_verify_cert): clean up
- (crypto_verify_pkcs12): validate pkcs#12 keys
* libnm-util/test-crypto.c
- Handle pkcs#12 keys
* libnm-util/nm-setting-8021x.c
libnm-util/nm-setting-8021x.h
libnm-util/libnm-util.ver
- Add two new properties, 'private-key-password' and
'phase2-private-key-password', to be used in conjunction with
pkcs#12 keys
- (nm_setting_802_1x_set_ca_cert_from_file,
nm_setting_802_1x_set_client_cert_from_file,
nm_setting_802_1x_set_phase2_ca_cert_from_file,
nm_setting_802_1x_set_phase2_client_from_file): return certificate
type
- (nm_setting_802_1x_get_private_key_password,
nm_setting_802_1x_get_phase2_private_key_password): return private
key passwords
- (nm_setting_802_1x_set_private_key_from_file,
nm_setting_802_1x_set_phase2_private_key_from_file): set the private
key from a file, and update the private key password at the same time
- (nm_setting_802_1x_get_private_key_type,
nm_setting_802_1x_get_phase2_private_key_type): return the private
key type
* src/supplicant-manager/nm-supplicant-settings-verify.c
- Whitelist private key passwords
* src/supplicant-manager/nm-supplicant-config.c
- (nm_supplicant_config_add_setting_8021x): for pkcs#12 private keys,
add the private key password to the supplicant config, but do not
add the client certificate (as required by wpa_supplicant)
git-svn-id: http://svn-archive.gnome.org/svn/NetworkManager/trunk@4280 4912f4e0-d625-0410-9fb7-b9a5a253dbdc
-rw-r--r-- | ChangeLog | 62 | ||||
-rw-r--r-- | libnm-util/crypto.c | 197 | ||||
-rw-r--r-- | libnm-util/crypto.h | 41 | ||||
-rw-r--r-- | libnm-util/crypto_gnutls.c | 109 | ||||
-rw-r--r-- | libnm-util/crypto_nss.c | 127 | ||||
-rw-r--r-- | libnm-util/libnm-util.ver | 4 | ||||
-rw-r--r-- | libnm-util/nm-setting-8021x.c | 346 | ||||
-rw-r--r-- | libnm-util/nm-setting-8021x.h | 35 | ||||
-rw-r--r-- | libnm-util/test-crypto.c | 35 | ||||
-rw-r--r-- | src/supplicant-manager/nm-supplicant-config.c | 47 | ||||
-rw-r--r-- | src/supplicant-manager/nm-supplicant-settings-verify.c | 2 |
11 files changed, 878 insertions, 127 deletions
@@ -1,3 +1,65 @@ +2008-11-13 Dan Williams <dcbw@redhat.com> + + Add support for PKCS#12 private keys (bgo #558982) + + * libnm-util/crypto.c + libnm-util/crypto.h + - (parse_old_openssl_key_file): rename from parse_key_file(); adapt to + take a GByteArray instead of a filename + - (file_to_g_byte_array): handle private key files too + - (decrypt_key): take a GByteArray rather than data + len + - (crypto_get_private_key_data): refactor crypto_get_private_key() into + one function that takes a filename, and one that takes raw data; + detect pkcs#12 files as well + - (crypto_load_and_verify_certificate): detect file type + - (crypto_is_pkcs12_data, crypto_is_pkcs12_file): add pkcs#12 detection + functions + + * libnm-util/crypto_gnutls.c + - (crypto_decrypt): take GByteArray rather than data + len; fix a bug + whereby tail padding was incorrectly handled, leading to erroneous + successes when trying to decrypt the data + - (crypto_verify_cert): rework somewhat + - (crypto_verify_pkcs12): validate pkcs#12 keys + + * libnm-util/crypto_nss.c + - (crypto_init): enable various pkcs#12 ciphers + - (crypto_decrypt): take a GByteArray rather than data + len + - (crypto_verify_cert): clean up + - (crypto_verify_pkcs12): validate pkcs#12 keys + + * libnm-util/test-crypto.c + - Handle pkcs#12 keys + + * libnm-util/nm-setting-8021x.c + libnm-util/nm-setting-8021x.h + libnm-util/libnm-util.ver + - Add two new properties, 'private-key-password' and + 'phase2-private-key-password', to be used in conjunction with + pkcs#12 keys + - (nm_setting_802_1x_set_ca_cert_from_file, + nm_setting_802_1x_set_client_cert_from_file, + nm_setting_802_1x_set_phase2_ca_cert_from_file, + nm_setting_802_1x_set_phase2_client_from_file): return certificate + type + - (nm_setting_802_1x_get_private_key_password, + nm_setting_802_1x_get_phase2_private_key_password): return private + key passwords + - (nm_setting_802_1x_set_private_key_from_file, + nm_setting_802_1x_set_phase2_private_key_from_file): set the private + key from a file, and update the private key password at the same time + - (nm_setting_802_1x_get_private_key_type, + nm_setting_802_1x_get_phase2_private_key_type): return the private + key type + + * src/supplicant-manager/nm-supplicant-settings-verify.c + - Whitelist private key passwords + + * src/supplicant-manager/nm-supplicant-config.c + - (nm_supplicant_config_add_setting_8021x): for pkcs#12 private keys, + add the private key password to the supplicant config, but do not + add the client certificate (as required by wpa_supplicant) + 2008-11-12 Tambet Ingo <tambet@gmail.com> * system-settings/plugins/keyfile/nm-keyfile-connection.c (copy_one_secret) diff --git a/libnm-util/crypto.c b/libnm-util/crypto.c index d391bd93fe..faf4f7541b 100644 --- a/libnm-util/crypto.c +++ b/libnm-util/crypto.c @@ -68,26 +68,24 @@ find_tag (const char *tag, const char *buf, gsize len) #define DEK_INFO_TAG "DEK-Info: " #define PROC_TYPE_TAG "Proc-Type: " -static char * -parse_key_file (const char *filename, - int key_type, - gsize *out_length, - char **out_cipher, - char **out_iv, - GError **error) +static GByteArray * +parse_old_openssl_key_file (GByteArray *contents, + int key_type, + char **out_cipher, + char **out_iv, + GError **error) { - char *contents = NULL; + GByteArray *bindata = NULL; char **lines = NULL; char **ln = NULL; - gsize length = 0; const char *pos; const char *end; GString *str = NULL; int enc_tags = 0; char *iv = NULL; char *cipher = NULL; - char *bindata = NULL; - gsize bindata_len = 0; + unsigned char *tmp = NULL; + gsize tmp_len = 0; const char *start_tag; const char *end_tag; @@ -109,19 +107,16 @@ parse_key_file (const char *filename, return NULL; } - if (!g_file_get_contents (filename, &contents, &length, error)) - return NULL; - - pos = find_tag (start_tag, contents, length); + pos = find_tag (start_tag, (const char *) contents->data, contents->len); if (!pos) goto parse_error; pos += strlen (start_tag); - end = find_tag (end_tag, pos, contents + length - pos); + end = find_tag (end_tag, pos, (const char *) contents->data + contents->len - pos); if (end == NULL) { g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_PEM_FORMAT_INVALID, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, _("PEM key file had no end tag '%s'."), end_tag); goto parse_error; @@ -131,7 +126,7 @@ parse_key_file (const char *filename, lines = g_strsplit (pos, "\n", 0); if (!lines || g_strv_length (lines) <= 1) { g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_PEM_FORMAT_INVALID, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, _("Doesn't look like a PEM private key file.")); goto parse_error; } @@ -155,7 +150,7 @@ parse_key_file (const char *filename, if (!strncmp (p, PROC_TYPE_TAG, strlen (PROC_TYPE_TAG))) { if (enc_tags++ != 0) { g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_PEM_FORMAT_INVALID, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, _("Malformed PEM file: Proc-Type was not first tag.")); goto parse_error; } @@ -163,7 +158,7 @@ parse_key_file (const char *filename, p += strlen (PROC_TYPE_TAG); if (strcmp (p, "4,ENCRYPTED")) { g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_PEM_FORMAT_INVALID, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, _("Malformed PEM file: unknown Proc-Type tag '%s'."), p); goto parse_error; @@ -173,7 +168,7 @@ parse_key_file (const char *filename, if (enc_tags++ != 1) { g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_PEM_FORMAT_INVALID, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, _("Malformed PEM file: DEK-Info was not the second tag.")); goto parse_error; } @@ -184,14 +179,14 @@ parse_key_file (const char *filename, comma = strchr (p, ','); if (!comma || (*(comma + 1) == '\0')) { g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_PEM_FORMAT_INVALID, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, _("Malformed PEM file: no IV found in DEK-Info tag.")); goto parse_error; } *comma++ = '\0'; if (!g_ascii_isxdigit (*comma)) { g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_PEM_FORMAT_INVALID, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, _("Malformed PEM file: invalid format of IV in DEK-Info tag.")); goto parse_error; } @@ -212,7 +207,7 @@ parse_key_file (const char *filename, } else { if ((enc_tags != 0) && (enc_tags != 2)) { g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_PEM_FORMAT_INVALID, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, "Malformed PEM file: both Proc-Type and DEK-Info tags are required."); goto parse_error; } @@ -220,8 +215,8 @@ parse_key_file (const char *filename, } } - bindata = (char *) g_base64_decode (str->str, &bindata_len); - if (bindata == NULL || !bindata_len) { + tmp = g_base64_decode (str->str, &tmp_len); + if (tmp == NULL || !tmp_len) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_DECODE_FAILED, _("Could not decode private key.")); @@ -230,36 +225,37 @@ parse_key_file (const char *filename, if (lines) g_strfreev (lines); - g_free (contents); + bindata = g_byte_array_sized_new (tmp_len); + g_byte_array_append (bindata, tmp, tmp_len); *out_iv = iv; *out_cipher = cipher; - *out_length = bindata_len; return bindata; parse_error: - g_free (bindata); g_free (cipher); g_free (iv); if (lines) g_strfreev (lines); - g_free (contents); return NULL; } static GByteArray * file_to_g_byte_array (const char *filename, + gboolean privkey, GError **error) { char *contents, *der = NULL; GByteArray *array = NULL; gsize length = 0; - const char *pos; + const char *pos = NULL; if (!g_file_get_contents (filename, &contents, &length, error)) return NULL; - pos = find_tag (pem_cert_begin, contents, length); + if (!privkey) + pos = find_tag (pem_cert_begin, contents, length); + if (pos) { const char *end; @@ -267,7 +263,7 @@ file_to_g_byte_array (const char *filename, end = find_tag (pem_cert_end, pos, contents + length - pos); if (end == NULL) { g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_PEM_FORMAT_INVALID, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, _("PEM certificate '%s' had no end tag '%s'."), filename, pem_cert_end); goto done; @@ -295,7 +291,7 @@ file_to_g_byte_array (const char *filename, if (array->len != length) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_OUT_OF_MEMORY, - _("Not enough memory to store certificate data.")); + _("Not enough memory to store file data.")); g_byte_array_free (array, TRUE); array = NULL; } @@ -420,8 +416,7 @@ error: static char * decrypt_key (const char *cipher, int key_type, - const char *data, - gsize data_len, + GByteArray *data, const char *iv, const char *password, gsize *out_len, @@ -443,7 +438,7 @@ decrypt_key (const char *cipher, goto out; output = crypto_decrypt (cipher, key_type, - data, data_len, + data, bin_iv, bin_iv_len, key, key_len, out_len, @@ -467,38 +462,60 @@ out: return output; } - GByteArray * -crypto_get_private_key (const char *file, - const char *password, - guint32 *out_key_type, - GError **error) +crypto_get_private_key_data (GByteArray *contents, + const char *password, + NMCryptoKeyType *out_key_type, + NMCryptoFileFormat *out_file_type, + GError **error) { GByteArray *array = NULL; - guint32 key_type = NM_CRYPTO_KEY_TYPE_RSA; - char *data = NULL; - gsize data_len = 0; + NMCryptoKeyType key_type = NM_CRYPTO_KEY_TYPE_RSA; + GByteArray *data; char *iv = NULL; char *cipher = NULL; char *decrypted = NULL; gsize decrypted_len = 0; - /* Try RSA first */ - data = parse_key_file (file, key_type, &data_len, &cipher, &iv, error); + g_return_val_if_fail (contents != NULL, NULL); + g_return_val_if_fail (password != NULL, NULL); + g_return_val_if_fail (out_key_type != NULL, NULL); + g_return_val_if_fail (out_key_type == NM_CRYPTO_KEY_TYPE_UNKNOWN, NULL); + g_return_val_if_fail (out_file_type != NULL, NULL); + g_return_val_if_fail (out_file_type == NM_CRYPTO_FILE_FORMAT_UNKNOWN, NULL); + + /* Try PKCS#12 first */ + if (crypto_verify_pkcs12 (contents, password, NULL)) { + *out_key_type = NM_CRYPTO_KEY_TYPE_ENCRYPTED; + *out_file_type = NM_CRYPTO_FILE_FORMAT_PKCS12; + + array = g_byte_array_sized_new (contents->len); + g_byte_array_append (array, contents->data, contents->len); + return array; + } + + /* OpenSSL non-standard legacy PEM files */ + + /* Try RSA keys first */ + data = parse_old_openssl_key_file (contents, key_type, &cipher, &iv, error); if (!data) { g_clear_error (error); /* DSA next */ key_type = NM_CRYPTO_KEY_TYPE_DSA; - data = parse_key_file (file, key_type, &data_len, &cipher, &iv, error); - if (!data) + data = parse_old_openssl_key_file (contents, key_type, &cipher, &iv, error); + if (!data) { + g_clear_error (error); + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, + _("Unable to determine private key type.")); goto out; + } } decrypted = decrypt_key (cipher, key_type, data, - data_len, iv, password, &decrypted_len, @@ -516,6 +533,7 @@ crypto_get_private_key (const char *file, g_byte_array_append (array, (const guint8 *) decrypted, decrypted_len); *out_key_type = key_type; + *out_file_type = NM_CRYPTO_FILE_FORMAT_RAW_KEY; out: if (decrypted) { @@ -523,27 +541,96 @@ out: memset (decrypted, 0, decrypted_len); g_free (decrypted); } - g_free (data); + if (data) + g_byte_array_free (data, TRUE); g_free (cipher); g_free (iv); return array; } GByteArray * +crypto_get_private_key (const char *file, + const char *password, + NMCryptoKeyType *out_key_type, + NMCryptoFileFormat *out_file_type, + GError **error) +{ + GByteArray *contents; + GByteArray *key = NULL; + + contents = file_to_g_byte_array (file, TRUE, error); + if (contents) { + key = crypto_get_private_key_data (contents, password, out_key_type, out_file_type, error); + g_byte_array_free (contents, TRUE); + } + return key; +} + +GByteArray * crypto_load_and_verify_certificate (const char *file, + NMCryptoFileFormat *out_file_format, GError **error) { GByteArray *array; - array = file_to_g_byte_array (file, error); + g_return_val_if_fail (file != NULL, NULL); + g_return_val_if_fail (out_file_format != NULL, NULL); + g_return_val_if_fail (*out_file_format == NM_CRYPTO_FILE_FORMAT_UNKNOWN, NULL); + + array = file_to_g_byte_array (file, FALSE, error); if (!array) return NULL; - if (!crypto_verify_cert (array->data, array->len, error)) { - g_byte_array_free (array, TRUE); - array = NULL; + *out_file_format = crypto_verify_cert (array->data, array->len, error); + if (*out_file_format == NM_CRYPTO_FILE_FORMAT_UNKNOWN) { + /* Try PKCS#12 */ + if (crypto_is_pkcs12_data (array)) { + *out_file_format = NM_CRYPTO_FILE_FORMAT_PKCS12; + g_clear_error (error); + } else { + g_byte_array_free (array, TRUE); + array = NULL; + } } return array; } +gboolean +crypto_is_pkcs12_data (const GByteArray *data) +{ + GError *error = NULL; + gboolean success; + + g_return_val_if_fail (data != NULL, FALSE); + + success = crypto_verify_pkcs12 (data, NULL, &error); + if (success) + return TRUE; + + /* If the error was just a decryption error, then it's pkcs#12 */ + if (error) { + if (g_error_matches (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED)) + success = TRUE; + g_error_free (error); + } + + return success; +} + +gboolean +crypto_is_pkcs12_file (const char *file) +{ + GByteArray *contents; + gboolean success = FALSE; + + g_return_val_if_fail (file != NULL, FALSE); + + contents = file_to_g_byte_array (file, TRUE, NULL); + if (contents) { + success = crypto_is_pkcs12_data (contents); + g_byte_array_free (contents, TRUE); + } + return success; +} + diff --git a/libnm-util/crypto.h b/libnm-util/crypto.h index 7cfd25c229..bb66c7757e 100644 --- a/libnm-util/crypto.h +++ b/libnm-util/crypto.h @@ -31,7 +31,7 @@ enum { NM_CRYPTO_ERR_NONE = 0, NM_CRYPTO_ERR_INIT_FAILED, NM_CRYPTO_ERR_CANT_READ_FILE, - NM_CRYPTO_ERR_PEM_FORMAT_INVALID, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, NM_CRYPTO_ERR_CERT_FORMAT_INVALID, NM_CRYPTO_ERR_DECODE_FAILED, NM_CRYPTO_ERR_OUT_OF_MEMORY, @@ -43,14 +43,22 @@ enum { NM_CRYPTO_ERR_CIPHER_SET_KEY_FAILED, NM_CRYPTO_ERR_CIPHER_SET_IV_FAILED, NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, + NM_CRYPTO_ERR_INVALID_PASSWORD, }; -enum { +typedef enum { NM_CRYPTO_KEY_TYPE_UNKNOWN = 0, NM_CRYPTO_KEY_TYPE_RSA, NM_CRYPTO_KEY_TYPE_DSA, -}; + NM_CRYPTO_KEY_TYPE_ENCRYPTED +} NMCryptoKeyType; +typedef enum { + NM_CRYPTO_FILE_FORMAT_UNKNOWN = 0, + NM_CRYPTO_FILE_FORMAT_X509, + NM_CRYPTO_FILE_FORMAT_RAW_KEY, + NM_CRYPTO_FILE_FORMAT_PKCS12 +} NMCryptoFileFormat; #define NM_CRYPTO_ERROR _nm_crypto_error_quark () GQuark _nm_crypto_error_quark (void); @@ -59,14 +67,27 @@ gboolean crypto_init (GError **error); void crypto_deinit (void); +GByteArray * crypto_get_private_key_data (GByteArray *contents, + const char *password, + NMCryptoKeyType *out_key_type, + NMCryptoFileFormat *out_file_format, + GError **error); + GByteArray * crypto_get_private_key (const char *file, const char *password, - guint32 *out_key_type, + NMCryptoKeyType *out_key_type, + NMCryptoFileFormat *out_file_format, GError **error); GByteArray * crypto_load_and_verify_certificate (const char *file, + NMCryptoFileFormat *out_file_format, GError **error); +gboolean crypto_is_pkcs12_file (const char *file); + +gboolean crypto_is_pkcs12_data (const GByteArray *data); + + /* Internal utils API bits for crypto providers */ gboolean crypto_md5_hash (const char *salt, @@ -79,8 +100,7 @@ gboolean crypto_md5_hash (const char *salt, char * crypto_decrypt (const char *cipher, int key_type, - const char *data, - gsize data_len, + GByteArray *data, const char *iv, const gsize iv_len, const char *key, @@ -88,8 +108,11 @@ char * crypto_decrypt (const char *cipher, gsize *out_len, GError **error); -gboolean crypto_verify_cert (const unsigned char *data, - gsize len, - GError **error); +NMCryptoFileFormat crypto_verify_cert (const unsigned char *data, + gsize len, + GError **error); +gboolean crypto_verify_pkcs12 (const GByteArray *data, + const char *password, + GError **error); diff --git a/libnm-util/crypto_gnutls.c b/libnm-util/crypto_gnutls.c index 9613cbad9a..f3ed4f0b83 100644 --- a/libnm-util/crypto_gnutls.c +++ b/libnm-util/crypto_gnutls.c @@ -26,6 +26,7 @@ #include <gcrypt.h> #include <gnutls/gnutls.h> #include <gnutls/x509.h> +#include <gnutls/pkcs12.h> #include "crypto.h" @@ -117,8 +118,7 @@ crypto_md5_hash (const char *salt, char * crypto_decrypt (const char *cipher, int key_type, - const char *data, - gsize data_len, + GByteArray *data, const char *iv, const gsize iv_len, const char *key, @@ -128,10 +128,10 @@ crypto_decrypt (const char *cipher, { gcry_cipher_hd_t ctx; gcry_error_t err; - int cipher_mech; + int cipher_mech, i; char *output = NULL; gboolean success = FALSE; - gsize len; + gsize pad_len; if (!strcmp (cipher, CIPHER_DES_EDE3_CBC)) cipher_mech = GCRY_CIPHER_3DES; @@ -145,7 +145,7 @@ crypto_decrypt (const char *cipher, return NULL; } - output = g_malloc0 (data_len + 1); + output = g_malloc0 (data->len + 1); if (!output) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_OUT_OF_MEMORY, @@ -180,7 +180,7 @@ crypto_decrypt (const char *cipher, goto out; } - err = gcry_cipher_decrypt (ctx, output, data_len, data, data_len); + err = gcry_cipher_decrypt (ctx, output, data->len, data->data, data->len); if (err) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, @@ -188,11 +188,21 @@ crypto_decrypt (const char *cipher, gcry_strsource (err), gcry_strerror (err)); goto out; } - len = data_len - output[data_len - 1]; - if (len > data_len) - goto out; + pad_len = output[data->len - 1]; + + /* Validate tail padding; last byte is the padding size, and all pad bytes + * should contain the padding size. + */ + for (i = 1; i <= pad_len; ++i) { + if (output[data->len - i] != pad_len) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, + _("Failed to decrypt the private key.")); + goto out; + } + } - *out_len = len; + *out_len = data->len - pad_len; output[*out_len] = '\0'; success = TRUE; @@ -200,7 +210,7 @@ out: if (!success) { if (output) { /* Don't expose key material */ - memset (output, 0, data_len); + memset (output, 0, data->len); g_free (output); output = NULL; } @@ -209,37 +219,96 @@ out: return output; } -gboolean +NMCryptoFileFormat crypto_verify_cert (const unsigned char *data, gsize len, GError **error) { - gnutls_x509_crt_t crt; + gnutls_x509_crt_t der; gnutls_datum dt; int err; - err = gnutls_x509_crt_init (&crt); + err = gnutls_x509_crt_init (&der); if (err < 0) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_CERT_FORMAT_INVALID, _("Error initializing certificate data: %s"), gnutls_strerror (err)); - return FALSE; + return NM_CRYPTO_FILE_FORMAT_UNKNOWN; } + /* Try DER first */ dt.data = (unsigned char *) data; dt.size = len; + err = gnutls_x509_crt_import (der, &dt, GNUTLS_X509_FMT_DER); + if (err == GNUTLS_E_SUCCESS) { + gnutls_x509_crt_deinit (der); + return NM_CRYPTO_FILE_FORMAT_X509; + } + + /* And PEM next */ + err = gnutls_x509_crt_import (der, &dt, GNUTLS_X509_FMT_PEM); + gnutls_x509_crt_deinit (der); + if (err == GNUTLS_E_SUCCESS) + return NM_CRYPTO_FILE_FORMAT_X509; + + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CERT_FORMAT_INVALID, + _("Couldn't decode certificate: %s"), + gnutls_strerror (err)); + return NM_CRYPTO_FILE_FORMAT_UNKNOWN; +} + +gboolean +crypto_verify_pkcs12 (const GByteArray *data, + const char *password, + GError **error) +{ + gnutls_pkcs12_t p12; + gnutls_datum dt; + gboolean success = FALSE; + int err; - err = gnutls_x509_crt_import (crt, &dt, GNUTLS_X509_FMT_DER); + g_return_val_if_fail (data != NULL, FALSE); + + dt.data = (unsigned char *) data->data; + dt.size = data->len; + + err = gnutls_pkcs12_init (&p12); if (err < 0) { g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_CERT_FORMAT_INVALID, - _("Couldn't decode certificate: %s"), + NM_CRYPTO_ERR_DECODE_FAILED, + _("Couldn't initialize PKCS#12 decoder: %s"), gnutls_strerror (err)); return FALSE; } - gnutls_x509_crt_deinit (crt); - return TRUE; + /* DER first */ + err = gnutls_pkcs12_import (p12, &dt, GNUTLS_X509_FMT_DER, 0); + if (err < 0) { + /* PEM next */ + err = gnutls_pkcs12_import (p12, &dt, GNUTLS_X509_FMT_PEM, 0); + if (err < 0) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, + _("Couldn't decode PKCS#12 file: %s"), + gnutls_strerror (err)); + goto out; + } + } + + err = gnutls_pkcs12_verify_mac (p12, password); + if (err == GNUTLS_E_SUCCESS) + success = TRUE; + else { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, + _("Couldn't verify PKCS#12 file: %s"), + gnutls_strerror (err)); + } + +out: + gnutls_pkcs12_deinit (p12); + return success; } diff --git a/libnm-util/crypto_nss.c b/libnm-util/crypto_nss.c index d5ad3d0213..284da556d5 100644 --- a/libnm-util/crypto_nss.c +++ b/libnm-util/crypto_nss.c @@ -21,6 +21,8 @@ * (C) Copyright 2007 - 2008 Red Hat, Inc. */ +#include "config.h" + #include <glib.h> #include <glib/gi18n.h> @@ -30,6 +32,9 @@ #include <pkcs11t.h> #include <cert.h> #include <prerror.h> +#include <p12.h> +#include <ciferfam.h> +#include <p12plcy.h> #include "crypto.h" @@ -54,6 +59,14 @@ crypto_init (GError **error) return FALSE; } + SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1); + SEC_PKCS12EnableCipher(PKCS12_RC4_128, 1); + SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1); + SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_128, 1); + SEC_PKCS12EnableCipher(PKCS12_DES_56, 1); + SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1); + SEC_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1); + initialized = TRUE; return TRUE; } @@ -125,8 +138,7 @@ crypto_md5_hash (const char *salt, char * crypto_decrypt (const char *cipher, int key_type, - const char *data, - gsize data_len, + GByteArray *data, const char *iv, const gsize iv_len, const char *key, @@ -159,7 +171,7 @@ crypto_decrypt (const char *cipher, return NULL; } - output = g_malloc0 (data_len + 1); + output = g_malloc0 (data->len + 1); if (!output) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_OUT_OF_MEMORY, @@ -206,9 +218,9 @@ crypto_decrypt (const char *cipher, s = PK11_CipherOp (ctx, (unsigned char *) output, &tmp1_len, - data_len, - (unsigned char *) data, - data_len); + data->len, + data->data, + data->len); if (s != SECSuccess) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, @@ -220,7 +232,7 @@ crypto_decrypt (const char *cipher, s = PK11_DigestFinal (ctx, (unsigned char *) (output + tmp1_len), &tmp2_len, - data_len - tmp1_len); + data->len - tmp1_len); if (s != SECSuccess) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, @@ -229,7 +241,7 @@ crypto_decrypt (const char *cipher, goto out; } len = tmp1_len + tmp2_len; - if (len > data_len) + if (len > data->len) goto out; *out_len = len; @@ -249,7 +261,7 @@ out: if (!success) { if (output) { /* Don't expose key material */ - memset (output, 0, data_len); + memset (output, 0, data->len); g_free (output); output = NULL; } @@ -257,23 +269,114 @@ out: return output; } -gboolean +NMCryptoFileFormat crypto_verify_cert (const unsigned char *data, gsize len, GError **error) { CERTCertificate *cert; + /* Try DER/PEM first */ cert = CERT_DecodeCertFromPackage ((char *) data, len); if (!cert) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_CERT_FORMAT_INVALID, _("Couldn't decode certificate: %d"), PORT_GetError()); - return FALSE; + return NM_CRYPTO_FILE_FORMAT_UNKNOWN; } - CERT_DestroyCertificate (cert); + CERT_DestroyCertificate (cert); + return NM_CRYPTO_FILE_FORMAT_X509; +} + +gboolean +crypto_verify_pkcs12 (const GByteArray *data, + const char *password, + GError **error) +{ + SEC_PKCS12DecoderContext *p12ctx = NULL; + SECItem pw = { 0 }; + PK11SlotInfo *slot = NULL; + SECStatus s; + char *ucs2_password; + glong ucs2_chars = 0; + guint16 *p; + + if (error) + g_return_val_if_fail (*error == NULL, FALSE); + + /* PKCS#12 passwords are apparently UCS2 BIG ENDIAN, and NSS doesn't do + * any conversions for us. + */ + if (password && strlen (password)) { + ucs2_password = (char *) g_utf8_to_utf16 (password, strlen (password), NULL, &ucs2_chars, NULL); + if (!ucs2_password || !ucs2_chars) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_INVALID_PASSWORD, + _("Couldn't convert password to UCS2: %d"), + PORT_GetError()); + return FALSE; + } + + ucs2_chars *= 2; /* convert # UCS2 characters -> bytes */ + pw.data = PORT_ZAlloc(ucs2_chars + 2); + memcpy (pw.data, ucs2_password, ucs2_chars); + pw.len = ucs2_chars + 2; /* include terminating NULL */ + + memset (ucs2_password, 0, ucs2_chars); + g_free (ucs2_password); + +#ifndef WORDS_BIGENDIAN + for (p = (guint16 *) pw.data; p < (guint16 *) (pw.data + pw.len); p++) + *p = GUINT16_SWAP_LE_BE (*p); +#endif + } else { + /* NULL password */ + pw.data = NULL; + pw.len = 0; + } + + slot = PK11_GetInternalKeySlot(); + p12ctx = SEC_PKCS12DecoderStart (&pw, slot, NULL, NULL, NULL, NULL, NULL, NULL); + if (!p12ctx) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_DECODE_FAILED, + _("Couldn't initialize PKCS#12 decoder: %d"), + PORT_GetError()); + goto error; + } + + s = SEC_PKCS12DecoderUpdate (p12ctx, data->data, data->len); + if (s != SECSuccess) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, + _("Couldn't decode PKCS#12 file: %d"), + PORT_GetError()); + goto error; + } + + s = SEC_PKCS12DecoderVerify (p12ctx); + if (s != SECSuccess) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, + _("Couldn't verify PKCS#12 file: %d"), + PORT_GetError()); + goto error; + } + + SEC_PKCS12DecoderFinish (p12ctx); + SECITEM_ZfreeItem (&pw, PR_FALSE); return TRUE; + +error: + if (p12ctx) + SEC_PKCS12DecoderFinish (p12ctx); + + if (slot) + PK11_FreeSlot(slot); + + SECITEM_ZfreeItem (&pw, PR_FALSE); + return FALSE; } diff --git a/libnm-util/libnm-util.ver b/libnm-util/libnm-util.ver index 8a122024fa..da1c0a8011 100644 --- a/libnm-util/libnm-util.ver +++ b/libnm-util/libnm-util.ver @@ -52,9 +52,13 @@ global: nm_setting_802_1x_set_phase2_client_cert_from_file; nm_setting_802_1x_get_phase2_private_key; nm_setting_802_1x_set_phase2_private_key_from_file; + nm_setting_802_1x_get_phase2_private_key_password; + nm_setting_802_1x_get_phase2_private_key_type; nm_setting_802_1x_get_pin; nm_setting_802_1x_get_private_key; nm_setting_802_1x_set_private_key_from_file; + nm_setting_802_1x_get_private_key_password; + nm_setting_802_1x_get_private_key_type; nm_setting_802_1x_get_psk; nm_setting_802_1x_get_type; nm_setting_802_1x_new; diff --git a/libnm-util/nm-setting-8021x.c b/libnm-util/nm-setting-8021x.c index c22981d743..7f3ce6cae7 100644 --- a/libnm-util/nm-setting-8021x.c +++ b/libnm-util/nm-setting-8021x.c @@ -89,7 +89,9 @@ typedef struct { char *pin; char *psk; GByteArray *private_key; + char *private_key_password; GByteArray *phase2_private_key; + char *phase2_private_key_password; } NMSetting8021xPrivate; enum { @@ -110,7 +112,9 @@ enum { PROP_PHASE2_CLIENT_CERT, PROP_PASSWORD, PROP_PRIVATE_KEY, + PROP_PRIVATE_KEY_PASSWORD, PROP_PHASE2_PRIVATE_KEY, + PROP_PHASE2_PRIVATE_KEY_PASSWORD, PROP_PIN, PROP_PSK, @@ -226,18 +230,39 @@ nm_setting_802_1x_get_ca_path (NMSetting8021x *setting) gboolean nm_setting_802_1x_set_ca_cert_from_file (NMSetting8021x *self, const char *filename, + NMSetting8021xCKType *out_ck_type, GError **err) { NMSetting8021xPrivate *priv; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; g_return_val_if_fail (NM_IS_SETTING_802_1X (self), FALSE); g_return_val_if_fail (filename != NULL, FALSE); + if (out_ck_type) + g_return_val_if_fail (*out_ck_type == NM_SETTING_802_1X_CK_TYPE_UNKNOWN, FALSE); priv = NM_SETTING_802_1X_GET_PRIVATE (self); if (priv->ca_cert) g_byte_array_free (priv->ca_cert, TRUE); - priv->ca_cert = crypto_load_and_verify_certificate (filename, err); + priv->ca_cert = crypto_load_and_verify_certificate (filename, &format, err); + if (priv->ca_cert) { + /* wpa_supplicant can only use raw x509 CA certs */ + switch (format) { + case NM_CRYPTO_FILE_FORMAT_X509: + if (out_ck_type) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_X509; + break; + default: + g_byte_array_free (priv->ca_cert, TRUE); + priv->ca_cert = NULL; + g_set_error (err, + NM_SETTING_802_1X_ERROR, + NM_SETTING_802_1X_ERROR_INVALID_PROPERTY, + NM_SETTING_802_1X_CA_CERT); + break; + } + } return priv->ca_cert != NULL; } @@ -253,18 +278,42 @@ nm_setting_802_1x_get_client_cert (NMSetting8021x *setting) gboolean nm_setting_802_1x_set_client_cert_from_file (NMSetting8021x *self, const char *filename, + NMSetting8021xCKType *out_ck_type, GError **err) { NMSetting8021xPrivate *priv; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; g_return_val_if_fail (NM_IS_SETTING_802_1X (self), FALSE); g_return_val_if_fail (filename != NULL, FALSE); + if (out_ck_type) + g_return_val_if_fail (*out_ck_type == NM_SETTING_802_1X_CK_TYPE_UNKNOWN, FALSE); priv = NM_SETTING_802_1X_GET_PRIVATE (self); if (priv->client_cert) g_byte_array_free (priv->client_cert, TRUE); - priv->client_cert = crypto_load_and_verify_certificate (filename, err); + priv->client_cert = crypto_load_and_verify_certificate (filename, &format, err); + if (priv->client_cert) { + switch (format) { + case NM_CRYPTO_FILE_FORMAT_X509: + if (out_ck_type) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_X509; + break; + case NM_CRYPTO_FILE_FORMAT_PKCS12: + if (out_ck_type) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_PKCS12; + break; + default: + g_byte_array_free (priv->client_cert, TRUE); + priv->client_cert = NULL; + g_set_error (err, + NM_SETTING_802_1X_ERROR, + NM_SETTING_802_1X_ERROR_INVALID_PROPERTY, + NM_SETTING_802_1X_CLIENT_CERT); + break; + } + } return priv->client_cert != NULL; } @@ -328,21 +377,41 @@ nm_setting_802_1x_get_phase2_ca_path (NMSetting8021x *setting) gboolean nm_setting_802_1x_set_phase2_ca_cert_from_file (NMSetting8021x *self, const char *filename, + NMSetting8021xCKType *out_ck_type, GError **err) { NMSetting8021xPrivate *priv; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; g_return_val_if_fail (NM_IS_SETTING_802_1X (self), FALSE); g_return_val_if_fail (filename != NULL, FALSE); + if (out_ck_type) + g_return_val_if_fail (*out_ck_type == NM_SETTING_802_1X_CK_TYPE_UNKNOWN, FALSE); priv = NM_SETTING_802_1X_GET_PRIVATE (self); if (priv->phase2_ca_cert) g_byte_array_free (priv->phase2_ca_cert, TRUE); - priv->phase2_ca_cert = crypto_load_and_verify_certificate (filename, err); + priv->phase2_ca_cert = crypto_load_and_verify_certificate (filename, &format, err); + if (priv->phase2_ca_cert) { + /* wpa_supplicant can only use X509 CA certs */ + switch (format) { + case NM_CRYPTO_FILE_FORMAT_X509: + if (out_ck_type) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_X509; + break; + default: + g_byte_array_free (priv->phase2_ca_cert, TRUE); + priv->phase2_ca_cert = NULL; + g_set_error (err, + NM_SETTING_802_1X_ERROR, + NM_SETTING_802_1X_ERROR_INVALID_PROPERTY, + NM_SETTING_802_1X_PHASE2_CA_CERT); + break; + } + } return priv->phase2_ca_cert != NULL; - } const GByteArray * @@ -356,18 +425,43 @@ nm_setting_802_1x_get_phase2_client_cert (NMSetting8021x *setting) gboolean nm_setting_802_1x_set_phase2_client_cert_from_file (NMSetting8021x *self, const char *filename, + NMSetting8021xCKType *out_ck_type, GError **err) { NMSetting8021xPrivate *priv; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; g_return_val_if_fail (NM_IS_SETTING_802_1X (self), FALSE); g_return_val_if_fail (filename != NULL, FALSE); + if (out_ck_type) + g_return_val_if_fail (*out_ck_type == NM_SETTING_802_1X_CK_TYPE_UNKNOWN, FALSE); priv = NM_SETTING_802_1X_GET_PRIVATE (self); if (priv->phase2_client_cert) g_byte_array_free (priv->phase2_client_cert, TRUE); - priv->phase2_client_cert = crypto_load_and_verify_certificate (filename, err); + priv->phase2_client_cert = crypto_load_and_verify_certificate (filename, &format, err); + if (priv->phase2_client_cert) { + /* Only X509 client certs should be used; not pkcs#12 */ + switch (format) { + case NM_CRYPTO_FILE_FORMAT_X509: + if (out_ck_type) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_X509; + break; + case NM_CRYPTO_FILE_FORMAT_PKCS12: + if (out_ck_type) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_PKCS12; + break; + default: + g_byte_array_free (priv->phase2_client_cert, TRUE); + priv->phase2_client_cert = NULL; + g_set_error (err, + NM_SETTING_802_1X_ERROR, + NM_SETTING_802_1X_ERROR_INVALID_PROPERTY, + NM_SETTING_802_1X_CLIENT_CERT); + break; + } + } return priv->phase2_client_cert != NULL; } @@ -404,18 +498,29 @@ nm_setting_802_1x_get_private_key (NMSetting8021x *setting) return NM_SETTING_802_1X_GET_PRIVATE (setting)->private_key; } +const char * +nm_setting_802_1x_get_private_key_password (NMSetting8021x *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE (setting)->private_key_password; +} + gboolean nm_setting_802_1x_set_private_key_from_file (NMSetting8021x *self, const char *filename, const char *password, + NMSetting8021xCKType *out_ck_type, GError **err) { NMSetting8021xPrivate *priv; - guint32 ignore; + NMCryptoKeyType ignore = NM_CRYPTO_KEY_TYPE_UNKNOWN; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; g_return_val_if_fail (NM_IS_SETTING_802_1X (self), FALSE); g_return_val_if_fail (filename != NULL, FALSE); - g_return_val_if_fail (password != NULL, FALSE); + if (out_ck_type) + g_return_val_if_fail (*out_ck_type == NM_SETTING_802_1X_CK_TYPE_UNKNOWN, FALSE); priv = NM_SETTING_802_1X_GET_PRIVATE (self); if (priv->private_key) { @@ -424,11 +529,61 @@ nm_setting_802_1x_set_private_key_from_file (NMSetting8021x *self, g_byte_array_free (priv->private_key, TRUE); } - priv->private_key = crypto_get_private_key (filename, password, &ignore, err); + g_free (priv->private_key_password); + priv->private_key_password = NULL; + + priv->private_key = crypto_get_private_key (filename, password, &ignore, &format, err); + if (priv->private_key) { + switch (format) { + case NM_CRYPTO_FILE_FORMAT_RAW_KEY: + if (out_ck_type) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_RAW_KEY; + break; + case NM_CRYPTO_FILE_FORMAT_PKCS12: + // FIXME: use secure memory + priv->private_key_password = g_strdup (password); + if (out_ck_type) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_PKCS12; + break; + default: + g_assert_not_reached (); + break; + } + + /* As required by NM, set the client-cert property to the same PKCS#12 data */ + if (priv->client_cert) + g_byte_array_free (priv->client_cert, TRUE); + + priv->client_cert = g_byte_array_sized_new (priv->private_key->len); + g_byte_array_append (priv->client_cert, priv->private_key->data, priv->private_key->len); + } else { + /* As a special case for private keys, even if the decrypt fails, + * return the key's file type. + */ + if (out_ck_type && crypto_is_pkcs12_file (filename)) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_PKCS12; + } return priv->private_key != NULL; } +NMSetting8021xCKType +nm_setting_802_1x_get_private_key_type (NMSetting8021x *setting) +{ + NMSetting8021xPrivate *priv; + + g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_TYPE_UNKNOWN); + priv = NM_SETTING_802_1X_GET_PRIVATE (setting); + + if (!priv->private_key) + return NM_SETTING_802_1X_CK_TYPE_UNKNOWN; + + if (crypto_is_pkcs12_data (priv->private_key)) + return NM_SETTING_802_1X_CK_TYPE_PKCS12; + + return NM_SETTING_802_1X_CK_TYPE_X509; +} + const GByteArray * nm_setting_802_1x_get_phase2_private_key (NMSetting8021x *setting) { @@ -437,18 +592,29 @@ nm_setting_802_1x_get_phase2_private_key (NMSetting8021x *setting) return NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_private_key; } +const char * +nm_setting_802_1x_get_phase2_private_key_password (NMSetting8021x *setting) +{ + g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_private_key_password; +} + gboolean nm_setting_802_1x_set_phase2_private_key_from_file (NMSetting8021x *self, const char *filename, const char *password, + NMSetting8021xCKType *out_ck_type, GError **err) { NMSetting8021xPrivate *priv; - guint32 ignore; + NMCryptoKeyType ignore = NM_CRYPTO_KEY_TYPE_UNKNOWN; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; g_return_val_if_fail (NM_IS_SETTING_802_1X (self), FALSE); g_return_val_if_fail (filename != NULL, FALSE); - g_return_val_if_fail (password != NULL, FALSE); + if (out_ck_type) + g_return_val_if_fail (*out_ck_type == NM_SETTING_802_1X_CK_TYPE_UNKNOWN, FALSE); priv = NM_SETTING_802_1X_GET_PRIVATE (self); if (priv->phase2_private_key) { @@ -457,11 +623,61 @@ nm_setting_802_1x_set_phase2_private_key_from_file (NMSetting8021x *self, g_byte_array_free (priv->phase2_private_key, TRUE); } - priv->phase2_private_key = crypto_get_private_key (filename, password, &ignore, err); + g_free (priv->phase2_private_key_password); + priv->phase2_private_key_password = NULL; + + priv->phase2_private_key = crypto_get_private_key (filename, password, &ignore, &format, err); + if (priv->phase2_private_key) { + switch (format) { + case NM_CRYPTO_FILE_FORMAT_RAW_KEY: + if (out_ck_type) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_RAW_KEY; + break; + case NM_CRYPTO_FILE_FORMAT_PKCS12: + // FIXME: use secure memory + priv->phase2_private_key_password = g_strdup (password); + if (out_ck_type) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_PKCS12; + break; + default: + g_assert_not_reached (); + break; + } + + /* As required by NM, set the client-cert property to the same PKCS#12 data */ + if (priv->phase2_client_cert) + g_byte_array_free (priv->phase2_client_cert, TRUE); + + priv->phase2_client_cert = g_byte_array_sized_new (priv->phase2_private_key->len); + g_byte_array_append (priv->phase2_client_cert, priv->phase2_private_key->data, priv->phase2_private_key->len); + } else { + /* As a special case for private keys, even if the decrypt fails, + * return the key's file type. + */ + if (out_ck_type && crypto_is_pkcs12_file (filename)) + *out_ck_type = NM_SETTING_802_1X_CK_TYPE_PKCS12; + } return priv->phase2_private_key != NULL; } +NMSetting8021xCKType +nm_setting_802_1x_get_phase2_private_key_type (NMSetting8021x *setting) +{ + NMSetting8021xPrivate *priv; + + g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_TYPE_UNKNOWN); + priv = NM_SETTING_802_1X_GET_PRIVATE (setting); + + if (!priv->phase2_private_key) + return NM_SETTING_802_1X_CK_TYPE_UNKNOWN; + + if (crypto_is_pkcs12_data (priv->phase2_private_key)) + return NM_SETTING_802_1X_CK_TYPE_PKCS12; + + return NM_SETTING_802_1X_CK_TYPE_X509; +} + static void need_secrets_password (NMSetting8021x *self, GPtrArray *secrets, @@ -484,6 +700,30 @@ need_secrets_sim (NMSetting8021x *self, g_ptr_array_add (secrets, NM_SETTING_802_1X_PIN); } +static gboolean +need_private_key_password (GByteArray *key, const char *password) +{ + GError *error = NULL; + gboolean needed = TRUE; + + /* See if a private key password is needed, which basically is whether + * or not the private key is a PKCS#12 file or not, since PKCS#1 files + * are decrypted by the settings service. + */ + if (!crypto_is_pkcs12_data (key)) + return FALSE; + + if (crypto_verify_pkcs12 (key, password, &error)) + return FALSE; /* pkcs#12 validation successful */ + + /* If the error was a decryption error then a password is needed */ + if (!error || g_error_matches (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED)) + needed = TRUE; + + g_clear_error (&error); + return needed; +} + static void need_secrets_tls (NMSetting8021x *self, GPtrArray *secrets, @@ -492,13 +732,15 @@ need_secrets_tls (NMSetting8021x *self, NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE (self); if (phase2) { - if ( priv->phase2_client_cert - && (!priv->phase2_private_key || !priv->phase2_private_key->len)) + if (!priv->phase2_private_key || !priv->phase2_private_key->len) g_ptr_array_add (secrets, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY); + else if (need_private_key_password (priv->phase2_private_key, priv->phase2_private_key_password)) + g_ptr_array_add (secrets, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD); } else { - if (priv->client_cert - && (!priv->private_key || !priv->private_key->len)) + if (!priv->private_key || !priv->private_key->len) g_ptr_array_add (secrets, NM_SETTING_802_1X_PRIVATE_KEY); + else if (need_private_key_password (priv->private_key, priv->private_key_password)) + g_ptr_array_add (secrets, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD); } } @@ -521,6 +763,27 @@ verify_tls (NMSetting8021x *self, gboolean phase2, GError **error) NM_SETTING_802_1X_PHASE2_CLIENT_CERT); return FALSE; } + + /* If the private key is PKCS#12, check that it matches the client cert */ + if (priv->phase2_private_key && crypto_is_pkcs12_data (priv->phase2_private_key)) { + if (priv->phase2_private_key->len != priv->phase2_client_cert->len) { + g_set_error (error, + NM_SETTING_802_1X_ERROR, + NM_SETTING_802_1X_ERROR_INVALID_PROPERTY, + NM_SETTING_802_1X_PHASE2_CLIENT_CERT); + return FALSE; + } + + if (memcmp (priv->phase2_private_key->data, + priv->phase2_client_cert->data, + priv->phase2_private_key->len)) { + g_set_error (error, + NM_SETTING_802_1X_ERROR, + NM_SETTING_802_1X_ERROR_INVALID_PROPERTY, + NM_SETTING_802_1X_PHASE2_CLIENT_CERT); + return FALSE; + } + } } else { if (!priv->client_cert) { g_set_error (error, @@ -535,6 +798,27 @@ verify_tls (NMSetting8021x *self, gboolean phase2, GError **error) NM_SETTING_802_1X_CLIENT_CERT); return FALSE; } + + /* If the private key is PKCS#12, check that it matches the client cert */ + if (priv->private_key && crypto_is_pkcs12_data (priv->private_key)) { + if (priv->private_key->len != priv->client_cert->len) { + g_set_error (error, + NM_SETTING_802_1X_ERROR, + NM_SETTING_802_1X_ERROR_INVALID_PROPERTY, + NM_SETTING_802_1X_CLIENT_CERT); + return FALSE; + } + + if (memcmp (priv->private_key->data, + priv->client_cert->data, + priv->private_key->len)) { + g_set_error (error, + NM_SETTING_802_1X_ERROR, + NM_SETTING_802_1X_ERROR_INVALID_PROPERTY, + NM_SETTING_802_1X_CLIENT_CERT); + return FALSE; + } + } } return TRUE; @@ -856,12 +1140,14 @@ finalize (GObject *object) g_byte_array_free (priv->client_cert, TRUE); if (priv->private_key) g_byte_array_free (priv->private_key, TRUE); + g_free (priv->private_key_password); if (priv->phase2_ca_cert) g_byte_array_free (priv->phase2_ca_cert, TRUE); if (priv->phase2_client_cert) g_byte_array_free (priv->phase2_client_cert, TRUE); if (priv->phase2_private_key) g_byte_array_free (priv->phase2_private_key, TRUE); + g_free (priv->phase2_private_key_password); G_OBJECT_CLASS (nm_setting_802_1x_parent_class)->finalize (object); } @@ -943,11 +1229,19 @@ set_property (GObject *object, guint prop_id, g_byte_array_free (priv->private_key, TRUE); priv->private_key = g_value_dup_boxed (value); break; + case PROP_PRIVATE_KEY_PASSWORD: + g_free (priv->private_key_password); + priv->private_key_password = g_value_dup_string (value); + break; case PROP_PHASE2_PRIVATE_KEY: if (priv->phase2_private_key) g_byte_array_free (priv->phase2_private_key, TRUE); priv->phase2_private_key = g_value_dup_boxed (value); break; + case PROP_PHASE2_PRIVATE_KEY_PASSWORD: + g_free (priv->phase2_private_key_password); + priv->phase2_private_key_password = g_value_dup_string (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1010,9 +1304,15 @@ get_property (GObject *object, guint prop_id, case PROP_PRIVATE_KEY: g_value_set_boxed (value, priv->private_key); break; + case PROP_PRIVATE_KEY_PASSWORD: + g_value_set_string (value, priv->private_key_password); + break; case PROP_PHASE2_PRIVATE_KEY: g_value_set_boxed (value, priv->phase2_private_key); break; + case PROP_PHASE2_PRIVATE_KEY_PASSWORD: + g_value_set_string (value, priv->phase2_private_key_password); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1166,6 +1466,14 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *setting_class) G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_SECRET)); g_object_class_install_property + (object_class, PROP_PRIVATE_KEY_PASSWORD, + g_param_spec_string (NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD, + "Private key password", + "Private key password", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_SECRET)); + + g_object_class_install_property (object_class, PROP_PHASE2_PRIVATE_KEY, _nm_param_spec_specialized (NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, "Phase2 private key", @@ -1173,6 +1481,14 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *setting_class) DBUS_TYPE_G_UCHAR_ARRAY, G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_SECRET)); + g_object_class_install_property + (object_class, PROP_PHASE2_PRIVATE_KEY_PASSWORD, + g_param_spec_string (NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD, + "Phase2 private key password", + "Phase2 private key password", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SERIALIZE | NM_SETTING_PARAM_SECRET)); + /* Initialize crypto lbrary. */ if (!nm_utils_init (&error)) { g_warning ("Couldn't initilize nm-utils/crypto system: %d %s", diff --git a/libnm-util/nm-setting-8021x.h b/libnm-util/nm-setting-8021x.h index e3ea07e464..91d494d9b3 100644 --- a/libnm-util/nm-setting-8021x.h +++ b/libnm-util/nm-setting-8021x.h @@ -30,6 +30,13 @@ G_BEGIN_DECLS +typedef enum { + NM_SETTING_802_1X_CK_TYPE_UNKNOWN = 0, + NM_SETTING_802_1X_CK_TYPE_X509, + NM_SETTING_802_1X_CK_TYPE_RAW_KEY, + NM_SETTING_802_1X_CK_TYPE_PKCS12 +} NMSetting8021xCKType; + #define NM_TYPE_SETTING_802_1X (nm_setting_802_1x_get_type ()) #define NM_SETTING_802_1X(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTING_802_1X, NMSetting8021x)) #define NM_SETTING_802_1X_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTING_802_1X, NMSetting8021xClass)) @@ -69,7 +76,9 @@ GQuark nm_setting_802_1x_error_quark (void); #define NM_SETTING_802_1X_PHASE2_CLIENT_CERT "phase2-client-cert" #define NM_SETTING_802_1X_PASSWORD "password" #define NM_SETTING_802_1X_PRIVATE_KEY "private-key" +#define NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD "private-key-password" #define NM_SETTING_802_1X_PHASE2_PRIVATE_KEY "phase2-private-key" +#define NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD "phase2-private-key-password" #define NM_SETTING_802_1X_PIN "pin" #define NM_SETTING_802_1X_PSK "psk" @@ -99,11 +108,13 @@ const GByteArray *nm_setting_802_1x_get_ca_cert (NMSetting8 const char * nm_setting_802_1x_get_ca_path (NMSetting8021x *setting); gboolean nm_setting_802_1x_set_ca_cert_from_file (NMSetting8021x *setting, const char *filename, + NMSetting8021xCKType *out_ck_type, GError **err); const GByteArray *nm_setting_802_1x_get_client_cert (NMSetting8021x *setting); gboolean nm_setting_802_1x_set_client_cert_from_file (NMSetting8021x *setting, const char *filename, + NMSetting8021xCKType *out_ck_type, GError **err); const char * nm_setting_802_1x_get_phase1_peapver (NMSetting8021x *setting); @@ -120,11 +131,13 @@ const GByteArray *nm_setting_802_1x_get_phase2_ca_cert (NMSetting8 const char * nm_setting_802_1x_get_phase2_ca_path (NMSetting8021x *setting); gboolean nm_setting_802_1x_set_phase2_ca_cert_from_file (NMSetting8021x *setting, const char *filename, + NMSetting8021xCKType *out_ck_type, GError **err); const GByteArray *nm_setting_802_1x_get_phase2_client_cert (NMSetting8021x *setting); gboolean nm_setting_802_1x_set_phase2_client_cert_from_file (NMSetting8021x *setting, const char *filename, + NMSetting8021xCKType *out_ck_type, GError **err); const char * nm_setting_802_1x_get_password (NMSetting8021x *setting); @@ -133,17 +146,39 @@ const char * nm_setting_802_1x_get_pin (NMSetting8 const char * nm_setting_802_1x_get_psk (NMSetting8021x *setting); +/* PRIVATE KEY NOTE: when PKCS#12 private keys are used, the PKCS#12 data must + * be passed to NetworkManager as PKCS#12 (ie, shrouded). In this case, the + * private key password must also be passed to NetworkManager, and the + * appropriate "client-cert" (or "phase2-client-cert") property of the + * NMSetting8021x object must also contain the exact same PKCS#12 data that the + * private key will when NetworkManager requests secrets. + * + * When OpenSSL-derived "traditional" format (ie S/MIME style, not PKCS#8) RSA + * and DSA keys are used, they must passed to NetworkManager completely + * decrypted because the OpenSSL "traditional" format is non-standard and is not + * complete enough for all crypto libraries to use. Thus, for OpenSSL + * "traditional" format keys, the private key password is not passed to + * NetworkManager, and the appropriate "client-cert" (or "phase2-client-cert") + * property of the NMSetting8021x object must be a valid client certificate. + */ + const GByteArray *nm_setting_802_1x_get_private_key (NMSetting8021x *setting); +const char * nm_setting_802_1x_get_private_key_password (NMSetting8021x *setting); gboolean nm_setting_802_1x_set_private_key_from_file (NMSetting8021x *setting, const char *filename, const char *password, + NMSetting8021xCKType *out_ck_type, GError **err); +NMSetting8021xCKType nm_setting_802_1x_get_private_key_type (NMSetting8021x *setting); const GByteArray *nm_setting_802_1x_get_phase2_private_key (NMSetting8021x *setting); +const char * nm_setting_802_1x_get_phase2_private_key_password (NMSetting8021x *setting); gboolean nm_setting_802_1x_set_phase2_private_key_from_file (NMSetting8021x *setting, const char *filename, const char *password, + NMSetting8021xCKType *out_ck_type, GError **err); +NMSetting8021xCKType nm_setting_802_1x_get_phase2_private_key_type (NMSetting8021x *setting); G_END_DECLS diff --git a/libnm-util/test-crypto.c b/libnm-util/test-crypto.c index 1063b18e94..6134a0d2c9 100644 --- a/libnm-util/test-crypto.c +++ b/libnm-util/test-crypto.c @@ -103,7 +103,6 @@ usage (const char *prgname) int main (int argc, char **argv) { - guint32 key_type = 0; int mode = 0; const char *file; GError *error = NULL; @@ -140,26 +139,54 @@ int main (int argc, char **argv) if (mode == MODE_CERT) { GByteArray *array; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; - array = crypto_load_and_verify_certificate (file, &error); + array = crypto_load_and_verify_certificate (file, &format, &error); if (!array) { g_warning ("Couldn't read certificate file '%s': %d %s", file, error->code, error->message); goto out; } + + switch (format) { + case NM_CRYPTO_FILE_FORMAT_X509: + g_message ("Format: pkcs#1"); + break; + case NM_CRYPTO_FILE_FORMAT_PKCS12: + g_message ("Format: pkcs#12"); + break; + default: + g_message ("Format: unknown"); + break; + } + g_byte_array_free (array, TRUE); } else if (mode == MODE_KEY) { + NMCryptoKeyType key_type = NM_CRYPTO_KEY_TYPE_UNKNOWN; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; const char *password = argv[3]; GByteArray *array; - array = crypto_get_private_key (file, password, &key_type, &error); + array = crypto_get_private_key (file, password, &key_type, &format, &error); if (!array) { g_warning ("Couldn't read key file '%s': %d %s", file, error->code, error->message); goto out; } - dump_key_to_pem ((const char *) array->data, array->len, key_type); + switch (format) { + case NM_CRYPTO_FILE_FORMAT_RAW_KEY: + g_message ("Original format: pkcs#1\n"); + dump_key_to_pem ((const char *) array->data, array->len, key_type); + break; + case NM_CRYPTO_FILE_FORMAT_PKCS12: + g_message ("Original format: pkcs#12"); + break; + default: + g_message ("Original format: unknown"); + break; + } + g_byte_array_free (array, TRUE); } else { g_assert_not_reached (); diff --git a/src/supplicant-manager/nm-supplicant-config.c b/src/supplicant-manager/nm-supplicant-config.c index 97be23ef79..d087b2c227 100644 --- a/src/supplicant-manager/nm-supplicant-config.c +++ b/src/supplicant-manager/nm-supplicant-config.c @@ -561,10 +561,10 @@ nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self, gboolean wired) { NMSupplicantConfigPrivate *priv; - char * value; + char *value, *tmp; gboolean success; GString *phase1, *phase2; - char *tmp; + const GByteArray *array; g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE); g_return_val_if_fail (setting != NULL, FALSE); @@ -620,18 +620,41 @@ nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self, ADD_STRING_VAL (phase2->str, "phase2", FALSE, FALSE, FALSE); g_string_free (phase2, TRUE); - /* Private key passwords are never passed to wpa_supplicant because the - * user agent is responsible for decoding and decrypting the private key, - * and file paths are never passed to wpa_supplicant to ensure that - * the supplicant can be locked down and doesn't try to read stuff from - * all over the drive. - */ ADD_BLOB_VAL (nm_setting_802_1x_get_ca_cert (setting), "ca_cert", connection_uid); - ADD_BLOB_VAL (nm_setting_802_1x_get_client_cert (setting), "client_cert", connection_uid); - ADD_BLOB_VAL (nm_setting_802_1x_get_private_key (setting), "private_key", connection_uid); + + array = nm_setting_802_1x_get_private_key (setting); + if (array) { + ADD_BLOB_VAL (array, "private_key", connection_uid); + + switch (nm_setting_802_1x_get_private_key_type (setting)) { + case NM_SETTING_802_1X_CK_TYPE_PKCS12: + /* Only add the private key password for PKCS#12 keys */ + ADD_STRING_VAL (nm_setting_802_1x_get_private_key_password (setting), "private_key_passwd", FALSE, FALSE, TRUE); + break; + default: + /* Only add the client cert if the private key is not PKCS#12 */ + ADD_BLOB_VAL (nm_setting_802_1x_get_client_cert (setting), "client_cert", connection_uid); + break; + } + } + ADD_BLOB_VAL (nm_setting_802_1x_get_phase2_ca_cert (setting), "ca_cert2", connection_uid); - ADD_BLOB_VAL (nm_setting_802_1x_get_phase2_client_cert (setting), "client_cert2", connection_uid); - ADD_BLOB_VAL (nm_setting_802_1x_get_phase2_private_key (setting), "private_key2", connection_uid); + + array = nm_setting_802_1x_get_phase2_private_key (setting); + if (array) { + ADD_BLOB_VAL (array, "private_key2", connection_uid); + + switch (nm_setting_802_1x_get_phase2_private_key_type (setting)) { + case NM_SETTING_802_1X_CK_TYPE_PKCS12: + /* Only add the private key password for PKCS#12 keys */ + ADD_STRING_VAL (nm_setting_802_1x_get_phase2_private_key_password (setting), "private_key2_passwd", FALSE, FALSE, TRUE); + break; + default: + /* Only add the client cert if the private key is not PKCS#12 */ + ADD_BLOB_VAL (nm_setting_802_1x_get_phase2_client_cert (setting), "client_cert2", connection_uid); + break; + } + } ADD_STRING_VAL (nm_setting_802_1x_get_identity (setting), "identity", FALSE, FALSE, FALSE); ADD_STRING_VAL (nm_setting_802_1x_get_anonymous_identity (setting), "anonymous_identity", FALSE, FALSE, FALSE); diff --git a/src/supplicant-manager/nm-supplicant-settings-verify.c b/src/supplicant-manager/nm-supplicant-settings-verify.c index 25c14cc24a..b8bd9fc4c0 100644 --- a/src/supplicant-manager/nm-supplicant-settings-verify.c +++ b/src/supplicant-manager/nm-supplicant-settings-verify.c @@ -104,12 +104,14 @@ static const struct Opt opt_table[] = { { "ca_cert", TYPE_BYTES, 0, 65536, FALSE, NULL }, { "client_cert", TYPE_BYTES, 0, 65536, FALSE, NULL }, { "private_key", TYPE_BYTES, 0, 65536, FALSE, NULL }, + { "private_key_passwd", TYPE_BYTES, 0, 1024, FALSE, NULL }, { "phase1", TYPE_KEYWORD, 0, 0, TRUE, phase1_allowed }, { "phase2", TYPE_KEYWORD, 0, 0, TRUE, phase2_allowed }, { "anonymous_identity", TYPE_BYTES, 0, 0, FALSE, NULL }, { "ca_cert2", TYPE_BYTES, 0, 65536, FALSE, NULL }, { "client_cert2", TYPE_BYTES, 0, 65536, FALSE, NULL }, { "private_key2", TYPE_BYTES, 0, 65536, FALSE, NULL }, + { "private_key2_passwd",TYPE_BYTES, 0, 1024, FALSE, NULL }, { "pin", TYPE_BYTES, 0, 0, FALSE, NULL }, { "pcsc", TYPE_BYTES, 0, 0, FALSE, NULL }, { "nai", TYPE_BYTES, 0, 0, FALSE, NULL }, |