/*
* 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, NORDUnet A/S
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - 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.
*
* - Neither the name of the NORDUnet nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* 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
* HOLDER 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 <stdint.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;
}
/*
* Encode a public key into an RFC 5280 SubjectPublicKeyInfo.
*/
hal_error_t hal_asn1_encode_spki(const uint8_t * const alg_oid, const size_t alg_oid_len,
const uint8_t * const curve_oid, const size_t curve_oid_len,
const uint8_t * const pubkey, const size_t pubkey_len,
uint8_t *der, size_t *der_len, const size_t der_max)
{
if (alg_oid == NULL || alg_oid_len == 0 || pubkey == NULL || pubkey_len == 0 || (curve_oid == NULL && curve_oid_len != 0))
return HAL_ERROR_BAD_ARGUMENTS;
const uint8_t curve_oid_tag = curve_oid == NULL ? ASN1_NULL : ASN1_OBJECT_IDENTIFIER;
hal_error_t err;
size_t hlen, hlen_spki, hlen_algid, hlen_alg, hlen_curve, hlen_bit;
if ((err = hal_asn1_encode_header(ASN1_OBJECT_IDENTIFIER, alg_oid_len, NULL, &hlen_alg, 0)) != HAL_OK ||
(err = hal_asn1_encode_header(curve_oid_tag, curve_oid_len, NULL, &hlen_curve, 0)) != HAL_OK ||
(err = hal_asn1_encode_header(ASN1_BIT_STRING, pubkey_len, NULL, &hlen_bit, 0)) != HAL_OK)
return err;
const size_t algid_len = hlen_alg + alg_oid_len + hlen_curve + curve_oid_len;
if ((err = hal_asn1_encode_header(ASN1_SEQUENCE, algid_len, NULL, &hlen_algid, 0)) != HAL_OK)
return err;
const size_t vlen = hlen_algid + hlen_alg + alg_oid_len + hlen_curve + curve_oid_len + hlen_bit + pubkey_len;
if ((err = hal_asn1_encode_header(ASN1_SEQUENCE, vlen, NULL, &hlen_spki, 0)) != HAL_OK)
return err;
/*
* Handle pubkey early, in case it was staged into our output buffer.
*/
if (der != NULL && hlen_spki + vlen <= der_max)
memmove(der + hlen_spki + vlen - pubkey_len, pubkey, pubkey_len);
err = hal_asn1_encode_header(ASN1_SEQUENCE, vlen, der, &hlen, der_max);
if (der_len != NULL)
*der_len = hlen + vlen;
if (der == NULL || err != HAL_OK)
return err;
uint8_t *d = der + hlen;
memset(d, 0, vlen);
if ((err = hal_asn1_encode_header(ASN1_SEQUENCE, algid_len, d, &hlen, der + der_max - d)) != HAL_OK)
return err;
d += hlen;
if ((err = hal_asn1_encode_header(ASN1_OBJECT_IDENTIFIER, alg_oid_len, d, &hlen, der + der_max - d)) != HAL_OK)
return err;
d += hlen;
memcpy(d, alg_oid, alg_oid_len);
d += alg_oid_len;
if ((err = hal_asn1_encode_header(curve_oid_tag, curve_oid_len, d, &hlen, der + der_max - d)) != HAL_OK)
return err;
d += hlen;
if (curve_oid != NULL)
memcpy(d, curve_oid, curve_oid_len);
d += curve_oid_len;
if ((err = hal_asn1_encode_header(ASN1_BIT_STRING, pubkey_len, d, &hlen, der + der_max - d)) != HAL_OK)
return err;
d += hlen;
d += pubkey_len; /* pubkey handled early, above. */
assert(d == der + hlen_spki + vlen);
assert(d <= der + der_max);
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;
}
/*
* Decode a public key from an RFC 5280 SubjectPublicKeyInfo.
*/
hal_error_t hal_asn1_decode_spki(const uint8_t **alg_oid, size_t *alg_oid_len,
const uint8_t **curve_oid, size_t *curve_oid_len,
const uint8_t **pubkey, size_t *pubkey_len,
const uint8_t *const der, const size_t der_len)
{
if (alg_oid == NULL || alg_oid_len == NULL || curve_oid == NULL || curve_oid_len == NULL ||
pubkey == NULL || pubkey_len == NULL || der == NULL)
return HAL_ERROR_BAD_ARGUMENTS;
size_t hlen, vlen;
hal_error_t err;
if ((err = hal_asn1_decode_header(ASN1_SEQUENCE, der, der_len, &hlen, &vlen)) != HAL_OK)
return err;
const uint8_t * const der_end = der + hlen + vlen;
const uint8_t *d = der + hlen;
if ((err = hal_asn1_decode_header(ASN1_SEQUENCE, der, der_end - d, &hlen, &vlen)) != HAL_OK)
return err;
d += hlen;
const uint8_t * const algid_end = d + vlen;
if ((err = hal_asn1_decode_header(ASN1_OBJECT_IDENTIFIER, d, algid_end - d, &hlen, &vlen)) != HAL_OK)
return err;
d += hlen;
if (vlen > algid_end - d)
return HAL_ERROR_ASN1_PARSE_FAILED;
*alg_oid = d;
*alg_oid_len = vlen;
d += vlen;
*curve_oid = NULL;
*curve_oid_len = 0;
if (d < algid_end) {
switch (*d) {
case ASN1_OBJECT_IDENTIFIER:
if ((err = hal_asn1_decode_header(ASN1_OBJECT_IDENTIFIER, d, algid_end - d, &hlen, &vlen)) != HAL_OK)
return err;
d += hlen;
if (vlen > algid_end - d)
return HAL_ERROR_ASN1_PARSE_FAILED;
*curve_oid = d;
*curve_oid_len = vlen;
d += vlen;
break;
case ASN1_NULL:
if ((err = hal_asn1_decode_header(ASN1_NULL, d, algid_end - d, &hlen, &vlen)) != HAL_OK)
return err;
d += hlen;
if (vlen == 0)
break;
default:
return HAL_ERROR_ASN1_PARSE_FAILED;
}
}
if (d != algid_end)
return HAL_ERROR_ASN1_PARSE_FAILED;
if ((err = hal_asn1_decode_header(ASN1_BIT_STRING, d, der_end - d, &hlen, &vlen)) != HAL_OK)
return err;
d += hlen;
if (vlen >= algid_end - d || vlen == 0 || *d != 0x00)
return HAL_ERROR_ASN1_PARSE_FAILED;
*pubkey = ++d;
*pubkey_len = --vlen;
d += vlen;
if (d != der_end)
return HAL_ERROR_ASN1_PARSE_FAILED;
return HAL_OK;
}
/*
* Local variables:
* indent-tabs-mode: nil
* End:
*/