diff options
Diffstat (limited to 'rsa.c')
-rw-r--r-- | rsa.c | 249 |
1 files changed, 245 insertions, 4 deletions
@@ -343,10 +343,10 @@ static hal_error_t find_prime(unsigned prime_length, fp_int *e, fp_int *result) return HAL_OK; } -hal_error_t hal_rsa_gen(hal_rsa_key_t *key_, - void *keybuf, const size_t keybuf_len, - const unsigned key_length, - const unsigned long public_exponent) +hal_error_t hal_rsa_key_gen(hal_rsa_key_t *key_, + void *keybuf, const size_t keybuf_len, + const unsigned key_length, + const unsigned long public_exponent) { struct rsa_key *key = keybuf; hal_error_t err = HAL_OK; @@ -410,6 +410,247 @@ hal_error_t hal_rsa_gen(hal_rsa_key_t *key_, } /* + * Minimal ASN.1 encoding and decoding for private keys. This is NOT + * a general-purpose ASN.1 implementation, just enough to read and + * write PKCS #1.5 RSAPrivateKey syntax (RFC 2313 section 7.2). + * + * If at some later date we need a full ASN.1 implementation we'll add + * it as (a) separate library module(s), but for now the goal is just + * to let us serialize private keys for internal use and debugging. + */ + +#define ASN1_INTEGER 0x02 +#define ASN1_SEQUENCE 0x30 + +static size_t count_length(size_t length) +{ + size_t result = 1; + + if (length >= 128) + for (; length > 0; length >>= 8) + result++; + + return result; +} + +static void encode_length(size_t length, size_t length_len, uint8_t *der) +{ + assert(der != NULL && length_len > 0 && length_len < 128); + + if (length < 128) { + assert(length_len == 1); + *der = (uint8_t) length; + } + + else { + *der = 0x80 | (uint8_t) --length_len; + while (length > 0 && length_len > 0) { + der[length_len--] = (uint8_t) (length & 0xFF); + length >>= 8; + } + assert(length == 0 && length_len == 0); + } +} + +static hal_error_t decode_header(const uint8_t tag, + const uint8_t * const der, size_t der_max, + size_t *hlen, size_t *vlen) +{ + assert(der != NULL && hlen != NULL && vlen != NULL); + + if (der_max < 2 || der[0] != tag) + return HAL_ERROR_ASN1_PARSE_FAILED; + + if ((der[1] & 0x80) == 0) { + *hlen = 2; + *vlen = der[1]; + } + + else { + *hlen = 2 + (der[1] & 0x7F); + *vlen = 0; + + if (*hlen > der_max) + return HAL_ERROR_ASN1_PARSE_FAILED; + + for (size_t i = 2; i < *hlen; i++) + *vlen = (*vlen << 8) + der[i]; + } + + if (*hlen + *vlen > der_max) + return HAL_ERROR_ASN1_PARSE_FAILED; + + return HAL_OK; +} + +static hal_error_t encode_integer(fp_int *bn, + uint8_t *der, size_t *der_len, const size_t der_max) +{ + if (bn == NULL || der_len == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + /* + * Calculate length. Need to pad data with a leading zero if most + * significant bit is set, to avoid flipping ASN.1 sign bit. If + * caller didn't supply a buffer, just return the total length. + */ + + const int cmp = fp_cmp_d(bn, 0); + + if (cmp != FP_EQ && cmp != FP_GT) + return HAL_ERROR_BAD_ARGUMENTS; + + const int leading_zero = (cmp == FP_EQ || (fp_count_bits(bn) & 7) == 0); + const size_t data_len = fp_unsigned_bin_size(bn) + leading_zero; + const size_t tag_len = 1; + const size_t length_len = count_length(data_len); + const size_t total_len = tag_len + length_len + data_len; + + *der_len = total_len; + + if (der == NULL) + return HAL_OK; + + if (total_len > der_max) + return HAL_ERROR_RESULT_TOO_LONG; + + /* + * Now encode. + */ + + *der++ = ASN1_INTEGER; + encode_length(data_len, length_len, der); + der += length_len; + if (leading_zero) + *der++ = 0x00; + fp_to_unsigned_bin(bn, der); + + return HAL_OK; +} + +static hal_error_t decode_integer(fp_int *bn, + const uint8_t * const der, size_t *der_len, const size_t der_max) +{ + if (bn == NULL || der == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + hal_error_t err; + size_t hlen, vlen; + + if ((err = decode_header(ASN1_INTEGER, der, der_max, &hlen, &vlen)) != HAL_OK) + return err; + + if (der_len != NULL) + *der_len = hlen + vlen; + + if (vlen < 1) + return HAL_ERROR_ASN1_PARSE_FAILED; + + fp_init(bn); + fp_read_unsigned_bin(bn, (uint8_t *) der + hlen, vlen); + return HAL_OK; +} + +/* + * RSAPrivateKey fields in the required order. + */ + +#define RSAPrivateKey_fields \ + _(&version); \ + _(&key->n); \ + _(&key->e); \ + _(&key->d); \ + _(&key->p); \ + _(&key->q); \ + _(&key->dP); \ + _(&key->dQ); \ + _(&key->u); + + +hal_error_t hal_rsa_key_to_der(hal_rsa_key_t key_, + uint8_t *der, size_t *der_len, const size_t der_max) +{ + struct rsa_key *key = key_.key; + hal_error_t err = HAL_OK; + + if (key == NULL || der_len == NULL || key->type != HAL_RSA_PRIVATE) + return HAL_ERROR_BAD_ARGUMENTS; + + fp_int version; + fp_zero(&version); + + /* + * Calculate length. + */ + + size_t data_len = 0; + +#define _(x) { size_t i; if ((err = encode_integer(x, NULL, &i, der_max - data_len)) != HAL_OK) return err; data_len += i; } + RSAPrivateKey_fields; +#undef _ + + const size_t tag_len = 1; + const size_t length_len = count_length(data_len); + const size_t total_len = tag_len + length_len + data_len; + + *der_len = total_len; + + if (der == NULL) + return HAL_OK; + + if (total_len > der_max) + return HAL_ERROR_RESULT_TOO_LONG; + + /* + * Now encode. + */ + + *der++ = ASN1_SEQUENCE; + encode_length(data_len, length_len, der); + der += length_len; + +#define _(x) { size_t i; if ((err = encode_integer(x, der, &i, data_len)) != HAL_OK) return err; der += i; data_len -= i; } + RSAPrivateKey_fields; +#undef _ + + return HAL_OK; +} + +hal_error_t hal_rsa_key_from_der(hal_rsa_key_t *key_, + void *keybuf, const size_t keybuf_len, + const uint8_t *der, const size_t der_len) +{ + if (key_ == NULL || keybuf == NULL || keybuf_len < sizeof(struct rsa_key) || der == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + memset(keybuf, 0, keybuf_len); + + struct rsa_key *key = keybuf; + + key->type = HAL_RSA_PRIVATE; + + hal_error_t err = HAL_OK; + size_t hlen, vlen; + + if ((err = decode_header(ASN1_SEQUENCE, der, der_len, &hlen, &vlen)) != HAL_OK) + return err; + + der += hlen; + + fp_int version; + fp_init(&version); + +#define _(x) { size_t i; if ((err = decode_integer(x, der, &i, vlen)) != HAL_OK) return err; der += i; vlen -= i; } + RSAPrivateKey_fields; +#undef _ + + if (fp_cmp_d(&version, 0) != FP_EQ) + return HAL_ERROR_ASN1_PARSE_FAILED; + + return HAL_OK; +} + +/* * Local variables: * indent-tabs-mode: nil * End: |