libnm-util: add nm_utils_rsa_key_encrypt() and fix crypto padding mixups

To be backwards compatible clients need to handle both paths to private
keys and the decrypted private key data, which is what used to get passed
in the private-key and phase2-private-key attributes of the 802.1x setting.
When moving a connection around between system-settings and user-settings,
if the private key is decrypted data, the settings service needs to store
that decrypted data somewhere so that the key can be sent to NM during
the connection process.

But we don't want to store the decrypted private key data, so we have to
re-encrypt it (possibly generating a private key password if one wasn't
sent with the decrypted data) and save it to disk, then send NM a path
to that private key during connection.

To help clients do this, and so that they don't have to carry around
multiple crypto implementations depending on whether they want to use
NSS or gnutls/gcrypt, add a helper to libnm-util.

Furthermore, I misunderstood a bunch of stuff with crypto padding when
writing the encrypt/decrypt functions long ago, so fix that up.  Don't
return padding as part of the decrypted data, and make sure to verify
the padding's expected lengths and values when decrypting.  Many thanks
to Nalin Dahyabhai for pointing me in the right direction.
This commit is contained in:
Dan Williams 2009-09-15 16:01:50 -07:00
parent a371951fbc
commit 8c35e96b60
14 changed files with 640 additions and 477 deletions

View file

@ -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 <glib.h>

View file

@ -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);

View file

@ -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 <dcbw@redhat.com>
@ -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 <glib.h>
@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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 <string.h>
@ -34,6 +34,7 @@
#include <glib.h>
#include <glib-object.h>
#include <glib/gi18n.h>
#include <dbus/dbus-glib.h>
#include <uuid/uuid.h>
@ -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 <alexl@redhat.com> 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;
}

View file

@ -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 */

View file

@ -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)

View file

@ -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 <glib.h>
@ -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]);

View file

@ -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

View file

@ -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 \

View file

@ -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 <glib.h>
#include <glib/gi18n.h>
#include <prinit.h>
#include <nss.h>
#include <pk11pub.h>
#include <pkcs11t.h>
#include <cert.h>
#include <prerror.h>
#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;
}

View file

@ -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 <glib.h>
GByteArray *
crypto_key_to_pem (const GByteArray *data,
const char *password,
GError **error);
GByteArray *crypto_random (gsize len, GError **error);
#endif /* _CRYPTO_H_ */

View file

@ -35,6 +35,7 @@
#include <nm-setting-8021x.h>
#include <nm-setting-ip4-config.h>
#include <nm-setting-pppoe.h>
#include <nm-utils.h>
#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 */