/* silcpgp_seckey.c Author: Pekka Riikonen Copyright (C) 2007 - 2008 Pekka Riikonen 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; version 2 of the License. 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. */ #include "silccrypto.h" #include "rsa.h" #include "dsa.h" /*************************** Private Key Routines ***************************/ /* Decode OpenPGP Secret Key packet */ int silc_pgp_packet_private_key_decode(unsigned char *key, SilcUInt32 key_len, const char *passphrase, SilcUInt32 passphrase_len, SilcPGPPrivateKey privkey) { SilcBufferStruct keybuf; SilcPGPPublicKey pubkey = NULL; unsigned char *iv, *salt = NULL, *dec = NULL; unsigned char *dec_key; SilcUInt32 iv_len = 0; SilcUInt16 pcksum; SilcUInt8 s2k_usage, s2k_count; SilcCipher cipher = NULL; int ret, ret_len; SILC_LOG_DEBUG(("Parsing OpenPGP private key")); if (!key || !key_len) return 0; silc_buffer_set(&keybuf, key, key_len); SILC_LOG_HEXDUMP(("OpenPGP private key"), key, key_len); pubkey = silc_calloc(1, sizeof(*pubkey)); if (!pubkey) { silc_free(privkey); return 0; } /* Parse public key from the private key */ ret = silc_pgp_packet_public_key_decode(key, key_len, pubkey); if (!ret) { SILC_LOG_DEBUG(("Malformed private key")); goto err; } if (!silc_buffer_pull(&keybuf, ret)) goto err; if (silc_buffer_len(&keybuf) < 12) goto err; /* Decode algorithm info */ s2k_usage = keybuf.data[0]; silc_buffer_pull(&keybuf, 1); switch (s2k_usage) { case 0: /* Plaintext private key */ SILC_LOG_DEBUG(("Private key is not encrypted")); break; case 254: case 255: /* Encrypted, string-to-key (S2K) specifier present */ if (silc_buffer_unformat(&keybuf, SILC_STR_ADVANCE, SILC_STR_UINT8(&privkey->cipher), SILC_STR_UINT8(&privkey->s2k_type), SILC_STR_UINT8(&privkey->s2k_hash), SILC_STR_END) < 0) { SILC_LOG_DEBUG(("Malformed S2K specifier in private key")); goto err; } SILC_LOG_DEBUG(("Private key S2K type %d", privkey->s2k_type)); switch (privkey->s2k_type) { case SILC_PGP_S2K_SIMPLE: /* Simple S2K */ iv_len = 0; break; case SILC_PGP_S2K_SALTED: /* Salted S2K */ if (silc_buffer_unformat(&keybuf, SILC_STR_ADVANCE, SILC_STR_DATA(&salt, 8), SILC_STR_END) < 0) { SILC_LOG_DEBUG(("Malformed S2K specifier in private key")); goto err; } break; case SILC_PGP_S2K_ITERATED_SALTED: /* Iterated and salted S2K */ if (silc_buffer_unformat(&keybuf, SILC_STR_ADVANCE, SILC_STR_DATA(&salt, 8), SILC_STR_UINT8(&s2k_count), SILC_STR_END) < 0) { SILC_LOG_DEBUG(("Malformed S2K specifier in private key")); goto err; } /* Get the iterator octet count, formula comes from the RFC */ privkey->s2k_count = ((SilcUInt32)16 + (s2k_count & 15)) << ((s2k_count >> 4) + 6); break; default: SILC_LOG_DEBUG(("Malformed private key")); goto err; } break; default: /* Encrypted with given algorithm */ privkey->cipher = keybuf.data[0]; silc_buffer_pull(&keybuf, 1); break; } ret_len = silc_buffer_headlen(&keybuf); /* Decrypt */ if (privkey->cipher) { cipher = silc_pgp_cipher_alloc(privkey->cipher); if (!cipher) goto err; iv_len = silc_cipher_get_iv_len(cipher); /* Get IV */ if (!silc_buffer_unformat(&keybuf, SILC_STR_ADVANCE, SILC_STR_DATA(&iv, iv_len), SILC_STR_END)) { SILC_LOG_DEBUG(("Malformed private key, IV not present")); goto err; } ret_len += iv_len; SILC_LOG_HEXDUMP(("IV, iv_len %d", iv_len), iv, iv_len); /* Generate decryption key from passphrase */ dec_key = silc_pgp_s2k(privkey->s2k_type, privkey->s2k_hash, passphrase, passphrase_len, silc_cipher_get_key_len(cipher) / 8, salt, privkey->s2k_count, NULL); if (!dec_key) goto err; SILC_LOG_HEXDUMP(("S2K"), dec_key, silc_cipher_get_key_len(cipher) / 8); /* Set decryption key */ silc_cipher_set_key(cipher, dec_key, silc_cipher_get_key_len(cipher), FALSE); silc_cipher_set_iv(cipher, iv); /* Decrypt the private key */ SILC_LOG_DEBUG(("Decrypting private key")); dec = silc_memdup(silc_buffer_data(&keybuf), silc_buffer_len(&keybuf)); if (!dec) goto err; silc_buffer_set(&keybuf, dec, silc_buffer_len(&keybuf)); if (pubkey->version >= 4) { silc_cipher_decrypt(cipher, keybuf.data, keybuf.data, silc_buffer_len(&keybuf), NULL); } else { /* Versions 2 and 3 */ /* Support may be added for these at some point. */ SILC_LOG_ERROR(("Version %d encrypted private keys not supported", pubkey->version)); goto err; } } /* Verify checksum to see if decryption succeeded */ if (s2k_usage == 254) { SilcHash sha1; unsigned char cksum_hash[20], pcksum_hash[20]; if (!silc_buffer_push_tail(&keybuf, 20)) { SILC_LOG_DEBUG(("Malformed private key, checksum not present")); goto err; } memcpy(pcksum_hash, keybuf.tail, 20); if (!silc_hash_alloc("sha1", &sha1)) goto err; silc_hash_init(sha1); silc_hash_update(sha1, silc_buffer_data(&keybuf), silc_buffer_len(&keybuf)); silc_hash_final(sha1, cksum_hash); silc_hash_free(sha1); /* Verify */ if (memcmp(cksum_hash, pcksum_hash, sizeof(cksum_hash))) { SILC_LOG_DEBUG(("Private key checksum invalid, decryption failed")); goto err; } ret_len += 20; } else { SilcUInt16 cksum = 0; int i; if (silc_buffer_unformat(&keybuf, SILC_STR_ADVANCE, SILC_STR_UINT16(&pcksum), SILC_STR_END) < 0) { SILC_LOG_DEBUG(("Malformed private key, checksum not present")); goto err; } for (i = 0; i < silc_buffer_len(&keybuf); i++) cksum = (cksum + keybuf.data[i]) % 0x10000; /* Verify */ if (cksum != pcksum) { SILC_LOG_DEBUG(("Private key checksum invalid, decryption failed")); goto err; } ret_len += 2; } /* Import the algorithm private key */ ret = pubkey->pkcs->import_private_key(pubkey->pkcs, silc_buffer_data(&keybuf), silc_buffer_len(&keybuf), &privkey->private_key); if (!ret) { SILC_LOG_DEBUG(("Malformed private key")); goto err; } silc_free(dec); privkey->public_key = pubkey; return ret_len + ret; err: if (pubkey) silc_pgp_public_key_free(pubkey); silc_free(dec); return 0; } /* Decode private key from PGP packets */ SilcBool silc_pgp_private_key_decode(SilcList *list, const char *passphrase, SilcUInt32 passphrase_len, SilcPGPPrivateKey *ret_private_key) { SilcPGPPrivateKey privkey, subkey; unsigned char *data; SilcUInt32 data_len; SilcPGPPacket prv, packet; SILC_LOG_DEBUG(("Parse OpenPGP private key")); privkey = silc_calloc(1, sizeof(*privkey)); if (!privkey) goto err; /* First packet must be private key packet */ prv = silc_list_get(*list); if (!prv) goto err; if (silc_pgp_packet_get_tag(prv) != SILC_PGP_PACKET_SECKEY && silc_pgp_packet_get_tag(prv) != SILC_PGP_PACKET_SECKEY_SUB) goto err; /* Parse the private key */ data = silc_pgp_packet_get_data(prv, &data_len); if (!silc_pgp_packet_private_key_decode(data, data_len, passphrase, passphrase_len, privkey)) goto err; /* Parse any and all packets until we hit end of the packets or next private key in the list. We simply copy the raw data, and actual parsing is done later if and when the packets are needed. */ if (silc_pgp_packet_get_tag(prv) == SILC_PGP_PACKET_SECKEY) { silc_list_init(privkey->packets, struct SilcPGPPacketStruct, next); /* Copy the raw private key packet */ packet = silc_pgp_packet_copy(prv); if (packet) silc_list_add(privkey->packets, packet); while ((packet = silc_list_get(*list))) { SILC_LOG_DEBUG(("Adding %d (%s) packet to private key", silc_pgp_packet_get_tag(packet), silc_pgp_packet_name(silc_pgp_packet_get_tag(packet)))); switch (silc_pgp_packet_get_tag(packet)) { case SILC_PGP_PACKET_SECKEY: /* Next private key, stop decoding. Set list pointer so that the list points to the next private key. */ list->current = packet; break; case SILC_PGP_PACKET_SECKEY_SUB: /* Parse subkeys recursively */ list->current = packet; if (!silc_pgp_private_key_decode(list, passphrase, passphrase_len, &subkey)) goto err; if (!privkey->subkeys) { privkey->subkeys = silc_dlist_init(); if (!privkey->subkeys) goto err; } silc_dlist_add(privkey->subkeys, subkey); default: /* Copy packet to the private key */ packet = silc_pgp_packet_copy(packet); if (packet) silc_list_add(privkey->packets, packet); break; } } } if (ret_private_key) *ret_private_key = privkey; return TRUE; err: silc_free(privkey); return FALSE; } /* Free private key */ void silc_pgp_private_key_free(SilcPGPPrivateKey private_key) { SilcPGPPrivateKey p; SilcPGPPacket packet; if (private_key->public_key && private_key->public_key->pkcs) private_key->public_key->pkcs->private_key_free(private_key-> public_key->pkcs, private_key->private_key); silc_pgp_public_key_free(private_key->public_key); if (private_key->subkeys) { silc_dlist_start(private_key->subkeys); while ((p = silc_dlist_get(private_key->subkeys))) silc_pgp_private_key_free(p); silc_dlist_uninit(private_key->subkeys); } silc_list_start(private_key->packets); while ((packet = silc_list_get(private_key->packets))) silc_pgp_packet_free(packet); silc_free(private_key); }