X-Git-Url: http://git.silcnet.org/gitweb/?p=crypto.git;a=blobdiff_plain;f=lib%2Fsilcpgp%2Fsilcpgp.c;fp=lib%2Fsilcpgp%2Fsilcpgp.c;h=f882c9e666cf01398454f200c4727804835fcbb7;hp=0000000000000000000000000000000000000000;hb=1b4e874f9401653b659a6adec2d2f046f9331586;hpb=7d4fb45c07b67b027b549f46c3689e44e81b3586 diff --git a/lib/silcpgp/silcpgp.c b/lib/silcpgp/silcpgp.c new file mode 100644 index 00000000..f882c9e6 --- /dev/null +++ b/lib/silcpgp/silcpgp.c @@ -0,0 +1,606 @@ +/* + + silcpgp.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" + +/************************* 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); +}