diff options
Diffstat (limited to 'asn1.c')
-rw-r--r-- | asn1.c | 227 |
1 files changed, 227 insertions, 0 deletions
@@ -0,0 +1,227 @@ +/* + * asn1.c + * ------ + * Minimal ASN.1 implementation in support of Cryptech libhal. + * + * The functions in this module are not intended to be part of the + * public API. Rather, these are utility functions used by more than + * one module within the library, which would otherwise have to be + * duplicated. The main reason for keeping these private is to avoid + * having the public API depend on any details of the underlying + * bignum implementation (currently libtfm, but that might change). + * + * As of this writing, the ASN.1 support we need is quite minimal, so, + * rather than attempting to clean all the unecessary cruft out of a + * general purpose ASN.1 implementation, we hand code the very small + * number of data types we need. At some point this will probably + * become impractical, at which point we might want to look into using + * something like the asn1c compiler. + * + * Authors: Rob Austein + * Copyright (c) 2015, SUNET + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <assert.h> + +#include "hal.h" + +#include "asn1_internal.h" + +/* + * Encode tag and length fields of an ASN.1 object. + * + * Sets *der_len to the size of of the ASN.1 header (tag and length + * fields); caller supplied length of value field, so presumably + * already knows it. + * + * If der is NULL, just return the size of the header that would be + * encoded and returns HAL_OK. + * + * If der isn't NULL, returns HAL_ERROR_RESULT_TOO_LONG unless full + * header plus value will fit; this is a bit weird, but is useful when + * using this to construct encoders for complte ASN.1 objects. + */ + +hal_error_t hal_asn1_encode_header(const uint8_t tag, + const size_t value_len, + uint8_t *der, size_t *der_len, const size_t der_max) +{ + size_t header_len = 2; /* Shortest encoding is one octet each for tag and length */ + + if (value_len >= 128) /* Add octets for longer length encoding as needed */ + for (size_t n = value_len; n > 0; n >>= 8) + ++header_len; + + if (der_len != NULL) + *der_len = header_len; + + if (der == NULL) /* If caller just wanted the length, we're done */ + return HAL_OK; + + /* + * Make sure there's enough room for header + value, then encode. + */ + + if (value_len + header_len > der_max) + return HAL_ERROR_RESULT_TOO_LONG; + + *der++ = tag; + + if (value_len < 128) { + *der = (uint8_t) value_len; + } + + else { + *der = 0x80 | (uint8_t) (header_len -= 2); + for (size_t n = value_len; n > 0 && header_len > 0; n >>= 8) + der[header_len--] = (uint8_t) (n & 0xFF); + } + + return HAL_OK; +} + +/* + * Encode an unsigned ASN.1 INTEGER from a libtfm bignum. If der is + * NULL, just return the length of what we would have encoded. + */ + +hal_error_t hal_asn1_encode_integer(const fp_int * const bn, + uint8_t *der, size_t *der_len, const size_t der_max) +{ + if (bn == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + /* + * We only handle unsigned INTEGERs, so we need to pad data with a + * leading zero if the most significant bit is set, to avoid + * flipping the ASN.1 sign bit. Conveniently, this also handles the + * difference between libtfm's and ASN.1's encoding of zero. + */ + + if (fp_cmp_d(unconst_fp_int(bn), 0) == FP_LT) + return HAL_ERROR_BAD_ARGUMENTS; + + const int leading_zero = fp_iszero(bn) || (fp_count_bits(unconst_fp_int(bn)) & 7) == 0; + const size_t vlen = fp_unsigned_bin_size(unconst_fp_int(bn)) + leading_zero; + hal_error_t err; + size_t hlen; + + err = hal_asn1_encode_header(ASN1_INTEGER, vlen, der, &hlen, der_max); + + if (der_len != NULL) + *der_len = hlen + vlen; + + if (der == NULL || err != HAL_OK) + return err; + + assert(hlen + vlen <= der_max); + + der += hlen; + if (leading_zero) + *der++ = 0x00; + fp_to_unsigned_bin(unconst_fp_int(bn), der); + + return HAL_OK; +} + +/* + * Parse tag and length of an ASN.1 object. Tag must match value + * specified by the caller. On success, sets hlen and vlen to lengths + * of header and value, respectively. + */ + +hal_error_t hal_asn1_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; +} + +/* + * Decode an ASN.1 INTEGER into a libtfm bignum. Since we only + * support (or need to support, or expect to see) unsigned integers, + * we return failure if the sign bit is set in the ASN.1 INTEGER. + */ + +hal_error_t hal_asn1_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 = hal_asn1_decode_header(ASN1_INTEGER, der, der_max, &hlen, &vlen)) != HAL_OK) + return err; + + if (der_len != NULL) + *der_len = hlen + vlen; + + if (vlen < 1 || (der[hlen] & 0x80) != 0x00) + return HAL_ERROR_ASN1_PARSE_FAILED; + + fp_init(bn); + fp_read_unsigned_bin(bn, (uint8_t *) der + hlen, vlen); + return HAL_OK; +} + +/* + * Local variables: + * indent-tabs-mode: nil + * End: + */ |