Implement RSA for NSS.

This commit is contained in:
Nick Mathewson 2018-07-19 12:03:01 -04:00
parent cb5cfe3177
commit aa45511250
4 changed files with 712 additions and 12 deletions

View File

@ -110,14 +110,6 @@ int crypto_pk_get_common_digests(crypto_pk_t *pk,
int crypto_pk_base64_encode_private(const crypto_pk_t *pk, char **priv_out);
crypto_pk_t *crypto_pk_base64_decode_private(const char *str, size_t len);
#ifdef TOR_UNIT_TESTS
#ifdef ENABLE_NSS
struct SECItemStr;
STATIC int secitem_uint_cmp(const struct SECItemStr *a,
const struct SECItemStr *b);
#endif
#endif
#ifdef ENABLE_OPENSSL
/* Prototypes for private functions only used by tortls.c, crypto.c, and the
* unit tests. */
@ -132,4 +124,12 @@ MOCK_DECL(struct evp_pkey_st *, crypto_pk_get_openssl_evp_pkey_,(
void crypto_pk_assign_public(crypto_pk_t *dest, const crypto_pk_t *src);
void crypto_pk_assign_private(crypto_pk_t *dest, const crypto_pk_t *src);
#ifdef TOR_UNIT_TESTS
#ifdef ENABLE_NSS
struct SECItemStr;
STATIC int secitem_uint_cmp(const struct SECItemStr *a,
const struct SECItemStr *b);
#endif
#endif
#endif

View File

@ -0,0 +1,699 @@
/* Copyright (c) 2001, Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file crypto_rsa.c
* \brief NSS implementations of our RSA code.
**/
#include "lib/crypt_ops/crypto_rsa.h"
#include "lib/crypt_ops/crypto_nss_mgt.h"
#include "lib/crypt_ops/crypto_util.h"
#include "lib/ctime/di_ops.h"
#include "lib/encoding/binascii.h"
#include "lib/fs/files.h"
#include "lib/intmath/cmp.h"
#include "lib/intmath/muldiv.h"
#include "lib/log/log.h"
#include "lib/log/util_bug.h"
#include <string.h>
#include <keyhi.h>
#include <pk11pub.h>
#include <secder.h>
#ifdef ENABLE_OPENSSL
#include <openssl/rsa.h>
#include <openssl/evp.h>
#endif
/** Declaration for crypto_pk_t structure. */
struct crypto_pk_t
{
SECKEYPrivateKey *seckey;
SECKEYPublicKey *pubkey;
};
/** Return true iff <b>key</b> contains the private-key portion of the RSA
* key. */
int
crypto_pk_key_is_private(const crypto_pk_t *key)
{
return key && key->seckey;
}
#ifdef ENABLE_OPENSSL
/** used by tortls.c: wrap an RSA* in a crypto_pk_t. Take ownership of the
* RSA object. */
crypto_pk_t *
crypto_new_pk_from_openssl_rsa_(RSA *rsa)
{
crypto_pk_t *pk = NULL;
unsigned char *buf = NULL;
int len = i2d_RSAPublicKey(rsa, &buf);
RSA_free(rsa);
if (len < 0 || buf == NULL)
goto end;
pk = crypto_pk_asn1_decode((const char *)buf, len);
end:
if (buf)
OPENSSL_free(buf);
return pk;
}
/** Helper, used by tor-gencert.c. Return the RSA from a
* crypto_pk_t. */
struct rsa_st *
crypto_pk_get_openssl_rsa_(crypto_pk_t *pk)
{
size_t buflen = crypto_pk_keysize(pk)*16;
unsigned char *buf = tor_malloc_zero(buflen);
const unsigned char *cp = buf;
RSA *rsa = NULL;
int used = crypto_pk_asn1_encode_private(pk, (char*)buf, buflen);
if (used < 0)
goto end;
rsa = d2i_RSAPrivateKey(NULL, &cp, used);
end:
memwipe(buf, 0, buflen);
tor_free(buf);
return rsa;
}
/** used by tortls.c: get an equivalent EVP_PKEY* for a crypto_pk_t. Iff
* private is set, include the private-key portion of the key. Return a valid
* pointer on success, and NULL on failure. */
MOCK_IMPL(struct evp_pkey_st *,
crypto_pk_get_openssl_evp_pkey_,(crypto_pk_t *pk, int private))
{
size_t buflen = crypto_pk_keysize(pk)*16;
unsigned char *buf = tor_malloc_zero(buflen);
const unsigned char *cp = buf;
RSA *rsa = NULL;
EVP_PKEY *result = NULL;
if (private) {
int len = crypto_pk_asn1_encode_private(pk, (char*)buf, buflen);
if (len < 0)
goto end;
rsa = d2i_RSAPrivateKey(NULL, &cp, len);
} else {
int len = crypto_pk_asn1_encode(pk, (char*)buf, buflen);
if (len < 0)
goto end;
rsa = d2i_RSAPublicKey(NULL, &cp, len);
}
if (!rsa)
goto end;
if (!(result = EVP_PKEY_new()))
goto end;
if (!(EVP_PKEY_assign_RSA(result, rsa))) {
EVP_PKEY_free(result);
RSA_free(rsa);
result = NULL;
}
end:
memwipe(buf, 0, buflen);
tor_free(buf);
return result;
}
#endif
/** Allocate and return storage for a public key. The key itself will not yet
* be set.
*/
MOCK_IMPL(crypto_pk_t *,
crypto_pk_new,(void))
{
crypto_pk_t *result = tor_malloc_zero(sizeof(crypto_pk_t));
return result;
}
/** Release the NSS objects held in <b>key</b> */
static void
crypto_pk_clear(crypto_pk_t *key)
{
if (key->pubkey)
SECKEY_DestroyPublicKey(key->pubkey);
if (key->seckey)
SECKEY_DestroyPrivateKey(key->seckey);
memset(key, 0, sizeof(crypto_pk_t));
}
/** Release a reference to an asymmetric key; when all the references
* are released, free the key.
*/
void
crypto_pk_free_(crypto_pk_t *key)
{
if (!key)
return;
crypto_pk_clear(key);
tor_free(key);
}
/** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>.
* Return 0 on success, -1 on failure.
*/
MOCK_IMPL(int,
crypto_pk_generate_key_with_bits,(crypto_pk_t *key, int bits))
{
tor_assert(key);
PK11RSAGenParams params = {
.keySizeInBits = bits,
.pe = TOR_RSA_EXPONENT
};
int result = -1;
PK11SlotInfo *slot = PK11_GetBestSlot(CKM_RSA_PKCS_KEY_PAIR_GEN, NULL);
SECKEYPrivateKey *seckey = NULL;
SECKEYPublicKey *pubkey = NULL;
if (!slot) {
crypto_nss_log_errors(LOG_WARN, "getting slot for RSA keygen");
goto done;
}
seckey = PK11_GenerateKeyPair(slot, CKM_RSA_PKCS_KEY_PAIR_GEN, &params,
&pubkey,
PR_FALSE /*isPerm */,
PR_FALSE /*isSensitive*/,
NULL);
if (seckey == NULL || pubkey == NULL) {
crypto_nss_log_errors(LOG_WARN, "generating an RSA key");
goto done;
}
crypto_pk_clear(key);
key->seckey = seckey;
key->pubkey = pubkey;
seckey = NULL;
pubkey = NULL;
result = 0;
done:
if (slot)
PK11_FreeSlot(slot);
if (pubkey)
SECKEY_DestroyPublicKey(pubkey);
if (seckey)
SECKEY_DestroyPrivateKey(seckey);
return result;
}
/** Return true iff <b>env</b> has a valid key.
*/
int
crypto_pk_check_key(crypto_pk_t *key)
{
return key && key->pubkey;
}
/** Return true iff <b>env</b> contains a public key whose public exponent
* equals 65537.
*/
int
crypto_pk_public_exponent_ok(crypto_pk_t *key)
{
return key &&
key->pubkey &&
key->pubkey->keyType == rsaKey &&
DER_GetUInteger(&key->pubkey->u.rsa.publicExponent) == TOR_RSA_EXPONENT;
}
/** Compare two big-endian integers stored in a and b; return a tristate.
*/
STATIC int
secitem_uint_cmp(const SECItem *a, const SECItem *b)
{
const unsigned abits = SECKEY_BigIntegerBitLength(a);
const unsigned bbits = SECKEY_BigIntegerBitLength(b);
if (abits < bbits)
return -1;
else if (abits > bbits)
return 1;
/* okay, they have the same number of bits set. Get a pair of aligned
* pointers to their bytes that are set... */
const unsigned nbytes = CEIL_DIV(abits, 8);
tor_assert(nbytes <= a->len);
tor_assert(nbytes <= b->len);
const unsigned char *aptr = a->data + (a->len - nbytes);
const unsigned char *bptr = b->data + (b->len - nbytes);
/* And compare them. */
return fast_memcmp(aptr, bptr, nbytes);
}
/** Compare the public-key components of a and b. Return less than 0
* if a\<b, 0 if a==b, and greater than 0 if a\>b. A NULL key is
* considered to be less than all non-NULL keys, and equal to itself.
*
* Note that this may leak information about the keys through timing.
*/
int
crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b)
{
int result;
char a_is_non_null = (a != NULL) && (a->pubkey != NULL);
char b_is_non_null = (b != NULL) && (b->pubkey != NULL);
char an_argument_is_null = !a_is_non_null | !b_is_non_null;
result = tor_memcmp(&a_is_non_null, &b_is_non_null, sizeof(a_is_non_null));
if (an_argument_is_null)
return result;
// This is all Tor uses with this structure.
tor_assert(a->pubkey->keyType == rsaKey);
tor_assert(b->pubkey->keyType == rsaKey);
const SECItem *a_n, *a_e, *b_n, *b_e;
a_n = &a->pubkey->u.rsa.modulus;
b_n = &b->pubkey->u.rsa.modulus;
a_e = &a->pubkey->u.rsa.publicExponent;
b_e = &b->pubkey->u.rsa.publicExponent;
result = secitem_uint_cmp(a_n, b_n);
if (result)
return result;
return secitem_uint_cmp(a_e, b_e);
}
/** Return the size of the public key modulus in <b>env</b>, in bytes. */
size_t
crypto_pk_keysize(const crypto_pk_t *key)
{
tor_assert(key);
tor_assert(key->pubkey);
return SECKEY_PublicKeyStrength(key->pubkey);
}
/** Return the size of the public key modulus of <b>env</b>, in bits. */
int
crypto_pk_num_bits(crypto_pk_t *key)
{
tor_assert(key);
tor_assert(key->pubkey);
return SECKEY_PublicKeyStrengthInBits(key->pubkey);
}
/**
* Make a copy of <b>key</b> and return it.
*/
crypto_pk_t *
crypto_pk_dup_key(crypto_pk_t *key)
{
crypto_pk_t *result = crypto_pk_new();
if (key->pubkey)
result->pubkey = SECKEY_CopyPublicKey(key->pubkey);
if (key->seckey)
result->seckey = SECKEY_CopyPrivateKey(key->seckey);
return result;
}
/** For testing: replace dest with src. (Dest must have a refcount
* of 1) */
void
crypto_pk_assign_public(crypto_pk_t *dest, const crypto_pk_t *src)
{
crypto_pk_clear(dest);
if (src->pubkey)
dest->pubkey = SECKEY_CopyPublicKey(src->pubkey);
}
/** For testing: replace dest with src. (Dest must have a refcount
* of 1) */
void
crypto_pk_assign_private(crypto_pk_t *dest, const crypto_pk_t *src)
{
crypto_pk_clear(dest);
if (src->pubkey)
dest->pubkey = SECKEY_CopyPublicKey(src->pubkey);
if (src->seckey)
dest->seckey = SECKEY_CopyPrivateKey(src->seckey);
}
/** Make a real honest-to-goodness copy of <b>env</b>, and return it.
* Returns NULL on failure. */
crypto_pk_t *
crypto_pk_copy_full(crypto_pk_t *key)
{
// These aren't reference-counted is nss, so it's fine to just
// use the same function.
return crypto_pk_dup_key(key);
}
static const CK_RSA_PKCS_OAEP_PARAMS oaep_params = {
.hashAlg = CKM_SHA_1,
.mgf = CKG_MGF1_SHA1,
.source = CKZ_DATA_SPECIFIED,
.pSourceData = NULL,
.ulSourceDataLen = 0
};
static const SECItem oaep_item = {
.type = siBuffer,
.data = (unsigned char *) &oaep_params,
.len = sizeof(oaep_params)
};
/** Return the mechanism code and parameters for a given padding method when
* used with RSA */
static CK_MECHANISM_TYPE
padding_to_mechanism(int padding, SECItem **item_out)
{
switch (padding) {
case PK_PKCS1_OAEP_PADDING:
*item_out = (SECItem *)&oaep_item;
return CKM_RSA_PKCS_OAEP;
default:
tor_assert_unreached();
*item_out = NULL;
return CKM_INVALID_MECHANISM;
}
}
/** Encrypt <b>fromlen</b> bytes from <b>from</b> with the public key
* in <b>env</b>, using the padding method <b>padding</b>. On success,
* write the result to <b>to</b>, and return the number of bytes
* written. On failure, return -1.
*
* <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
* at least the length of the modulus of <b>env</b>.
*/
int
crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen,
const char *from, size_t fromlen, int padding)
{
tor_assert(env);
tor_assert(to);
tor_assert(from);
tor_assert(tolen < INT_MAX);
tor_assert(fromlen < INT_MAX);
if (BUG(!crypto_pk_check_key(env)))
return -1;
unsigned int result_len = 0;
SECItem *item = NULL;
CK_MECHANISM_TYPE m = padding_to_mechanism(padding, &item);
SECStatus s = PK11_PubEncrypt(env->pubkey, m, item,
(unsigned char *)to, &result_len,
(unsigned int)tolen,
(const unsigned char *)from,
(unsigned int)fromlen,
NULL);
if (s != SECSuccess) {
crypto_nss_log_errors(LOG_WARN, "encrypting to an RSA key");
return -1;
}
return (int)result_len;
}
/** Decrypt <b>fromlen</b> bytes from <b>from</b> with the private key
* in <b>env</b>, using the padding method <b>padding</b>. On success,
* write the result to <b>to</b>, and return the number of bytes
* written. On failure, return -1.
*
* <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
* at least the length of the modulus of <b>key</b>.
*/
int
crypto_pk_private_decrypt(crypto_pk_t *key, char *to,
size_t tolen,
const char *from, size_t fromlen,
int padding, int warnOnFailure)
{
tor_assert(key);
tor_assert(to);
tor_assert(from);
tor_assert(tolen < INT_MAX);
tor_assert(fromlen < INT_MAX);
if (!crypto_pk_key_is_private(key))
return -1; /* Not a private key. */
unsigned int result_len = 0;
SECItem *item = NULL;
CK_MECHANISM_TYPE m = padding_to_mechanism(padding, &item);
SECStatus s = PK11_PrivDecrypt(key->seckey, m, item,
(unsigned char *)to, &result_len,
(unsigned int)tolen,
(const unsigned char *)from,
(unsigned int)fromlen);
if (s != SECSuccess) {
const int severity = warnOnFailure ? LOG_WARN : LOG_INFO;
crypto_nss_log_errors(severity, "decrypting with an RSA key");
return -1;
}
return (int)result_len;
}
/** Check the signature in <b>from</b> (<b>fromlen</b> bytes long) with the
* public key in <b>key</b>, using PKCS1 padding. On success, write the
* signed data to <b>to</b>, and return the number of bytes written.
* On failure, return -1.
*
* <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
* at least the length of the modulus of <b>key</b>.
*/
MOCK_IMPL(int,
crypto_pk_public_checksig,(const crypto_pk_t *key, char *to,
size_t tolen,
const char *from, size_t fromlen))
{
tor_assert(key);
tor_assert(to);
tor_assert(from);
tor_assert(tolen < INT_MAX);
tor_assert(fromlen < INT_MAX);
tor_assert(key->pubkey);
SECItem sig = {
.type = siBuffer,
.data = (unsigned char *) from,
.len = (unsigned int) fromlen,
};
SECItem dsig = {
.type = siBuffer,
.data = (unsigned char *) to,
.len = (unsigned int) tolen
};
SECStatus s;
s = PK11_VerifyRecover(key->pubkey, &sig, &dsig, NULL);
if (s != SECSuccess)
return -1;
return (int)dsig.len;
}
/** Sign <b>fromlen</b> bytes of data from <b>from</b> with the private key in
* <b>env</b>, using PKCS1 padding. On success, write the signature to
* <b>to</b>, and return the number of bytes written. On failure, return
* -1.
*
* <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
* at least the length of the modulus of <b>env</b>.
*/
int
crypto_pk_private_sign(const crypto_pk_t *key, char *to, size_t tolen,
const char *from, size_t fromlen)
{
tor_assert(key);
tor_assert(to);
tor_assert(from);
tor_assert(tolen < INT_MAX);
tor_assert(fromlen < INT_MAX);
if (BUG(!crypto_pk_key_is_private(key)))
return -1;
SECItem sig = {
.type = siBuffer,
.data = (unsigned char *)to,
.len = (unsigned int) tolen
};
SECItem hash = {
.type = siBuffer,
.data = (unsigned char *)from,
.len = (unsigned int) fromlen
};
CK_MECHANISM_TYPE m = CKM_RSA_PKCS;
SECStatus s = PK11_SignWithMechanism(key->seckey, m, NULL,
&sig, &hash);
if (s != SECSuccess) {
crypto_nss_log_errors(LOG_WARN, "signing with an RSA key");
return -1;
}
return (int)sig.len;
}
/* "This has lead to people trading hard-to-find object identifiers and ASN.1
* definitions like baseball cards" - Peter Gutmann, "X.509 Style Guide". */
static const unsigned char RSA_OID[] = {
/* RSADSI */ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
/* PKCS1 */ 0x01, 0x01,
/* RSA */ 0x01
};
/** ASN.1-encode the public portion of <b>pk</b> into <b>dest</b>.
* Return -1 on error, or the number of characters used on success.
*/
int
crypto_pk_asn1_encode(const crypto_pk_t *pk, char *dest, size_t dest_len)
{
tor_assert(pk);
if (pk->pubkey == NULL)
return -1;
CERTSubjectPublicKeyInfo *info;
info = SECKEY_CreateSubjectPublicKeyInfo(pk->pubkey);
if (! info)
return -1;
const SECItem *item = &info->subjectPublicKey;
size_t actual_len = (item->len) >> 3; /* bits to bytes */
size_t n_used = MIN(actual_len, dest_len);
memcpy(dest, item->data, n_used);
SECKEY_DestroySubjectPublicKeyInfo(info);
return (int) n_used;
}
/** Decode an ASN.1-encoded public key from <b>str</b>; return the result on
* success and NULL on failure.
*/
crypto_pk_t *
crypto_pk_asn1_decode(const char *str, size_t len)
{
tor_assert(str);
if (len >= INT_MAX)
return NULL;
CERTSubjectPublicKeyInfo info = {
.algorithm = {
.algorithm = {
.type = siDEROID,
.data = (unsigned char *)RSA_OID,
.len = sizeof(RSA_OID)
}
},
.subjectPublicKey = {
.type = siBuffer,
.data = (unsigned char *)str,
.len = (unsigned int)(len << 3) /* bytes to bits */
}
};
SECKEYPublicKey *pub = SECKEY_ExtractPublicKey(&info);
if (pub == NULL)
return NULL;
crypto_pk_t *result = crypto_pk_new();
result->pubkey = pub;
return result;
}
DISABLE_GCC_WARNING(unused-parameter)
/** Given a crypto_pk_t <b>pk</b>, allocate a new buffer containing the Base64
* encoding of the DER representation of the private key into the
* <b>dest_len</b>-byte buffer in <b>dest</b>.
* Return the number of bytes written on success, -1 on failure.
*/
int
crypto_pk_asn1_encode_private(const crypto_pk_t *pk,
char *dest, size_t destlen)
{
tor_assert(destlen <= INT_MAX);
if (!crypto_pk_key_is_private(pk))
return -1;
SECKEYPrivateKeyInfo *info = PK11_ExportPrivKeyInfo(pk->seckey, NULL);
if (!info)
return -1;
SECItem *item = &info->privateKey;
if (destlen < item->len) {
SECKEY_DestroyPrivateKeyInfo(info, PR_TRUE);
return -1;
}
int result = (int)item->len;
memcpy(dest, item->data, item->len);
SECKEY_DestroyPrivateKeyInfo(info, PR_TRUE);
return result;
}
/** Given a buffer containing the DER representation of the
* private key <b>str</b>, decode and return the result on success, or NULL
* on failure.
*/
crypto_pk_t *
crypto_pk_asn1_decode_private(const char *str, size_t len)
{
tor_assert(str);
tor_assert(len < INT_MAX);
SECKEYPrivateKeyInfo info = {
.algorithm = {
.algorithm = {
.type = siBuffer,
.data = (unsigned char *)RSA_OID,
.len = sizeof(RSA_OID)
}
},
.privateKey = {
.type = siBuffer,
.data = (unsigned char *)str,
.len = (int)len,
}
};
PK11SlotInfo *slot = PK11_GetBestSlot(CKM_RSA_PKCS, NULL);
SECStatus s;
SECKEYPrivateKey *seckey = NULL;
s = PK11_ImportPrivateKeyInfoAndReturnKey(slot, &info,
NULL /* nickname */,
NULL /* publicValue */,
PR_FALSE /* isPerm */,
PR_FALSE /* isPrivate */,
KU_ALL /* keyUsage */,
&seckey, NULL);
crypto_pk_t *output = NULL;
if (s == SECSuccess && seckey) {
output = crypto_pk_new();
output->seckey = seckey;
output->pubkey = SECKEY_ConvertToPublicKey(seckey);
tor_assert(output->pubkey);
} else {
crypto_nss_log_errors(LOG_WARN, "decoding an RSA private key");
}
return output;
}

View File

@ -76,7 +76,7 @@ crypto_new_pk_from_openssl_rsa_(RSA *rsa)
RSA *
crypto_pk_get_openssl_rsa_(crypto_pk_t *env)
{
return RSA_PrivateKeyDup(env->key);
return RSAPrivateKey_dup(env->key);
}
/** used by tortls.c: get an equivalent EVP_PKEY* for a crypto_pk_t. Iff

View File

@ -19,7 +19,6 @@ src_lib_libtor_crypt_ops_a_SOURCES = \
src/lib/crypt_ops/crypto_pwbox.c \
src/lib/crypt_ops/crypto_rand.c \
src/lib/crypt_ops/crypto_rsa.c \
src/lib/crypt_ops/crypto_rsa_openssl.c \
src/lib/crypt_ops/crypto_s2k.c \
src/lib/crypt_ops/crypto_util.c \
src/lib/crypt_ops/digestset.c
@ -28,10 +27,12 @@ if USE_NSS
src_lib_libtor_crypt_ops_a_SOURCES += \
src/lib/crypt_ops/aes_nss.c \
src/lib/crypt_ops/crypto_dh_nss.c \
src/lib/crypt_ops/crypto_nss_mgt.c
src/lib/crypt_ops/crypto_nss_mgt.c \
src/lib/crypt_ops/crypto_rsa_nss.c
else
src_lib_libtor_crypt_ops_a_SOURCES += \
src/lib/crypt_ops/aes_openssl.c
src/lib/crypt_ops/aes_openssl.c \
src/lib/crypt_ops/crypto_rsa_openssl.c
endif
if USE_OPENSSL