mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-01-17 09:32:44 +01:00
sys/net/credman: add key loading functions
This allows to load private and public keys encoded in DER format.
This commit is contained in:
parent
b6dee72161
commit
9980aa35b3
@ -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_%
|
||||
|
@ -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
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#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
|
||||
|
@ -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
|
||||
|
@ -18,15 +18,31 @@
|
||||
|
||||
#include "net/credman.h"
|
||||
#include "mutex.h"
|
||||
#include "kernel_defines.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user