From fde8aa8c7b1952d14fe9275ae36836fe995ea943 Mon Sep 17 00:00:00 2001 From: Pekka Riikonen Date: Sat, 2 Nov 2002 20:06:34 +0000 Subject: [PATCH] Added support for encrypted private key files. The passphrase is now used when creating new key pair, and if necessary prompted for when loading the key pair. --- CHANGES | 17 ++ TODO | 3 - apps/irssi/src/silc/core/clientutil.c | 18 +- apps/irssi/src/silc/core/silc-core.c | 43 ++-- apps/silcd/server.c | 6 +- apps/silcd/serverconfig.c | 6 +- apps/silcd/silcd.c | 15 +- lib/silccrypt/silcpkcs.c | 313 ++++++++++++++++++++++---- lib/silccrypt/silcpkcs.h | 45 ++-- lib/silcutil/silcapputil.c | 40 +++- lib/silcutil/silcapputil.h | 11 +- 11 files changed, 414 insertions(+), 103 deletions(-) diff --git a/CHANGES b/CHANGES index 19e6d29b..0e76816d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,20 @@ +Sat Nov 2 21:26:56 EET 2002 Pekka Riikonen + + * Added support for encrypted private key files. Now + passphrase must be provided when new key pair is created + (can be empty though), and prompted when loading the + private key. Added support for loading the old-style + non-encrypted private keys as well. Affected files + lib/silccrypt/silcpkcs.[ch], Irssi SILC client and + SILC Server. + + * Fixed silc_get_input to accept input on all terminals. + Affected file lib/silcutil/silcutil.c. + + * Moved the Irssi SILC client key loading and other stuff + to be called after the arguments and UI is initialized. + Affected file irssi/src/silc/core/silc-core.c. Bug #31. + Sat Nov 2 12:53:09 EET 2002 Pekka Riikonen * Fixed connection closing in client library to not crash. diff --git a/TODO b/TODO index 2e5e4059..d1c34b1e 100644 --- a/TODO +++ b/TODO @@ -36,9 +36,6 @@ TODO for SILC Server 1.0 TODO/bugs In SILC Libraries =========================== - o Private key encryption to silc_pkcs_[save/load]_private_key[_data] - function. - o WIN32 silc_net_create_connection_async does not work the same way than on Unix. Do it with threads on WIN32. The function works but is not actually async currently (Fix this to 0.9.x). diff --git a/apps/irssi/src/silc/core/clientutil.c b/apps/irssi/src/silc/core/clientutil.c index cdc0fc9e..818444dd 100644 --- a/apps/irssi/src/silc/core/clientutil.c +++ b/apps/irssi/src/silc/core/clientutil.c @@ -238,7 +238,7 @@ int silc_client_check_silc_dir() silc_create_key_pair(SILC_CLIENT_DEF_PKCS, SILC_CLIENT_DEF_PKCS_LEN, file_public_key, file_private_key, NULL, - NULL, NULL, NULL, FALSE); + NULL, NULL, NULL, NULL, FALSE); printf("Press to continue...\n"); getchar(); } else { @@ -260,7 +260,7 @@ int silc_client_check_silc_dir() silc_create_key_pair(SILC_CLIENT_DEF_PKCS, SILC_CLIENT_DEF_PKCS_LEN, file_public_key, file_private_key, NULL, - NULL, NULL, NULL, FALSE); + NULL, NULL, NULL, NULL, FALSE); printf("Press to continue...\n"); getchar(); } else { @@ -312,7 +312,7 @@ int silc_client_check_silc_dir() silc_create_key_pair(SILC_CLIENT_DEF_PKCS, SILC_CLIENT_DEF_PKCS_LEN, file_public_key, file_private_key, NULL, - NULL, NULL, NULL, FALSE); + NULL, NULL, NULL, NULL, FALSE); printf("Press to continue...\n"); getchar(); } else { @@ -335,6 +335,7 @@ int silc_client_load_keys(SilcClient client) { char pub[256], prv[256]; struct passwd *pw; + bool ret; SILC_LOG_DEBUG(("Loading public and private keys")); @@ -350,6 +351,13 @@ int silc_client_load_keys(SilcClient client) snprintf(pub, sizeof(pub) - 1, "%s/%s", get_irssi_dir(), SILC_CLIENT_PUBLIC_KEY_NAME); - return silc_load_key_pair(pub, prv, &client->pkcs, &client->public_key, - &client->private_key); + /* Try loading first with "" passphrase, for those that didn't set + passphrase for private key, and only if that fails let it prompt + for passphrase. */ + ret = silc_load_key_pair(pub, prv, "", &client->pkcs, &client->public_key, + &client->private_key); + if (!ret) + ret = silc_load_key_pair(pub, prv, NULL, &client->pkcs, + &client->public_key, &client->private_key); + return ret; } diff --git a/apps/irssi/src/silc/core/silc-core.c b/apps/irssi/src/silc/core/silc-core.c index aec63667..b7b9b005 100644 --- a/apps/irssi/src/silc/core/silc-core.c +++ b/apps/irssi/src/silc/core/silc-core.c @@ -310,11 +310,32 @@ void silc_opt_callback(poptContext con, silc_hash_register_default(); silc_hmac_register_default(); silc_create_key_pair(opt_pkcs, opt_bits, NULL, NULL, NULL, - NULL, NULL, NULL, TRUE); + NULL, NULL, NULL, NULL, TRUE); exit(0); } } +static void sig_init_finished(void) +{ + /* Check ~/.silc directory and public and private keys */ + if (!silc_client_check_silc_dir()) { + idletag = -1; + exit(1); + } + + /* Load public and private key */ + if (!silc_client_load_keys(silc_client)) { + idletag = -1; + exit(1); + } + + /* Initialize the SILC client */ + if (!silc_client_init(silc_client)) { + idletag = -1; + exit(1); + } +} + /* Init SILC. Called from src/fe-text/silc.c */ void silc_core_init(void) @@ -383,6 +404,7 @@ void silc_core_init(void) #endif signal_add("setup changed", (SIGNAL_FUNC) sig_setup_changed); + signal_add("irssi init finished", (SIGNAL_FUNC) sig_init_finished); silc_init_userinfo(); @@ -411,24 +433,6 @@ void silc_core_init(void) silc_client->hostname = silc_net_localhost(); silc_client->realname = g_strdup(settings_get_str("real_name")); - /* Check ~/.silc directory and public and private keys */ - if (silc_client_check_silc_dir() == FALSE) { - idletag = -1; - return; - } - - /* Load public and private key */ - if (silc_client_load_keys(silc_client) == FALSE) { - idletag = -1; - return; - } - - /* Initialize the SILC client */ - if (!silc_client_init(silc_client)) { - idletag = -1; - return; - } - silc_log_set_callback(SILC_LOG_INFO, silc_log_misc, NULL); silc_log_set_callback(SILC_LOG_WARNING, silc_log_misc, NULL); silc_log_set_callback(SILC_LOG_ERROR, silc_log_misc, NULL); @@ -473,6 +477,7 @@ void silc_core_deinit(void) signal_emit("chat protocol deinit", 1, chat_protocol_find("SILC")); signal_remove("setup changed", (SIGNAL_FUNC) sig_setup_changed); + signal_remove("irssi init finished", (SIGNAL_FUNC) sig_init_finished); silc_server_deinit(); silc_channels_deinit(); diff --git a/apps/silcd/server.c b/apps/silcd/server.c index b1d06032..42b5e78b 100644 --- a/apps/silcd/server.c +++ b/apps/silcd/server.c @@ -988,8 +988,12 @@ SILC_TASK_CALLBACK(silc_server_connect_router) silc_server_connect_to_router_retry, context, 0, 1, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL); - else + else { silc_server_config_unref(&sconn->conn); + silc_free(sconn->remote_host); + silc_free(sconn->backup_replace_ip); + silc_free(sconn); + } return; } diff --git a/apps/silcd/serverconfig.c b/apps/silcd/serverconfig.c index 39f00afe..0bd9ab57 100644 --- a/apps/silcd/serverconfig.c +++ b/apps/silcd/serverconfig.c @@ -536,9 +536,9 @@ SILC_CONFIG_CALLBACK(fetch_serverinfo) /* try to load specified file, if fail stop config parsing */ if (!silc_pkcs_load_private_key(file_tmp, &server_info->private_key, - SILC_PKCS_FILE_BIN)) + "", 0, SILC_PKCS_FILE_BIN)) if (!silc_pkcs_load_private_key(file_tmp, &server_info->private_key, - SILC_PKCS_FILE_PEM)) { + "", 0, SILC_PKCS_FILE_PEM)) { SILC_SERVER_LOG_ERROR(("Error: Could not load private key file.")); return SILC_CONFIG_EPRINTLINE; } @@ -1202,11 +1202,11 @@ static const SilcConfigTable table_routerconn[] = { }; static const SilcConfigTable table_main[] = { - { "general", SILC_CONFIG_ARG_BLOCK, NULL, table_general }, { "cipher", SILC_CONFIG_ARG_BLOCK, fetch_cipher, table_cipher }, { "hash", SILC_CONFIG_ARG_BLOCK, fetch_hash, table_hash }, { "hmac", SILC_CONFIG_ARG_BLOCK, fetch_hmac, table_hmac }, { "pkcs", SILC_CONFIG_ARG_BLOCK, fetch_pkcs, table_pkcs }, + { "general", SILC_CONFIG_ARG_BLOCK, NULL, table_general }, { "serverinfo", SILC_CONFIG_ARG_BLOCK, fetch_serverinfo, table_serverinfo }, { "logging", SILC_CONFIG_ARG_BLOCK, NULL, table_logging }, { "connectionparams", SILC_CONFIG_ARG_BLOCK, fetch_connparam, table_connparam }, diff --git a/apps/silcd/silcd.c b/apps/silcd/silcd.c index c110a649..b0155df1 100644 --- a/apps/silcd/silcd.c +++ b/apps/silcd/silcd.c @@ -526,7 +526,7 @@ int main(int argc, char **argv) silc_hash_register_default(); silc_hmac_register_default(); silc_create_key_pair(opt_pkcs, opt_bits, pubfile, prvfile, - opt_identifier, NULL, NULL, NULL, FALSE); + opt_identifier, "", NULL, NULL, NULL, FALSE); exit(0); } @@ -539,12 +539,25 @@ int main(int argc, char **argv) if (ret == FALSE) goto fail; + /* Register default crypto stuff since we are going to need them + in the configuration file parsing phase */ + silc_cipher_register_default(); + silc_pkcs_register_default(); + silc_hash_register_default(); + silc_hmac_register_default(); + /* Read configuration files */ silcd->config = silc_server_config_alloc(silcd_config_file); if (silcd->config == NULL) goto fail; silcd->config_file = silcd_config_file; + /* Unregister the default crypto stuff so that configuration takes effect */ + silc_cipher_unregister_all(); + silc_pkcs_unregister_all(); + silc_hash_unregister_all(); + silc_hmac_unregister_all(); + /* Check for another silcd running */ silc_server_checkpid(silcd); diff --git a/lib/silccrypt/silcpkcs.c b/lib/silccrypt/silcpkcs.c index f3282f92..891e9afa 100644 --- a/lib/silccrypt/silcpkcs.c +++ b/lib/silccrypt/silcpkcs.c @@ -904,6 +904,7 @@ silc_pkcs_private_key_encode(SilcPrivateKey private_key, SilcUInt32 *len) ret = silc_calloc(buf->len, sizeof(*ret)); memcpy(ret, buf->data, buf->len); + silc_buffer_clear(buf); silc_buffer_free(buf); return ret; @@ -933,6 +934,7 @@ silc_pkcs_private_key_data_encode(unsigned char *prv, SilcUInt32 prv_len, ret = silc_calloc(buf->len, sizeof(*ret)); memcpy(ret, buf->data, buf->len); + silc_buffer_clear(buf); silc_buffer_free(buf); return ret; @@ -960,11 +962,15 @@ bool silc_pkcs_private_key_decode(unsigned char *data, SilcUInt32 data_len, silc_buffer_unformat(buf, SILC_STR_UI16_NSTRING_ALLOC(&pkcs_name, &pkcs_len), SILC_STR_END); - if (ret == -1) + if (ret == -1) { + SILC_LOG_DEBUG(("Cannot decode private key buffer")); goto err; + } - if (pkcs_len < 1 || pkcs_len > buf->truelen) + if (pkcs_len < 1 || pkcs_len > buf->truelen) { + SILC_LOG_DEBUG(("Malformed private key buffer")); goto err; + } /* See if we support this algorithm (check only if PKCS are registered). */ if (SILC_PKCS_LIST && !silc_pkcs_is_supported(pkcs_name)) { @@ -986,8 +992,10 @@ bool silc_pkcs_private_key_decode(unsigned char *data, SilcUInt32 data_len, (check only if PKCS are registered) */ if (SILC_PKCS_LIST) { silc_pkcs_alloc(pkcs_name, &alg); - if (!alg->pkcs->set_private_key(alg->context, key_data, key_len)) + if (!alg->pkcs->set_private_key(alg->context, key_data, key_len)) { + SILC_LOG_DEBUG(("Could not set private key data")); goto err; + } silc_pkcs_free(alg); } @@ -998,6 +1006,7 @@ bool silc_pkcs_private_key_decode(unsigned char *data, SilcUInt32 data_len, (*private_key)->prv_len = key_len; } + silc_buffer_clear(buf); silc_buffer_free(buf); return TRUE; @@ -1006,6 +1015,7 @@ bool silc_pkcs_private_key_decode(unsigned char *data, SilcUInt32 data_len, silc_free(pkcs_name); if (key_data) silc_free(key_data); + silc_buffer_clear(buf); silc_buffer_free(buf); return FALSE; } @@ -1057,10 +1067,13 @@ bool silc_pkcs_save_public_key(char *filename, SilcPublicKey public_key, { unsigned char *data; SilcUInt32 data_len; + bool ret; data = silc_pkcs_public_key_encode(public_key, &data_len); - return silc_pkcs_save_public_key_internal(filename, data, data_len, - encoding); + ret = silc_pkcs_save_public_key_internal(filename, data, data_len, + encoding); + silc_free(data); + return ret; } /* Saves public key into file */ @@ -1073,17 +1086,123 @@ bool silc_pkcs_save_public_key_data(char *filename, unsigned char *data, encoding); } +#define SILC_PKCS_PRIVATE_KEY_MAGIC 0x738df531 + /* Internal routine to save private key. */ static bool silc_pkcs_save_private_key_internal(char *filename, unsigned char *data, SilcUInt32 data_len, + unsigned char *key, + SilcUInt32 key_len, SilcUInt32 encoding) { - SilcBuffer buf; - SilcUInt32 len; + SilcCipher aes; + SilcHash md5; + SilcHmac sha1hmac; + SilcBuffer buf, enc; + SilcUInt32 len, blocklen; + unsigned char tmp[32], keymat[32]; + int i; - switch(encoding) { + memset(tmp, 0, sizeof(tmp)); + memset(keymat, 0, sizeof(keymat)); + + /* Allocate the AES cipher */ + if (!silc_cipher_alloc("aes-256-cbc", &aes)) { + SILC_LOG_ERROR(("Could not allocate AES cipher, probably not registered")); + return FALSE; + } + blocklen = silc_cipher_get_block_len(aes); + if (blocklen * 2 > sizeof(tmp)) + return FALSE; + + /* Allocate MD5 hash */ + if (!silc_hash_alloc("md5", &md5)) { + SILC_LOG_ERROR(("Could not allocate MD5 hash, probably not registered")); + silc_cipher_free(aes); + return FALSE; + } + + /* Allocate HMAC */ + if (!silc_hmac_alloc("hmac-sha1-96", NULL, &sha1hmac)) { + SILC_LOG_ERROR(("Could not allocate SHA1 HMAC, probably not registered")); + silc_hash_free(md5); + silc_cipher_free(aes); + return FALSE; + } + + /* Derive the encryption key from the provided key material. The key + is 256 bits length, and derived by taking hash of the data, then + re-hashing the data and the previous digest, and using the first and + second digest as the key. */ + silc_hash_init(md5); + silc_hash_update(md5, key, key_len); + silc_hash_final(md5, keymat); + silc_hash_init(md5); + silc_hash_update(md5, key, key_len); + silc_hash_update(md5, keymat, 16); + silc_hash_final(md5, keymat + 16); + + /* Set the key to the cipher */ + silc_cipher_set_key(aes, keymat, sizeof(keymat) * 8); + + /* Encode the buffer to be encrypted. Add padding to it too, at least + block size of the cipher. */ + + /* Allocate buffer for encryption */ + len = silc_hmac_len(sha1hmac); + enc = silc_buffer_alloc_size(data_len + 4 + 4 + + (blocklen + (data_len % blocklen)) + len); + if (!enc) { + silc_hmac_free(sha1hmac); + silc_hash_free(md5); + silc_cipher_free(aes); + return FALSE; + } + + /* Generate padding */ + for (i = 0; i < blocklen + (data_len % blocklen); i++) + tmp[i] = silc_rng_global_get_byte_fast(); + + /* Put magic number */ + SILC_PUT32_MSB(SILC_PKCS_PRIVATE_KEY_MAGIC, enc->data); + silc_buffer_pull(enc, 4); + + /* Encode the buffer */ + silc_buffer_format(enc, + SILC_STR_UI_INT(data_len), + SILC_STR_UI_XNSTRING(data, data_len), + SILC_STR_UI_XNSTRING(tmp, blocklen + (data_len % + blocklen)), + SILC_STR_END); + + /* Encrypt. */ + silc_cipher_encrypt(aes, enc->data, enc->data, enc->len - len, + silc_cipher_get_iv(aes)); + + silc_buffer_push(enc, 4); + + /* Compute HMAC over the encrypted data and append the MAC to data. + The key is the first digest of the original key material. */ + data_len = enc->len - len; + silc_hmac_init_with_key(sha1hmac, keymat, 16); + silc_hmac_update(sha1hmac, enc->data, data_len); + silc_buffer_pull(enc, data_len); + silc_hmac_final(sha1hmac, enc->data, NULL); + silc_buffer_push(enc, data_len); + + /* Cleanup */ + memset(keymat, 0, sizeof(keymat)); + memset(tmp, 0, sizeof(tmp)); + silc_hmac_free(sha1hmac); + silc_hash_free(md5); + silc_cipher_free(aes); + + data = enc->data; + data_len = enc->len; + + switch (encoding) { case SILC_PKCS_FILE_BIN: break; case SILC_PKCS_FILE_PEM: @@ -1092,11 +1211,10 @@ static bool silc_pkcs_save_private_key_internal(char *filename, break; } + /* Encode the data and save to file */ len = data_len + (strlen(SILC_PKCS_PRIVATE_KEYFILE_BEGIN) + strlen(SILC_PKCS_PRIVATE_KEYFILE_END)); - buf = silc_buffer_alloc(len); - silc_buffer_pull_tail(buf, SILC_BUFFER_END(buf)); - + buf = silc_buffer_alloc_size(len); silc_buffer_format(buf, SILC_STR_UI32_STRING(SILC_PKCS_PRIVATE_KEYFILE_BEGIN), SILC_STR_UI_XNSTRING(data, data_len), @@ -1105,39 +1223,38 @@ static bool silc_pkcs_save_private_key_internal(char *filename, /* Save into a file */ if (silc_file_writefile_mode(filename, buf->data, buf->len, 0600)) { + silc_buffer_clear(buf); silc_buffer_free(buf); + silc_buffer_clear(enc); + silc_buffer_free(enc); return FALSE; } + silc_buffer_clear(buf); silc_buffer_free(buf); + silc_buffer_clear(enc); + silc_buffer_free(enc); return TRUE; } /* Saves private key into file. */ -/* XXX The buffer should be encrypted if passphrase is provided. */ bool silc_pkcs_save_private_key(char *filename, SilcPrivateKey private_key, unsigned char *passphrase, + SilcUInt32 passphrase_len, SilcUInt32 encoding) { unsigned char *data; SilcUInt32 data_len; + bool ret; data = silc_pkcs_private_key_encode(private_key, &data_len); - return silc_pkcs_save_private_key_internal(filename, data, data_len, - encoding); -} - -/* Saves private key into file. */ -/* XXX The buffer should be encrypted if passphrase is provided. */ - -bool silc_pkcs_save_private_key_data(char *filename, unsigned char *data, - SilcUInt32 data_len, - unsigned char *passphrase, - SilcUInt32 encoding) -{ - return silc_pkcs_save_private_key_internal(filename, data, data_len, - encoding); + ret = silc_pkcs_save_private_key_internal(filename, data, data_len, + passphrase, passphrase_len, + encoding); + memset(data, 0, data_len); + silc_free(data); + return ret; } /* Loads public key from file and allocates new public key. Returns TRUE @@ -1198,13 +1315,19 @@ bool silc_pkcs_load_public_key(char *filename, SilcPublicKey *public_key, /* Load private key from file and allocates new private key. Returns TRUE if loading was successful. */ -/* XXX Should support encrypted private key files */ bool silc_pkcs_load_private_key(char *filename, SilcPrivateKey *private_key, + unsigned char *passphrase, + SilcUInt32 passphrase_len, SilcUInt32 encoding) { + SilcCipher aes; + SilcHash md5; + SilcHmac sha1hmac; + SilcUInt32 blocklen; + unsigned char tmp[32], keymat[32]; unsigned char *cp, *old, *data, byte; - SilcUInt32 i, data_len, len; + SilcUInt32 i, data_len, len, magic, mac_len; old = data = silc_file_readfile(filename, &data_len); if (!data) @@ -1225,23 +1348,137 @@ bool silc_pkcs_load_private_key(char *filename, SilcPrivateKey *private_key, data = cp; /* Decode private key */ - if (private_key) { - len = data_len - (strlen(SILC_PKCS_PRIVATE_KEYFILE_BEGIN) + - strlen(SILC_PKCS_PRIVATE_KEYFILE_END)); + len = data_len - (strlen(SILC_PKCS_PRIVATE_KEYFILE_BEGIN) + + strlen(SILC_PKCS_PRIVATE_KEYFILE_END)); - switch(encoding) { - case SILC_PKCS_FILE_BIN: - break; - case SILC_PKCS_FILE_PEM: - data = silc_pem_decode(data, len, &len); - break; + switch(encoding) { + case SILC_PKCS_FILE_BIN: + break; + case SILC_PKCS_FILE_PEM: + data = silc_pem_decode(data, len, &len); + if (!data) { + memset(old, 0, data_len); + silc_free(old); + return FALSE; } + break; + } - if (!data || !silc_pkcs_private_key_decode(data, len, private_key)) { + memset(tmp, 0, sizeof(tmp)); + memset(keymat, 0, sizeof(keymat)); + + /* Private key files without the specific magic number are assumed + to be the old-style private keys that are not encrypted. */ + SILC_GET32_MSB(magic, data); + if (magic != SILC_PKCS_PRIVATE_KEY_MAGIC) { + /* Now decode the actual private key */ + if (!silc_pkcs_private_key_decode(data, len, private_key)) { memset(old, 0, data_len); silc_free(old); return FALSE; } + + memset(old, 0, data_len); + silc_free(old); + return TRUE; + } + + /* Allocate the AES cipher */ + if (!silc_cipher_alloc("aes-256-cbc", &aes)) { + SILC_LOG_ERROR(("Could not allocate AES cipher, probably not registered")); + memset(old, 0, data_len); + silc_free(old); + return FALSE; + } + blocklen = silc_cipher_get_block_len(aes); + if (blocklen * 2 > sizeof(tmp)) { + memset(old, 0, data_len); + silc_free(old); + return FALSE; + } + + /* Allocate MD5 hash */ + if (!silc_hash_alloc("md5", &md5)) { + SILC_LOG_ERROR(("Could not allocate MD5 hash, probably not registered")); + silc_cipher_free(aes); + memset(old, 0, data_len); + silc_free(old); + return FALSE; + } + + /* Allocate HMAC */ + if (!silc_hmac_alloc("hmac-sha1-96", NULL, &sha1hmac)) { + SILC_LOG_ERROR(("Could not allocate SHA1 HMAC, probably not registered")); + silc_hash_free(md5); + silc_cipher_free(aes); + memset(old, 0, data_len); + silc_free(old); + return FALSE; + } + + /* Derive the decryption key from the provided key material. The key + is 256 bits length, and derived by taking hash of the data, then + re-hashing the data and the previous digest, and using the first and + second digest as the key. */ + silc_hash_init(md5); + silc_hash_update(md5, passphrase, passphrase_len); + silc_hash_final(md5, keymat); + silc_hash_init(md5); + silc_hash_update(md5, passphrase, passphrase_len); + silc_hash_update(md5, keymat, 16); + silc_hash_final(md5, keymat + 16); + + /* Set the key to the cipher */ + silc_cipher_set_key(aes, keymat, sizeof(keymat) * 8); + + /* First, verify the MAC of the private key data */ + mac_len = silc_hmac_len(sha1hmac); + silc_hmac_init_with_key(sha1hmac, keymat, 16); + silc_hmac_update(sha1hmac, data, len - mac_len); + silc_hmac_final(sha1hmac, tmp, NULL); + if (memcmp(tmp, data + (len - mac_len), mac_len)) { + SILC_LOG_DEBUG(("Integrity check for private key failed")); + memset(keymat, 0, sizeof(keymat)); + memset(tmp, 0, sizeof(tmp)); + silc_hmac_free(sha1hmac); + silc_hash_free(md5); + silc_cipher_free(aes); + memset(old, 0, data_len); + silc_free(old); + return FALSE; + } + data += 4; + len -= 4; + + /* Decrypt the private key buffer */ + silc_cipher_decrypt(aes, data, data, len - mac_len, silc_cipher_get_iv(aes)); + SILC_GET32_MSB(i, data); + if (i > len) { + SILC_LOG_DEBUG(("Bad private key length in buffer!")); + memset(keymat, 0, sizeof(keymat)); + memset(tmp, 0, sizeof(tmp)); + silc_hmac_free(sha1hmac); + silc_hash_free(md5); + silc_cipher_free(aes); + memset(old, 0, data_len); + silc_free(old); + return FALSE; + } + data += 4; + len = i; + + /* Cleanup */ + memset(keymat, 0, sizeof(keymat)); + memset(tmp, 0, sizeof(tmp)); + silc_hmac_free(sha1hmac); + silc_hash_free(md5); + silc_cipher_free(aes); + + /* Now decode the actual private key */ + if (!silc_pkcs_private_key_decode(data, len, private_key)) { + memset(old, 0, data_len); + silc_free(old); + return FALSE; } memset(old, 0, data_len); diff --git a/lib/silccrypt/silcpkcs.h b/lib/silccrypt/silcpkcs.h index 7b704b44..d96d9ef6 100644 --- a/lib/silccrypt/silcpkcs.h +++ b/lib/silccrypt/silcpkcs.h @@ -850,7 +850,8 @@ bool silc_pkcs_save_public_key(char *filename, SilcPublicKey public_key, * * DESCRIPTION * - * Saves public key into file. Returns FALSE on error. + * Saves public key into file. The public key is already encoded as + * data when calling this function. Returns FALSE on error. * ***/ bool silc_pkcs_save_public_key_data(char *filename, unsigned char *data, @@ -862,38 +863,23 @@ bool silc_pkcs_save_public_key_data(char *filename, unsigned char *data, * SYNOPSIS * * bool silc_pkcs_save_private_key(char *filename, - * SilcPrivateKey private_key, - * unsigned char *passphrase, - * SilcUInt32 encoding); + * SilcPrivateKey private_key, + * unsigned char *passphrase, + * SilcUInt32 passphrase_len, + * SilcUInt32 encoding); * * DESCRIPTION * - * Saves private key into file. Returns FALSE on error. + * Saves private key into file. The private key is encrypted into + * the file with the `passphrase' as a key. The encryption algorithm + * is AES with 256 bit key in CBC mode. Returns FALSE on error. * ***/ bool silc_pkcs_save_private_key(char *filename, SilcPrivateKey private_key, unsigned char *passphrase, + SilcUInt32 passphrase_len, SilcUInt32 encoding); -/****f* silccrypt/SilcPKCSAPI/silc_pkcs_save_private_key_data - * - * SYNOPSIS - * - * bool silc_pkcs_save_private_key_data(char *filename, unsigned char *data, - * SilcUInt32 data_len, - * unsigned char *passphrase, - * SilcUInt32 encoding); - * - * DESCRIPTION - * - * Saves private key into file. Returns FALSE on error. - * - ***/ -bool silc_pkcs_save_private_key_data(char *filename, unsigned char *data, - SilcUInt32 data_len, - unsigned char *passphrase, - SilcUInt32 encoding); - /****f* silccrypt/SilcPKCSAPI/silc_pkcs_load_public_key * * SYNOPSIS @@ -915,16 +901,21 @@ bool silc_pkcs_load_public_key(char *filename, SilcPublicKey *public_key, * SYNOPSIS * * bool silc_pkcs_load_private_key(char *filename, - * SilcPrivateKey *private_key, - * SilcUInt32 encoding); + * SilcPrivateKey *private_key, + * unsigned char *passphrase, + * SilcUInt32 passphrase_len, + * SilcUInt32 encoding); * * DESCRIPTION * * Load private key from file and allocates new private key. Returns TRUE - * if loading was successful. + * if loading was successful. The `passphrase' is used as decryption + * key of the private key file. * ***/ bool silc_pkcs_load_private_key(char *filename, SilcPrivateKey *private_key, + unsigned char *passphrase, + SilcUInt32 passphrase_len, SilcUInt32 encoding); #endif /* SILCPKCS_H */ diff --git a/lib/silcutil/silcapputil.c b/lib/silcutil/silcapputil.c index 6c0eccaa..4d05f16c 100644 --- a/lib/silcutil/silcapputil.c +++ b/lib/silcutil/silcapputil.c @@ -59,6 +59,7 @@ bool silc_create_key_pair(const char *pkcs_name, const char *pub_filename, const char *prv_filename, const char *pub_identifier, + const char *passphrase, SilcPKCS *return_pkcs, SilcPublicKey *return_public_key, SilcPrivateKey *return_private_key, @@ -75,6 +76,7 @@ bool silc_create_key_pair(const char *pkcs_name, char *prvfile = prv_filename ? strdup(prv_filename) : NULL; char *alg = pkcs_name ? strdup(pkcs_name) : NULL; char *identifier = pub_identifier ? strdup(pub_identifier) : NULL; + char *pass = passphrase ? strdup(passphrase) : NULL; if (interactive && (!alg || !pub_filename || !prv_filename)) printf("\ @@ -155,7 +157,7 @@ New pair of keys will be created. Please, answer to following questions.\n\ if (!pkfile) { if (interactive) { memset(line, 0, sizeof(line)); - snprintf(line, sizeof(line), "Public key filename [public_key.pub] "); + snprintf(line, sizeof(line), "Public key filename [public_key.pub]: "); pkfile = silc_get_input(line, FALSE); } if (!pkfile) @@ -165,13 +167,21 @@ New pair of keys will be created. Please, answer to following questions.\n\ if (!prvfile) { if (interactive) { memset(line, 0, sizeof(line)); - snprintf(line, sizeof(line), "Public key filename [private_key.prv] "); + snprintf(line, sizeof(line), "Private key filename [private_key.prv]: "); prvfile = silc_get_input(line, FALSE); } if (!prvfile) prvfile = strdup("private_key.prv"); } + if (!pass) { + memset(line, 0, sizeof(line)); + snprintf(line, sizeof(line), "Private key passphrase: "); + pass = silc_get_input(line, TRUE); + if (!pass) + pass = strdup(""); + } + /* Generate keys */ silc_pkcs_alloc(alg, &pkcs); silc_pkcs_generate_key(pkcs, key_len_bits, rng); @@ -192,7 +202,9 @@ New pair of keys will be created. Please, answer to following questions.\n\ key = silc_pkcs_get_private_key(pkcs, &key_len); prv_key = silc_pkcs_private_key_alloc(silc_pkcs_get_name(pkcs), key, key_len); - silc_pkcs_save_private_key(prvfile, prv_key, NULL, SILC_PKCS_FILE_BIN); + silc_pkcs_save_private_key(prvfile, prv_key, + (unsigned char *)pass, strlen(pass), + SILC_PKCS_FILE_BIN); if (return_private_key) *return_private_key = prv_key; else @@ -217,6 +229,8 @@ New pair of keys will be created. Please, answer to following questions.\n\ silc_free(pkfile); silc_free(prvfile); silc_free(identifier); + memset(pass, 0, strlen(pass)); + silc_free(pass); return TRUE; } @@ -225,23 +239,38 @@ New pair of keys will be created. Please, answer to following questions.\n\ bool silc_load_key_pair(const char *pub_filename, const char *prv_filename, + const char *passphrase, SilcPKCS *return_pkcs, SilcPublicKey *return_public_key, SilcPrivateKey *return_private_key) { + char *pass = passphrase ? strdup(passphrase) : NULL; + SILC_LOG_DEBUG(("Loading public and private keys")); if (silc_pkcs_load_public_key((char *)pub_filename, return_public_key, SILC_PKCS_FILE_PEM) == FALSE) if (silc_pkcs_load_public_key((char *)pub_filename, return_public_key, - SILC_PKCS_FILE_BIN) == FALSE) + SILC_PKCS_FILE_BIN) == FALSE) { + memset(pass, 0, strlen(pass)); return FALSE; + } + + if (!pass) { + pass = silc_get_input("Private key passphrase: ", TRUE); + if (!pass) + pass = strdup(""); + } if (silc_pkcs_load_private_key((char *)prv_filename, return_private_key, + (unsigned char *)pass, strlen(pass), SILC_PKCS_FILE_BIN) == FALSE) if (silc_pkcs_load_private_key((char *)prv_filename, return_private_key, - SILC_PKCS_FILE_PEM) == FALSE) + (unsigned char *)pass, strlen(pass), + SILC_PKCS_FILE_PEM) == FALSE) { + memset(pass, 0, strlen(pass)); return FALSE; + } if (return_pkcs) { silc_pkcs_alloc((*return_public_key)->name, return_pkcs); @@ -249,6 +278,7 @@ bool silc_load_key_pair(const char *pub_filename, silc_pkcs_private_key_set(*return_pkcs, *return_private_key); } + memset(pass, 0, strlen(pass)); return TRUE; } diff --git a/lib/silcutil/silcapputil.h b/lib/silcutil/silcapputil.h index f054fa4d..827c2ff9 100644 --- a/lib/silcutil/silcapputil.h +++ b/lib/silcutil/silcapputil.h @@ -44,6 +44,7 @@ * const char *pub_filename, * const char *prv_filename, * const char *pub_identifier, + * const char *passphrase, * SilcPKCS *return_pkcs, * SilcPublicKey *return_public_key, * SilcPrivateKey *return_private_key, @@ -60,6 +61,10 @@ * "UN=foobar, HN=hostname"), or if NULL the routine generates it * automatically. * + * The `passphrase' is the passphrase that is used to encrypt the + * private key file. It is recommended that you would protect your + * private key file with a passphrase. + * * The routine returns FALSE if error occurs during key generation. * Function returns TRUE when success and returns the created SilcPKCS * object, which can be used to perform public key cryptography into @@ -84,6 +89,7 @@ bool silc_create_key_pair(const char *pkcs_name, const char *pub_filename, const char *prv_filename, const char *pub_identifier, + const char *passphrase, SilcPKCS *return_pkcs, SilcPublicKey *return_public_key, SilcPrivateKey *return_private_key, @@ -96,6 +102,7 @@ bool silc_create_key_pair(const char *pkcs_name, * bool silc_load_key_pair(const char *pub_filename, * const char *prv_filename, * SilcPKCS *return_pkcs, + * const char *passphrase, * SilcPublicKey *return_public_key, * SilcPrivateKey *return_private_key); * @@ -107,11 +114,13 @@ bool silc_create_key_pair(const char *pkcs_name, * public key into `return_public_key' pointer, private key into * `return_private_key' pointer and the SilcPKCS object to the * `return_pkcs'. The SilcPKCS can be used to perform public key - * cryptographic operations. + * cryptographic operations. The `passphrase' is the passphrase + * which will be used to decrypt the private key file. * ***/ bool silc_load_key_pair(const char *pub_filename, const char *prv_filename, + const char *passphrase, SilcPKCS *return_pkcs, SilcPublicKey *return_public_key, SilcPrivateKey *return_private_key); -- 2.24.0