/* silcpkcs1.c Author: Pekka Riikonen Copyright (C) 2003 - 2007 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. */ /* $Id$ */ #include "silc.h" #include "rsa.h" /************************** PKCS #1 message format ***************************/ /* Minimum padding in block */ #define SILC_PKCS1_MIN_PADDING 8 /* Encodes PKCS#1 data block from the `data' according to the block type indicated by `bt'. When encoding signatures the `bt' must be SILC_PKCS1_BT_PRV1 and when encoding encryption blocks the `bt' must be SILC_PKCS1_BT_PUB. The encoded data is copied into the `dest_data' buffer which is size of `dest_data_size'. If the `dest_data' is not able to hold the encoded block this returns FALSE. The `rng' must be set when `bt' is SILC_PKCS1_BT_PUB. This function returns TRUE on success. */ SilcBool silc_pkcs1_encode(SilcPkcs1BlockType bt, const unsigned char *data, SilcUInt32 data_len, unsigned char *dest_data, SilcUInt32 dest_data_size, SilcRng rng) { SilcInt32 padlen; int i; SILC_LOG_DEBUG(("PKCS#1 encoding, bt %d", bt)); if (!data || !dest_data || dest_data_size < SILC_PKCS1_MIN_PADDING + 3 || dest_data_size < data_len) { SILC_LOG_DEBUG(("Data to be encoded is too long")); return FALSE; } /* Start of block */ dest_data[0] = 0x00; dest_data[1] = (unsigned char)bt; padlen = (SilcInt32)dest_data_size - (SilcInt32)data_len - 3; if (padlen < SILC_PKCS1_MIN_PADDING) { SILC_LOG_DEBUG(("Data to be encoded is too long")); return FALSE; } /* Encode according to block type */ switch (bt) { case SILC_PKCS1_BT_PRV0: case SILC_PKCS1_BT_PRV1: /* Signature */ memset(dest_data + 2, bt == SILC_PKCS1_BT_PRV1 ? 0xff : 0x00, padlen); break; case SILC_PKCS1_BT_PUB: /* Encryption */ if (!rng) { SILC_LOG_ERROR(("Cannot encrypt: random number generator not provided")); return FALSE; } /* It is guaranteed this routine does not return zero byte. */ for (i = 2; i < padlen; i++) dest_data[i] = silc_rng_get_byte_fast(rng); break; } /* Copy the data */ dest_data[padlen + 2] = 0x00; memcpy(dest_data + padlen + 3, data, data_len); return TRUE; } /* Decodes the PKCS#1 encoded block according to the block type `bt'. When verifying signatures the `bt' must be SILC_PKCS1_BT_PRV1 and when decrypting it must be SILC_PKCS1_BT_PUB. This copies the decoded data into `dest_data' which is size of `dest_data_size'. If the deocded block does not fit to `dest_data' this returns FALSE. Returns TRUE on success. */ SilcBool silc_pkcs1_decode(SilcPkcs1BlockType bt, const unsigned char *data, SilcUInt32 data_len, unsigned char *dest_data, SilcUInt32 dest_data_size, SilcUInt32 *dest_len) { int i = 0; SILC_LOG_DEBUG(("PKCS#1 decoding, bt %d", bt)); /* Sanity checks */ if (!data || !dest_data || dest_data_size < 3 || data[0] != 0x00 || data[1] != (unsigned char)bt) { SILC_LOG_DEBUG(("Malformed block")); return FALSE; } /* Decode according to block type */ switch (bt) { case SILC_PKCS1_BT_PRV0: /* Do nothing */ break; case SILC_PKCS1_BT_PRV1: /* Verification */ for (i = 2; i < data_len; i++) if (data[i] != 0xff) break; break; case SILC_PKCS1_BT_PUB: /* Decryption */ for (i = 2; i < data_len; i++) if (data[i] == 0x00) break; break; } /* Sanity checks */ if (data[i++] != 0x00) { SILC_LOG_DEBUG(("Malformed block")); return FALSE; } if (i - 1 < SILC_PKCS1_MIN_PADDING) { SILC_LOG_DEBUG(("Malformed block")); return FALSE; } if (dest_data_size < data_len - i) { SILC_LOG_DEBUG(("Destination buffer too small")); return FALSE; } /* Copy the data */ memcpy(dest_data, data + i, data_len - i); /* Return data length */ if (dest_len) *dest_len = data_len - i; return TRUE; } /***************************** PKCS #1 PKCS API ******************************/ /* Generates RSA key pair. */ SilcBool silc_pkcs1_generate_key(SilcUInt32 keylen, SilcRng rng, void **ret_public_key, void **ret_private_key) { SilcUInt32 prime_bits = keylen / 2; SilcMPInt p, q; SilcBool found = FALSE; if (keylen < 768 || keylen > 16384) return FALSE; silc_mp_init(&p); silc_mp_init(&q); /* Find p and q */ while (!found) { silc_math_gen_prime(&p, prime_bits, FALSE, rng); silc_math_gen_prime(&q, prime_bits, FALSE, rng); if ((silc_mp_cmp(&p, &q)) != 0) found = TRUE; } /* If p is smaller than q, switch them */ if ((silc_mp_cmp(&p, &q)) > 0) { SilcMPInt hlp; silc_mp_init(&hlp); silc_mp_set(&hlp, &p); silc_mp_set(&p, &q); silc_mp_set(&q, &hlp); silc_mp_uninit(&hlp); } /* Generate the actual keys */ if (!rsa_generate_keys(keylen, &p, &q, ret_public_key, ret_private_key)) return FALSE; silc_mp_uninit(&p); silc_mp_uninit(&q); return TRUE; } /* Import PKCS #1 compliant public key */ SilcBool silc_pkcs1_import_public_key(unsigned char *key, SilcUInt32 key_len, void **ret_public_key) { SilcAsn1 asn1 = NULL; SilcBufferStruct alg_key; RsaPublicKey *pubkey; if (!ret_public_key) return FALSE; asn1 = silc_asn1_alloc(); if (!asn1) return FALSE; /* Allocate RSA public key */ *ret_public_key = pubkey = silc_calloc(1, sizeof(*pubkey)); if (!pubkey) goto err; /* Parse the PKCS #1 public key */ silc_buffer_set(&alg_key, key, key_len); if (!silc_asn1_decode(asn1, &alg_key, SILC_ASN1_OPTS(SILC_ASN1_ALLOC), SILC_ASN1_SEQUENCE, SILC_ASN1_INT(&pubkey->n), SILC_ASN1_INT(&pubkey->e), SILC_ASN1_END, SILC_ASN1_END)) goto err; /* Set key length */ pubkey->bits = silc_mp_sizeinbase(&pubkey->n, 2); silc_asn1_free(asn1); return TRUE; err: silc_asn1_free(asn1); return FALSE; } /* Export PKCS #1 compliant public key */ unsigned char *silc_pkcs1_export_public_key(void *public_key, SilcUInt32 *ret_len) { RsaPublicKey *key = public_key; SilcAsn1 asn1 = NULL; SilcBufferStruct alg_key; unsigned char *ret; asn1 = silc_asn1_alloc(); if (!asn1) goto err; /* Encode to PKCS #1 public key */ memset(&alg_key, 0, sizeof(alg_key)); if (!silc_asn1_encode(asn1, &alg_key, SILC_ASN1_OPTS(SILC_ASN1_ALLOC), SILC_ASN1_SEQUENCE, SILC_ASN1_INT(&key->n), SILC_ASN1_INT(&key->e), SILC_ASN1_END, SILC_ASN1_END)) goto err; ret = silc_buffer_steal(&alg_key, ret_len); silc_asn1_free(asn1); return ret; err: if (asn1) silc_asn1_free(asn1); return NULL; } /* Returns key length */ SilcUInt32 silc_pkcs1_public_key_bitlen(void *public_key) { RsaPublicKey *key = public_key; return key->bits; } /* Copy public key */ void *silc_pkcs1_public_key_copy(void *public_key) { RsaPublicKey *key = public_key, *new_key; new_key = silc_calloc(1, sizeof(*new_key)); if (!new_key) return NULL; silc_mp_init(&new_key->n); silc_mp_init(&new_key->e); silc_mp_set(&new_key->n, &key->n); silc_mp_set(&new_key->e, &key->e); new_key->bits = key->bits; return new_key; } /* Compare public keys */ SilcBool silc_pkcs1_public_key_compare(void *key1, void *key2) { RsaPublicKey *k1 = key1, *k2 = key2; if (k1->bits != k2->bits) return FALSE; if (silc_mp_cmp(&k1->e, &k2->e) != 0) return FALSE; if (silc_mp_cmp(&k1->n, &k2->n) != 0) return FALSE; return TRUE; } /* Frees public key */ void silc_pkcs1_public_key_free(void *public_key) { RsaPublicKey *key = public_key; silc_mp_uninit(&key->n); silc_mp_uninit(&key->e); silc_free(key); } /* Import PKCS #1 compliant private key */ SilcBool silc_pkcs1_import_private_key(unsigned char *key, SilcUInt32 key_len, void **ret_private_key) { SilcAsn1 asn1; SilcBufferStruct alg_key; RsaPrivateKey *privkey; SilcUInt32 ver; if (!ret_private_key) return FALSE; asn1 = silc_asn1_alloc(); if (!asn1) return FALSE; /* Allocate RSA private key */ *ret_private_key = privkey = silc_calloc(1, sizeof(*privkey)); if (!privkey) goto err; /* Parse the PKCS #1 private key */ silc_buffer_set(&alg_key, key, key_len); if (!silc_asn1_decode(asn1, &alg_key, SILC_ASN1_OPTS(SILC_ASN1_ALLOC), SILC_ASN1_SEQUENCE, SILC_ASN1_SHORT_INT(&ver), SILC_ASN1_INT(&privkey->n), SILC_ASN1_INT(&privkey->e), SILC_ASN1_INT(&privkey->d), SILC_ASN1_INT(&privkey->p), SILC_ASN1_INT(&privkey->q), SILC_ASN1_INT(&privkey->dP), SILC_ASN1_INT(&privkey->dQ), SILC_ASN1_INT(&privkey->qP), SILC_ASN1_END, SILC_ASN1_END)) goto err; if (ver != 0) goto err; /* Set key length */ privkey->bits = silc_mp_sizeinbase(&privkey->n, 2); silc_asn1_free(asn1); return TRUE; err: silc_asn1_free(asn1); return FALSE; } /* Export PKCS #1 compliant private key */ unsigned char *silc_pkcs1_export_private_key(void *private_key, SilcUInt32 *ret_len) { RsaPrivateKey *key = private_key; SilcAsn1 asn1; SilcBufferStruct alg_key; unsigned char *ret; asn1 = silc_asn1_alloc(); if (!asn1) return FALSE; /* Encode to PKCS #1 private key */ memset(&alg_key, 0, sizeof(alg_key)); if (!silc_asn1_encode(asn1, &alg_key, SILC_ASN1_OPTS(SILC_ASN1_ALLOC), SILC_ASN1_SEQUENCE, SILC_ASN1_SHORT_INT(0), SILC_ASN1_INT(&key->n), SILC_ASN1_INT(&key->e), SILC_ASN1_INT(&key->d), SILC_ASN1_INT(&key->p), SILC_ASN1_INT(&key->q), SILC_ASN1_INT(&key->dP), SILC_ASN1_INT(&key->dQ), SILC_ASN1_INT(&key->qP), SILC_ASN1_END, SILC_ASN1_END)) goto err; ret = silc_buffer_steal(&alg_key, ret_len); silc_asn1_free(asn1); return ret; err: silc_asn1_free(asn1); return NULL; } /* Returns key length */ SilcUInt32 silc_pkcs1_private_key_bitlen(void *private_key) { RsaPrivateKey *key = private_key; return key->bits; } /* Frees private key */ void silc_pkcs1_private_key_free(void *private_key) { RsaPrivateKey *key = private_key; silc_mp_uninit(&key->n); silc_mp_uninit(&key->e); silc_mp_uninit(&key->d); silc_mp_uninit(&key->dP); silc_mp_uninit(&key->dQ); silc_mp_uninit(&key->qP); silc_mp_uninit(&key->p); silc_mp_uninit(&key->q); silc_free(key); } /* PKCS #1 RSA routines */ SilcBool silc_pkcs1_encrypt(void *public_key, unsigned char *src, SilcUInt32 src_len, unsigned char *dst, SilcUInt32 dst_size, SilcUInt32 *ret_dst_len, SilcRng rng) { RsaPublicKey *key = public_key; SilcMPInt mp_tmp; SilcMPInt mp_dst; unsigned char padded[2048 + 1]; SilcUInt32 len = (key->bits + 7) / 8; if (sizeof(padded) < len) return FALSE; if (dst_size < len) return FALSE; /* Pad data */ if (!silc_pkcs1_encode(SILC_PKCS1_BT_PUB, src, src_len, padded, len, rng)) return FALSE; silc_mp_init(&mp_tmp); silc_mp_init(&mp_dst); /* Data to MP */ silc_mp_bin2mp(padded, len, &mp_tmp); /* Encrypt */ rsa_public_operation(key, &mp_tmp, &mp_dst); /* MP to data */ silc_mp_mp2bin_noalloc(&mp_dst, dst, len); *ret_dst_len = len; memset(padded, 0, sizeof(padded)); silc_mp_uninit(&mp_tmp); silc_mp_uninit(&mp_dst); return TRUE; } SilcBool silc_pkcs1_decrypt(void *private_key, unsigned char *src, SilcUInt32 src_len, unsigned char *dst, SilcUInt32 dst_size, SilcUInt32 *ret_dst_len) { RsaPrivateKey *key = private_key; SilcMPInt mp_tmp; SilcMPInt mp_dst; unsigned char *padded, unpadded[2048 + 1]; SilcUInt32 padded_len; if (dst_size < (key->bits + 7) / 8) return FALSE; silc_mp_init(&mp_tmp); silc_mp_init(&mp_dst); /* Data to MP */ silc_mp_bin2mp(src, src_len, &mp_tmp); /* Decrypt */ rsa_private_operation(key, &mp_tmp, &mp_dst); /* MP to data */ padded = silc_mp_mp2bin(&mp_dst, (key->bits + 7) / 8, &padded_len); /* Unpad data */ if (!silc_pkcs1_decode(SILC_PKCS1_BT_PUB, padded, padded_len, unpadded, sizeof(unpadded), ret_dst_len)) { memset(padded, 0, padded_len); silc_free(padded); silc_mp_uninit(&mp_tmp); silc_mp_uninit(&mp_dst); return FALSE; } /* Copy to destination */ memcpy(dst, unpadded, *ret_dst_len); memset(padded, 0, padded_len); memset(unpadded, 0, sizeof(unpadded)); silc_free(padded); silc_mp_uninit(&mp_tmp); silc_mp_uninit(&mp_dst); return TRUE; } SilcBool silc_pkcs1_sign(void *private_key, unsigned char *src, SilcUInt32 src_len, unsigned char *signature, SilcUInt32 signature_size, SilcUInt32 *ret_signature_len, SilcHash hash) { return FALSE; } SilcBool silc_pkcs1_verify(void *public_key, unsigned char *signature, SilcUInt32 signature_len, unsigned char *data, SilcUInt32 data_len, SilcHash hash) { return FALSE; } /* PKCS #1 sign without hash oid */ SilcBool silc_pkcs1_sign_no_oid(void *private_key, unsigned char *src, SilcUInt32 src_len, unsigned char *signature, SilcUInt32 signature_size, SilcUInt32 *ret_signature_len, SilcHash hash) { RsaPrivateKey *key = private_key; SilcMPInt mp_tmp; SilcMPInt mp_dst; unsigned char padded[2048 + 1], hashr[SILC_HASH_MAXLEN]; SilcUInt32 len = (key->bits + 7) / 8; if (sizeof(padded) < len) return FALSE; if (signature_size < len) return FALSE; /* Compute hash if requested */ if (hash) { silc_hash_make(hash, src, src_len, hashr); src = hashr; src_len = silc_hash_len(hash); } /* Pad data */ if (!silc_pkcs1_encode(SILC_PKCS1_BT_PRV1, src, src_len, padded, len, NULL)) return FALSE; silc_mp_init(&mp_tmp); silc_mp_init(&mp_dst); /* Data to MP */ silc_mp_bin2mp(padded, len, &mp_tmp); /* Sign */ rsa_private_operation(key, &mp_tmp, &mp_dst); /* MP to data */ silc_mp_mp2bin_noalloc(&mp_dst, signature, len); *ret_signature_len = len; memset(padded, 0, sizeof(padded)); silc_mp_uninit(&mp_tmp); silc_mp_uninit(&mp_dst); if (hash) memset(hashr, 0, sizeof(hashr)); return TRUE; } /* PKCS #1 verify without hash oid */ SilcBool silc_pkcs1_verify_no_oid(void *public_key, unsigned char *signature, SilcUInt32 signature_len, unsigned char *data, SilcUInt32 data_len, SilcHash hash) { RsaPublicKey *key = public_key; int ret = TRUE; SilcMPInt mp_tmp2; SilcMPInt mp_dst; unsigned char *verify, unpadded[2048 + 1], hashr[SILC_HASH_MAXLEN]; SilcUInt32 verify_len, len = (key->bits + 7) / 8; silc_mp_init(&mp_tmp2); silc_mp_init(&mp_dst); /* Format the signature into MP int */ silc_mp_bin2mp(signature, signature_len, &mp_tmp2); /* Verify */ rsa_public_operation(key, &mp_tmp2, &mp_dst); /* MP to data */ verify = silc_mp_mp2bin(&mp_dst, len, &verify_len); /* Unpad data */ if (!silc_pkcs1_decode(SILC_PKCS1_BT_PRV1, verify, verify_len, unpadded, sizeof(unpadded), &len)) { memset(verify, 0, verify_len); silc_free(verify); silc_mp_uninit(&mp_tmp2); silc_mp_uninit(&mp_dst); return FALSE; } /* Hash data if requested */ if (hash) { silc_hash_make(hash, data, data_len, hashr); data = hashr; data_len = silc_hash_len(hash); } /* Compare */ if (len != data_len) ret = FALSE; else if (memcmp(data, unpadded, len)) ret = FALSE; memset(verify, 0, verify_len); memset(unpadded, 0, sizeof(unpadded)); silc_free(verify); silc_mp_uninit(&mp_tmp2); silc_mp_uninit(&mp_dst); if (hash) memset(hashr, 0, sizeof(hashr)); return ret; }