Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ set(LIBNAT20_SOURCES
src/core/asn1.c
src/core/cbor.c
src/core/cose.c
src/core/cwt.c
src/core/functionality.c
src/core/oid.c
src/core/stream.c
Expand All @@ -112,6 +113,7 @@ set(LIBNAT20_PUB_HEADERS
include/nat20/cbor.h
include/nat20/constants.h
include/nat20/cose.h
include/nat20/cwt.h
include/nat20/crypto.h
include/nat20/endian.h
include/nat20/error.h
Expand Down Expand Up @@ -150,6 +152,7 @@ set(LIBNAT20_TEST_SOURCES
src/core/test/asn1.cpp
src/core/test/cbor.cpp
src/core/test/cose.cpp
src/core/test/cwt.cpp
src/core/test/functionality.cpp
src/core/test/oid.cpp
src/core/test/stream.cpp
Expand Down
172 changes: 172 additions & 0 deletions include/nat20/cwt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright 2026 Aurora Operations, Inc.
*
* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0
*
* This work is dual licensed.
* You may use it under Apache-2.0 or GPL-2.0 at your option.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* OR
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see
* <https://www.gnu.org/licenses/>.
*/

#pragma once

#include <nat20/cose.h>
#include <nat20/crypto.h>
#include <nat20/open_dice.h>
#include <nat20/stream.h>
#include <nat20/types.h>

#ifdef __cplusplus
extern "C" {
#endif

/** @file */

/**
* @defgroup open_dice_cwt_labels Open DICE CWT Label Constants
*
* These constants are defined in the OpenDice specification and represent
* the labels used in the Open DICE CWT structure.
*
* [OpenDICE specification]
* (https://pigweed.googlesource.com/open-dice/+/HEAD/docs/specification.md#cbor-cdi-certificates)
*
* @{
*/
/** Label for code hash. */
#define N20_OPEN_DICE_CWT_LABEL_CODE_HASH (-4670545)
/** Label for code descriptor. */
#define N20_OPEN_DICE_CWT_LABEL_CODE_DESCRIPTOR (-4670546)
/** Label for configuration hash. */
#define N20_OPEN_DICE_CWT_LABEL_CONFIGURATION_HASH (-4670547)
/** Label for configuration descriptor. */
#define N20_OPEN_DICE_CWT_LABEL_CONFIGURATION_DESCRIPTOR (-4670548)
/** Label for authority hash. */
#define N20_OPEN_DICE_CWT_LABEL_AUTHORITY_HASH (-4670549)
/** Label for authority descriptor. */
#define N20_OPEN_DICE_CWT_LABEL_AUTHORITY_DESCRIPTOR (-4670550)
/** Label for DICE mode. */
#define N20_OPEN_DICE_CWT_LABEL_MODE (-4670551)
/** Label for subject public key. */
#define N20_OPEN_DICE_CWT_LABEL_SUBJECT_PUBLIC_KEY (-4670552)
/** Label for key usage. */
#define N20_OPEN_DICE_CWT_LABEL_KEY_USAGE (-4670553)
/** Label for profile name. */
#define N20_OPEN_DICE_CWT_LABEL_PROFILE (-4670554)
/** @} */

/**
* @defgroup cwt_labels CBOR Web Token (CWT) Label Constants
*
* These constants are defined in the CWT specification and represent
* the labels used in the CWT structure.
*
* [CWT specification](https://tools.ietf.org/html/rfc8392)
*
* @{
*/
#define N20_CWT_LABEL_ISSUER (1) /**< Label for issuer. */
#define N20_CWT_LABEL_SUBJECT (2) /**< Label for subject. */
/** @} */

/**
* @brief Convert Open DICE public key information to COSE key format.
*
* If either argument is NULL this function is a no-op.
*
* If the @p key_info->algorithm is not supported, @p cose_key will be left unmodified.
*
* Supported algorithms are Ed25519, P-256, and P-384 which are expressed
* by @ref n20_crypto_key_type_ed25519_e, @ref n20_crypto_key_type_secp256r1_e,
* and @ref n20_crypto_key_type_secp384r1_e respectively.
*
* The function sets the algorithm ID to @ref n20_cose_algorithm_id_eddsa_e,
* @ref n20_cose_algorithm_id_es256_e, or @ref n20_cose_algorithm_id_es384_e
* respectively. Note that these values have been deprecated according to [1]
* but are still in use.
*
* The function also populates the public key from the the supplied buffer
* in @p key_info->key.
*
* In the case of P-256 and P-384, the function assumes that the key buffer
* is formatted as an uncompressed EC point. If the buffer size is odd it
* is assumed that the first byte is the format header `0x04` which gets ignored.
* The point is then split into its X and Y coordinates and stored in
* @p cose_key->x and @p cose_key->y respectively. @p cose_key->d is set to an
* empty slice.
*
* In the case of ED25519, the function expects the key buffer to be in the
* standard format for EdDSA keys and stores in @p cose_key->x.
* @p cose_key->y and @p cose_key->d are set to empty slices.
*
* Buffers are not copied. Both @p key_info and @p cose_key point to the
* same underlying buffers. No ownership transfer is implied. The buffers
* must remain valid for the life time of both structures.
*
* [1] https://www.iana.org/assignments/cose/cose.xhtml
*
* @param cose_key Pointer to the COSE key structure to populate.
* @param key_info Pointer to the Open DICE public key information.
*/
extern void n20_cwt_key_info_to_cose(n20_cose_key_t *const cose_key,
n20_open_dice_public_key_info_t const *const key_info);

/**
* @brief Render a CBOR web token (CWT) structure.
*
* This function encodes a CWT (RFC8392) structure into a CBOR representation
* including the issuer and subject claims, the subject public key and the OpenDICE
* claims as described in the OpenDICE specification [1].
*
* The function never fails, however, the stream needs to be checked
* for buffer @ref n20_stream_has_buffer_overflow (or write position overflow
* @ref n20_stream_has_write_position_overflow if the to-be-rendered size is measured)
* after the call to ensure that it was rendered to the buffer successfully.
*
* The function is not responsible for managing the lifetime of the buffers
* used by the input parameters. The caller must ensure that these buffers
* remain valid for the duration of the function call.
*
* The function does not sanitize the input and may not produce a valid CWT.
* E.g. if the key info is missing, or if the key algorithm is not supported.
* the public key field will be rendered as a CBOR null value
* @sa n20_cwt_key_info_to_cose.
*
* [1] https://pigweed.googlesource.com/open-dice/+/HEAD/docs/specification.md
*
* @param s The stream to write the CBOR representation to.
* @param cert_info The CWT structure to render.
*/
extern void n20_open_dice_cwt_write(n20_stream_t *const s,
n20_open_dice_cert_info_t const *const cert_info);

#ifdef __cplusplus
}
#endif
190 changes: 190 additions & 0 deletions src/core/cwt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright 2026 Aurora Operations, Inc.
*
* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0
*
* This work is dual licensed.
* You may use it under Apache-2.0 or GPL-2.0 at your option.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* OR
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see
* <https://www.gnu.org/licenses/>.
*/

#include <nat20/cbor.h>
#include <nat20/cose.h>
#include <nat20/crypto.h>
#include <nat20/cwt.h>
#include <nat20/error.h>
#include <nat20/stream.h>
#include <nat20/types.h>

static void n20_open_dice_write_name_as_hex(n20_stream_t *const s, n20_slice_t const name) {
if (name.size != 0 && name.buffer == NULL) {
n20_cbor_write_null(s);
return;
}

for (size_t i = 0; i < name.size; ++i) {
uint8_t byte = name.buffer[name.size - (i + 1)];
uint8_t hex[2] = {(byte >> 4) + '0', (byte & 0x0f) + '0'};
if (hex[0] > '9') {
hex[0] += 39; // Convert to 'a' - 'f'
}
if (hex[1] > '9') {
hex[1] += 39; // Convert to 'a' - 'f'
}
n20_stream_prepend(s, hex, sizeof(hex));
}
n20_cbor_write_header(s, n20_cbor_type_string_e, name.size * 2);
}

void n20_cwt_key_info_to_cose(n20_cose_key_t *const cose_key,
n20_open_dice_public_key_info_t const *const key_info) {
if (key_info == NULL || cose_key == NULL) {
return;
}

switch (key_info->algorithm) {
case n20_crypto_key_type_ed25519_e:
cose_key->algorithm_id = n20_cose_algorithm_id_eddsa_e;
cose_key->x = key_info->key;
cose_key->y = N20_SLICE_NULL;
cose_key->d = N20_SLICE_NULL;
return;
case n20_crypto_key_type_secp256r1_e:
cose_key->algorithm_id = n20_cose_algorithm_id_es256_e;
break;
case n20_crypto_key_type_secp384r1_e:
cose_key->algorithm_id = n20_cose_algorithm_id_es384_e;
break;
default:
/* Unsupported algorithm -> don't touch the COSE key structure. */
return;
}

/* For NIST curves P-256 and P-384 the key is split into
* X and Y coordinates. */
size_t coordinate_size = key_info->key.size / 2;

/* If the key size is odd skip the first byte to swallow
* the format header. */
cose_key->x.buffer = key_info->key.buffer + (key_info->key.size & 1);
cose_key->x.size = coordinate_size;
cose_key->y.buffer = cose_key->x.buffer + coordinate_size;
cose_key->y.size = coordinate_size;
cose_key->d = N20_SLICE_NULL;
}

void n20_open_dice_cwt_write(n20_stream_t *const s, n20_open_dice_cert_info_t const *const cwt) {
uint32_t pairs = 0;

// Write Profile Name
if (cwt->open_dice_input.profile_name.size > 0) {
n20_cbor_write_text_string(s, cwt->open_dice_input.profile_name);
n20_cbor_write_int(s, N20_OPEN_DICE_CWT_LABEL_PROFILE);
pairs++;
}

// Write Key Usage
n20_cbor_write_byte_string(
s,
(n20_slice_t){.buffer = (uint8_t *)cwt->key_usage,
.size = (cwt->key_usage[1] != 0 ? 2 : (cwt->key_usage[0] != 0))});

n20_cbor_write_int(s, N20_OPEN_DICE_CWT_LABEL_KEY_USAGE);
++pairs; // Key Usage

size_t mark = n20_stream_byte_count(s);

n20_cose_key_t subject_public_key = {0};
n20_cose_key_ops_set(&subject_public_key.key_ops, n20_cose_key_op_verify_e);

n20_cwt_key_info_to_cose(&subject_public_key, &cwt->subject_public_key);

// Subject public key
n20_cose_write_key(s, &subject_public_key);
n20_cbor_write_header(s, n20_cbor_type_bytes_e, n20_stream_byte_count(s) - mark);
n20_cbor_write_int(s, N20_OPEN_DICE_CWT_LABEL_SUBJECT_PUBLIC_KEY);
++pairs;

// Convert mode to uint8_t
uint8_t mode = (uint8_t)cwt->open_dice_input.mode;

n20_cbor_write_byte_string(s, (n20_slice_t){.buffer = &mode, .size = 1});
n20_cbor_write_int(s, N20_OPEN_DICE_CWT_LABEL_MODE);
++pairs;

if (cwt->open_dice_input.authority_descriptor.size > 0) {
n20_cbor_write_byte_string(s, cwt->open_dice_input.authority_descriptor);
n20_cbor_write_int(s, N20_OPEN_DICE_CWT_LABEL_AUTHORITY_DESCRIPTOR);
++pairs;
}

if (cwt->open_dice_input.authority_hash.size > 0) {
n20_cbor_write_byte_string(s, cwt->open_dice_input.authority_hash);
n20_cbor_write_int(s, N20_OPEN_DICE_CWT_LABEL_AUTHORITY_HASH);
++pairs;
}

if (cwt->open_dice_input.configuration_descriptor.size > 0) {
n20_cbor_write_byte_string(s, cwt->open_dice_input.configuration_descriptor);
n20_cbor_write_int(s, N20_OPEN_DICE_CWT_LABEL_CONFIGURATION_DESCRIPTOR);
++pairs;
}

if (cwt->open_dice_input.configuration_hash.size > 0) {
n20_cbor_write_byte_string(s, cwt->open_dice_input.configuration_hash);
n20_cbor_write_int(s, N20_OPEN_DICE_CWT_LABEL_CONFIGURATION_HASH);
++pairs;
}
if (cwt->open_dice_input.code_descriptor.size > 0) {
n20_cbor_write_byte_string(s, cwt->open_dice_input.code_descriptor);
n20_cbor_write_int(s, N20_OPEN_DICE_CWT_LABEL_CODE_DESCRIPTOR);
++pairs;
}
if (cwt->open_dice_input.code_hash.size > 0) {
n20_cbor_write_byte_string(s, cwt->open_dice_input.code_hash);
n20_cbor_write_int(s, N20_OPEN_DICE_CWT_LABEL_CODE_HASH);
++pairs;
}

if (cwt->subject.size > 0) {
n20_open_dice_write_name_as_hex(s, cwt->subject);
n20_cbor_write_int(s, N20_CWT_LABEL_SUBJECT);
++pairs;
}

if (cwt->issuer.size > 0) {
n20_open_dice_write_name_as_hex(s, cwt->issuer);
n20_cbor_write_int(s, N20_CWT_LABEL_ISSUER);
++pairs;
}

// Write the map header with the number of pairs.
n20_cbor_write_map_header(s, pairs);
}
Loading