Added OpenPGP library to lib/silcpgp
[crypto.git] / lib / silcpgp / silcpgp_pubkey.c
diff --git a/lib/silcpgp/silcpgp_pubkey.c b/lib/silcpgp/silcpgp_pubkey.c
new file mode 100644 (file)
index 0000000..c33fbae
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+
+  silcpgp_pubkey.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"
+
+/************************ Static utility functions **************************/
+
+/* Computes fingerprint of the OpenPGP public key and saves it to the key.
+   Saves also the key IDs to public key context. */
+
+static SilcBool silc_pgp_compute_fingerprint(SilcBuffer keybuf,
+                                            SilcPGPPublicKey pubkey)
+{
+  SILC_LOG_DEBUG(("Computing fingerprint"));
+
+  if (pubkey->version >= 4) {
+    /* Version 4 */
+    SilcHash sha1;
+    unsigned char tmp[3];
+
+    if (!silc_hash_alloc("sha1", &sha1))
+      return FALSE;
+
+    tmp[0] = 0x99;
+    SILC_PUT16_MSB(silc_buffer_len(keybuf), tmp + 1);
+
+    silc_hash_init(sha1);
+    silc_hash_update(sha1, tmp, 3);
+    silc_hash_update(sha1, silc_buffer_data(keybuf), silc_buffer_len(keybuf));
+    silc_hash_final(sha1, pubkey->fingerprint);
+    silc_hash_free(sha1);
+
+    /* Save key ID */
+    memcpy(pubkey->key_id, pubkey->fingerprint + 12, 8);
+  } else {
+    /* Versions 2 and 3 */
+    SilcHash md5;
+    unsigned char *n, *e;
+    SilcUInt16 n_len, e_len;
+
+    if (!silc_hash_alloc("md5", &md5))
+      return FALSE;
+
+    silc_buffer_format(keybuf,
+                      SILC_STR_OFFSET(8),
+                      SILC_STR_UI16_NSTRING(&n, &n_len),
+                      SILC_STR_UI16_NSTRING(&e, &e_len),
+                      SILC_STR_END);
+
+    n_len = (n_len + 7) / 8;
+    e_len = (e_len + 7) / 8;
+
+    silc_hash_init(md5);
+    silc_hash_update(md5, n, n_len);
+    silc_hash_update(md5, e, e_len);
+    silc_hash_final(md5, pubkey->fingerprint);
+    silc_hash_free(md5);
+
+    /* Save key ID */
+    memcpy(pubkey->key_id, n + (n_len - 8), 8);
+  }
+
+  return TRUE;
+}
+
+/*************************** Public Key Routines ****************************/
+
+/* Decode OpenPGP Public Key packet */
+
+int silc_pgp_packet_public_key_decode(unsigned char *key, SilcUInt32 key_len,
+                                     SilcPGPPublicKey pubkey)
+{
+  SilcBufferStruct keybuf, fbuf;
+  const SilcPKCSAlgorithm *pkcs;
+  int ret;
+
+  SILC_LOG_DEBUG(("Parse OpenPGP public key packet"));
+
+  if (!key || !key_len)
+    return 0;
+  silc_buffer_set(&keybuf, key, key_len);
+
+  SILC_LOG_HEXDUMP(("OpenPGP public key"), key, key_len);
+
+  /* Decode the key */
+  if (silc_buffer_unformat(&keybuf,
+                          SILC_STR_ADVANCE,
+                          SILC_STR_UINT8(&pubkey->version),
+                          SILC_STR_UI_INT(&pubkey->created),
+                          SILC_STR_END) < 0) {
+    SILC_LOG_DEBUG(("Malformed public key"));
+    goto err;
+  }
+
+  if (pubkey->version < 2) {
+    SILC_LOG_DEBUG(("Invalid version %d", pubkey->version));
+    goto err;
+  }
+
+  SILC_LOG_DEBUG(("Public key version %d", pubkey->version));
+
+  if (pubkey->version <= 3) {
+    /* Versions 2 and 3 */
+    if (silc_buffer_unformat(&keybuf,
+                            SILC_STR_ADVANCE,
+                            SILC_STR_UINT16(&pubkey->valid),
+                            SILC_STR_UINT8(&pubkey->algorithm),
+                            SILC_STR_END) < 0) {
+      SILC_LOG_DEBUG(("Malformed public key"));
+      goto err;
+    }
+  } else {
+    /* Version 4 */
+    if (silc_buffer_unformat(&keybuf,
+                            SILC_STR_ADVANCE,
+                            SILC_STR_UINT8(&pubkey->algorithm),
+                            SILC_STR_END) < 0) {
+      SILC_LOG_DEBUG(("Malformed public key"));
+      goto err;
+    }
+  }
+
+  SILC_LOG_DEBUG(("Parse algorithm %d", pubkey->algorithm));
+
+  /* Decode the public key algorithm */
+  switch (pubkey->algorithm) {
+  case SILC_PGP_PKCS_RSA:
+  case SILC_PGP_PKCS_RSA_ENC_ONLY:
+  case SILC_PGP_PKCS_RSA_SIG_ONLY:
+    /* Get PKCS object */
+    pkcs = silc_pkcs_find_algorithm("rsa", "openpgp");
+    if (!pkcs) {
+      SILC_LOG_ERROR(("Unsupported PKCS algorithm (rsa/openpgp)"));
+      goto err;
+    }
+    break;
+
+  case SILC_PGP_PKCS_DSA:
+    /* Get PKCS object */
+    pkcs = silc_pkcs_find_algorithm("dsa", "openpgp");
+    if (!pkcs) {
+      SILC_LOG_ERROR(("Unsupported PKCS algorithm (dsa/openpgp)"));
+      goto err;
+    }
+    break;
+
+  case SILC_PGP_PKCS_ELGAMAL_ENC_ONLY:
+  case SILC_PGP_PKCS_ELGAMAL:
+    /* Get PKCS object */
+    pkcs = silc_pkcs_find_algorithm("elgamal", "openpgp");
+    if (!pkcs) {
+      SILC_LOG_ERROR(("Unsupported PKCS algorithm (elgamal/openpgp)"));
+      goto err;
+    }
+    break;
+
+  default:
+    SILC_LOG_DEBUG(("Unsupported OpenPGP public key algorithm %d",
+                   pubkey->algorithm));
+    goto err;
+  }
+  pubkey->pkcs = pkcs;
+
+  /* Import the algorithm public key */
+  ret = pkcs->import_public_key(pkcs, silc_buffer_data(&keybuf),
+                               silc_buffer_len(&keybuf),
+                               &pubkey->public_key);
+  if (!ret) {
+    SILC_LOG_DEBUG(("Malformed public key"));
+    goto err;
+  }
+
+  /* Compute and save fingerprint */
+  silc_buffer_set(&fbuf, key, silc_buffer_headlen(&keybuf) + ret);
+  if (!silc_pgp_compute_fingerprint(&fbuf, pubkey))
+    goto err;
+
+  return silc_buffer_headlen(&keybuf) + ret;
+
+ err:
+  return 0;
+}
+
+/* Decode public key from PGP packets */
+
+SilcBool silc_pgp_public_key_decode(SilcList *list,
+                                   SilcPGPPublicKey *ret_public_key)
+{
+  SilcPGPPublicKey pubkey, subkey;
+  unsigned char *data;
+  SilcUInt32 data_len;
+  SilcPGPPacket pub, packet;
+
+  SILC_LOG_DEBUG(("Parse OpenPGP public key"));
+
+  pubkey = silc_calloc(1, sizeof(*pubkey));
+  if (!pubkey)
+    goto err;
+
+  /* First packet must be public key packet */
+  pub = silc_list_get(*list);
+  if (!pub)
+    goto err;
+  if (silc_pgp_packet_get_tag(pub) != SILC_PGP_PACKET_PUBKEY &&
+      silc_pgp_packet_get_tag(pub) != SILC_PGP_PACKET_PUBKEY_SUB)
+    goto err;
+
+  /* Parse the public key */
+  data = silc_pgp_packet_get_data(pub, &data_len);
+  if (!silc_pgp_packet_public_key_decode(data, data_len, pubkey))
+    goto err;
+
+  /* Parse any and all packets until we hit end of the packets or next
+     public 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(pub) == SILC_PGP_PACKET_PUBKEY) {
+    silc_list_init(pubkey->packets, struct SilcPGPPacketStruct, next);
+
+    /* Copy the raw public key packet */
+    packet = silc_pgp_packet_copy(pub);
+    if (packet)
+      silc_list_add(pubkey->packets, packet);
+
+    while ((packet = silc_list_get(*list))) {
+      SILC_LOG_DEBUG(("Adding %d (%s) packet to public 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_PUBKEY:
+       /* Next public key, stop decoding.  Set list pointer so that the list
+          points to the next public key. */
+       list->current = packet;
+       break;
+
+      case SILC_PGP_PACKET_PUBKEY_SUB:
+       /* Parse subkeys recursively */
+       list->current = packet;
+       if (!silc_pgp_public_key_decode(list, &subkey))
+         goto err;
+
+       if (!pubkey->subkeys) {
+         pubkey->subkeys = silc_dlist_init();
+         if (!pubkey->subkeys)
+           goto err;
+       }
+       silc_dlist_add(pubkey->subkeys, subkey);
+
+      default:
+       /* Copy packet to the public key */
+       packet = silc_pgp_packet_copy(packet);
+       if (packet)
+         silc_list_add(pubkey->packets, packet);
+       break;
+      }
+    }
+  }
+
+  if (ret_public_key)
+    *ret_public_key = pubkey;
+
+  return TRUE;
+
+ err:
+  silc_free(pubkey);
+  return FALSE;
+}
+
+/* Free public key */
+
+void silc_pgp_public_key_free(SilcPGPPublicKey public_key)
+{
+  SilcPGPPublicKey p;
+  SilcPGPPacket packet;
+
+  if (public_key->pkcs)
+    public_key->pkcs->public_key_free(public_key->pkcs,
+                                     public_key->public_key);
+
+  if (public_key->subkeys) {
+    silc_dlist_start(public_key->subkeys);
+    while ((p = silc_dlist_get(public_key->subkeys)))
+      silc_pgp_public_key_free(p);
+    silc_dlist_uninit(public_key->subkeys);
+  }
+
+  silc_list_start(public_key->packets);
+  while ((packet = silc_list_get(public_key->packets)))
+    silc_pgp_packet_free(packet);
+
+  silc_free(public_key);
+}