diff --git a/libnm-util/crypto.c b/libnm-util/crypto.c index cce7144c28..02ec644cfe 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 - 2008 Red Hat, Inc. + * (C) Copyright 2007 - 2009 Red Hat, Inc. */ #include diff --git a/libnm-util/crypto.h b/libnm-util/crypto.h index f8c3ffd68c..38471cea63 100644 --- a/libnm-util/crypto.h +++ b/libnm-util/crypto.h @@ -44,6 +44,8 @@ enum { NM_CRYPTO_ERR_CIPHER_SET_IV_FAILED, NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, NM_CRYPTO_ERR_INVALID_PASSWORD, + NM_CRYPTO_ERR_CIPHER_ENCRYPT_FAILED, + NM_CRYPTO_ERR_RANDOMIZE_FAILED }; typedef enum { @@ -108,6 +110,17 @@ char * crypto_decrypt (const char *cipher, gsize *out_len, GError **error); +char * crypto_encrypt (const char *cipher, + const GByteArray *data, + const char *iv, + gsize iv_len, + const char *key, + gsize key_len, + gsize *out_len, + GError **error); + +gboolean crypto_randomize (void *buffer, gsize buffer_len, GError **error); + NMCryptoFileFormat crypto_verify_cert (const unsigned char *data, gsize len, GError **error); diff --git a/libnm-util/crypto_gnutls.c b/libnm-util/crypto_gnutls.c index f3ed4f0b83..edfc16af13 100644 --- a/libnm-util/crypto_gnutls.c +++ b/libnm-util/crypto_gnutls.c @@ -1,3 +1,4 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* NetworkManager Wireless Applet -- Display wireless access points and allow user control * * Dan Williams @@ -17,7 +18,7 @@ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * - * (C) Copyright 2007 - 2008 Red Hat, Inc. + * (C) Copyright 2007 - 2009 Red Hat, Inc. */ #include @@ -30,6 +31,8 @@ #include "crypto.h" +#define SALT_LEN 8 + static gboolean initialized = FALSE; gboolean @@ -76,7 +79,7 @@ crypto_md5_hash (const char *salt, char *p = buffer; if (salt) - g_return_val_if_fail (salt_len >= 8, FALSE); + g_return_val_if_fail (salt_len >= SALT_LEN, FALSE); g_return_val_if_fail (password != NULL, FALSE); g_return_val_if_fail (password_len > 0, FALSE); @@ -99,7 +102,7 @@ crypto_md5_hash (const char *salt, gcry_md_write (ctx, digest, digest_len); gcry_md_write (ctx, password, password_len); if (salt) - gcry_md_write (ctx, salt, 8); /* Only use 8 bytes of salt */ + gcry_md_write (ctx, salt, SALT_LEN); /* Only use 8 bytes of salt */ gcry_md_final (ctx); memcpy (digest, gcry_md_read (ctx, 0), digest_len); gcry_md_reset (ctx); @@ -131,13 +134,15 @@ crypto_decrypt (const char *cipher, int cipher_mech, i; char *output = NULL; gboolean success = FALSE; - gsize pad_len; + gsize pad_len, real_iv_len; - if (!strcmp (cipher, CIPHER_DES_EDE3_CBC)) + if (!strcmp (cipher, CIPHER_DES_EDE3_CBC)) { cipher_mech = GCRY_CIPHER_3DES; - else if (!strcmp (cipher, CIPHER_DES_CBC)) + real_iv_len = SALT_LEN; + } else if (!strcmp (cipher, CIPHER_DES_CBC)) { cipher_mech = GCRY_CIPHER_DES; - else { + real_iv_len = SALT_LEN; + } else { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_UNKNOWN_CIPHER, _("Private key cipher '%s' was unknown."), @@ -145,7 +150,15 @@ crypto_decrypt (const char *cipher, return NULL; } - output = g_malloc0 (data->len + 1); + if (iv_len < real_iv_len) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_RAW_IV_INVALID, + _("Invalid IV length (must be at least %zd)."), + real_iv_len); + return NULL; + } + + output = g_malloc0 (data->len); if (!output) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_OUT_OF_MEMORY, @@ -190,6 +203,14 @@ crypto_decrypt (const char *cipher, } pad_len = output[data->len - 1]; + /* Check if the padding at the end of the decrypted data is valid */ + if (pad_len == 0 || pad_len > real_iv_len) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, + _("Failed to decrypt the private key: unexpected padding length.")); + goto out; + } + /* Validate tail padding; last byte is the padding size, and all pad bytes * should contain the padding size. */ @@ -203,7 +224,6 @@ crypto_decrypt (const char *cipher, } *out_len = data->len - pad_len; - output[*out_len] = '\0'; success = TRUE; out: @@ -219,6 +239,113 @@ out: return output; } +char * +crypto_encrypt (const char *cipher, + const GByteArray *data, + const char *iv, + const gsize iv_len, + const char *key, + gsize key_len, + gsize *out_len, + GError **error) +{ + gcry_cipher_hd_t ctx; + gcry_error_t err; + int cipher_mech; + char *output = NULL; + gboolean success = FALSE; + gsize padded_buf_len, pad_len, output_len; + char *padded_buf = NULL; + guint32 i; + + if (!strcmp (cipher, CIPHER_DES_EDE3_CBC)) + cipher_mech = GCRY_CIPHER_3DES; + else { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_UNKNOWN_CIPHER, + _("Private key cipher '%s' was unknown."), + cipher); + return NULL; + } + + /* If data->len % ivlen == 0, then we add another complete block + * onto the end so that the decrypter knows there's padding. + */ + pad_len = iv_len - (data->len % iv_len); + output_len = padded_buf_len = data->len + pad_len; + padded_buf = g_malloc0 (padded_buf_len); + + memcpy (padded_buf, data->data, data->len); + for (i = 0; i < pad_len; i++) + padded_buf[data->len + i] = (guint8) (pad_len & 0xFF); + + output = g_malloc0 (output_len); + if (!output) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Could not allocate memory for encrypting.")); + return NULL; + } + + err = gcry_cipher_open (&ctx, cipher_mech, GCRY_CIPHER_MODE_CBC, 0); + if (err) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_INIT_FAILED, + _("Failed to initialize the encryption cipher context: %s / %s."), + gcry_strsource (err), gcry_strerror (err)); + goto out; + } + + err = gcry_cipher_setkey (ctx, key, key_len); + if (err) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_SET_KEY_FAILED, + _("Failed to set symmetric key for encryption: %s / %s."), + gcry_strsource (err), gcry_strerror (err)); + goto out; + } + + /* gcrypt only wants 8 bytes of the IV (same as the DES block length) */ + err = gcry_cipher_setiv (ctx, iv, SALT_LEN); + if (err) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_SET_IV_FAILED, + _("Failed to set IV for encryption: %s / %s."), + gcry_strsource (err), gcry_strerror (err)); + goto out; + } + + err = gcry_cipher_encrypt (ctx, output, output_len, padded_buf, padded_buf_len); + if (err) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, + _("Failed to encrypt the data: %s / %s."), + gcry_strsource (err), gcry_strerror (err)); + goto out; + } + + *out_len = output_len; + success = TRUE; + +out: + if (padded_buf) { + memset (padded_buf, 0, padded_buf_len); + g_free (padded_buf); + padded_buf = NULL; + } + + if (!success) { + if (output) { + /* Don't expose key material */ + memset (output, 0, output_len); + g_free (output); + output = NULL; + } + } + gcry_cipher_close (ctx); + return output; +} + NMCryptoFileFormat crypto_verify_cert (const unsigned char *data, gsize len, @@ -312,3 +439,10 @@ out: return success; } +gboolean +crypto_randomize (void *buffer, gsize buffer_len, GError **error) +{ + gcry_randomize (buffer, buffer_len, GCRY_STRONG_RANDOM); + return TRUE; +} + diff --git a/libnm-util/crypto_nss.c b/libnm-util/crypto_nss.c index 8cbdd9f525..b5a3a7f122 100644 --- a/libnm-util/crypto_nss.c +++ b/libnm-util/crypto_nss.c @@ -18,7 +18,7 @@ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * - * (C) Copyright 2007 - 2008 Red Hat, Inc. + * (C) Copyright 2007 - 2009 Red Hat, Inc. */ #include "config.h" @@ -147,8 +147,7 @@ crypto_decrypt (const char *cipher, GError **error) { char *output = NULL; - int tmp1_len = 0; - unsigned int tmp2_len = 0; + int decrypted_len = 0; CK_MECHANISM_TYPE cipher_mech; PK11SlotInfo *slot = NULL; SECItem key_item; @@ -157,13 +156,16 @@ crypto_decrypt (const char *cipher, PK11Context *ctx = NULL; SECStatus s; gboolean success = FALSE; - gsize len; + unsigned int pad_len = 0; + guint32 i, real_iv_len = 0; - if (!strcmp (cipher, CIPHER_DES_EDE3_CBC)) + if (!strcmp (cipher, CIPHER_DES_EDE3_CBC)) { cipher_mech = CKM_DES3_CBC_PAD; - else if (!strcmp (cipher, CIPHER_DES_CBC)) + real_iv_len = 8; + } else if (!strcmp (cipher, CIPHER_DES_CBC)) { cipher_mech = CKM_DES_CBC_PAD; - else { + real_iv_len = 8; + } else { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_UNKNOWN_CIPHER, _("Private key cipher '%s' was unknown."), @@ -171,7 +173,15 @@ crypto_decrypt (const char *cipher, return NULL; } - output = g_malloc0 (data->len + 1); + if (iv_len < real_iv_len) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_RAW_IV_INVALID, + _("Invalid IV length (must be at least %d)."), + real_iv_len); + return NULL; + } + + output = g_malloc0 (data->len); if (!output) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_OUT_OF_MEMORY, @@ -198,7 +208,7 @@ crypto_decrypt (const char *cipher, } key_item.data = (unsigned char *) iv; - key_item.len = iv_len; + key_item.len = real_iv_len; sec_param = PK11_ParamFromIV (cipher_mech, &key_item); if (!sec_param) { g_set_error (error, NM_CRYPTO_ERROR, @@ -217,7 +227,7 @@ crypto_decrypt (const char *cipher, s = PK11_CipherOp (ctx, (unsigned char *) output, - &tmp1_len, + &decrypted_len, data->len, data->data, data->len); @@ -229,10 +239,17 @@ crypto_decrypt (const char *cipher, goto out; } + if (decrypted_len > data->len) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, + _("Failed to decrypt the private key: decrypted data too large.")); + goto out; + } + s = PK11_DigestFinal (ctx, - (unsigned char *) (output + tmp1_len), - &tmp2_len, - data->len - tmp1_len); + (unsigned char *) (output + decrypted_len), + &pad_len, + data->len - decrypted_len); if (s != SECSuccess) { g_set_error (error, NM_CRYPTO_ERROR, NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, @@ -240,12 +257,29 @@ crypto_decrypt (const char *cipher, PORT_GetError ()); goto out; } - len = tmp1_len + tmp2_len; - if (len > data->len) - goto out; + pad_len = data->len - decrypted_len; - *out_len = len; - output[*out_len] = '\0'; + /* Check if the padding at the end of the decrypted data is valid */ + if (pad_len == 0 || pad_len > real_iv_len) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED, + _("Failed to decrypt the private key: unexpected padding length.")); + goto out; + } + + /* 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 = decrypted_len; success = TRUE; out: @@ -269,6 +303,134 @@ out: return output; } +char * +crypto_encrypt (const char *cipher, + const GByteArray *data, + const char *iv, + gsize iv_len, + const char *key, + gsize key_len, + gsize *out_len, + GError **error) +{ + SECStatus ret; + CK_MECHANISM_TYPE cipher_mech = CKM_DES3_CBC_PAD; + PK11SlotInfo *slot = NULL; + SECItem key_item = { .data = (unsigned char *) key, .len = key_len }; + SECItem iv_item = { .data = (unsigned char *) iv, .len = iv_len }; + PK11SymKey *sym_key = NULL; + SECItem *sec_param = NULL; + PK11Context *ctx = NULL; + unsigned char *output, *padded_buf; + gsize output_len; + int encrypted_len = 0, i; + gboolean success = FALSE; + gsize padded_buf_len, pad_len; + + if (!strcmp (cipher, CIPHER_DES_EDE3_CBC)) + cipher_mech = CKM_DES3_CBC_PAD; + else { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_UNKNOWN_CIPHER, + _("Private key cipher '%s' was unknown."), + cipher); + return NULL; + } + + /* If data->len % ivlen == 0, then we add another complete block + * onto the end so that the decrypter knows there's padding. + */ + pad_len = iv_len - (data->len % iv_len); + output_len = padded_buf_len = data->len + pad_len; + padded_buf = g_malloc0 (padded_buf_len); + + memcpy (padded_buf, data->data, data->len); + for (i = 0; i < pad_len; i++) + padded_buf[data->len + i] = (guint8) (pad_len & 0xFF); + + output = g_malloc0 (output_len); + if (!output) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Could not allocate memory for encrypting.")); + return NULL; + } + + slot = PK11_GetBestSlot (cipher_mech, NULL); + if (!slot) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_INIT_FAILED, + _("Failed to initialize the encryption cipher slot.")); + goto out; + } + + sym_key = PK11_ImportSymKey (slot, cipher_mech, PK11_OriginUnwrap, CKA_ENCRYPT, &key_item, NULL); + if (!sym_key) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_SET_KEY_FAILED, + _("Failed to set symmetric key for encryption.")); + goto out; + } + + sec_param = PK11_ParamFromIV (cipher_mech, &iv_item); + if (!sec_param) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_SET_IV_FAILED, + _("Failed to set IV for encryption.")); + goto out; + } + + ctx = PK11_CreateContextBySymKey (cipher_mech, CKA_ENCRYPT, sym_key, sec_param); + if (!ctx) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_INIT_FAILED, + _("Failed to initialize the encryption context.")); + goto out; + } + + ret = PK11_CipherOp (ctx, output, &encrypted_len, output_len, padded_buf, padded_buf_len); + if (ret != SECSuccess) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_ENCRYPT_FAILED, + _("Failed to encrypt: %d."), + PORT_GetError ()); + goto out; + } + + if (encrypted_len != output_len) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_CIPHER_ENCRYPT_FAILED, + _("Unexpected amount of data after encrypting.")); + goto out; + } + + *out_len = encrypted_len; + success = TRUE; + +out: + if (ctx) + PK11_DestroyContext (ctx, PR_TRUE); + if (sym_key) + PK11_FreeSymKey (sym_key); + if (sec_param) + SECITEM_FreeItem (sec_param, PR_TRUE); + if (slot) + PK11_FreeSlot (slot); + + if (padded_buf) { + memset (padded_buf, 0, padded_buf_len); + g_free (padded_buf); + padded_buf = NULL; + } + + if (!success) { + memset (output, 0, output_len); + g_free (output); + output = NULL; + } + return (char *) output; +} + NMCryptoFileFormat crypto_verify_cert (const unsigned char *data, gsize len, @@ -382,3 +544,18 @@ error: return FALSE; } +gboolean +crypto_randomize (void *buffer, gsize buffer_len, GError **error) +{ + SECStatus s; + + s = PK11_GenerateRandom (buffer, buffer_len); + if (s != SECSuccess) { + g_set_error_literal (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_RANDOMIZE_FAILED, + _("Could not generate random data.")); + return FALSE; + } + return TRUE; +} + diff --git a/libnm-util/libnm-util.ver b/libnm-util/libnm-util.ver index 1471e54be9..2bf48955ab 100644 --- a/libnm-util/libnm-util.ver +++ b/libnm-util/libnm-util.ver @@ -322,6 +322,7 @@ global: nm_setting_olpc_mesh_get_channel; nm_setting_olpc_mesh_get_dhcp_anycast_address; nm_utils_deinit; + nm_utils_rsa_key_encrypt; nm_utils_escape_ssid; nm_utils_gvalue_hash_dup; nm_utils_init; diff --git a/libnm-util/nm-utils.c b/libnm-util/nm-utils.c index a034121405..c8820a08c6 100644 --- a/libnm-util/nm-utils.c +++ b/libnm-util/nm-utils.c @@ -21,7 +21,7 @@ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * - * (C) Copyright 2005 - 2008 Red Hat, Inc. + * (C) Copyright 2005 - 2009 Red Hat, Inc. */ #include @@ -34,6 +34,7 @@ #include #include +#include #include #include @@ -1498,3 +1499,205 @@ out: return buf; } +static char * +make_key (const char *salt, + const gsize salt_len, + const char *password, + gsize *out_len, + GError **error) +{ + char *key; + guint32 digest_len = 24; /* DES-EDE3-CBC */ + + g_return_val_if_fail (salt != NULL, NULL); + g_return_val_if_fail (salt_len >= 8, NULL); + g_return_val_if_fail (password != NULL, NULL); + g_return_val_if_fail (out_len != NULL, NULL); + + key = g_malloc0 (digest_len + 1); + if (!key) { + g_set_error (error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Not enough memory to make encryption key.")); + return NULL; + } + + if (!crypto_md5_hash (salt, salt_len, password, strlen (password), key, digest_len, error)) { + *out_len = 0; + memset (key, 0, digest_len); + g_free (key); + key = NULL; + } else + *out_len = digest_len; + + return key; +} + +/* + * utils_bin2hexstr + * + * Convert a byte-array into a hexadecimal string. + * + * Code originally by Alex Larsson and + * copyright Red Hat, Inc. under terms of the LGPL. + * + */ +static char * +utils_bin2hexstr (const char *bytes, int len, int final_len) +{ + static char hex_digits[] = "0123456789abcdef"; + char *result; + int i; + gsize buflen = (len * 2) + 1; + + g_return_val_if_fail (bytes != NULL, NULL); + g_return_val_if_fail (len > 0, NULL); + g_return_val_if_fail (len < 4096, NULL); /* Arbitrary limit */ + if (final_len > -1) + g_return_val_if_fail (final_len < buflen, NULL); + + result = g_malloc0 (buflen); + for (i = 0; i < len; i++) + { + result[2*i] = hex_digits[(bytes[i] >> 4) & 0xf]; + result[2*i+1] = hex_digits[bytes[i] & 0xf]; + } + /* Cut converted key off at the correct length for this cipher type */ + if (final_len > -1) + result[final_len] = '\0'; + else + result[buflen - 1] = '\0'; + + return result; +} + +/** + * nm_utils_rsa_key_encrypt: + * @data: RSA private key data to be encrypted + * @in_password: existing password to use, if any + * @out_password: if @in_password was NULL, a random password will be generated + * and returned in this argument + * @error: detailed error information on return, if an error occurred + * + * Encrypts the given RSA private key data with the given password (or generates + * a password if no password was given) and converts the data to PEM format + * suitable for writing to a file. + * + * Returns: on success, PEM-formatted data suitable for writing to a PEM-formatted + * certificate/private key file. + **/ +GByteArray * +nm_utils_rsa_key_encrypt (const GByteArray *data, + const char *in_password, + char **out_password, + GError **error) +{ + char salt[8]; + char *key = NULL, *enc = NULL, *pw_buf[32]; + gsize key_len = 0, enc_len = 0; + GString *pem = NULL; + char *tmp, *tmp_password = NULL; + gboolean success = FALSE; + int left; + const char *p; + GByteArray *ret = NULL; + + g_return_val_if_fail (data != NULL, NULL); + g_return_val_if_fail (data->len > 0, NULL); + if (out_password) + g_return_val_if_fail (*out_password == NULL, NULL); + + /* Make the password if needed */ + if (!in_password) { + if (!crypto_randomize (pw_buf, sizeof (pw_buf), error)) + return NULL; + in_password = tmp_password = utils_bin2hexstr ((const char *) pw_buf, sizeof (pw_buf), -1); + } + + if (!crypto_randomize (salt, sizeof (salt), error)) + goto out; + + key = make_key (&salt[0], sizeof (salt), in_password, &key_len, error); + if (!key) + goto out; + + enc = crypto_encrypt (CIPHER_DES_EDE3_CBC, data, salt, sizeof (salt), key, key_len, &enc_len, error); + if (!enc) + goto out; + + pem = g_string_sized_new (enc_len * 2 + 100); + if (!pem) { + g_set_error_literal (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Could not allocate memory for PEM file creation.")); + goto out; + } + + g_string_append (pem, "-----BEGIN RSA PRIVATE KEY-----\n"); + g_string_append (pem, "Proc-Type: 4,ENCRYPTED\n"); + + /* Convert the salt to a hex string */ + tmp = utils_bin2hexstr ((const char *) salt, sizeof (salt), 16); + if (!tmp) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Could not allocate memory for writing IV to PEM file.")); + goto out; + } + + g_string_append_printf (pem, "DEK-Info: DES-EDE3-CBC,%s\n\n", tmp); + g_free (tmp); + + /* Convert the encrypted key to a base64 string */ + p = tmp = g_base64_encode ((const guchar *) enc, enc_len); + if (!tmp) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Could not allocate memory for writing encrypted key to PEM file.")); + goto out; + } + + left = strlen (tmp); + while (left > 0) { + g_string_append_len (pem, p, (left < 64) ? left : 64); + g_string_append_c (pem, '\n'); + left -= 64; + p += 64; + } + g_free (tmp); + + g_string_append (pem, "-----END RSA PRIVATE KEY-----\n"); + + ret = g_byte_array_sized_new (pem->len); + if (!ret) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Could not allocate memory for PEM file data.")); + goto out; + } + g_byte_array_append (ret, (const unsigned char *) pem->str, pem->len); + if (tmp_password && out_password) + *out_password = g_strdup (tmp_password); + success = TRUE; + +out: + if (key) { + memset (key, 0, key_len); + g_free (key); + } + if (!enc) { + memset (enc, 0, enc_len); + g_free (enc); + } + if (pem) + g_string_free (pem, TRUE); + + if (tmp_password) { + memset (tmp_password, 0, strlen (tmp_password)); + g_free (tmp_password); + } + + return ret; +} + diff --git a/libnm-util/nm-utils.h b/libnm-util/nm-utils.h index 810c6e7e9d..bbb304f4db 100644 --- a/libnm-util/nm-utils.h +++ b/libnm-util/nm-utils.h @@ -205,4 +205,9 @@ void nm_utils_ip6_dns_to_gvalue (GSList *list, GValue *value); char *nm_utils_uuid_generate (void); char *nm_utils_uuid_generate_from_string (const char *s); +GByteArray *nm_utils_rsa_key_encrypt (const GByteArray *data, + const char *in_password, + char **out_password, + GError **error); + #endif /* NM_UTILS_H */ diff --git a/libnm-util/tests/Makefile.am b/libnm-util/tests/Makefile.am index ea34ebc38a..770f4e5388 100644 --- a/libnm-util/tests/Makefile.am +++ b/libnm-util/tests/Makefile.am @@ -26,6 +26,7 @@ test_crypto_CPPFLAGS = \ test_crypto_LDADD = \ $(top_builddir)/libnm-util/libtest-crypto.la \ + $(top_builddir)/libnm-util/libnm-util.la \ $(GLIB_LIBS) diff --git a/libnm-util/tests/test-crypto.c b/libnm-util/tests/test-crypto.c index 18920342d4..de4d6290e3 100644 --- a/libnm-util/tests/test-crypto.c +++ b/libnm-util/tests/test-crypto.c @@ -18,7 +18,7 @@ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * - * (C) Copyright 2007 - 2008 Red Hat, Inc. + * (C) Copyright 2007 - 2009 Red Hat, Inc. */ #include @@ -30,6 +30,7 @@ #include "nm-test-helpers.h" #include "crypto.h" +#include "nm-utils.h" #if 0 static const char *pem_rsa_key_begin = "-----BEGIN RSA PRIVATE KEY-----"; @@ -216,6 +217,67 @@ test_is_pkcs12 (const char *path, gboolean expect_fail, const char *desc) ASSERT (is_pkcs12 == TRUE, desc, "couldn't read PKCS#12 file '%s'", path); } +static void +test_encrypt_private_key (const char *path, + const char *password, + const char *desc) +{ + NMCryptoKeyType key_type = NM_CRYPTO_KEY_TYPE_UNKNOWN; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + GByteArray *array, *encrypted, *re_decrypted; + GError *error = NULL; + + array = crypto_get_private_key (path, password, &key_type, &format, &error); + + ASSERT (array != NULL, desc, + "couldn't read private key file '%s': %d %s", + path, error->code, error->message); + + ASSERT (format == NM_CRYPTO_FILE_FORMAT_RAW_KEY, desc, + "%s: unexpected private key file format (expected %d, got %d)", + path, NM_CRYPTO_FILE_FORMAT_RAW_KEY, format); + + ASSERT (key_type == NM_CRYPTO_KEY_TYPE_RSA, desc, + "%s: unexpected private key type (expected %d, got %d)", + path, NM_CRYPTO_KEY_TYPE_RSA, format); + + /* Now re-encrypt the private key */ + encrypted = nm_utils_rsa_key_encrypt (array, password, NULL, &error); + ASSERT (encrypted != NULL, desc, + "couldn't re-encrypt private key file '%s': %d %s", + path, error->code, error->message); + + /* Then re-decrypt the private key */ + key_type = NM_CRYPTO_KEY_TYPE_UNKNOWN; + format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + re_decrypted = crypto_get_private_key_data (encrypted, password, &key_type, &format, &error); + + ASSERT (re_decrypted != NULL, desc, + "couldn't read private key file '%s': %d %s", + path, error->code, error->message); + + ASSERT (format == NM_CRYPTO_FILE_FORMAT_RAW_KEY, desc, + "%s: unexpected private key file format (expected %d, got %d)", + path, NM_CRYPTO_FILE_FORMAT_RAW_KEY, format); + + ASSERT (key_type == NM_CRYPTO_KEY_TYPE_RSA, desc, + "%s: unexpected private key type (expected %d, got %d)", + path, NM_CRYPTO_KEY_TYPE_RSA, format); + + /* Compare the original decrypted key with the re-decrypted key */ + ASSERT (array->len == re_decrypted->len, desc, + "%s: unexpected re-decrypted private key length (expected %d, got %d)", + path, array->len, re_decrypted->len); + + ASSERT (!memcmp (array->data, re_decrypted->data, array->len), desc, + "%s: unexpected private key data", + path); + + g_byte_array_free (re_decrypted, TRUE); + g_byte_array_free (encrypted, TRUE); + g_byte_array_free (array, TRUE); +} + int main (int argc, char **argv) { GError *error = NULL; @@ -250,6 +312,8 @@ int main (int argc, char **argv) test_is_pkcs12 (pk12, FALSE, "is-pkcs12"); test_is_pkcs12 (priv_key, TRUE, "is-pkcs12-not-pkcs12"); + test_encrypt_private_key (priv_key, priv_key_password, "private-key"); + crypto_deinit (); progname = g_path_get_basename (argv[0]); diff --git a/po/POTFILES.in b/po/POTFILES.in index e635f92c48..8266a76c9d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -4,6 +4,7 @@ libnm-util/crypto.c libnm-util/crypto_gnutls.c libnm-util/crypto_nss.c +libnm-util/nm-utils.c src/nm-netlink-monitor.c src/NetworkManager.c src/dhcp-manager/nm-dhcp-dhclient.c diff --git a/system-settings/plugins/ifcfg-rh/Makefile.am b/system-settings/plugins/ifcfg-rh/Makefile.am index e282421d67..2368b308a8 100644 --- a/system-settings/plugins/ifcfg-rh/Makefile.am +++ b/system-settings/plugins/ifcfg-rh/Makefile.am @@ -16,9 +16,7 @@ libifcfg_rh_io_la_SOURCES = \ errors.c \ common.h \ utils.c \ - utils.h \ - crypto.c \ - crypto.h + utils.h INCLUDES = \ -I$(top_srcdir)/src/system-settings \ @@ -35,7 +33,10 @@ libifcfg_rh_io_la_CPPFLAGS = \ -DSYSCONFDIR=\"$(sysconfdir)\" \ -DSBINDIR=\"$(sbindir)\" -libifcfg_rh_io_la_LIBADD = $(GLIB_LIBS) $(NSS_LIBS) +libifcfg_rh_io_la_LIBADD = \ + $(top_builddir)/libnm-util/libnm-util.la \ + $(GLIB_LIBS) \ + $(NSS_LIBS) libnm_settings_plugin_ifcfg_rh_la_SOURCES = \ plugin.c \ diff --git a/system-settings/plugins/ifcfg-rh/crypto.c b/system-settings/plugins/ifcfg-rh/crypto.c deleted file mode 100644 index bd8d09cb69..0000000000 --- a/system-settings/plugins/ifcfg-rh/crypto.c +++ /dev/null @@ -1,391 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* NetworkManager system settings service - keyfile plugin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Copyright (C) 2009 Red Hat, Inc. - */ - -#include "config.h" - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "common.h" -#include "crypto.h" -#include "utils.h" - -static gboolean initialized = FALSE; - -static gboolean -crypto_init (GError **error) -{ - SECStatus ret; - - if (initialized) - return TRUE; - - PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 1); - ret = NSS_NoDB_Init (NULL); - if (ret != SECSuccess) { - PR_Cleanup (); - g_set_error (error, ifcfg_plugin_error_quark (), 0, - _("Failed to initialize the crypto engine: %d."), - PR_GetError ()); - return FALSE; - } - - initialized = TRUE; - return TRUE; -} - -static gboolean -nss_md5_hash (const unsigned char *salt, - const gsize salt_len, - const char *password, - gsize password_len, - unsigned char *buffer, - gsize buflen, - GError **error) -{ - PK11Context *ctx; - int nkey = buflen; - unsigned int digest_len; - int count = 0; - char digest[20]; /* MD5 hash length */ - unsigned char *p = buffer; - - if (salt) - g_return_val_if_fail (salt_len >= 8, FALSE); - - g_return_val_if_fail (password != NULL, FALSE); - g_return_val_if_fail (password_len > 0, FALSE); - g_return_val_if_fail (buffer != NULL, FALSE); - g_return_val_if_fail (buflen > 0, FALSE); - - ctx = PK11_CreateDigestContext (SEC_OID_MD5); - if (!ctx) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Failed to initialize the MD5 context: %d.", - PORT_GetError ()); - return FALSE; - } - - while (nkey > 0) { - int i = 0; - - PK11_DigestBegin (ctx); - if (count++) - PK11_DigestOp (ctx, (const unsigned char *) digest, digest_len); - PK11_DigestOp (ctx, (const unsigned char *) password, password_len); - if (salt) - PK11_DigestOp (ctx, salt, 8); /* Only use 8 bytes of salt */ - PK11_DigestFinal (ctx, (unsigned char *) digest, &digest_len, sizeof (digest)); - - while (nkey && (i < digest_len)) { - *(p++) = digest[i++]; - nkey--; - } - } - - memset (digest, 0, sizeof (digest)); - PK11_DestroyContext (ctx, PR_TRUE); - return TRUE; -} - -static unsigned char * -make_key (const unsigned char *salt, - gsize salt_len, - const char *password, - gsize *out_len, - GError **error) -{ - unsigned char *key; - guint32 digest_len = 24; /* DES-EDE3-CBC */ - - g_return_val_if_fail (salt != NULL, NULL); - g_return_val_if_fail (salt_len >= 8, NULL); - g_return_val_if_fail (password != NULL, NULL); - g_return_val_if_fail (out_len != NULL, NULL); - - key = g_malloc0 (digest_len + 1); - if (!key) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Not enough memory to decrypt private key."); - return NULL; - } - - if (!nss_md5_hash (salt, salt_len, password, strlen (password), key, digest_len, error)) { - *out_len = 0; - memset (key, 0, digest_len); - g_free (key); - key = NULL; - } else - *out_len = digest_len; - - return key; -} - -static unsigned char * -nss_des3_encrypt (const unsigned char *key, - gsize key_len, - const unsigned char *iv, - gsize iv_len, - const unsigned char *data, - gsize data_len, - gsize *out_len, - GError **error) -{ - SECStatus ret; - CK_MECHANISM_TYPE cipher_mech = CKM_DES3_CBC_PAD; - PK11SlotInfo *slot = NULL; - SECItem key_item = { .data = (unsigned char *) key, .len = key_len }; - SECItem iv_item = { .data = (unsigned char *) iv, .len = iv_len }; - PK11SymKey *sym_key = NULL; - SECItem *sec_param = NULL; - PK11Context *ctx = NULL; - unsigned char *buf; - gsize buflen = data_len + 64; - int tmp1_len = 0; - unsigned int tmp2_len = 0, len; - gboolean success = FALSE; - - buf = g_malloc0 (buflen); - if (!buf) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Could not allocate memory encrypting private key."); - return NULL; - } - - slot = PK11_GetBestSlot (cipher_mech, NULL); - if (!slot) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Failed to initialize the encryption cipher slot."); - goto out; - } - - sym_key = PK11_ImportSymKey (slot, cipher_mech, PK11_OriginUnwrap, CKA_ENCRYPT, &key_item, NULL); - if (!sym_key) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Failed to set symmetric key for encryption."); - goto out; - } - - sec_param = PK11_ParamFromIV (cipher_mech, &iv_item); - if (!sec_param) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Failed to set IV for encryption."); - goto out; - } - - ctx = PK11_CreateContextBySymKey (cipher_mech, CKA_ENCRYPT, sym_key, sec_param); - if (!ctx) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Failed to initialize the encryption context."); - goto out; - } - - ret = PK11_CipherOp (ctx, buf, &tmp1_len, buflen, (unsigned char *) data, data_len); - if (ret != SECSuccess) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Failed to encrypt the private key: %d.", - PORT_GetError ()); - goto out; - } - - ret = PK11_DigestFinal (ctx, - (unsigned char *) (buf + tmp1_len), - &tmp2_len, - buflen - tmp1_len); - if (ret != SECSuccess) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Failed to finalize encryption of the private key: %d.", - PORT_GetError ()); - goto out; - } - len = tmp1_len + tmp2_len; - if (len > buflen) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Error encrypting private key; too much data."); - goto out; - } - - *out_len = len; - buf[*out_len] = '\0'; - success = TRUE; - -out: - if (ctx) - PK11_DestroyContext (ctx, PR_TRUE); - if (sym_key) - PK11_FreeSymKey (sym_key); - if (sec_param) - SECITEM_FreeItem (sec_param, PR_TRUE); - if (slot) - PK11_FreeSlot (slot); - - if (!success) { - memset (buf, 0, buflen); - g_free (buf); - buf = NULL; - } - return buf; -} - -#define PEM_RSA_KEY_BEGIN "-----BEGIN RSA PRIVATE KEY-----"; -#define PEM_RSA_KEY_END "-----END RSA PRIVATE KEY-----"; - -GByteArray * -crypto_key_to_pem (const GByteArray *data, - const char *password, - GError **error) -{ - SECStatus s; - unsigned char salt[32]; - unsigned char *key = NULL, *enc = NULL; - gsize key_len = 0, enc_len = 0; - GString *pem = NULL; - char *tmp; - gboolean success = FALSE; - int left; - const char *p; - GByteArray *ret = NULL; - - g_return_val_if_fail (data != NULL, NULL); - g_return_val_if_fail (data->len > 0, NULL); - g_return_val_if_fail (password != NULL, NULL); - - if (!crypto_init (error)) - return NULL; - - s = PK11_GenerateRandom (salt, sizeof (salt)); - if (s != SECSuccess) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Could not generate random IV for encrypting private key."); - return NULL; - } - - key = make_key (&salt[0], sizeof (salt), password, &key_len, error); - if (!key) - return NULL; - - enc = nss_des3_encrypt (key, key_len, salt, sizeof (salt), data->data, data->len, &enc_len, error); - if (!enc) - goto out; - - pem = g_string_sized_new (enc_len * 2 + 100); - if (!pem) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Could not allocate memory for PEM file creation."); - goto out; - } - - g_string_append (pem, "-----BEGIN RSA PRIVATE KEY-----\n"); - g_string_append (pem, "Proc-Type: 4,ENCRYPTED\n"); - - /* Convert the salt to a hex string */ - tmp = utils_bin2hexstr ((const char *) salt, sizeof (salt), 16); - if (!tmp) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Could not allocate memory for writing IV to PEM file."); - goto out; - } - - g_string_append_printf (pem, "DEK-Info: DES-EDE3-CBC,%s\n\n", tmp); - g_free (tmp); - - /* Convert the encrypted key to a base64 string */ - p = tmp = g_base64_encode (enc, enc_len); - if (!tmp) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Could not allocate memory for writing encrypted key to PEM file."); - goto out; - } - - left = strlen (tmp); - while (left > 0) { - g_string_append_len (pem, p, (left < 64) ? left : 64); - g_string_append_c (pem, '\n'); - left -= 64; - p += 64; - } - g_free (tmp); - - g_string_append (pem, "-----END RSA PRIVATE KEY-----\n"); - - ret = g_byte_array_sized_new (pem->len); - if (!ret) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Could not allocate memory for PEM file data."); - goto out; - } - g_byte_array_append (ret, (const unsigned char *) pem->str, pem->len); - success = TRUE; - -out: - if (key) { - memset (key, 0, key_len); - g_free (key); - } - if (!enc) { - memset (enc, 0, enc_len); - g_free (enc); - } - if (pem) - g_string_free (pem, TRUE); - - return ret; -} - -GByteArray * -crypto_random (gsize len, GError **error) -{ - SECStatus s; - GByteArray *array; - unsigned char *buf; - - if (!crypto_init (error)) - return NULL; - - buf = g_malloc (len); - if (!buf) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Could not allocate memory for random data."); - return NULL; - } - - s = PK11_GenerateRandom (buf, len); - if (s != SECSuccess) { - g_set_error (error, ifcfg_plugin_error_quark (), 0, - "Could not generate random IV for encrypting private key."); - g_free (buf); - return NULL; - } - - array = g_byte_array_sized_new (len); - g_byte_array_append (array, buf, len); - memset (buf, 0, len); - g_free (buf); - - return array; -} - diff --git a/system-settings/plugins/ifcfg-rh/crypto.h b/system-settings/plugins/ifcfg-rh/crypto.h deleted file mode 100644 index b145020fd8..0000000000 --- a/system-settings/plugins/ifcfg-rh/crypto.h +++ /dev/null @@ -1,34 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* NetworkManager system settings service - keyfile plugin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Copyright (C) 2009 Red Hat, Inc. - */ - -#ifndef _CRYPTO_H_ -#define _CRYPTO_H_ - -#include - -GByteArray * -crypto_key_to_pem (const GByteArray *data, - const char *password, - GError **error); - -GByteArray *crypto_random (gsize len, GError **error); - -#endif /* _CRYPTO_H_ */ - diff --git a/system-settings/plugins/ifcfg-rh/writer.c b/system-settings/plugins/ifcfg-rh/writer.c index 01e0712499..b20fab5a33 100644 --- a/system-settings/plugins/ifcfg-rh/writer.c +++ b/system-settings/plugins/ifcfg-rh/writer.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "common.h" #include "shvar.h" @@ -381,30 +382,17 @@ write_8021x_certs (NMSetting8021x *s_8021x, * private key file, it'll be encrypted, so we don't need to re-encrypt. */ if (blob && !is_pkcs12) { - GByteArray *array; - - /* If the private key is an unencrypted blob, re-encrypt it with a - * random password since we don't store unencrypted private keys on disk. - */ - if (!password) { - /* Create a random private key */ - array = crypto_random (32, error); - if (!array) - goto out; - - password = generated_pw = utils_bin2hexstr ((const char *) array->data, array->len, -1); - memset (array->data, 0, array->len); - g_byte_array_free (array, TRUE); - } - /* Encrypt the unencrypted private key with the fake password */ - enc_key = crypto_key_to_pem (blob, password, error); + enc_key = nm_utils_rsa_key_encrypt (blob, password, &generated_pw, error); if (!enc_key) goto out; + + if (generated_pw) + password = generated_pw; } /* Save the private key */ - if (!write_object (s_8021x, ifcfg, enc_key, otype, error)) + if (!write_object (s_8021x, ifcfg, enc_key ? enc_key : blob, otype, error)) goto out; /* Private key password */