Added OpenPGP library to lib/silcpgp
[crypto.git] / lib / silcpgp / silcpgp_seckey.c
diff --git a/lib/silcpgp/silcpgp_seckey.c b/lib/silcpgp/silcpgp_seckey.c
new file mode 100644 (file)
index 0000000..acfddbc
--- /dev/null
@@ -0,0 +1,385 @@
+/*
+
+  silcpgp_seckey.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  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);
+}