summaryrefslogtreecommitdiff
path: root/libnm-util/crypto.c
diff options
context:
space:
mode:
authorDan Williams <dcbw@redhat.com>2011-03-02 12:00:47 -0600
committerDan Williams <dcbw@redhat.com>2011-03-02 12:00:47 -0600
commit28e6523b8d4eb031777dd0d3f5118bbfc8fa45a9 (patch)
tree17be4c968bfe8d2976dbe49cd0c513ffe551f705 /libnm-util/crypto.c
parent02f676c0010ccaa4d70099a32cac8dc4663612a9 (diff)
downloadNetworkManager-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.c414
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;
+}
+