diff options
author | Dan Williams <dcbw@redhat.com> | 2011-03-02 12:00:47 -0600 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2011-03-02 12:00:47 -0600 |
commit | 28e6523b8d4eb031777dd0d3f5118bbfc8fa45a9 (patch) | |
tree | 17be4c968bfe8d2976dbe49cd0c513ffe551f705 /libnm-util/crypto.c | |
parent | 02f676c0010ccaa4d70099a32cac8dc4663612a9 (diff) | |
download | NetworkManager-28e6523b8d4eb031777dd0d3f5118bbfc8fa45a9.tar.gz |
libnm-util: rework certificate and private key handling
First, it was not easily possible to set a private key without
also providing a password. This used to be OK, but now with
secret flags it may be the case that when the connection is read,
there's no private key password. So functions that set the
private key must account for NULL passwords.
Unfortunately, the crytpo code did not handle this case well.
We need to be able to independently (a) verify that a file looks
like a certificate or private key and (b) that a given password
decrypts a private key. Previously the crypto code would fail
to verify the file when the password was NULL.
So this change fixes up the crytpo code for a more distinct
split between these two operations, such that if no password is
given, the file is still checked to ensure that it's a private
key or a certificate. If a password is given, the password is
checked against the private key file.
This commit also changes how private keys and certificates were
handled with the BLOB scheme. Previously only the first certificate
or first private key was included in the property data, while now
the entire file is encoded in the data. This is intended to fix
cases where multiple private keys or certificates are present in
a PEM file. It also allows clients to push certificate data to
NetworkManager for storage in system settings locations, which was
not as flexible before when only part of the certificate or key
was sent as the data.
Diffstat (limited to 'libnm-util/crypto.c')
-rw-r--r-- | libnm-util/crypto.c | 414 |
1 files changed, 232 insertions, 182 deletions
diff --git a/libnm-util/crypto.c b/libnm-util/crypto.c index 70872db414..e68b5c64a4 100644 --- a/libnm-util/crypto.c +++ b/libnm-util/crypto.c @@ -18,7 +18,7 @@ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * - * (C) Copyright 2007 - 2009 Red Hat, Inc. + * (C) Copyright 2007 - 2011 Red Hat, Inc. */ #include <glib.h> @@ -41,36 +41,43 @@ _nm_crypto_error_quark (void) } -static const char *pem_rsa_key_begin = "-----BEGIN RSA PRIVATE KEY-----"; -static const char *pem_rsa_key_end = "-----END RSA PRIVATE KEY-----"; +#define PEM_RSA_KEY_BEGIN "-----BEGIN RSA PRIVATE KEY-----" +#define PEM_RSA_KEY_END "-----END RSA PRIVATE KEY-----" -static const char *pem_dsa_key_begin = "-----BEGIN DSA PRIVATE KEY-----"; -static const char *pem_dsa_key_end = "-----END DSA PRIVATE KEY-----"; +#define PEM_DSA_KEY_BEGIN "-----BEGIN DSA PRIVATE KEY-----" +#define PEM_DSA_KEY_END "-----END DSA PRIVATE KEY-----" -static const char *pem_cert_begin = "-----BEGIN CERTIFICATE-----"; -static const char *pem_cert_end = "-----END CERTIFICATE-----"; +#define PEM_CERT_BEGIN "-----BEGIN CERTIFICATE-----" +#define PEM_CERT_END "-----END CERTIFICATE-----" -static const char * -find_tag (const char *tag, const char *buf, gsize len) +static gboolean +find_tag (const char *tag, + const GByteArray *array, + gsize start_at, + gsize *out_pos) { gsize i, taglen; + gsize len = array->len - start_at; - taglen = strlen (tag); - if (len < taglen) - return NULL; + g_return_val_if_fail (out_pos != NULL, FALSE); - for (i = 0; i < len - taglen + 1; i++) { - if (memcmp (buf + i, tag, taglen) == 0) - return buf + i; + taglen = strlen (tag); + if (len >= taglen) { + for (i = 0; i < len - taglen + 1; i++) { + if (memcmp (array->data + start_at + i, tag, taglen) == 0) { + *out_pos = start_at + i; + return TRUE; + } + } } - return NULL; + return FALSE; } #define DEK_INFO_TAG "DEK-Info: " #define PROC_TYPE_TAG "Proc-Type: " static GByteArray * -parse_old_openssl_key_file (GByteArray *contents, +parse_old_openssl_key_file (const GByteArray *contents, int key_type, char **out_cipher, char **out_iv, @@ -79,8 +86,7 @@ parse_old_openssl_key_file (GByteArray *contents, GByteArray *bindata = NULL; char **lines = NULL; char **ln = NULL; - const char *pos; - const char *end; + gsize start = 0, end = 0; GString *str = NULL; int enc_tags = 0; char *iv = NULL; @@ -89,15 +95,16 @@ parse_old_openssl_key_file (GByteArray *contents, gsize tmp_len = 0; const char *start_tag; const char *end_tag; + guint8 save_end = 0; switch (key_type) { case NM_CRYPTO_KEY_TYPE_RSA: - start_tag = pem_rsa_key_begin; - end_tag = pem_rsa_key_end; + start_tag = PEM_RSA_KEY_BEGIN; + end_tag = PEM_RSA_KEY_END; break; case NM_CRYPTO_KEY_TYPE_DSA: - start_tag = pem_dsa_key_begin; - end_tag = pem_dsa_key_end; + start_tag = PEM_DSA_KEY_BEGIN; + end_tag = PEM_DSA_KEY_END; break; default: g_set_error (error, NM_CRYPTO_ERROR, @@ -108,23 +115,23 @@ parse_old_openssl_key_file (GByteArray *contents, return NULL; } - pos = find_tag (start_tag, (const char *) contents->data, contents->len); - if (!pos) + if (!find_tag (start_tag, contents, 0, &start)) goto parse_error; - pos += strlen (start_tag); - - end = find_tag (end_tag, pos, (const char *) contents->data + contents->len - pos); - if (end == NULL) { + start += strlen (start_tag); + if (!find_tag (end_tag, contents, start, &end)) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_FILE_FORMAT_INVALID, _("PEM key file had no end tag '%s'."), end_tag); goto parse_error; } - *((char *) end) = '\0'; - lines = g_strsplit (pos, "\n", 0); + save_end = contents->data[end]; + contents->data[end] = '\0'; + lines = g_strsplit ((const char *) (contents->data + start), "\n", 0); + contents->data[end] = save_end; + if (!lines || g_strv_length (lines) <= 1) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_FILE_FORMAT_INVALID, @@ -132,7 +139,7 @@ parse_old_openssl_key_file (GByteArray *contents, goto parse_error; } - str = g_string_new_len (NULL, end - pos); + str = g_string_new_len (NULL, end - start); if (!str) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_OUT_OF_MEMORY, @@ -242,64 +249,24 @@ parse_error: } static GByteArray * -file_to_g_byte_array (const char *filename, - gboolean privkey, - GError **error) +file_to_g_byte_array (const char *filename, GError **error) { - char *contents, *der = NULL; + char *contents; GByteArray *array = NULL; gsize length = 0; - const char *pos = NULL; - - if (!g_file_get_contents (filename, &contents, &length, error)) - return NULL; - - if (!privkey) - pos = find_tag (pem_cert_begin, contents, length); - - if (pos) { - const char *end; - - pos += strlen (pem_cert_begin); - end = find_tag (pem_cert_end, pos, contents + length - pos); - if (end == NULL) { - g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_FILE_FORMAT_INVALID, - _("PEM certificate '%s' had no end tag '%s'."), - filename, pem_cert_end); - goto done; - } - contents[end - contents - 1] = '\0'; - der = (char *) g_base64_decode (pos, &length); - if (der == NULL || !length) { + if (g_file_get_contents (filename, &contents, &length, error)) { + array = g_byte_array_sized_new (length); + if (array) { + g_byte_array_append (array, (guint8 *) contents, length); + g_assert (array->len == length); + } else { g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_DECODE_FAILED, - _("Failed to decode certificate.")); - goto done; + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Not enough memory to store certificate data.")); } + g_free (contents); } - - array = g_byte_array_sized_new (length); - if (!array) { - g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_OUT_OF_MEMORY, - _("Not enough memory to store certificate data.")); - goto done; - } - - g_byte_array_append (array, der ? (unsigned char *) der : (unsigned char *) contents, length); - if (array->len != length) { - g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_OUT_OF_MEMORY, - _("Not enough memory to store file data.")); - g_byte_array_free (array, TRUE); - array = NULL; - } - -done: - g_free (der); - g_free (contents); return array; } @@ -414,13 +381,12 @@ error: return NULL; } -static char * +static GByteArray * decrypt_key (const char *cipher, int key_type, GByteArray *data, const char *iv, const char *password, - gsize *out_len, GError **error) { char *bin_iv = NULL; @@ -428,6 +394,10 @@ decrypt_key (const char *cipher, char *key = NULL; gsize key_len = 0; char *output = NULL; + gsize decrypted_len = 0; + GByteArray *decrypted = NULL; + + g_return_val_if_fail (password != NULL, NULL); bin_iv = convert_iv (iv, &bin_iv_len, error); if (!bin_iv) @@ -442,58 +412,45 @@ decrypt_key (const char *cipher, data, bin_iv, bin_iv_len, key, key_len, - out_len, + &decrypted_len, error); - if (!output) - goto out; - - if (*out_len == 0) { - g_free (output); - output = NULL; - goto out; + if (output && decrypted_len) { + decrypted = g_byte_array_sized_new (decrypted_len); + if (decrypted) + g_byte_array_append (decrypted, (guint8 *) output, decrypted_len); + else { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Not enough memory to store decrypted private key.")); + } } - + out: - if (key) { - /* Don't leak stale key material */ + /* Don't leak stale key material */ + if (key) memset (key, 0, key_len); - g_free (key); - } + g_free (output); + g_free (key); g_free (bin_iv); - return output; + + return decrypted; } GByteArray * -crypto_get_private_key_data (GByteArray *contents, - const char *password, - NMCryptoKeyType *out_key_type, - NMCryptoFileFormat *out_file_type, - GError **error) +crypto_decrypt_private_key_data (const GByteArray *contents, + const char *password, + NMCryptoKeyType *out_key_type, + GError **error) { - GByteArray *array = NULL; + GByteArray *decrypted = NULL; NMCryptoKeyType key_type = NM_CRYPTO_KEY_TYPE_RSA; GByteArray *data; char *iv = NULL; char *cipher = NULL; - char *decrypted = NULL; - gsize decrypted_len = 0; 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; - } + if (out_key_type) + g_return_val_if_fail (*out_key_type == NM_CRYPTO_KEY_TYPE_UNKNOWN, NULL); /* OpenSSL non-standard legacy PEM files */ @@ -510,91 +467,137 @@ crypto_get_private_key_data (GByteArray *contents, 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, - iv, - password, - &decrypted_len, - error); - if (!decrypted) - goto out; - - array = g_byte_array_sized_new (decrypted_len); - if (!array) { - g_set_error (error, NM_CRYPTO_ERROR, - NM_CRYPTO_ERR_OUT_OF_MEMORY, - _("Not enough memory to store decrypted private key.")); - goto out; + if (data) { + /* return the key type even if decryption failed */ + if (out_key_type) + *out_key_type = key_type; + + if (password) { + decrypted = decrypt_key (cipher, + key_type, + data, + iv, + password, + error); + } + g_byte_array_free (data, TRUE); } - 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) { - /* Don't expose key material */ - memset (decrypted, 0, decrypted_len); - g_free (decrypted); - } - if (data) - g_byte_array_free (data, TRUE); g_free (cipher); g_free (iv); - return array; + + return decrypted; } GByteArray * -crypto_get_private_key (const char *file, - const char *password, - NMCryptoKeyType *out_key_type, - NMCryptoFileFormat *out_file_type, - GError **error) +crypto_decrypt_private_key (const char *file, + const char *password, + NMCryptoKeyType *out_key_type, + GError **error) { GByteArray *contents; GByteArray *key = NULL; - contents = file_to_g_byte_array (file, TRUE, error); + contents = file_to_g_byte_array (file, error); if (contents) { - key = crypto_get_private_key_data (contents, password, out_key_type, out_file_type, error); + key = crypto_decrypt_private_key_data (contents, password, out_key_type, error); g_byte_array_free (contents, TRUE); } return key; } +static GByteArray * +extract_pem_cert_data (GByteArray *contents, GError **error) +{ + GByteArray *cert = NULL; + gsize start = 0, end = 0; + unsigned char *der = NULL; + guint8 save_end; + gsize length = 0; + + if (!find_tag (PEM_CERT_BEGIN, contents, 0, &start)) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, + _("PEM certificate had no start tag '%s'."), + PEM_CERT_BEGIN); + goto done; + } + + start += strlen (PEM_CERT_BEGIN); + if (!find_tag (PEM_CERT_END, contents, start, &end)) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_FILE_FORMAT_INVALID, + _("PEM certificate had no end tag '%s'."), + PEM_CERT_END); + goto done; + } + + /* g_base64_decode() wants a NULL-terminated string */ + save_end = contents->data[end]; + contents->data[end] = '\0'; + der = g_base64_decode ((const char *) (contents->data + start), &length); + contents->data[end] = save_end; + + if (der && length) { + cert = g_byte_array_sized_new (length); + if (cert) { + g_byte_array_append (cert, der, length); + g_assert (cert->len == length); + } else { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Not enough memory to store certificate data.")); + } + } else { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_DECODE_FAILED, + _("Failed to decode certificate.")); + } + +done: + g_free (der); + return cert; +} + GByteArray * crypto_load_and_verify_certificate (const char *file, NMCryptoFileFormat *out_file_format, GError **error) { - GByteArray *array; + GByteArray *array, *contents; 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) + contents = file_to_g_byte_array (file, error); + if (!contents) + return NULL; + + /* Check for PKCS#12 */ + if (crypto_is_pkcs12_data (contents)) { + *out_file_format = NM_CRYPTO_FILE_FORMAT_PKCS12; + return contents; + } + + array = extract_pem_cert_data (contents, error); + if (!array) { + g_byte_array_free (contents, TRUE); return 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; - } + g_byte_array_free (array, TRUE); + + if (*out_file_format != NM_CRYPTO_FILE_FORMAT_X509) { + g_byte_array_free (contents, TRUE); + contents = NULL; } - return array; + return contents; } gboolean @@ -606,16 +609,14 @@ crypto_is_pkcs12_data (const GByteArray *data) 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); + if (success == FALSE) { + /* 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; } @@ -627,7 +628,7 @@ crypto_is_pkcs12_file (const char *file, GError **error) g_return_val_if_fail (file != NULL, FALSE); - contents = file_to_g_byte_array (file, TRUE, error); + contents = file_to_g_byte_array (file, error); if (contents) { success = crypto_is_pkcs12_data (contents); g_byte_array_free (contents, TRUE); @@ -635,3 +636,52 @@ crypto_is_pkcs12_file (const char *file, GError **error) return success; } +/* Verifies that a private key can be read, and if a password is given, that + * the private key can be decrypted with that password. + */ +NMCryptoFileFormat +crypto_verify_private_key_data (const GByteArray *contents, + const char *password, + GError **error) +{ + GByteArray *tmp; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + NMCryptoKeyType ktype = NM_CRYPTO_KEY_TYPE_UNKNOWN; + + g_return_val_if_fail (contents != NULL, FALSE); + + /* Check for PKCS#12 first */ + if (crypto_is_pkcs12_data (contents)) { + if (!password || crypto_verify_pkcs12 (contents, password, error)) + format = NM_CRYPTO_FILE_FORMAT_PKCS12; + } else { + tmp = crypto_decrypt_private_key_data (contents, password, &ktype, error); + if (tmp) { + /* Don't leave decrypted key data around */ + memset (tmp->data, 0, tmp->len); + g_byte_array_free (tmp, TRUE); + format = NM_CRYPTO_FILE_FORMAT_RAW_KEY; + } else if (!password && (ktype != NM_CRYPTO_KEY_TYPE_UNKNOWN)) + format = NM_CRYPTO_FILE_FORMAT_RAW_KEY; + } + return format; +} + +NMCryptoFileFormat +crypto_verify_private_key (const char *filename, + const char *password, + GError **error) +{ + GByteArray *contents; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + + g_return_val_if_fail (filename != NULL, FALSE); + + contents = file_to_g_byte_array (filename, error); + if (contents) { + format = crypto_verify_private_key_data (contents, password, error); + g_byte_array_free (contents, TRUE); + } + return format; +} + |