From 55116cc564649433cf4a8515d4a37cbf00dd6199 Mon Sep 17 00:00:00 2001 From: Rob Austein Date: Thu, 27 Aug 2015 12:39:55 -0400 Subject: Add point validation check to hal_ecdsa_verify(). Update README.md and code comments. --- README.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++---- ecdsa.c | 48 ++++++++++++++++++++++++++++---------- tests/test-ecdsa.c | 9 ------- tests/test-ecdsa.py | 2 +- 4 files changed, 99 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 66669e3..71fc0a0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ libhal ====== +## Overview ## + This library combines a set of low-level API functions which talk to the Cryptech FPGA cores with a set of higher-level functions providing various cryptographic services. @@ -25,16 +27,21 @@ Current contents of the library: * An implementation of RSA using the Cryptech ModExp core. +* An implementation of ECDSA, currently entirely in software. + * Test code for all of the above. Most of these are fairly well self-contained, although the PBKDF2 implementation uses the hash-core-based HMAC implementation. -The major exception is the RSA implementation, which uses an external -bignum implementation (libtfm) to handle a lot of the arithmetic. In -the long run, much or all of this may end up being implemented in -Verilog, but for the moment all of the RSA math except for modular -exponentiation is happening in software. +The major exceptions are the RSA and ECDSA implementations, which uses +an external bignum implementation (libtfm) to handle a lot of the +arithmetic. In the long run, much or all of this may end up being +implemented in Verilog, but for the moment all of the RSA math except +for modular exponentiation is happening in software, as is all of the +math for ECDSA. + +## RSA ## The RSA implementation includes a compile-time option to bypass the ModExp core and do everything in software, because the ModExp core is @@ -44,3 +51,53 @@ The RSA implementation includes optional blinding (enabled by default) and just enough ASN.1 code to read and write private keys; the expectation is that the latter will be used in combination with the AES Key Wrap code. + +## ECDSA ## + +The ECDSA implementation is specific to the NIST prime curves P-256, +P-384, and P-521. + +The ECDSA implementation includes a compile-time option to allow test +code to bypass the CSPRNG in order to test against known test vectors. +Do **NOT** enable this in production builds, as ECDSA depends on good +random numbers not just for private keys but for individual +signatures, and an attacker who knows the random number used for a +particular signature can use this to recover the private key. +Arguably, this option should be removed from the code entirely, once +the implementation is stable. + +The ECDSA implementation includes enough ASN.1 to read and write ECDSA +signatures and ECDSA private keys in RFC 5915 format; the expectation +is that the latter will be used in combination with AES Key Wrap. + +The ECDSA implementation attempts to be constant-time, to reduce the +risk of timing channel attacks. The algorithms chosen for the point +arithmetic are a tradeoff between speed and code complexity, and can +probably be improved upon even in software; reimplementing at least +the field arithmetic in hardware would probably also help. + +The current point addition and point doubling algorithms come from the +[EFD][]. At least at the moment, we're only interested in ECDSA with +the NIST prime curves, so we use algorithms optimized for a=-3. + +The point multiplication algorithm is a Montgomery Ladder, which is +not the fastest possible algorithm, but is relatively easy to confirm +by inspection as constant-time. Point multiplication could probably +be made faster by using a non-adjacent form (NAF) representation for +the scalar, but the author doesn't yet understand that well enough to +implement it as a constant-time algorithm. In theory, changing to a +NAF representation could be done without any change to the public API. + +Points stored in keys and curve parameters are in affine format, but +all point arithmetic is performed in Jacobian projective coordinates, +with the coordinates in Montgomery form; final mapping back to affine +coordinates also handles the final Montgomery reduction. + +## API ## + +Yeah, we ought to document the API, Real Soon Now, perhaps using +[Doxygen][]. For the moment, see the function prototypes in hal.h and +comments in the code. + +[EFD]: http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html +[Doxygen]: http://www.doxygen.org/ diff --git a/ecdsa.c b/ecdsa.c index 933cb5f..09ab3f6 100644 --- a/ecdsa.c +++ b/ecdsa.c @@ -1,15 +1,17 @@ /* * ecdsa.c * ------- - * Basic ECDSA functions. + * Elliptic Curve Digital Signature Algorithm for NIST prime curves. * - * At some point we may want to refactor this to separate - * functionality that appiles to all elliptic curve cryptography from - * functions specific to ECDSA over the NIST Suite B prime curves, but - * it's simplest to keep this all in one place initially. + * At some point we may want to refactor this code to separate + * functionality that applies to all elliptic curve cryptography into + * a separate module from functions specific to ECDSA over the NIST + * prime curves, but it's simplest to keep this all in one place + * initially. * * Much of the code in this module is based, at least loosely, on Tom - * St Denis's libtomcrypt code. + * St Denis's libtomcrypt code. Algorithms for point addition and + * point doubling courtesy of the hyperelliptic.org formula database. * * Authors: Rob Austein * Copyright (c) 2015, SUNET @@ -55,6 +57,7 @@ * * We use a lot of one-element arrays (fp_int[1] instead of plain * fp_int) to avoid having to prefix every use of an fp_int with "&". + * Perhaps we should encapsulate this idiom in a typedef. * * Unfortunately, libtfm is bad about const-ification, but we want to * hide that from our users, so our public API uses const as @@ -632,7 +635,7 @@ static hal_error_t point_scalar_multiply(const fp_int * const k, #if HAL_ECDSA_DEBUG_ONLY_STATIC_TEST_VECTOR_RANDOM -#warning hal_ecdsa random number generator overriden for test purposes +#warning hal_ecdsa random number generator overridden for test purposes #warning DO NOT USE THIS IN PRODUCTION typedef hal_error_t (*rng_override_test_function_t)(void *, const size_t); @@ -719,8 +722,9 @@ static hal_error_t point_pick_random(const ecdsa_curve_t * const curve, } /* - * Test whether a point really is on a particular curve (sometimes - * called "validation when applied to a public key"). + * Test whether a point really is on a particular curve. This is + * called "validation" when applied to a public key, and is required + * before verifying a signature. */ static int point_is_on_curve(const ec_point_t * const P, @@ -891,7 +895,9 @@ hal_error_t hal_ecdsa_key_load_public(hal_ecdsa_key_t **key_, } /* - * Load a private key from components. + * Load a private key from components; does all the same things as + * hal_ecdsa_key_load_public(), then loads the private key itself and + * adjusts the key type. * * For extra paranoia, we could check Q == dG. */ @@ -919,6 +925,9 @@ hal_error_t hal_ecdsa_key_load_private(hal_ecdsa_key_t **key_, /* * Write private key in RFC 5915 ASN.1 DER format. + * + * This is hand-coded, and is approaching the limit where one should + * probably be using an ASN.1 compiler like asn1c instead. */ hal_error_t hal_ecdsa_key_to_der(const hal_ecdsa_key_t * const key, @@ -1006,6 +1015,11 @@ hal_error_t hal_ecdsa_key_to_der(const hal_ecdsa_key_t * const key, return HAL_OK; } +/* + * Convenience wrapper to return how many bytes a private key would + * take if encoded as DER. + */ + size_t hal_ecdsa_key_to_der_len(const hal_ecdsa_key_t * const key) { size_t len; @@ -1014,6 +1028,9 @@ size_t hal_ecdsa_key_to_der_len(const hal_ecdsa_key_t * const key) /* * Read private key in RFC 5915 ASN.1 DER format. + * + * This is hand-coded, and is approaching the limit where one should + * probably be using an ASN.1 compiler like asn1c instead. */ hal_error_t hal_ecdsa_key_from_der(hal_ecdsa_key_t **key_, @@ -1127,7 +1144,7 @@ hal_error_t hal_ecdsa_sign(const hal_ecdsa_key_t * const key, do { /* - * Pick random curve point R, then calculate r = R.x % n. + * Pick random curve point R, then calculate r = Rx % n. * If r == 0, we can't use this point, so go try again. */ @@ -1188,6 +1205,10 @@ hal_error_t hal_ecdsa_sign(const hal_ecdsa_key_t * const key, return err; } +/* + * Verify a signature using a caller-supplied hash. + */ + hal_error_t hal_ecdsa_verify(const hal_ecdsa_key_t * const key, const uint8_t * const hash, const size_t hash_len, const uint8_t * const signature, const size_t signature_len) @@ -1199,6 +1220,9 @@ hal_error_t hal_ecdsa_verify(const hal_ecdsa_key_t * const key, if (curve == NULL) return HAL_ERROR_IMPOSSIBLE; + if (!point_is_on_curve(key->Q, curve)) + return HAL_ERROR_KEY_NOT_ON_CURVE; + fp_int * const n = unconst_fp_int(curve->n); size_t len1, len2; @@ -1212,7 +1236,7 @@ hal_error_t hal_ecdsa_verify(const hal_ecdsa_key_t * const key, memset(R, 0, sizeof(R)); /* - * First, we have to parse the ASN.1 SEQUENCE { INTEGER r, INTEGER s }. + * Parse the ASN.1 SEQUENCE { INTEGER r, INTEGER s }. */ if ((err = hal_asn1_decode_header(ASN1_SEQUENCE, signature, signature_len, &len1, &len2)) != HAL_OK) diff --git a/tests/test-ecdsa.c b/tests/test-ecdsa.c index c4cf25f..d57b440 100644 --- a/tests/test-ecdsa.c +++ b/tests/test-ecdsa.c @@ -57,15 +57,6 @@ #include "test-ecdsa.h" -/* - * Supplied test vectors don't use ASN.1 encoding. Don't want to - * trust our own ASN.1 code for this (it's one of the things we're - * testing) so use Python pyasn1 or ecdsa.der code to build what we - * need and supply them as test vector data too. This is probably - * also the right way to test our encoding and decoding of private - * keys too. - */ - #if HAL_ECDSA_DEBUG_ONLY_STATIC_TEST_VECTOR_RANDOM /* diff --git a/tests/test-ecdsa.py b/tests/test-ecdsa.py index a52f85b..1ecfef9 100644 --- a/tests/test-ecdsa.py +++ b/tests/test-ecdsa.py @@ -60,7 +60,7 @@ def long_to_bytes(l): def bytes_to_bits(b): # # This, on the other hand, is not just plain nasty, this is fancy nasty. - # This is nasty with rasins in it. + # This is nasty with raisins in it. # bits = bin(long(b.encode("hex"), 16))[2:] if len(bits) % 8: -- cgit v1.2.3