Added OpenPGP library to lib/silcpgp
[crypto.git] / lib / silcpgp / silcpgp.c
diff --git a/lib/silcpgp/silcpgp.c b/lib/silcpgp/silcpgp.c
new file mode 100644 (file)
index 0000000..f882c9e
--- /dev/null
@@ -0,0 +1,606 @@
+/*
+
+  silcpgp.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"
+
+/************************* Static utility functions *************************/
+
+/* Parse PGP packet */
+
+static int
+silc_pgp_packet_parse(const unsigned char *data, SilcUInt32 data_len,
+                     SilcPGPPacket *ret_packet)
+{
+  SilcPGPPacket packet;
+  SilcBufferStruct buf;
+  SilcUInt8 tag;
+  SilcBool partial = FALSE;
+  SilcUInt32 len;
+
+  SILC_LOG_DEBUG(("Parsing OpenPGP packet"));
+
+  if (!data || data_len < 2)
+    return 0;
+  silc_buffer_set(&buf, (unsigned char *)data, data_len);
+
+  packet = silc_calloc(1, sizeof(*packet));
+  if (!packet)
+    return 0;
+
+  while (silc_buffer_len(&buf) > 0) {
+    tag = buf.data[0];
+    silc_buffer_pull(&buf, 1);
+
+    if (!(tag & 0x80)) {
+      SILC_LOG_DEBUG(("Invalid tag"));
+      goto err;
+    }
+
+    if (tag & 0x40) {
+      /* New format */
+
+      /* Packet type */
+      if (!packet->tag) {
+       packet->tag = tag & 0x3f;
+       SILC_LOG_DEBUG(("Packet type %d (%s)", packet->tag,
+                       silc_pgp_packet_name(packet->tag)));
+      }
+
+      /* Packet length */
+      len = buf.data[0];
+      if (len >= 192 && len <= 223) {
+       /* 2 byte length */
+       if (silc_buffer_len(&buf) < 2)
+         goto err;
+       len = ((len - 192) << 8) + buf.data[1] + 192;
+       silc_buffer_pull(&buf, 2);
+      } else if (len == 255) {
+       /* 5 byte length */
+       if (silc_buffer_len(&buf) < 5)
+         goto err;
+       silc_buffer_pull(&buf, 1);
+       SILC_GET32_MSB(len, buf.data);
+       silc_buffer_pull(&buf, 4);
+      } else if (len >= 224 && len < 255) {
+       /* Partial length */
+       if (silc_buffer_len(&buf) < 1)
+         goto err;
+       len = 1 << (len & 0x1f);
+       silc_buffer_pull(&buf, 1);
+       partial = TRUE;
+      }
+    } else {
+      /* Old format */
+      SilcUInt8 llen;
+
+      /* Pakcet type */
+      if (!packet->tag) {
+       packet->tag = (tag >> 2) & 0x0f;
+       SILC_LOG_DEBUG(("Packet type %d (%s)", packet->tag,
+                       silc_pgp_packet_name(packet->tag)));
+      }
+
+      if ((tag & 0x03) == 3) {
+       /* Indeterminate length, use whole buffer */
+       len = silc_buffer_len(&buf);
+      } else {
+       for (llen = 1 << (tag & 0x03), len = 0 ; llen; llen--) {
+         len <<= 8;
+         len |= buf.data[0];
+         if (!silc_buffer_pull(&buf, 1))
+           goto err;
+       }
+      }
+    }
+
+    if (silc_buffer_len(&buf) < len) {
+      SILC_LOG_DEBUG(("Too short packet (%d < %d)",
+                     silc_buffer_len(&buf), len));
+      goto err;
+    }
+
+    /* Get data */
+    if (silc_buffer_format(&packet->data,
+                          SILC_STR_ADVANCE,
+                          SILC_STR_DATA(silc_buffer_data(&buf), len),
+                          SILC_STR_END) < 0)
+      goto err;
+
+    silc_buffer_pull(&buf, len);
+
+    if (!partial)
+      break;
+  }
+
+  silc_buffer_start(&packet->data);
+
+  SILC_LOG_HEXDUMP(("Packet, len %d", silc_buffer_len(&packet->data)),
+                   silc_buffer_data(&packet->data),
+                   silc_buffer_len(&packet->data));
+
+  *ret_packet = packet;
+
+  return silc_buffer_headlen(&buf);
+
+ err:
+  silc_buffer_purge(&packet->data);
+  silc_free(packet);
+  return 0;
+}
+
+/****************************** PGP Algorithms ******************************/
+
+/* Allocates cipher */
+
+SilcCipher silc_pgp_cipher_alloc(SilcPGPCipher cipher)
+{
+  SilcCipher c;
+
+  SILC_LOG_DEBUG(("Allocate cipher %d", cipher));
+
+  switch (cipher) {
+  case SILC_PGP_CIPHER_IDEA:
+    if (!silc_cipher_alloc("idea-128-cfb", &c)) {
+      SILC_LOG_ERROR(("Unsupported algorithm idea-128-cfb"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_CIPHER_3DES:
+    if (!silc_cipher_alloc("3des-168-cfb", &c)) {
+      SILC_LOG_ERROR(("Unsupported algorithm 3des-168-cfb"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_CIPHER_CAST5:
+    if (!silc_cipher_alloc("cast5-128-cfb", &c)) {
+      SILC_LOG_ERROR(("Unsupported algorithm cast5-168-cfb"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_CIPHER_BLOWFISH:
+    if (!silc_cipher_alloc("blowfish-128-cfb", &c)) {
+      SILC_LOG_ERROR(("Unsupported algorithm blowfish-128-cfb"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_CIPHER_AES128:
+    if (!silc_cipher_alloc("aes-128-cfb", &c)) {
+      SILC_LOG_ERROR(("Unsupported algorithm aes-128-cfb"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_CIPHER_AES192:
+    if (!silc_cipher_alloc("aes-192-cfb", &c)) {
+      SILC_LOG_ERROR(("Unsupported algorithm aes-192-cfb"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_CIPHER_AES256:
+    if (!silc_cipher_alloc("aes-256-cfb", &c)) {
+      SILC_LOG_ERROR(("Unsupported algorithm aes-256-cfb"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_CIPHER_TWOFISH:
+    if (!silc_cipher_alloc("twofish-256-cfb", &c)) {
+      SILC_LOG_ERROR(("Unsupported algorithm twofish-256-cfb"));
+      return NULL;
+    }
+    break;
+
+  default:
+    return NULL;
+    break;
+  }
+
+  return c;
+}
+
+/* Allocates hash function */
+
+SilcHash silc_pgp_hash_alloc(SilcPGPHash hash)
+{
+  SilcHash h;
+
+  SILC_LOG_DEBUG(("Allocate hash %d", hash));
+
+  switch (hash) {
+  case SILC_PGP_HASH_MD5:
+    if (!silc_hash_alloc("md5", &h)) {
+      SILC_LOG_ERROR(("Unsupported algorithm md5"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_HASH_SHA1:
+    if (!silc_hash_alloc("sha1", &h)) {
+      SILC_LOG_ERROR(("Unsupported algorithm sha1"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_HASH_RIPEMD160:
+    if (!silc_hash_alloc("ripemd160", &h)) {
+      SILC_LOG_ERROR(("Unsupported algorithm ripemd160"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_HASH_SHA256:
+    if (!silc_hash_alloc("sha256", &h)) {
+      SILC_LOG_ERROR(("Unsupported algorithm sha256"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_HASH_SHA384:
+    if (!silc_hash_alloc("sha384", &h)) {
+      SILC_LOG_ERROR(("Unsupported algorithm sha384"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_HASH_SHA512:
+    if (!silc_hash_alloc("sha512", &h)) {
+      SILC_LOG_ERROR(("Unsupported algorithm sha512"));
+      return NULL;
+    }
+    break;
+
+  case SILC_PGP_HASH_SHA224:
+    if (!silc_hash_alloc("sha244", &h)) {
+      SILC_LOG_ERROR(("Unsupported algorithm sha224"));
+      return NULL;
+    }
+    break;
+
+  default:
+    return NULL;
+    break;
+  }
+
+  return h;
+}
+
+/************************* OpenPGP Packet routines **************************/
+
+#ifdef SILC_DEBUG
+/* Return packet tag as string */
+
+const char *silc_pgp_packet_name(SilcPGPPacketTag tag)
+{
+  if (tag == SILC_PGP_PACKET_PKENC_SK)
+    return "PKENC_SK";
+  if (tag == SILC_PGP_PACKET_SIGNATURE)
+    return "SIGNATURE";
+  if (tag == SILC_PGP_PACKET_SENC_SK)
+    return "SENC_SK";
+  if (tag == SILC_PGP_PACKET_OP_SIGNATURE)
+    return "OP_SIGNATUER";
+  if (tag == SILC_PGP_PACKET_SECKEY)
+    return "SECKEY";
+  if (tag == SILC_PGP_PACKET_PUBKEY)
+    return "PUBKEY";
+  if (tag == SILC_PGP_PACKET_SECKEY_SUB)
+    return "SECKEY_SUB";
+  if (tag == SILC_PGP_PACKET_COMP_DATA)
+    return "COMP_DATA";
+  if (tag == SILC_PGP_PACKET_SENC_DATA)
+    return "SENC_DATA";
+  if (tag == SILC_PGP_PACKET_MARKER)
+    return "MARKER";
+  if (tag == SILC_PGP_PACKET_LITERAL_DATA)
+    return "LITERAL_DATA";
+  if (tag == SILC_PGP_PACKET_TRUST)
+    return "TRUST";
+  if (tag == SILC_PGP_PACKET_USER_ID)
+    return "USER_ID";
+  if (tag == SILC_PGP_PACKET_PUBKEY_SUB)
+    return "PUBKEY_SUB";
+  if (tag == SILC_PGP_PACKET_USER_ATTR)
+    return "USER_ATTR";
+  if (tag == SILC_PGP_PACKET_SENC_I_DATA)
+    return "SENC_I_DATA";
+  if (tag == SILC_PGP_PACKET_MDC)
+    return "MDC";
+  return "UNKNOWN";
+}
+#endif /* SILC_DEBUG */
+
+/* Copy packet */
+
+SilcPGPPacket silc_pgp_packet_copy(SilcPGPPacket packet)
+{
+  SilcPGPPacket newpacket;
+  unsigned char *data;
+
+  newpacket = silc_calloc(1, sizeof(*newpacket));
+  if (!newpacket)
+    return NULL;
+
+  data = silc_memdup(packet->data.head, silc_buffer_truelen(&packet->data));
+  if (!data) {
+    silc_free(newpacket);
+    return NULL;
+  }
+
+  silc_buffer_set(&newpacket->data, data, silc_buffer_truelen(&packet->data));
+  newpacket->tag = packet->tag;
+
+  return newpacket;
+}
+
+/* Decode all PGP packets into a list */
+
+int silc_pgp_packet_decode(const unsigned char *data,
+                          SilcUInt32 data_len,
+                          SilcBool *success,
+                          SilcList *ret_list)
+{
+  SilcBufferStruct buf;
+  SilcPGPPacket packet;
+  int ret;
+
+  SILC_LOG_DEBUG(("Parsing OpenPGP packets"));
+
+  if (success)
+    *success = TRUE;
+
+  if (!data || data_len < 2)
+    return 0;
+
+  silc_buffer_set(&buf, (unsigned char *)data, data_len);
+  silc_list_init(*ret_list, struct SilcPGPPacketStruct, next);
+
+  /* Parse one by one */
+  while (silc_buffer_len(&buf) > 0) {
+    ret = silc_pgp_packet_parse(silc_buffer_data(&buf),
+                               silc_buffer_len(&buf), &packet);
+    if (!ret) {
+      if (success)
+       *success = FALSE;
+      break;
+    }
+
+    silc_buffer_pull(&buf, ret);
+    silc_list_add(*ret_list, packet);
+  }
+
+  SILC_LOG_DEBUG(("Parsed %d packets", silc_list_count(*ret_list)));
+
+  silc_list_start(*ret_list);
+
+  return silc_list_count(*ret_list);
+}
+
+/* Get PGP packet tag (packet type) */
+
+SilcPGPPacketTag silc_pgp_packet_get_tag(SilcPGPPacket packet)
+{
+  return packet->tag;
+}
+
+/* Get PGP packet data */
+
+unsigned char *silc_pgp_packet_get_data(SilcPGPPacket packet,
+                                       SilcUInt32 *data_len)
+{
+  unsigned char *ptr = silc_buffer_data(&packet->data);
+  if (data_len)
+    *data_len = silc_buffer_len(&packet->data);
+  return ptr;
+}
+
+/* Free PGP packet from  */
+
+void silc_pgp_packet_free(SilcPGPPacket packet)
+{
+  silc_buffer_purge(&packet->data);
+  silc_free(packet);
+}
+
+/* Free PGP packets from list */
+
+void silc_pgp_packet_free_list(SilcList *list)
+{
+  SilcPGPPacket packet;
+
+  silc_list_start(*list);
+  while ((packet = silc_list_get(*list))) {
+    silc_buffer_purge(&packet->data);
+    silc_free(packet);
+  }
+}
+
+/****************************** String to Key *******************************/
+
+/* PGP String-to-key.  Converts passphrases to encryption and decryption
+   keys.  This can be used to create both encryption and decryption key. */
+
+unsigned char *silc_pgp_s2k(SilcPGPS2KType type,
+                           SilcPGPHash hash,
+                           const char *passphrase,
+                           SilcUInt32 passphrase_len,
+                           SilcUInt32 key_len,
+                           unsigned char *salt,
+                           SilcUInt32 iter_octet_count,
+                           SilcRng rng)
+{
+  SilcHash h = NULL;
+  unsigned char *key = NULL, digest[SILC_HASH_MAXLEN], preload[8], esalt[8];
+  SilcUInt32 hash_len;
+  int i, k;
+
+  if (!passphrase)
+    return NULL;
+
+  SILC_LOG_DEBUG(("Compute S2K for %s", salt ? "decryption" : "encryption"));
+
+  h = silc_pgp_hash_alloc(hash);
+  if (!h)
+    return NULL;
+  hash_len = silc_hash_len(h);
+
+  key = silc_malloc(key_len);
+  if (!key)
+    goto err;
+
+  memset(preload, 0, sizeof(preload));
+  silc_hash_init(h);
+
+  /* If salt is NULL, we'll create one for encryption */
+  if (!salt) {
+    silc_rng_get_rn_data(rng, 8, esalt, sizeof(esalt));
+    salt = esalt;
+  }
+
+  switch (type) {
+  case SILC_PGP_S2K_SIMPLE:
+    /* Hash passphrase */
+    for (i = 0; i < key_len; i += hash_len) {
+      if (i && i < sizeof(preload)) {
+       silc_hash_init(h);
+       silc_hash_update(h, preload, i);
+      }
+
+      silc_hash_update(h, passphrase, passphrase_len);
+      silc_hash_final(h, digest);
+      memcpy(key + i, digest,
+            (key_len - i) > hash_len ? hash_len : key_len - i);
+    }
+    break;
+
+  case SILC_PGP_S2K_SALTED:
+    /* Hash passphrase with salt */
+    for (i = 0; i < key_len; i += hash_len) {
+      if (i && i < sizeof(preload)) {
+       silc_hash_init(h);
+       silc_hash_update(h, preload, i);
+      }
+
+      silc_hash_update(h, salt, 8);
+      silc_hash_update(h, passphrase, passphrase_len);
+      silc_hash_final(h, digest);
+      memcpy(key + i, digest,
+            (key_len - i) > hash_len ? hash_len : key_len - i);
+    }
+    break;
+
+  case SILC_PGP_S2K_ITERATED_SALTED:
+    /* Hash passphrase with salt iteratively.  This is very poorly defined
+       in the RFC. */
+    if (iter_octet_count < 8 + passphrase_len)
+      iter_octet_count = 8 + passphrase_len;
+
+    for (i = 0; i < key_len; i += hash_len) {
+      if (i && i < sizeof(preload)) {
+       silc_hash_init(h);
+       silc_hash_update(h, preload, i);
+      }
+
+      for (k = 0; k < iter_octet_count; k += (8 + passphrase_len)) {
+       if (iter_octet_count - k < 8) {
+         silc_hash_update(h, salt, iter_octet_count - k);
+       } else {
+         silc_hash_update(h, salt, 8);
+         if (iter_octet_count - k - 8 < passphrase_len)
+           silc_hash_update(h, passphrase, iter_octet_count - k - 8);
+         else
+           silc_hash_update(h, passphrase, passphrase_len);
+       }
+      }
+
+      silc_hash_final(h, digest);
+      memcpy(key + i, digest,
+            (key_len - i) > hash_len ? hash_len : key_len - i);
+    }
+    break;
+
+  default:
+    goto err;
+    break;
+  }
+
+  memset(digest, 0, sizeof(digest));
+  memset(esalt, 0, sizeof(esalt));
+  silc_hash_free(h);
+
+  return key;
+
+ err:
+  silc_hash_free(h);
+  silc_free(key);
+  return NULL;
+}
+
+/****************************** ASCII armoring ******************************/
+
+/* Adds ASCII armor */
+
+unsigned char *silc_pgp_armor(unsigned char *data,
+                             SilcUInt32 data_len)
+{
+  /* XXX TODO */
+  return NULL;
+}
+
+/* Removes ASCII armoring */
+
+unsigned char *silc_pgp_dearmor(unsigned char *data,
+                               SilcUInt32 data_len,
+                               SilcUInt32 *ret_len)
+{
+  int i, k;
+
+  if (data_len < 28)
+    return NULL;
+
+  if (memcmp(data, "-----BEGIN PGP ", 15))
+    return NULL;
+
+  /* Get beginning of base64 encoded data */
+  for (i = 0; i < data_len; i++) {
+    if (i + 3 < data_len && data[i] == '\n' && data[i + 1] == '\n') {
+      i += 2;
+      break;
+    }
+    if (i + 3 < data_len &&
+       data[i] == '\n' && data[i + 1] == ' ' && data[i + 2] == '\n') {
+      i += 3;
+      break;
+    }
+  }
+
+  /* Get end of base64 encoded data, ignore OpenPGP radix64 CRC */
+  for (k = i; k < data_len; k++) {
+    if (k + 1 < data_len && data[k] == '=') {
+      data_len -= (data_len - ++k);
+      break;
+    }
+  }
+
+  return silc_base64_decode(NULL, data + i, data_len, ret_len);
+}