From 9980aa35b3e1fe464fc9230aeb615e7b2569f8d9 Mon Sep 17 00:00:00 2001 From: Leandro Lanzieri Date: Mon, 29 Mar 2021 16:04:45 +0200 Subject: [PATCH] sys/net/credman: add key loading functions This allows to load private and public keys encoded in DER format. --- makefiles/pseudomodules.inc.mk | 1 + sys/Makefile.dep | 4 + sys/include/net/credman.h | 78 +++++++++ sys/net/credman/Kconfig | 5 + sys/net/credman/credman.c | 279 +++++++++++++++++++++++++++++++++ 5 files changed, 367 insertions(+) diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 3fc47c3f52..4ef3e4cd6b 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -21,6 +21,7 @@ PSEUDOMODULES += cortexm_fpu PSEUDOMODULES += cortexm_svc PSEUDOMODULES += cpp PSEUDOMODULES += cpu_check_address +PSEUDOMODULES += credman_load PSEUDOMODULES += dbgpin PSEUDOMODULES += devfs_% PSEUDOMODULES += dhcpv6_% diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 2db422293a..c97a44a2c5 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -699,6 +699,10 @@ ifneq (,$(filter sock_dtls, $(USEMODULE))) USEMODULE += sock_udp endif +ifneq (,$(filter credman_load, $(USEMODULE))) + USEPKG += tiny-asn1 +endif + ifneq (,$(filter suit,$(USEMODULE))) USEPKG += nanocbor USEPKG += libcose diff --git a/sys/include/net/credman.h b/sys/include/net/credman.h index 86b30d0579..2f89e6de2a 100644 --- a/sys/include/net/credman.h +++ b/sys/include/net/credman.h @@ -29,6 +29,7 @@ #include #include +#include "kernel_defines.h" #ifdef __cplusplus extern "C" { @@ -45,6 +46,13 @@ extern "C" { #ifndef CONFIG_CREDMAN_MAX_CREDENTIALS #define CONFIG_CREDMAN_MAX_CREDENTIALS (2) #endif + +/** + * @brief Maximum number of ASN.1 objects when decoding keys. + */ +#ifndef CONFIG_CREDMAN_MAX_ASN1_OBJ +#define CONFIG_CREDMAN_MAX_ASN1_OBJ (8) +#endif /** @} */ /** @@ -185,6 +193,76 @@ void credman_delete(credman_tag_t tag, credman_type_t type); */ int credman_get_used_count(void); +#if IS_USED(MODULE_CREDMAN_LOAD) || DOXYGEN +/** + * @brief Load a public key from a buffer, as a `SubjectPublicKeyInfo` sequence, according to + * RFC5280. The key should be encoded in DER format. + * + * @pre `buf != NULL && out != NULL`. + * + * @note To use this functionality include the module `credman_load`. Credman only supports ECDSA + * for now, so [RFC5480](https://tools.ietf.org/html/rfc5480) applies. + * + * @experimental This API is considered experimental and will probably change without notice! + * + * @see https://tools.ietf.org/html/rfc5280#section-4.1 + * + * @param[in] buf Buffer holding the encoded public key + * @param[in] buf_len Length of @p buf + * @param[out] out ECDSA public key to populate + * + * @retval CREDMAN_OK on success + * @retval CREDMAN_INVALID if the key is not valid + */ +int credman_load_public_key(const void *buf, size_t buf_len, ecdsa_public_key_t *out); + +/** + * @brief Load a private key from a buffer, as a `OneAsymmetricKey` sequence, according to RFC5958. + * This is compatible with the previous version PKCS#8 (defined in RFC5208). If the optional + * respective public key is present, it will be loaded as well. The key should be encoded in + * DER format. + * + * @pre `buf != NULL && cred != NULL` + * + * @note To use this functionality include the module `credman_load`. Credman only supports ECDSA + * for now. + * + * @experimental This API is considered experimental and will probably change without notice! + * + * @see https://tools.ietf.org/html/rfc5958#section-2 + * + * @param[in] buf Buffer holding the encoded private key + * @param[in] buf_len Length of @p buf + * @param[out] cred Credential to populate + * + * @retval CREDMAN_OK on success + * @retval CREDMAN_INVALID if the key is not valid + */ +int credman_load_private_key(const void *buf, size_t buf_len, credman_credential_t *cred); + +/** + * @brief Load an ECC private key from a buffer, as an `ECPrivateKey` sequence, according to RFC5915. + * If the optional respective public key is present, it will be loaded as well. The key + * should be encoded in DER format. + * + * @pre `buf != NULL && cred != NULL` + * + * @note To use this functionality include the module `credman_load`. + * + * @experimental This API is considered experimental and will probably change without notice! + * + * @see https://tools.ietf.org/html/rfc5915#section-3 + * + * @param[in] buf Buffer holding the encoded private key + * @param[in] buf_len Length of @p buf + * @param[out] cred Credential to populate + * + * @retval CREDMAN_OK on success + * @retval CREDMAN_INVALID if the key is not valid + */ +int credman_load_private_ecc_key(const void *buf, size_t buf_len, credman_credential_t *cred); +#endif /* MODULE_CREDMAN_LOAD || DOXYGEN */ + #ifdef TEST_SUITES /** * @brief Empties the credential pool diff --git a/sys/net/credman/Kconfig b/sys/net/credman/Kconfig index b5ccfc96be..30c860f977 100644 --- a/sys/net/credman/Kconfig +++ b/sys/net/credman/Kconfig @@ -20,4 +20,9 @@ config CREDMAN_MAX_CREDENTIALS Configure 'CONFIG_CREDMAN_MAX_CREDENTIALS', the maximum number of allowed credentials in the credential pool. +config CREDMAN_MAX_ASN1_OBJ + int "Maximum number of ASN.1 objects when decoding keys" + default 8 + depends on USEMODULE_CREDMAN_LOAD + endif # KCONFIG_USEMODULE_CREDMAN diff --git a/sys/net/credman/credman.c b/sys/net/credman/credman.c index c165d5938b..b50d3fe8bf 100644 --- a/sys/net/credman/credman.c +++ b/sys/net/credman/credman.c @@ -18,15 +18,31 @@ #include "net/credman.h" #include "mutex.h" +#include "kernel_defines.h" #include #include +#include #define ENABLE_DEBUG 0 #include "debug.h" static mutex_t _mutex = MUTEX_INIT; +#if IS_USED(MODULE_CREDMAN_LOAD) +#include "tiny-asn1.h" + +/* Context-specific tag in DER encoding + * (see section 8.1.2.2 of ITU-T X.690 https://www.itu.int/rec/T-REC-X.690-200811-S) */ +#define ASN1_CONTEXT_TAG(v) (0xA0 | (v & 0x1F)) + +/* ASN.1 representation of ecPublicKey - OID 1.2.840.10045.2.1 + * (see https://oidref.com/1.2.840.10045.2.1) */ +static const uint8_t ecPublicKey[] = { 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }; + +static int _parse_ecc_point(const asn1_tree *key, const void **x, const void **y); +#endif /* MODULE_CREDMAN_LOAD */ + static credman_credential_t credentials[CONFIG_CREDMAN_MAX_CREDENTIALS]; static unsigned used = 0; @@ -90,6 +106,269 @@ end: return ret; } +#if IS_USED(MODULE_CREDMAN_LOAD) +int credman_load_public_key(const void *buf, size_t buf_len, ecdsa_public_key_t *out) +{ + asn1_tree objects[CONFIG_CREDMAN_MAX_ASN1_OBJ]; + asn1_tree pub_key; + + assert(buf); + assert(out); + + int obj_count = der_object_count((uint8_t *)buf, buf_len); + if (obj_count <= 0) { + DEBUG("credman: could not calculate the number of elements within the key\n"); + return CREDMAN_INVALID; + } + + if (obj_count > CONFIG_CREDMAN_MAX_ASN1_OBJ) { + DEBUG("credman: not enough ASN.1 objects to decode key.\n"); + DEBUG("credman: current max is %d, and we need %d\n", + CONFIG_CREDMAN_MAX_ASN1_OBJ, obj_count); + return CREDMAN_INVALID; + } + + int32_t res = der_decode((uint8_t *)buf, buf_len, &pub_key, objects, obj_count); + if (res < 0) { + DEBUG("credman: could not parse the key (%" PRId32 ")\n", res); + return CREDMAN_INVALID; + } + + /* + * From https://tools.ietf.org/html/rfc5280#section-4.1 + * + * SubjectPublicKeyInfo ::= SEQUENCE { + * algorithm AlgorithmIdentifier, + * subjectPublicKey BIT STRING } + * + * AlgorithmIdentifier ::= SEQUENCE { + * algorithm OBJECT IDENTIFIER, + * parameters ANY DEFINED BY algorithm OPTIONAL } + */ + + /* the outer container is a 'SubjectPublicKeyInfo', which should be a SEQUENCE */ + if (pub_key.type != ASN1_TYPE_SEQUENCE) { + DEBUG("credman: the public key information should be contained in an ASN.1 SEQUENCE\n"); + return CREDMAN_INVALID; + } + + asn1_tree *algorithm_id = pub_key.child; + asn1_tree *algorithm = algorithm_id->child; + + /* for now only ECDSA is supported by credman */ + /* the algorithm should be Elliptic Curve Public Key (OID 1.2.840.10045.2.1) */ + if (sizeof(ecPublicKey) != algorithm->length) { + DEBUG("credman: wrong OID length for algorithm\n"); + return CREDMAN_INVALID; + } + + if (memcmp(ecPublicKey, algorithm->data, sizeof(ecPublicKey)) != 0) { + DEBUG("credman: wrong OID for algorithm. Expected 1.2.840.10045.2.1\n"); + return CREDMAN_INVALID; + } + + return _parse_ecc_point(algorithm_id->next, &out->x, &out->y); +} + +int credman_load_private_key(const void *buf, size_t buf_len, credman_credential_t *cred) +{ + + asn1_tree objects[CONFIG_CREDMAN_MAX_ASN1_OBJ]; + asn1_tree priv_key; + + assert(buf); + assert(cred); + + int obj_count = der_object_count((uint8_t *)buf, buf_len); + if (obj_count <= 0) { + DEBUG("credman: could not calculate the number of elements within the key\n"); + return CREDMAN_INVALID; + } + + if (obj_count > CONFIG_CREDMAN_MAX_ASN1_OBJ) { + DEBUG("credman: not enough ASN.1 objects to decode key.\n"); + DEBUG("credman: current max is %d, and we need %d\n", + CONFIG_CREDMAN_MAX_ASN1_OBJ, obj_count); + return CREDMAN_INVALID; + } + + if (der_decode((uint8_t *)buf, buf_len, &priv_key, objects, obj_count) < 0) { + DEBUG("credman: could not parse the key\n"); + return CREDMAN_INVALID; + } + + /* + * From https://tools.ietf.org/html/rfc5958#section-2 + * + * OneAsymmetricKey ::= SEQUENCE { + * version Version, + * privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, + * privateKey PrivateKey, + * attributes [0] Attributes OPTIONAL, + * ..., + * [[2: publicKey [1] PublicKey OPTIONAL ]], + * ... + * } + * + * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier + * { PUBLIC-KEY, + * { PrivateKeyAlgorithms } } + * + * AlgorithmIdentifier ::= SEQUENCE { + * algorithm OBJECT IDENTIFIER, + * parameters ANY DEFINED BY algorithm OPTIONAL } + * + * PrivateKey ::= OCTET STRING + * -- Content varies based on type of key. The + * -- algorithm identifier dictates the format of + * -- the key. + */ + + /* the outer container is a 'OneAsymmetricKey', which should be a SEQUENCE */ + if (priv_key.type != ASN1_TYPE_SEQUENCE) { + DEBUG("credman: the private key information should be contained in an ASN.1 SEQUENCE\n"); + return CREDMAN_INVALID; + } + + /* point to version */ + asn1_tree *node = priv_key.child; + + if (!node || node->type != ASN1_TYPE_INTEGER) { + DEBUG("credman: invalid private key version\n"); + return CREDMAN_INVALID; + } + + /* point to privateKeyAlgorithm */ + node = node->next; + if (!node || node->type != ASN1_TYPE_SEQUENCE || !node->length) { + DEBUG("credman: invalid private key algorithm identifier\n"); + return CREDMAN_INVALID; + } + + /* for now only ECDSA is supported by credman */ + /* the algorithm should be Elliptic Curve Public Key (OID 1.2.840.10045.2.1) */ + asn1_tree *algorithm = node->child; + if (sizeof(ecPublicKey) != algorithm->length) { + DEBUG("credman: wrong private key algorithm, only ecPublicKey is supported\n"); + return CREDMAN_INVALID; + } + + if (memcmp(ecPublicKey, algorithm->data, sizeof(ecPublicKey)) != 0) { + DEBUG("credman: wrong OID for algorithm. Expected 1.2.840.10045.2.1\n"); + return CREDMAN_INVALID; + } + + /* point to privateKey */ + node = node->next; + if (!node || node->type != ASN1_TYPE_OCTET_STRING || !node->data || !node->length) { + DEBUG("credman: no private key found\n"); + return CREDMAN_INVALID; + } + + return credman_load_private_ecc_key(node->data, node->length, cred); + +} + +int credman_load_private_ecc_key(const void *buf, size_t buf_len, credman_credential_t *cred) +{ + asn1_tree objects[CONFIG_CREDMAN_MAX_ASN1_OBJ]; + asn1_tree priv_key; + + assert(buf); + assert(cred); + + int obj_count = der_object_count((uint8_t *)buf, buf_len); + if (obj_count <= 0) { + DEBUG("credman: could not calculate the number of elements within the key\n"); + return CREDMAN_INVALID; + } + + if (obj_count > CONFIG_CREDMAN_MAX_ASN1_OBJ) { + DEBUG("credman: not enough ASN.1 objects to decode key.\n"); + DEBUG("credman: current max is %d, and we need %d\n", + CONFIG_CREDMAN_MAX_ASN1_OBJ, obj_count); + return CREDMAN_INVALID; + } + + if (der_decode((uint8_t *)buf, buf_len, &priv_key, objects, obj_count) < 0) { + DEBUG("credman: could not parse the key\n"); + return CREDMAN_INVALID; + } + + /* + * From https://tools.ietf.org/html/rfc5915#section-3 + * + * ECPrivateKey ::= SEQUENCE { + * version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + * privateKey OCTET STRING, + * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + * publicKey [1] BIT STRING OPTIONAL + * } + */ + + /* point to version, it SHALL be 1 */ + asn1_tree *node = priv_key.child; + if (!node || node->type != ASN1_TYPE_INTEGER || node->data[0] != 0x01) { + DEBUG("credman: invalid private key version\n"); + return CREDMAN_INVALID; + } + + /* point to privateKey */ + node = node->next; + if (!node || node->type != ASN1_TYPE_OCTET_STRING || !node->data || !node->length) { + DEBUG("credman: invalid private key\n"); + return CREDMAN_INVALID; + } + + cred->type = CREDMAN_TYPE_ECDSA; + cred->params.ecdsa.public_key.x = NULL; + cred->params.ecdsa.public_key.y = NULL; + + cred->params.ecdsa.private_key = node->data; + + /* try to find a publicKey by tag */ + while (node && node->type != ASN1_CONTEXT_TAG(1)) { + node = node->next; + } + + if (!node) { + return CREDMAN_OK; + } + + return _parse_ecc_point(node->child, &cred->params.ecdsa.public_key.x, + &cred->params.ecdsa.public_key.y); +} + +static int _parse_ecc_point(const asn1_tree *key, const void **x, const void **y) +{ + if (!key || key->type != ASN1_TYPE_BIT_STRING) { + DEBUG("credman: the key should be an ASN.1 BIT STRING\n"); + return CREDMAN_INVALID; + } + + if (!key->length) { + DEBUG("credman: the key is missing\n"); + return CREDMAN_INVALID; + } + + /* SEC 1: Elliptic Curve Cryptography - Section 2.3.4 (https://www.secg.org/sec1-v2.pdf) */ + /* check for uncompressed key format */ + if (key->data[1] != 0x04) { + DEBUG("credman: only uncompressed format is supported\n"); + return CREDMAN_INVALID; + } + + size_t coords_len = (key->length - 2) / 2; + uint8_t *_x = &key->data[2]; /* skip format specifier and unused bits */ + uint8_t *_y = &_x[coords_len]; + + *x = _x; + *y = _y; + + return CREDMAN_OK; +} +#endif /* MODULE_CREDMAN_LOAD */ + int credman_get(credman_credential_t *credential, credman_tag_t tag, credman_type_t type) {