diff --git a/CMakeLists.txt b/CMakeLists.txt index 48034a3..7931c17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(LIBNAT20_SOURCES # Add the core library source files here. src/core/asn1.c + src/core/cbor.c + src/core/functionality.c src/core/oid.c src/core/stream.c src/core/x509_ext_open_dice_input.c @@ -58,8 +60,10 @@ set(LIBNAT20_PUB_HEADERS # Add the public headers here. # These files will be included in the generation of the API documentation. include/nat20/asn1.h + include/nat20/cbor.h include/nat20/crypto.h include/nat20/error.h + include/nat20/functionality.h include/nat20/oid.h include/nat20/stream.h include/nat20/types.h @@ -87,9 +91,13 @@ set(LIBNAT20_CRYPTO_NAT20_PUB_HEADERS set(LIBNAT20_TEST_SOURCES # Add test source files here. - src/core/test/test.cpp src/core/test/asn1.cpp + src/core/test/cbor.cpp + src/core/test/functionality.cpp + src/core/test/oid.cpp src/core/test/stream.cpp + src/core/test/test.cpp + src/core/test/test_vectors.cpp src/core/test/x509.cpp src/core/test/x509_ext_open_dice_input.cpp src/core/test/x509_ext_tcg_dice_tcb_freshness.cpp @@ -97,6 +105,22 @@ set(LIBNAT20_TEST_SOURCES src/core/test/x509_ext_tcg_dice_ueid.cpp ) +set(LIBNAT20_SERVICE_SOURCES + # Add service source files here. + src/service/gnostic.c + src/service/messages.c + src/service/proxy.c + src/service/service.c +) + +set(LIBNAT20_SERVICE_PUB_HEADERS + # Add service public headers here. + include/nat20/service/gnostic.h + include/nat20/service/messages.h + include/nat20/service/proxy.h + include/nat20/service/service.h +) + set(LIBNAT20_DOC_PAGES # Add additional input files for doxygen here. README.md @@ -153,6 +177,31 @@ target_include_directories(nat20 ################################################################################################### +################################################################################################### +# The nat20_service library is also part of the product of this project. +# It will always be compiled. +add_library(nat20_service STATIC) + +target_sources(nat20_service + PRIVATE ${LIBNAT20_SERVICE_SOURCES} + INTERFACE ${LIBNAT20_SERVICE_PUB_HEADERS} +) + +target_compile_options(nat20_service + PRIVATE -pedantic + PRIVATE -Wall + PRIVATE -Wextra + PRIVATE -Werror +) + +target_include_directories(nat20_service + PUBLIC include +) + +target_link_libraries(nat20_service nat20) + +################################################################################################### + ################################################################################################### # The nat20_crypto_nat20 library implements some crypto # algorithms using the native C standard library. @@ -216,6 +265,29 @@ endif() ################################################################################################### +################################################################################################### +# Service binary. + +add_executable(nat20_service_bin) +target_sources(nat20_service_bin + PRIVATE src/service/gnostic_main.c +) +target_link_libraries(nat20_service_bin nat20_service nat20_crypto_nat20 nat20_crypto_boringssl) + +################################################################################################### + +################################################################################################### +# Client binary. + +add_executable(n20_client_stateless_bin) +target_sources(n20_client_stateless_bin + PRIVATE src/service/client_stateless.c +) +target_link_libraries(n20_client_stateless_bin nat20_service nat20_crypto_nat20 nat20_crypto_boringssl) + +################################################################################################### + + ################################################################################################### # The libnat20 test suite. It, along with its additional dependencies is only compiled # when selected by setting `-DNAT20_WITH_TESTS=ON` on the `cmake -B` command line. @@ -227,8 +299,8 @@ if (NAT20_WITH_TESTS) include(FetchContent) FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/5376968f6948923e2411081fd9372e71a59d8e77.zip + googletest + URL https://github.com/google/googletest/archive/5376968f6948923e2411081fd9372e71a59d8e77.zip ) # For Windows: Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) @@ -242,6 +314,12 @@ if (NAT20_WITH_TESTS) FILES_MATCHING PATTERN "*" ) + file(INSTALL + "src/core/test/test_data/" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/test_data/core" + FILES_MATCHING PATTERN "*" + ) + # Add the nat20 library test utils. add_library(nat20_test_utils STATIC) target_sources(nat20_test_utils diff --git a/include/nat20/asn1.h b/include/nat20/asn1.h index ba90320..e2e1954 100644 --- a/include/nat20/asn1.h +++ b/include/nat20/asn1.h @@ -463,7 +463,7 @@ extern void n20_asn1_object_identifier(n20_stream_t *s, /** * @brief Write an integer complete with ASN.1 header to the given stream. * - * The function expects a buffer `n` of length `len` bytes which + * The function expects a buffer slice @p n which * it will interpret as integer according to the parameters `little_endian` * and `two_complement`. It will format an ASN1 INTEGER into the stream using * DER, i.e., leading zero bytes (unsigned) or bytes that have all bits set @@ -472,16 +472,14 @@ extern void n20_asn1_object_identifier(n20_stream_t *s, * If `n` is NULL this function behaves like @ref n20_asn1_null. * * @param s The stream that is to be updated. - * @param n The buffer holding the integer. - * @param len The size of the buffer in bytes. + * @param n The buffer slice holding the integer. * @param little_endian Indicates if the byteorder of the integer in the given buffer. * @param two_complement If `true` the buffer is interpreted as signed 2-complement integer. * @param tag_info Tag info override. * @sa N20_ASN1_TAG_INTEGER */ extern void n20_asn1_integer(n20_stream_t *s, - uint8_t const *n, - size_t len, + n20_slice_t const n, bool little_endian, bool two_complement, n20_asn1_tag_info_t tag_info); diff --git a/include/nat20/cbor.h b/include/nat20/cbor.h new file mode 100644 index 0000000..aeba2ca --- /dev/null +++ b/include/nat20/cbor.h @@ -0,0 +1,366 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @file */ + +/** + * @brief Represents the CBOR data types. + * + * This enumeration defines the major types of CBOR (Concise Binary Object Representation) + * data items as specified in RFC 8949. Each type corresponds to a specific kind of data + * that can be encoded in CBOR. + * + * @sa https://tools.ietf.org/html/rfc8949 + */ +typedef enum n20_cbor_type_s { + /** + * @brief No value type. + * + * This is not a valid CBOR type. It is used to indicate that + * no value is present. + */ + n20_cbor_type_none_e = 0xFF, + /** + * @brief Unsigned integer type. + * + * Represents non-negative integer values. + */ + n20_cbor_type_uint_e = 0, + /** + * @brief Negative integer type. + * + * Represents negative integer values. The value is encoded as the + * absolute value minus one. E.g. -1 is encoded as 0, -2 as 1, etc. + */ + n20_cbor_type_nint_e = 1, + /** + * @brief Byte string type. + * + * Represents a sequence of raw binary data. + */ + n20_cbor_type_bytes_e = 2, + /** + * @brief Text string type. + * + * Represents a sequence of UTF-8 encoded characters. + */ + n20_cbor_type_string_e = 3, + /** + * @brief Array type. + * + * Represents an ordered collection of CBOR data items. + */ + n20_cbor_type_array_e = 4, + /** + * @brief Map type. + * + * Represents a collection of key-value pairs, where keys are unique. + */ + n20_cbor_type_map_e = 5, + /** + * @brief Tag type. + * + * Represents a tagged data item, used to indicate semantic meaning. + */ + n20_cbor_type_tag_e = 6, + /** + * @brief Simple value or floating-point type. + * + * Represents simple values (e.g., true, false, null) or floating-point numbers. + */ + n20_cbor_type_simple_float_e = 7, +} n20_cbor_type_t; + +/** + * @brief Write a CBOR header to the given stream. + * + * This function writes the CBOR header for a given type and value to the stream. + * + * @param s The stream to write to. + * @param type The CBOR type (see @ref n20_cbor_type_t). + * @param value The value associated with the CBOR type. + */ +extern void n20_cbor_write_header(n20_stream_t *s, n20_cbor_type_t type, uint64_t value); + +/** @brief Write a NULL to the stream in CBOR format. + * + * This function encodes the NULL value using the CBOR encoding rules. + */ +extern void n20_cbor_write_null(n20_stream_t *const s); + +/** @brief Write a boolean to the stream in CBOR format. + * + * + * This function encodes the boolean value using the CBOR encoding rules. + * The value `true` is encoded as 21 and `false` as 20 with a major type of 7. + * + * @param s The stream to write to. + * @param b The boolean value to write. + */ +extern void n20_cbor_write_bool(n20_stream_t *const s, bool const b); + +/** @brief Write a CBOR tag to the stream. + * + * This function encodes a CBOR tag using the CBOR encoding rules. + * + * @param s The stream to write to. + * @param tag The tag to write. + */ +extern void n20_cbor_write_tag(n20_stream_t *const s, uint64_t const tag); + +/** + * @brief Write an unsigned integer to the stream in CBOR format. + * + * This function encodes the unsigned integer using the CBOR encoding rules. + * The result is a CBOR header with major type 0 (unsigned integer) and the + * value of the integer. + * + * @param s The stream to write to. + * @param n The unsigned integer to write. + */ +extern void n20_cbor_write_uint(n20_stream_t *s, uint64_t value); + +/** + * @brief Writes a signed integer to the stream in CBOR format. + * + * This function uses both major types 0 (unsigned integer) and 1 (negative integer) + * dpending on the sign of the integer. Positive integers are written using the + * unsigned integer type, while negative integers are written using the negative + * integer type. + * + * @param s The stream to write to. + * @param n The signed integer to write. + */ +extern void n20_cbor_write_int(n20_stream_t *s, int64_t value); + +/** + * @brief Write a CBOR byte string to the given stream. + * + * This function encodes a byte string in CBOR format and writes it to the stream. + * if @par data.size is not 0 but @par data.buffer is NULL, it writes a NULL value + * instead. + * + * @param s The stream to write to. + * @param data The byte string to encode. + */ +extern void n20_cbor_write_byte_string(n20_stream_t *s, n20_slice_t const data); + +/** + * @brief Write a CBOR text string to the given stream. + * + * This function encodes a text string in CBOR format and writes it to the stream. + * if @par text.size is not 0 but @par text.buffer is NULL, it writes a NULL value + * instead. + * + * @param s The stream to write to. + * @param text The text string to encode. + */ +extern void n20_cbor_write_text_string(n20_stream_t *s, n20_string_slice_t const text); + +extern bool n20_read_cbor_header(n20_istream_t *const s, + n20_cbor_type_t *const type, + uint64_t *const n); + +extern bool n20_cbor_skip_item(n20_istream_t *const s); + +/** + * @brief Write a CBOR array header to the given stream. + * + * This function writes the CBOR header for an array to the stream. + * + * @param s The stream to write to. + * @param size The number of elements in the array. + */ +extern void n20_cbor_write_array_header(n20_stream_t *s, size_t size); + +/** + * @brief Write a CBOR map header to the given stream. + * + * This function writes the CBOR header for a map to the stream. + * + * @param s The stream to write to. + * @param size The number of key-value pairs in the map. + */ +extern void n20_cbor_write_map_header(n20_stream_t *s, size_t size); + +/** + * @brief Mode inputs to the DICE. + */ +enum n20_cwt_open_dice_modes_s { + /** + * @brief No security features (e.g. verified boot) have been configured on the device. + */ + n20_cwt_open_dice_not_configured_e = 0, + /** + * @brief Device is operating normally with security features enabled. + */ + n20_cwt_open_dice_normal_e = 1, + /** + * @brief Device is in debug mode, which is a non-secure state. + */ + n20_cwt_open_dice_debug_e = 2, + /** + * @brief Device is in a debug or maintenance mode. + */ + n20_cwt_open_dice_recovery_e = 3, +}; + +typedef enum n20_cwt_open_dice_modes_s n20_cwt_open_dice_modes_t; + +struct n20_cwt_s { + n20_string_slice_t issuer; + n20_string_slice_t subject; +}; + +typedef struct n20_cwt_s n20_cwt_t; + +enum n20_cose_key_ops_s { + n20_cose_key_op_sign_e = 1, // Key used for signing + n20_cose_key_op_verify_e = 2, // Key used for verifying signatures + n20_cose_key_op_encrypt_e = 3, // Key used for encryption + n20_cose_key_op_decrypt_e = 4, // Key used for decryption + n20_cose_key_op_wrap_e = 5, // Key used for wrapping keys + n20_cose_key_op_unwrap_e = 6, // Key used for unwrapping keys + n20_cose_key_op_derive_key_e = 7, // Key used for key derivation + n20_cose_key_op_derive_bits_e = 8, // Key used for deriving bits not a key + n20_cose_key_op_mac_sign_e = 9, // Key used message authentication code signing + n20_cose_key_op_mac_verify_e = 10, // Key used for message authentication code verification +}; + +typedef enum n20_cose_key_ops_s n20_cose_key_ops_t; +typedef uint16_t n20_cose_key_ops_map_t; + +inline static void n20_set_cose_key_ops(n20_cose_key_ops_map_t *key_ops, n20_cose_key_ops_t op) { + *key_ops |= 1 << (unsigned int)op; +} + +inline static void n20_unset_cose_key_ops(n20_cose_key_ops_map_t *key_ops, n20_cose_key_ops_t op) { + *key_ops &= ~(1 << (unsigned int)op); +} + +inline static bool n20_is_cose_key_op_set(n20_cose_key_ops_map_t key_ops, n20_cose_key_ops_t op) { + return (key_ops & (1 << (unsigned int)op)) != 0; +} + +/** + * @brief COSE Key structure. + * + * This structure represents a COSE key, which is used in the + * CBOR Object Signing and Encryption (COSE) format. + * It contains information about the key type, operations, + * public and private keys, and the algorithm used. + */ +struct n20_cose_key_s { + /** + * @brief Compressed COSE Key Operations. + * + * This is a bitmask representing the operations that can be performed + * with this key. Each bit corresponds to a specific operation, such as + * signing, verifying, encrypting, decrypting, wrapping, unwrapping, + * deriving keys, deriving bits, and message authentication code (MAC) + * signing and verification. + * + * @see n20_cose_key_ops_s + * @see n20_cose_key_ops_map_t + */ + n20_cose_key_ops_map_t key_ops; + /** + * @brief Algorithm Identifier. + * + * This is an integer that identifies the algorithm used with this key. + * It is used to specify the cryptographic algorithm that the key is + * associated with, such as EdDSA, ECDSA, or AES. + * + * The values are defined in the COSE Algorithm Registry. + * @see https://www.iana.org/assignments/cose/cose.xhtml#cose-algorithm + * + * Relevant values include: + * - -9: ESP256 (ECDSA using P-256 and SHA-256) + * - -51: ESP384 (ECDSA using P-384 and SHA-384) + * - -19: Ed25519 (EdDSA using Ed25519) + */ + int32_t algorithm_id; + n20_slice_t x; // X coordinate for EC keys + n20_slice_t y; // Y coordinate for EC keys + n20_slice_t d; // Private key for EC keys +}; + +typedef struct n20_cose_key_s n20_cose_key_t; + +/** + * @brief Render a COSE key structure as CBOR map. + * + * This function encodes a COSE key structure into a CBOR map format. + * It writes the key type, operations, algorithm identifier, and + * coordinates (X, Y) and private key (D) if available. + * The function infers the key type (kty) and curve (crv) from the + * given algorithm id. But it is the responsibility of the caller + * to populate the x, y, and d fields of the key structure + * with the appropriate values. + * I.e., For an ED25519 key, the x field should contain the + * public key, the y field should be empty, and the d field + * may contain the private key. For an ECDSA key, the x and y fields + * must contain the public key coordinates, and the d field + * may contain the private key. + * + * If the key type is not supported, it writes a null value. + * + * @param s The stream to write the CBOR map to. + * @param key The COSE key structure to encode. + */ +extern void n20_cose_write_key(n20_stream_t *const s, n20_cose_key_t const *const key); + +struct n20_open_dice_cwt_s { + n20_slice_t issuer; // Issuer of the CWT + n20_slice_t subject; // Subject of the CWT + n20_slice_t code_hash; // Hash of the code descriptor + n20_slice_t code_descriptor; // Code descriptor + n20_slice_t configuration_hash; // Hash of the configuration descriptor + n20_slice_t configuration_descriptor; // Configuration descriptor + n20_slice_t authority_hash; // Authority hash (optional) + n20_slice_t authority_descriptor; // Authority descriptor (optional) + n20_cwt_open_dice_modes_t mode; // DICE mode + n20_cose_key_t subject_public_key; // Public key of the subject + uint8_t key_usage[2]; // Key usage flags +}; + +typedef struct n20_open_dice_cwt_s n20_open_dice_cwt_t; + +extern void n20_open_dice_cwt_write(n20_stream_t *const s, n20_open_dice_cwt_t const *const cwt); + +extern n20_error_t n20_cose_sign1_payload(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t const signing_key, + int32_t signing_key_algorith_id, + void (*payload_callback)(n20_stream_t *s, void *ctx), + void *payload_ctx, + uint8_t *cose_sign1, + size_t *cose_sign1_size); + +#ifdef __cplusplus +} +#endif diff --git a/include/nat20/crypto.h b/include/nat20/crypto.h index 89010d6..d3ba729 100644 --- a/include/nat20/crypto.h +++ b/include/nat20/crypto.h @@ -64,6 +64,13 @@ typedef enum n20_crypto_digest_algorithm_s n20_crypto_digest_algorithm_t; * the following cryptographic key types. */ enum n20_crypto_key_type_s { + /** + * @brief No key type. + * + * This value is used as default initialization value + * for key types. It indicates that no key type is selected. + */ + n20_crypto_key_type_none_e = 0, /** * @brief Secp256r1. * @@ -104,8 +111,9 @@ typedef enum n20_crypto_key_type_s n20_crypto_key_type_t; * opaque to the caller. * * The lifecycle begins with a call to @ref n20_crypto_context_t.kdf - * or @ref n20_crypto_context_t.get_cdi and ends with a call to - * @ref n20_crypto_context_t.key_free. + * or an implementation specific factory function that + * returns a key of the type @ref n20_crypto_key_type_t + * and ends with a call to @ref n20_crypto_context_t.key_free. */ typedef void* n20_crypto_key_t; @@ -130,7 +138,7 @@ struct n20_crypto_gather_list_s { /** * @brief Points to an array of @ref n20_slice_t. * - * The array pointed to must accommodate at leaset @ref count + * The array pointed to must accommodate at least @ref count * elements of @ref n20_slice_t. * * This structure does not take ownership of the array. @@ -518,12 +526,9 @@ struct n20_crypto_context_s { * * n20_crypto_context_s *ctx = open_my_crypto_implementation(); * - * // Get local cdi. - * n20_crypto_key_t cdi = NULL; - * rc = ctx->get_cdi(ctx, &cdi); - * if (rc != n20_error_ok_e) { - * // error handling - * } + * // Get local cdi or uds by means of an implementation specific + * // mechanism. + * n20_crypto_key_t cdi = my_crypto_implementation_get_secret_handle(); * * // Assemble the derivation context. * char const context_str[] = "kdf context"; @@ -653,31 +658,7 @@ struct n20_crypto_context_s { n20_crypto_gather_list_t const* msg_in, uint8_t* signature_out, size_t* signature_size_in_out); - /** - * @brief Return the local cdi handle. - * - * This function is used to bootstrap all key derivation for the - * current DICE service level. - * - * The function places the handle to the CDI into @p key_out. - * - * The CDI key handle must be destroyed with @ref key_free. - * - * ## Errors - * - @ref n20_error_crypto_invalid_context_e must be returned - * if ctx is NULL. - * Additional mechanisms may be implemented to determine - * if the context is valid, but an implementation must - * accept an instance if it was created with the implementation - * specific factory and not freed. - * - @ref n20_error_crypto_unexpected_null_key_out_e must be returned - * if @p key_out is NULL. - * - * @param ctx The crypto context. - * @param key_out A buffer to take the opaque key handle of the root - * secret that is the local CDI. - */ - n20_error_t (*get_cdi)(struct n20_crypto_context_s* ctx, n20_crypto_key_t* key_out); + /** * @brief Export the public key of an asymmetric key. * @@ -736,7 +717,8 @@ struct n20_crypto_context_s { /** * @brief Destroy a key handle. * - * Destroys a key handle obtained by calling @ref get_cdi or @ref kdf. + * Destroys a key handle obtained by calling @ref kdf or an implementation + * specific method to create a key handle. * * Unless an invalid context is given, this function shall not fail. * diff --git a/include/nat20/crypto_bssl/crypto.h b/include/nat20/crypto_bssl/crypto.h index f21e422..8966e64 100644 --- a/include/nat20/crypto_bssl/crypto.h +++ b/include/nat20/crypto_bssl/crypto.h @@ -23,9 +23,71 @@ extern "C" { #endif -n20_error_t n20_crypto_open_boringssl(n20_crypto_context_t** ctx, n20_slice_t const* cdi); +/** + * @brief Open a new BoringSSL cryptographic context. + * + * Instantiates a new cryptographic context using BoringSSL. + * + * Each call to this function must be paired with a call to + * @ref n20_crypto_boringssl_close to release the resources. + * + * Note that in the current implementation, each call to this function + * returns the same context and does not allocate new resources. + * The `close` counterpart is a no-op. + * However, because the implementation is entirely stateless, + * and reentrant it is still possible to write code that + * instantiates multiple contexts and closes them. + * + * This behavior is subject to change in the future and must + * not be relied upon by the caller. For a single context instance, + * no thread-safety guarantees may be assumed. + * + * @param ctx A pointer to the context to be opened. + * @return n20_error_t The result of the operation. + */ +n20_error_t n20_crypto_boringssl_open(n20_crypto_context_t** ctx); -n20_error_t n20_crypto_close_boringssl(n20_crypto_context_t* ctx); +/** + * @brief Close a BoringSSL cryptographic context. + * + * Releases the resources associated with the given context. + * + * This function must be called for each context opened with + * @ref n20_crypto_boringssl_open. + * + * This is a no-op in the current implementation, however, this + * behavior is subject to change in the future and must not + * be relied upon by the caller. + * + * @param ctx The context to be closed. + * @return n20_error_t The result of the operation. + */ +n20_error_t n20_crypto_boringssl_close(n20_crypto_context_t* ctx); + +/** + * @brief Create a new a key handle from a secret value. + * + * This function stores the given secret value in memory + * and makes it available as a key handle of type + * @ref n20_crypto_key_type_cdi_e. + * + * This key handle can then be used with the @ref n20_crypto_context_s.kdf + * function of the same context as given by the @p ctx parameter. And + * must eventually be freed with @ref n20_crypto_context_s.key_free. + * + * Note that this is a functional reference implementation that keeps + * the secret in memory with no additional protection. + * Integrators should provide their own cryptographic context and means + * to create opaque key handles. + * + * @param ctx The cryptographic context. + * @param secret_in The input secret value. + * @param key_out The output key. + * @return n20_error_t The result of the operation. + */ +n20_error_t n20_crypto_boringssl_make_secret(struct n20_crypto_context_s* ctx, + n20_slice_t const* secret_in, + n20_crypto_key_t* key_out); #ifdef __cplusplus } diff --git a/include/nat20/error.h b/include/nat20/error.h index 791ea37..54f6837 100644 --- a/include/nat20/error.h +++ b/include/nat20/error.h @@ -40,6 +40,34 @@ enum n20_error_s { * @brief No error occurred. */ n20_error_ok_e = 0, + + /** + * @brief The function requires a crypto context to be passed, but none was provided. + */ + n20_error_missing_crypto_context_e = 1, + /** + * @brief Insufficient buffer size. + */ + n20_error_insufficient_buffer_size_e = 3, + /** + * @brief Unexpected NULL pointer in buffer size argument. + */ + n20_error_unexpected_null_buffer_size_e = 4, + /** + * @brief Unexpected NULL pointer key_handle argument. + */ + n20_error_unexpected_null_key_handle_e = 5, + + n20_error_missing_node_state_e = 6, + n20_error_client_slot_index_out_of_range_e = 7, + n20_error_client_slot_empty_e = 8, + n20_error_missing_callback_function_or_context_e = 9, + n20_error_parent_path_size_exceeds_max_e = 10, + n20_error_missing_parent_service_e = 11, + n20_error_unexpected_message_structure_e = 12, + n20_error_unrecognized_request_type_e = 13, + n20_error_write_position_overflow_e = 14, + n20_error_incompatible_compressed_input_size_e = 15, /** * @brief The crypto context given to an interface was invalid. * @@ -68,7 +96,6 @@ enum n20_error_s { * argument. * * @sa n20_crypto_context_t.kdf - * @sa n20_crypto_context_t.get_cdi */ n20_error_crypto_unexpected_null_key_out_e = 0x1003, /** diff --git a/include/nat20/functionality.h b/include/nat20/functionality.h new file mode 100644 index 0000000..58d54e6 --- /dev/null +++ b/include/nat20/functionality.h @@ -0,0 +1,389 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(N20_INPUT_COMPRESSION_SHA256) +#define N20_FUNC_COMPRESSED_INPUT_SIZE 32 +#define N20_FUNC_COMPRESSED_INPUT_ALGORITHM n20_crypto_digest_algorithm_sha2_256_e +#else +#define N20_FUNC_COMPRESSED_INPUT_SIZE 64 +#define N20_FUNC_COMPRESSED_INPUT_ALGORITHM n20_crypto_digest_algorithm_sha2_512_e +#endif + +typedef uint8_t n20_compressed_input_t[N20_FUNC_COMPRESSED_INPUT_SIZE]; + +/** + * @brief Mode inputs to the DICE. + */ +enum n20_open_dice_mode_s { + /** + * @brief No security features (e.g. verified boot) have been configured on the device. + */ + n20_open_dice_mode_not_configured_e = 0, + /** + * @brief Device is operating normally with security features enabled. + */ + n20_open_dice_mode_normal_e = 1, + /** + * @brief Device is in debug mode, which is a non-secure state. + */ + n20_open_dice_mode_debug_e = 2, + /** + * @brief Device is in a debug or maintenance mode. + */ + n20_open_dice_mode_recovery_e = 3, + /** + * @brief Maximum value for the mode enumeration. + */ + n20_open_dice_mode_max_e = 4 +}; + +typedef enum n20_open_dice_mode_s n20_open_dice_mode_t; + +/** + * @brief Certificate formats. + * This enumeration defines the formats of certificates that can be used + * in the OpenDICE context. + * It is used to specify the format of the certificate when issuing + * certificates in the OpenDICE framework. + * + * The numbers are used in communication protocols to identify + * the requested certificate format. So they must be stable. + */ +enum n20_certificate_format_s { + /** + * @brief Default value indicating no specific certificate format. + * + * This is used as default initialization value or when no + * specific format is requested. + */ + n20_certificate_format_none_e = 0, + /** + * @brief X.509 certificate format. + * + * This is used to request an X.509 certificate with DER encoding. + */ + n20_certificate_format_x509_e = 1, + /** + * @brief COSE certificate format. + * + * This is used to request a COSE (CBOR Object Signing and Encryption) + * certificate with CBOR encoding. The issued certificate will be + * a COSE Sign1 object containing a CWT (CBOR Web Token) with the + * appropriate claims as specified by the OpenDICE profile. + */ + n20_certificate_format_cose_e = 2, +}; + +typedef enum n20_certificate_format_s n20_certificate_format_t; + +/** + * @brief OpenDICE input content context. + * + * This is the context expected by + * @ref n20_x509_ext_open_dice_input_content. + * An instance of this object must be passed to the callback. + * This is typically done using @ref n20_x509_extension by + * initializing @ref n20_x509_extension_t.content_cb with + * @ref n20_x509_ext_open_dice_input_content and setting + * @ref n20_x509_extension_t.context to an instance of this + * struct. + * + * (See Open Profile for DICE, Section X.509 CDI Certificates) + * @sa OID_OPEN_DICE_INPUT + */ +typedef struct n20_dice_context_s { + /** + * @brief Digest of the code used as input to the DICE. + * + * No ownerships is taken. The user has to + * assure that the buffer outlives the instance + * of this structure. + */ + n20_slice_t code_hash; + + n20_slice_t code_descriptor; + n20_slice_t configuration_hash; + n20_slice_t configuration_descriptor; + n20_slice_t authority_hash; + n20_slice_t authority_descriptor; + n20_open_dice_mode_t mode; + n20_slice_t hidden; + n20_string_slice_t profile_name; +} n20_open_dice_input_t; + +extern n20_error_t n20_compress_input(n20_crypto_digest_context_t *crypto_ctx, + n20_open_dice_input_t const *context, + n20_compressed_input_t digest); + +typedef uint8_t n20_cdi_id_t[20]; + +extern n20_error_t n20_open_dice_cdi_id(n20_crypto_digest_context_t *crypto_ctx, + n20_slice_t const public_key, + n20_cdi_id_t cdi_id); + +/** + * @brief Derives a key from the given CDI secret. + * + * This function derives a key from the given CDI secret using + * the given salt and tag. The derived key is returned in the + * given buffer. + * + * @param crypto_ctx The crypto context. + * @param cdi_secret The CDI secret to derive the key from. + * @param derived The derived key. + * @param key_type The type of the derived key. + * @param salt The salt to use for the derivation. + * @param tag The tag to use for the derivation. + * + * @return n20_error_ok_e on success, or an error code on failure. + */ +extern n20_error_t n20_derive_key(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t cdi_secret, + n20_crypto_key_t *derived, + n20_crypto_key_type_t key_type, + n20_slice_t const salt, + n20_slice_t const tag); + +/** + * @brief Derives the next level CDI secret from the given CDI secret. + * + * This function derives the next level CDI secret from the given + * CDI secret using the given salt and tag. The derived key is + * returned in the given buffer. + * + * @param crypto_ctx The crypto context. + * @param current The current CDI secret to derive the key from. + * @param next The derived key. + * @param info The information to use for the derivation. + * + * @return n20_error_ok_e on success, or an error code on failure. + */ +extern n20_error_t n20_next_level_cdi_attest(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t current, + n20_crypto_key_t *next, + n20_compressed_input_t info); + +/** + * @brief Derives an attestation key from the given CDI secret. + * + * This function derives an attestation key from the given CDI + * secret using the given salt and tag. The derived key is returned + * in the given buffer. + * + * @param crypto_ctx The crypto context. + * @param cdi_secret The CDI secret to derive the key from. + * @param derived The derived key. + * @param key_type The type of the derived key. + * + * @return n20_error_ok_e on success, or an error code on failure. + */ +extern n20_error_t n20_derive_attestation_key(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t cdi_secret, + n20_crypto_key_t *derived, + n20_crypto_key_type_t key_type); + +/** + * @brief Initializes the algorithm identifier structure. + * + * This function initializes the algorithm identifier structure + * with the given key type. + * + * @param algorithm_identifier The algorithm identifier structure to initialize. + * @param key_type The type of the key. + * + * @return n20_error_ok_e on success, or an error code on failure. + */ +extern n20_error_t n20_init_algorithm_identifier( + n20_x509_algorithm_identifier_t *algorithm_identifier, n20_crypto_key_type_t key_type); + +/** + * @brief Initializes the key info structure. + * + * This function initializes the key info structure with the + * given key type and public key. + * + * @param key_info The key info structure to initialize. + * @param key_type The type of the key. + * @param public_key The public key to use. + * @param public_key_size The size of the public key. + * + * @return n20_error_ok_e on success, or an error code on failure. + */ +extern n20_error_t n20_init_key_info(n20_x509_public_key_info_t *key_info, + n20_crypto_key_type_t key_type, + uint8_t *public_key, + size_t public_key_size); + +typedef n20_error_t (*n20_signer_callback_t)(void *signer, + n20_slice_t tbs, + uint8_t *signature, + size_t *signature_size); + +typedef struct n20_signer_s { + n20_crypto_context_t *crypto_ctx; + n20_crypto_key_t signing_key; + n20_signer_callback_t cb; +} n20_signer_t; + +typedef struct n20_name_t { + n20_string_slice_t country_name; + n20_string_slice_t locality_name; + n20_string_slice_t organization_name; + n20_string_slice_t organization_unit_name; + n20_string_slice_t common_name; + n20_slice_t serial_number; +} n20_name_t; + +extern void n20_init_x509_name(n20_x509_name_t *name, n20_name_t const *n); + +extern n20_error_t n20_prepare_x509_cert(n20_open_dice_input_t const *context, + n20_signer_t *signer, + n20_crypto_key_type_t issuer_key_type, + n20_name_t *issuer_name, + n20_crypto_key_type_t subject_key_type, + n20_name_t *subject_name, + uint8_t *public_key, + size_t public_key_size, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size); +/** + * @brief Issues a new attestation certificate. + * + * This function generates a new CDI secret from a CDI or UDS given + * as opaque crypto key handle and context information. It uses the + * crypto context to generate a new CDI secret and then issues a + * new attestation certificate for the given CDI level. + * To that end it uses the derived CDI secret to derive a new + * attestation key pair and formats the attestation certificate + * using the given context information to generate the OpenDICE input + * extension. + * The attestation certificate is then signed using the attestation + * key pair of the given CDI level. + * The attestation certificate is then returned in the given + * buffer. + * The size of the attestation certificate buffer is given as + * pointer which is updated to the actual size of the attestation + * certificate. + * + * Important: Because of the way the attestation certificate is + * rendered, the resulting certificate is not written to the + * beginning of the buffer but to the end. Thus the certificate + * is located at + * `attestation_certificate + in_buffer_size - out_buffer_size`. + * + * The function returns @ref n20_error_ok_e on success, or an error + * code on failure. + */ +extern n20_error_t n20_opendice_attestation_key_and_certificate( + n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t parent_secret, + n20_crypto_key_t parent_attestation_key, + n20_crypto_key_type_t parent_key_type, + n20_crypto_key_type_t key_type, + n20_open_dice_input_t const *context, + n20_certificate_format_t certificate_format, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size); + +/** + * @brief Issues a new ECA attestation certificate. + * + * This function generates an ECA (Embedded CA) attestation certificate + * using the parent secret to derive an ECA key pair. Unlike CDI certificates, + * ECA certificates are end-entity certificates (not CA certificates) and don't + * derive a new level of secrets. + * + * The ECA key is derived directly from the parent secret using the tag "ECA_KEY". + * The certificate is signed by the parent attestation key and contains the + * ECA public key. + * + * Important: Because of the way the attestation certificate is rendered, + * the resulting certificate is not written to the beginning of the buffer + * but to the end. Thus the certificate is located at + * `attestation_certificate + in_buffer_size - out_buffer_size`. + * + * @param crypto_ctx The crypto context. + * @param parent_secret The parent CDI secret. + * @param parent_attestation_key The parent attestation key for signing. + * @param parent_key_type The type of the parent key. + * @param key_type The type of the ECA key to generate. + * @param context Context descriptor of the key identity and/or purpose. + * @param key_usage Key usage as intended by the client. + * @param challenge Challenge (nonce) - a high entropy value. + * @param certificate_format The format of the certificate (X.509 or COSE). + * @param attestation_certificate Buffer to write the certificate. + * @param attestation_certificate_size In/out parameter for buffer size. + * @return n20_error_ok_e on success, or an error code on failure. + */ +extern n20_error_t n20_eca_attestation_key_and_certificate( + n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t parent_secret, + n20_crypto_key_t parent_attestation_key, + n20_crypto_key_type_t parent_key_type, + n20_crypto_key_type_t key_type, + n20_string_slice_t context, + n20_slice_t key_usage, + n20_slice_t challenge, + n20_certificate_format_t certificate_format, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size); + +/** + * @brief Sign a message with an ECA key. + * + * This function derives an ECA key using the provided parameters + * and signs the given message with it. + * + * @param crypto_ctx The crypto context. + * @param parent_secret The parent CDI secret. + * @param key_type The type of the ECA key to generate. + * @param context Context descriptor of the key identity and/or purpose. + * @param key_usage Key usage as intended by the client. + * @param challenge Challenge (nonce) - a high entropy value. + * @param message The message to sign. + * @param signature Buffer to write the signature. + * @param signature_size In/out parameter for buffer size. + * @return n20_error_ok_e on success, or an error code on failure. + */ +extern n20_error_t n20_eca_sign_message(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t parent_secret, + n20_crypto_key_type_t key_type, + n20_string_slice_t context, + n20_slice_t key_usage, + n20_slice_t challenge, + n20_slice_t message, + uint8_t *signature, + size_t *signature_size); + +#ifdef __cplusplus +} +#endif diff --git a/include/nat20/oid.h b/include/nat20/oid.h index a2b2cae..4b29615 100644 --- a/include/nat20/oid.h +++ b/include/nat20/oid.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include @@ -329,3 +330,29 @@ N20_ASN1_DECLARE_OID(OID_TCG_DICE_UEID); N20_ASN1_DECLARE_OID(OID_TCG_DICE_TCB_FRESHNESS); /** @} */ /* End of n20_asn1_oids group */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Compare two object identifiers for equality. + * + * This function compares two object identifiers for equality. + * It returns true if the object identifiers are equal, false otherwise. + * + * If both object identifiers are NULL, the function returns true. + * If one is NULL and the other is not, it returns false. + * If the element counts differ, it returns false. + * If any of the first count elements differ, it returns false. + * + * @param oid1 The first object identifier to compare. + * @param oid2 The second object identifier to compare. + * @return true if the object identifiers are equal, false otherwise. + */ +bool n20_asn1_oid_equals(n20_asn1_object_identifier_t const *oid1, + n20_asn1_object_identifier_t const *oid2); + +#ifdef __cplusplus +} +#endif diff --git a/include/nat20/service/gnostic.h b/include/nat20/service/gnostic.h new file mode 100644 index 0000000..3e7ea7b --- /dev/null +++ b/include/nat20/service/gnostic.h @@ -0,0 +1,104 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifndef N20_GNOSTIC_MAX_CLIENT_SLOTS +/** + * @brief The maximum number of client slots in a gnostic node. + * + * This is the maximum number of clients that can be registered + * with a gnostic node. It is used to allocate the array of + * client states in @ref n20_gnostic_node_state_t. + */ +#define N20_GNOSTIC_MAX_CLIENT_SLOTS 3 +#endif // N20_GNOSTIC_MAX_CLIENT_SLOTS + +#ifdef __cplusplus +extern "C" { +#endif + +struct n20_gnostic_client_state_s { + /** + * @brief The minimal compound device identifier (CDI) usable by client. + */ + n20_crypto_key_t min_cdi; +}; + +typedef struct n20_gnostic_client_state_s n20_gnostic_client_state_t; + +struct n20_gnostic_node_state_s { + /** The cryptographic context for the node. */ + n20_crypto_context_t *crypto_context; + /** Array of client states. */ + n20_gnostic_client_state_t client_slots[N20_GNOSTIC_MAX_CLIENT_SLOTS]; +}; + +typedef struct n20_gnostic_node_state_s n20_gnostic_node_state_t; + +extern void n20_gnostic_init(n20_gnostic_node_state_t *node_state, + n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t client_min_cdis[N20_GNOSTIC_MAX_CLIENT_SLOTS]); + +extern n20_error_t n20_gnostic_promote(n20_gnostic_node_state_t *node_state, + size_t client_slot_index, + n20_compressed_input_t compressed_context); + +extern n20_error_t n20_gnostic_issue_cdi_certificate(n20_gnostic_node_state_t *node_state, + size_t client_slot_index, + n20_crypto_key_type_t parent_key_type, + n20_crypto_key_type_t key_type, + n20_compressed_input_t *parent_path, + size_t parent_path_size, + n20_open_dice_input_t const *next_context, + n20_certificate_format_t certificate_format, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size); + +extern n20_error_t n20_gnostic_issue_eca_certificate(n20_gnostic_node_state_t *node_state, + size_t client_slot_index, + n20_crypto_key_type_t parent_key_type, + n20_crypto_key_type_t key_type, + n20_compressed_input_t *parent_path, + size_t parent_path_size, + n20_string_slice_t context, + n20_slice_t key_usage, + n20_slice_t challenge, + n20_certificate_format_t certificate_format, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size); + +extern n20_error_t n20_gnostic_eca_sign(n20_gnostic_node_state_t *node_state, + size_t client_slot_index, + n20_crypto_key_type_t key_type, + n20_compressed_input_t *parent_path, + size_t parent_path_size, + n20_string_slice_t context, + n20_slice_t key_usage, + n20_slice_t challenge, + n20_slice_t message, + uint8_t *signature, + size_t *signature_size); + +#ifdef __cplusplus +} +#endif diff --git a/include/nat20/service/messages.h b/include/nat20/service/messages.h new file mode 100644 index 0000000..f838603 --- /dev/null +++ b/include/nat20/service/messages.h @@ -0,0 +1,330 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum n20_msg_request_type_s { + n20_msg_request_type_none_e = 0, + /** + * @brief Request to promote the caller's CDI to the next level. + */ + n20_msg_request_type_promote_e = 1, + + /** + * @brief Request to issue a CDI certificate. + */ + n20_msg_request_type_issue_cdi_cert_e = 2, + + /** + * @brief Request to issue an ECA certificate. + */ + n20_msg_request_type_issue_eca_cert_e = 3, + + /** + * @brief Request to sign with an ECA key. + */ + n20_msg_request_type_eca_sign_e = 4, + + n20_msg_request_type_count_e = 5, +}; + +typedef enum n20_msg_request_type_s n20_msg_request_type_t; + +struct n20_msg_promote_request_s { + n20_slice_t compressed_context; +}; + +typedef struct n20_msg_promote_request_s n20_msg_promote_request_t; + +struct n20_msg_issue_cdi_cert_request_s { + /** + * @brief The type of the parent key. + * + * This is used to determine how to derive the new CDI. + */ + n20_crypto_key_type_t parent_key_type; + + /** + * @brief The type of the new key to be issued. + * + * This is the type of the CDI that is being requested. + */ + n20_crypto_key_type_t key_type; + + /** + * @brief The length of the parent path. + * + * This is used to determine how many elements are in the parent path. + */ + + n20_open_dice_input_t next_context; + + /** + * @brief The length of the parent path. + * + * This is used to determine how many elements are in the parent path. + */ + size_t parent_path_length; + /** + * @brief The compressed path to the parent CDI. + * + * This is used to derive the new CDI from the parent CDI. + */ + n20_slice_t parent_path[N20_STATELESS_MAX_PATH_LENGTH]; + /** + * @brief The format of the certificate to be issued. + * + * This is used to determine how the certificate should be formatted. + */ + n20_certificate_format_t certificate_format; +}; + +typedef struct n20_msg_issue_cdi_cert_request_s n20_msg_issue_cdi_cert_request_t; + +struct n20_msg_issue_eca_cert_request_s { + /** + * @brief The type of the parent key. + * + * This is used to sign the ECA certificate. + */ + n20_crypto_key_type_t parent_key_type; + + /** + * @brief The type of the ECA key to be issued. + * + * This is the type of the ECA key that is being requested. + */ + n20_crypto_key_type_t key_type; + + /** + * @brief The length of the parent path. + * + * This is used to determine how many elements are in the parent path. + */ + size_t parent_path_length; + + /** + * @brief The compressed path to the parent CDI. + * + * This is used to derive the parent secret for ECA key generation. + */ + n20_slice_t parent_path[N20_STATELESS_MAX_PATH_LENGTH]; + + /** + * @brief The format of the certificate to be issued. + * + * This is used to determine how the certificate should be formatted. + */ + n20_certificate_format_t certificate_format; + + /** + * @brief Context descriptor of the key identity and/or purpose. + * + * This is used in ECA_CTX derivation. + */ + n20_string_slice_t context; + + /** + * @brief Key usage as intended by the client. + * + * This is used in ECA_CTX derivation and certificate generation. + */ + n20_slice_t key_usage; + + /** + * @brief Challenge (nonce) - a high entropy value. + * + * This is used in ECA_CTX derivation and included in the certificate. + */ + n20_slice_t challenge; +}; + +typedef struct n20_msg_issue_eca_cert_request_s n20_msg_issue_eca_cert_request_t; + +struct n20_msg_eca_sign_request_s { + /** + * @brief The type of the parent key. + * + * This is used to sign the message. + */ + n20_crypto_key_type_t parent_key_type; + + /** + * @brief The type of the ECA key to derive. + * + * This is the type of the ECA key used for signing. + */ + n20_crypto_key_type_t key_type; + + /** + * @brief The length of the parent path. + * + * This is used to determine how many elements are in the parent path. + */ + size_t parent_path_length; + + /** + * @brief The compressed path to the parent CDI. + * + * This is used to derive the parent secret for ECA key generation. + */ + n20_slice_t parent_path[N20_STATELESS_MAX_PATH_LENGTH]; + + /** + * @brief Context descriptor of the key identity and/or purpose. + * + * This is used in ECA_CTX derivation. + */ + n20_string_slice_t context; + + /** + * @brief Key usage as intended by the client. + * + * This is used in ECA_CTX derivation. + */ + n20_slice_t key_usage; + + /** + * @brief Challenge (nonce) - a high entropy value. + * + * This is used in ECA_CTX derivation. + */ + n20_slice_t challenge; + + /** + * @brief The message to be signed. + */ + n20_slice_t message; +}; + +typedef struct n20_msg_eca_sign_request_s n20_msg_eca_sign_request_t; + +union n20_msg_request_payload_u { + n20_msg_promote_request_t promote; + n20_msg_issue_cdi_cert_request_t issue_cdi_cert; + n20_msg_issue_eca_cert_request_t issue_eca_cert; + n20_msg_eca_sign_request_t eca_sign; +}; + +typedef union n20_msg_request_payload_u n20_msg_request_payload_t; + +struct n20_msg_request_s { + /** + * @brief The request type. + * + * This is a unique identifier for the request. + */ + n20_msg_request_type_t request_type; + + /** + * @brief The payload of the request. + * + * This is the data that is being sent with the request. + */ + n20_msg_request_payload_t payload; +}; + +struct n20_msg_error_response_s { + /** + * @brief The error code of the response. + * + * This is used to indicate success or failure of the request. + */ + n20_error_t error_code; +}; + +typedef struct n20_msg_error_response_s n20_msg_error_response_t; + +typedef struct n20_msg_request_s n20_msg_request_t; + +struct n20_msg_issue_cdi_cert_response_s { + /** + * @brief The error code of the response. + * + * This is used to indicate success or failure of the request. + */ + n20_error_t error_code; + /** + * @brief The payload of the response. + * + * This is the data that is being sent with the response. + */ + n20_slice_t certificate; +}; + +typedef struct n20_msg_issue_cdi_cert_response_s n20_msg_issue_cdi_cert_response_t; + +/* ECA certificate response uses the same structure as CDI certificate response */ +typedef n20_msg_issue_cdi_cert_response_t n20_msg_issue_eca_cert_response_t; + +struct n20_msg_eca_sign_response_s { + /** + * @brief The error code of the response. + * + * This is used to indicate success or failure of the request. + */ + n20_error_t error_code; + /** + * @brief The signature. + */ + n20_slice_t signature; +}; + +typedef struct n20_msg_eca_sign_response_s n20_msg_eca_sign_response_t; + +extern n20_error_t n20_msg_request_read(n20_msg_request_t *request, n20_slice_t msg_buffer); + +extern n20_error_t n20_msg_request_write(n20_msg_request_t const *request, + uint8_t *buffer, + size_t *buffer_size); + +extern n20_error_t n20_msg_issue_cdi_cert_response_read(n20_msg_issue_cdi_cert_response_t *response, + n20_slice_t buffer); + +extern n20_error_t n20_msg_issue_cdi_cert_response_write( + n20_msg_issue_cdi_cert_response_t const *response, + uint8_t *buffer, + size_t *const buffer_size_in_out); + +/* ECA certificate response uses the same read/write functions as CDI certificate response */ +#define n20_msg_issue_eca_cert_response_read n20_msg_issue_cdi_cert_response_read +#define n20_msg_issue_eca_cert_response_write n20_msg_issue_cdi_cert_response_write + +extern n20_error_t n20_msg_error_response_read(n20_msg_error_response_t *response, + n20_slice_t buffer); +extern n20_error_t n20_msg_error_response_write(n20_msg_error_response_t const *response, + uint8_t *buffer, + size_t *const buffer_size_in_out); + +extern n20_error_t n20_msg_eca_sign_response_read(n20_msg_eca_sign_response_t *response, + n20_slice_t buffer); +extern n20_error_t n20_msg_eca_sign_response_write(n20_msg_eca_sign_response_t const *response, + uint8_t *buffer, + size_t *const buffer_size_in_out); + +#ifdef __cplusplus +} +#endif diff --git a/include/nat20/service/proxy.h b/include/nat20/service/proxy.h new file mode 100644 index 0000000..c7633c1 --- /dev/null +++ b/include/nat20/service/proxy.h @@ -0,0 +1,105 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#pragma once + +#include +#include +#include + +#include "nat20/functionality.h" + +#ifndef N20_PROXY_MAX_CLIENT_SLOTS +/** + * @brief The maximum number of client slots in a proxy node. + * + * This is the maximum number of clients that can be registered + * with a proxy node. It is used to allocate the array of + * client states in @ref n20_proxy_node_state_t. + */ +#define N20_PROXY_MAX_CLIENT_SLOTS 3 +#endif // N20_PROXY_MAX_CLIENT_SLOTS + +#ifndef N20_PROXY_MAX_PARENT_PATH_SIZE +/** + * @brief The maximum size of the parent path in a proxy node. + * + * This is the maximum size of the parent path that can be stored + * in a proxy node. It is used to allocate the array of parent paths + * in @ref n20_proxy_client_state_t. + */ +#define N20_PROXY_MAX_PARENT_PATH_SIZE 8 +#endif // N20_PROXY_MAX_PARENT_PATH_SIZE + +#ifdef __cplusplus +extern "C" { +#endif + +struct n20_parent_service_s { + /** + * @brief Promote the caller to become the child identified by the given + * compressed context. + * + * @param compressed_context The compressed context that identifies the child. + * @return n20_error_t Error code indicating success or failure. + */ + n20_error_t (*promote)(struct n20_parent_service_s *self, + n20_compressed_input_t compressed_context); + /** + * @brief Issues a CDI certificate for the given parent key type and key type. + * + * @param parent_key_type The type of the parent key. + * @param key_type The type of the key to be issued. + * @param parent_path The path to the parent in compressed form. + * @param parent_path_size The size of the parent path. + * @param next_context The next context for the Open DICE input. + * @param attestation_certificate Output buffer for the generated attestation certificate. + * @param attestation_certificate_size Size of the output buffer. + * @return n20_error_t Error code indicating success or failure. + */ + n20_error_t (*issue_cdi_certificate)(struct n20_parent_service_s *self, + n20_crypto_key_type_t parent_key_type, + n20_crypto_key_type_t key_type, + n20_compressed_input_t const *parent_path, + size_t parent_path_size, + n20_open_dice_input_t const *next_context, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size); +}; + +typedef struct n20_parent_service_s n20_parent_service_t; + +struct n20_proxy_client_state_s { + size_t parent_path_size; + + n20_compressed_input_t parent_path[N20_PROXY_MAX_PARENT_PATH_SIZE]; +}; + +typedef struct n20_proxy_client_state_s n20_proxy_client_state_t; + +struct n20_proxy_node_state_s { + n20_parent_service_t *parent_service; + /** The cryptographic context for the node. */ + n20_crypto_digest_context_t *crypto_digest_context; + /** Array of client states. */ + n20_proxy_client_state_t client_slots[N20_PROXY_MAX_CLIENT_SLOTS]; +}; + +typedef struct n20_proxy_node_state_s n20_proxy_node_state_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/nat20/service/service.h b/include/nat20/service/service.h new file mode 100644 index 0000000..c17f885 --- /dev/null +++ b/include/nat20/service/service.h @@ -0,0 +1,27 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define N20_STATELESS_MAX_PATH_LENGTH 8 + +#ifdef __cplusplus +} +#endif diff --git a/include/nat20/stream.h b/include/nat20/stream.h index d8657a4..cd3761d 100644 --- a/include/nat20/stream.h +++ b/include/nat20/stream.h @@ -234,6 +234,135 @@ extern void n20_stream_prepend(n20_stream_t *s, uint8_t const *src, size_t src_l */ extern void n20_stream_put(n20_stream_t *s, uint8_t c); +/** + * @brief Represents an input stream buffer. + * + * A `n20_istream` is used to safely extract data from a + * buffer of a given size. + */ +typedef struct n20_istream_s { + /** + * @brief Points to the beginning of the underlying buffer. + * + * This may be NULL. + */ + uint8_t const *begin; + /** + * @brief The size of the underlying buffer. + * + * This is effectively ignored if @ref begin is NULL. + */ + size_t size; + /** + * @brief Indicates the read position in bytes. + * + * This is initialized with `0` and incremented with + * each byte extracted. + */ + size_t read_position; + /** + * @brief Indicates that the data requested from the stream exceeded the buffer size. + * + */ + bool buffer_underrun; +} n20_istream_t; + +/** + * @brief Initialize an @ref n20_istream_t structure. + * + * Initializes a structure of @ref n20_istream_t. + * It is safe to call this function with `buffer == NULL`. + * In this case the `buffer_size` parameter is effectively ignored + * and the stream will merely count the bytes written + * to it, which can be used for calculating a buffer size hint. + * If `buffer` is given it must point to a buffer of at least + * `buffer_size` bytes, or an out of bounds write will occur. + * + * ## Ownership and life time + * + * The initialized stream does not take ownership of the provided + * buffer and the buffer must outlive the stream object. + * + * Calling this function with `s == NULL` is safe but a noop. + * + * @param s A pointer to the to be initialized @ref n20_istream_t structure. + * @param buffer A pointer to the target stream buffer or NULL. + * @param buffer_size Size of `buffer` in bytes. + */ +extern void n20_istream_init(n20_istream_t *s, uint8_t const *buffer, size_t buffer_size); + +/** + * @brief Reads data from the input stream into a buffer. + * + * This function reads a specified number of bytes from the input stream + * into the provided buffer. If the read operation exceeds the available + * data in the stream, the `buffer_underrun` flag is set and the provided + * buffer remains unmodified. In this case, the function returns `false`. + * + * @note The function does not check for buffer overflows in the provided + * buffer. It is the caller's responsibility to ensure that the buffer is + * dereferenceable and that it is at least `buffer_size` bytes long. + * + * @param s The input stream to read from. + * @param buffer The buffer to store the read data. + * @param buffer_size The number of bytes to read. + * @return `true` if the read operation was successful, `false` otherwise. + */ +extern bool n20_istream_read(n20_istream_t *s, uint8_t *buffer, size_t buffer_size); + +/** + * @brief Reads a single byte from the input stream. + * + * This function reads a single byte from the input stream and stores it + * in the provided variable. If the read operation exceeds the available + * data in the stream, the `buffer_underrun` flag is set and the provided + * buffer remains unmodified. In this case, the function returns `false`. + * + * @note The function does not check for buffer overflows in the provided + * uint_t variable. It is the caller's responsibility to ensure that the + * variable is dereferenceable. + * + * @param s The input stream to read from. + * @param c Pointer to a variable where the read byte will be stored. + * @return `true` if the read operation was successful, `false` otherwise. + */ +extern bool n20_istream_get(n20_istream_t *s, uint8_t *c); + +/** @brief Gets a buffer slice from the input stream. + * + * This function advances the read position of the input stream by the + * specified size and returns a slice of the input stream buffer. + * + * @param s The input stream to read from. + * @param size The size of the slice to read. + * @return A pointer to the slice of the input stream buffer or NULL if + * the read operation exceeds the available data in the stream. + */ +extern uint8_t const *n20_istream_get_slice(n20_istream_t *s, size_t size); + +/** + * @brief Checks if the input stream has encountered a buffer underrun. + * + * This function returns whether the input stream has encountered a buffer + * underrun, which occurs when a read operation exceeds the available data + * in the stream. + * + * @param s The input stream to check. + * @return `true` if a buffer underrun has occurred, `false` otherwise. + */ +extern bool n20_istream_has_buffer_underrun(n20_istream_t const *s); + +/** + * @brief Gets the current read position of the input stream. + * + * This function returns the current read position in the input stream. + * If the stream is `NULL`, it returns 0. + * + * @param s The input stream to query. + * @return The current read position, or 0 if the stream is `NULL`. + */ +extern size_t n20_istream_read_position(n20_istream_t const *s); + #ifdef __cplusplus } #endif diff --git a/include/nat20/x509.h b/include/nat20/x509.h index d8c2ac8..c5f731d 100644 --- a/include/nat20/x509.h +++ b/include/nat20/x509.h @@ -59,32 +59,61 @@ extern n20_string_slice_t n20_x509_unix_epoch; * in the issuerName and subjectName fields of an x509 certificate. * * As of this writing only elements that are presented as - * PritableString are supported. This means that the provided - * nul terminated string must only contain allowed characters - * as outlined in @ref n20_asn1_printablestring. No checking - * is done on the provided string, this means that if the - * caller provides a string containing invalid characters, - * the resulting certificate will be malformed. + * PrintableString are supported. + * + * If @ref type is @ref OID_SERIAL_NUMBER the @ref bytes variant + * must be initialized to an octet string containing the serial + * number in big-endian order. * * No ownership is taken. The user is required to assure * that the pointer targets outlive instances of this structure. * * (See RFC5280 Section 4.1.2.4.) + * + * @sa bytes + * @sa string */ struct n20_x509_rdn_s { /** * @brief The object identifier of the RDNSequence element. - */ - n20_asn1_object_identifier_t *type; - /** - * @brief The value of the RDNSequence element. * - * Must be a nul terminated string of characters of the - * following set; `[A..Z][a..z][0..9][ '()+,-./:=?]`. + * This must point to a valid object identifier that outlives + * the instance of this structure. E.g. any of the library defined + * object identifiers in @ref include/nat20/oid.h which have static + * storage duration. * - * @sa n20_asn1_printablestring + * @ref OID_SERIAL_NUMBER is a special case where the + * @ref bytes variant must be initialized. */ - n20_string_slice_t value; + n20_asn1_object_identifier_t *type; + union { + /** + * @brief The value of the RDNSequence element. + * + * This variant must be initialized unless the @ref type + * is @ref OID_SERIAL_NUMBER. This variant is a + * printable string variant and must be initialized to a valid + * printable string of characters of the following set: + * `[A..Z][a..z][0..9][ '()+,-./:=?]`. + * + * @sa n20_asn1_printablestring + */ + n20_string_slice_t string; + /** + * @brief The value of the RDNSequence element. + * + * This variant must be initialized if the @ref type is + * @ref OID_SERIAL_NUMBER. + * + * The value must be initialized to an octet string containing + * the serial number in big-endian order. The serial number + * will be rendered as a hexadecimal string with printable + * string encoding. + * + * @sa n20_asn1_printablestring + */ + n20_slice_t bytes; + }; }; /** @@ -106,7 +135,7 @@ typedef struct n20_x509_rdn_s n20_x509_rdn_t; * @endcode */ #define N20_X509_RDN(type__, value__) \ - (n20_x509_rdn_t) { .type = type__, .value = N20_STR_C(value__) } + (n20_x509_rdn_t) { .type = type__, .string = N20_STR_C(value__) } /** * @brief Represents an RDNSequence. @@ -701,9 +730,20 @@ struct n20_x509_tbs_s { /** * @brief The certificate's serial number. * + * This is a positive integer that is unique for the certificate + * issuer. The serial number is used to identify the certificate + * in certificate revocation lists and other places. + * + * It is given as a buffer of up to 20 bytes in size. + * And represents an Integer In Big Endian order. + * + * If the serial number is exactly 20 bytes long, the most significant + * bit in the most significant byte must be zero so as not to exceed the + * 20 bytes limit due to sign extension in the ASN.1 integer encoding. + * * See RFC5280 Section 4.1.2.2. */ - uint64_t serial_number; + n20_slice_t serial_number; /** * @brief The signature algorithm identifier. @@ -743,7 +783,8 @@ struct n20_x509_tbs_s { * @sa n20_x509_public_key_info_t */ n20_x509_public_key_info_t subject_public_key_info; - + n20_slice_t issuer_unique_id; /**< @brief Optional issuer unique ID. */ + n20_slice_t subject_unique_id; /**< @brief Optional subject unique ID. */ /** * @brief The X509 v3 extensions. * diff --git a/src/core/asn1.c b/src/core/asn1.c index bd1e706..2855824 100644 --- a/src/core/asn1.c +++ b/src/core/asn1.c @@ -14,10 +14,17 @@ * limitations under the License. */ +#ifdef __AVR__ +#define BYTE_ORDER 0xaa55 +#define LITTLE_ENDIAN 0xaa55 +#define BIG_ENDIAN 0x55aa +#else #include +#endif #include #include #include +#include #include #include @@ -173,8 +180,7 @@ void n20_asn1_object_identifier(n20_stream_t *const s, } struct n20_asn1_number_s { - uint8_t const *n; - size_t size; + n20_slice_t n; bool little_endian; bool two_complement; }; @@ -184,9 +190,9 @@ static void n20_asn1_integer_internal_content(n20_stream_t *const s, void *ctx) // n is never NULL because all of the call sites are in this // compilation unit and assure that it is never NULL. - uint8_t const *msb = number->n; - uint8_t const *end = number->n + number->size; - ssize_t inc = 1; + uint8_t const *msb = number->n.buffer; + uint8_t const *end = number->n.buffer + number->n.size; + int inc = 1; int add_extra = 0; uint8_t extra = 0; @@ -197,7 +203,7 @@ static void n20_asn1_integer_internal_content(n20_stream_t *const s, void *ctx) // - point the most significant pointer to the last byte. msb = end - 1; // - point the end pointer one position before the first byte. - end = number->n - 1; + end = number->n.buffer - 1; // Now the rest of the algorithm traverses the buffer in reverse order. } @@ -232,20 +238,18 @@ static void n20_asn1_integer_internal_content(n20_stream_t *const s, void *ctx) } void n20_asn1_integer(n20_stream_t *const s, - uint8_t const *const n, - size_t const len, + n20_slice_t const n, bool const little_endian, bool const two_complement, n20_asn1_tag_info_t const tag_info) { // If the integer n is NULL, write an ASN1 NULL and return. - if (n == NULL) { + if (n.buffer == NULL) { n20_asn1_null(s, n20_asn1_tag_info_no_override()); return; } struct n20_asn1_number_s number = { .n = n, - .size = len, .little_endian = little_endian, .two_complement = two_complement, }; @@ -261,8 +265,7 @@ void n20_asn1_integer(n20_stream_t *const s, void n20_asn1_uint64(n20_stream_t *const s, uint64_t const n, n20_asn1_tag_info_t const tag_info) { n20_asn1_integer(s, - (uint8_t *)&n, - sizeof(n), + (n20_slice_t){sizeof(n), (uint8_t const *)&n}, LITTLE_ENDIAN == BYTE_ORDER, /*two_complement=*/false, tag_info); @@ -270,8 +273,7 @@ void n20_asn1_uint64(n20_stream_t *const s, uint64_t const n, n20_asn1_tag_info_ void n20_asn1_int64(n20_stream_t *const s, int64_t const n, n20_asn1_tag_info_t const tag_info) { n20_asn1_integer(s, - (uint8_t *)&n, - sizeof(n), + (n20_slice_t){sizeof(n), (uint8_t const *)&n}, LITTLE_ENDIAN == BYTE_ORDER, /*two_complement=*/true, tag_info); diff --git a/src/core/cbor.c b/src/core/cbor.c new file mode 100644 index 0000000..f2582cf --- /dev/null +++ b/src/core/cbor.c @@ -0,0 +1,524 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +void n20_cbor_write_header(n20_stream_t *const s, n20_cbor_type_t cbor_type, uint64_t n) { + uint8_t header = (uint8_t)(cbor_type << 5); + + size_t value_size = 0; + + if (n < 24) { + header |= (uint8_t)n; + n20_stream_prepend(s, &header, /*src_len=*/1); + return; + } else if (n < 0x100) { + header |= 24; + value_size = 1; + } else if (n < 0x10000) { + header |= 25; + value_size = 2; + } else if (n < 0x100000000) { + header |= 26; + value_size = 4; + } else { + header |= 27; + value_size = 8; + } + + for (size_t i = 0; i < value_size; i++) { + uint8_t byte = (uint8_t)(n >> (i * 8)); + n20_stream_prepend(s, &byte, /*src_len=*/1); + } + + n20_stream_prepend(s, &header, /*src_len=*/1); +} + +void n20_cbor_write_null(n20_stream_t *const s) { + n20_cbor_write_header(s, n20_cbor_type_simple_float_e, 22); +} + +void n20_cbor_write_bool(n20_stream_t *const s, bool const b) { + n20_cbor_write_header(s, n20_cbor_type_simple_float_e, b ? 21 : 20); +} + +void n20_cbor_write_tag(n20_stream_t *const s, uint64_t const tag) { + n20_cbor_write_header(s, n20_cbor_type_tag_e, tag); +} + +void n20_cbor_write_uint(n20_stream_t *const s, uint64_t const n) { + n20_cbor_write_header(s, n20_cbor_type_uint_e, n); +} + +void n20_cbor_write_int(n20_stream_t *const s, int64_t const n) { + if (n >= 0) { + n20_cbor_write_uint(s, (uint64_t)n); + } else { + n20_cbor_write_header(s, n20_cbor_type_nint_e, (uint64_t)(-n - 1)); + } +} + +void n20_cbor_write_byte_string(n20_stream_t *const s, n20_slice_t const bytes) { + if (bytes.size > 0 && bytes.buffer == NULL) { + n20_cbor_write_null(s); + return; + } + + n20_stream_prepend(s, bytes.buffer, bytes.size); + n20_cbor_write_header(s, n20_cbor_type_bytes_e, bytes.size); +} + +void n20_cbor_write_text_string(n20_stream_t *const s, n20_string_slice_t const text) { + if (text.size > 0 && text.buffer == NULL) { + n20_cbor_write_null(s); + return; + } + + n20_stream_prepend(s, (uint8_t const *)text.buffer, text.size); + n20_cbor_write_header(s, n20_cbor_type_string_e, text.size); +} + +void n20_cbor_write_array_header(n20_stream_t *const s, size_t const len) { + n20_cbor_write_header(s, n20_cbor_type_array_e, len); +} + +void n20_cbor_write_map_header(n20_stream_t *const s, size_t const len) { + n20_cbor_write_header(s, n20_cbor_type_map_e, len); +} + +bool n20_read_cbor_header(n20_istream_t *const s, n20_cbor_type_t *const type, uint64_t *const n) { + uint8_t header = 0; + if (!n20_istream_get(s, &header)) { + return false; + } + + *type = (n20_cbor_type_t)(header >> 5); + uint8_t additional_info = header & 0x1f; + + if (additional_info < 24 || additional_info > 27) { + *n = additional_info; + return true; + } + + *n = 0; + + uint8_t additional_bytes = 1 << (additional_info - 24); + for (int i = 0; i < additional_bytes; i++) { + uint8_t byte = 0; + if (!n20_istream_get(s, &byte)) { + return false; + } + *n = (*n << 8) | byte; + } + + return true; +} + +bool n20_iterate_cbor_item(n20_istream_t *const s) { + n20_cbor_type_t type = n20_cbor_type_none_e; + uint64_t n = 0; + if (!n20_read_cbor_header(s, &type, &n)) { + return false; + } + + switch (type) { + case n20_cbor_type_array_e: + for (size_t i = 0; i < n; i++) { + if (!n20_iterate_cbor_item(s)) { + return false; + } + } + break; + case n20_cbor_type_map_e: + for (size_t i = 0; i < n; i++) { + if (!n20_iterate_cbor_item(s)) { + return false; + } + if (!n20_iterate_cbor_item(s)) { + return false; + } + } + break; + case n20_cbor_type_bytes_e: + case n20_cbor_type_string_e: { + uint8_t const *slice = n20_istream_get_slice(s, n); + if (slice == NULL) { + return false; + } + break; + } + default: + break; + } + + return true; +} + +bool n20_cbor_skip_item(n20_istream_t *const s) { + n20_cbor_type_t type = n20_cbor_type_none_e; + uint64_t n = 0; + if (!n20_read_cbor_header(s, &type, &n)) { + return false; + } + + switch (type) { + case n20_cbor_type_array_e: + for (size_t i = 0; i < n; i++) { + if (!n20_cbor_skip_item(s)) { + return false; + } + } + break; + case n20_cbor_type_map_e: + for (size_t i = 0; i < n; i++) { + if (!n20_cbor_skip_item(s)) { + return false; + } + if (!n20_cbor_skip_item(s)) { + return false; + } + } + break; + case n20_cbor_type_bytes_e: + case n20_cbor_type_string_e: { + uint8_t const *slice = n20_istream_get_slice(s, n); + if (slice == NULL) { + return false; + } + break; + } + case n20_cbor_type_tag_e: + return n20_cbor_skip_item(s); // Skip the tag and the item it refers to. + default: + break; + } + + return true; +} + +/** + * @brief Render a COSE key structure as CBOR map. + * + * This function encodes a COSE key structure into a CBOR map format. + * It writes the key type, operations, algorithm identifier, and + * coordinates (X, Y) and private key (D) if available. + * The function infers the key type (kty) and curve (crv) from the + * given algorithm id. But it is the responsibility of the caller + * to populate the x, y, and d fields of the key structure + * with the appropriate values. + * I.e., For an ED25519 key, the x field should contain the + * public key, the y field should be empty, and the d field + * may contain the private key. For an ECDSA key, the x and y fields + * must contain the public key coordinates, and the d field + * may contain the private key. + * + * If the key type is not supported, it writes a null value. + * + * @param s The stream to write the CBOR map to. + * @param key The COSE key structure to encode. + */ +void n20_cose_write_key(n20_stream_t *const s, n20_cose_key_t const *const key) { + uint32_t pairs = 0; + + uint32_t crv = 0; // Curve type + uint32_t key_type = 0; // Curve type + switch (key->algorithm_id) { + case -7: // ES256 + case -9: // ESP256 + crv = 1; // P-256 + key_type = 2; // EC2 + break; + case -35: // ES384 + case -51: // ESP384 + crv = 2; // P-384 + key_type = 2; // EC2 + break; + case -8: // EdDSA + case -19: // Ed25519 + crv = 6; // Ed25519 + key_type = 1; // EC2 + break; + default: + n20_cbor_write_null(s); // Unsupported key type + return; + } + + if (key->d.size > 0) { + n20_cbor_write_byte_string(s, key->d); + n20_cbor_write_int(s, -4); // Private Key + ++pairs; + } + + if (key->y.size > 0) { + n20_cbor_write_byte_string(s, key->y); + n20_cbor_write_int(s, -3); // Y coordinate + ++pairs; + } + + if (key->x.size > 0) { + n20_cbor_write_byte_string(s, key->x); + n20_cbor_write_int(s, -2); // X coordinate + ++pairs; + } + + n20_cbor_write_int(s, crv); + n20_cbor_write_int(s, -1); // Curve type + ++pairs; + + uint32_t ops = 0; + for (int i = 11; i != 0; --i) { + if (n20_is_cose_key_op_set(key->key_ops, (n20_cose_key_ops_t)i)) { + n20_cbor_write_int(s, i); // COSE Key Operation + ++ops; + } + } + + n20_cbor_write_array_header(s, ops); + n20_cbor_write_int(s, 4); // COSE Key Ops + ++pairs; + + n20_cbor_write_int(s, key->algorithm_id); // Algorithm Identifier + n20_cbor_write_int(s, 3); // Algorithm + ++pairs; + n20_cbor_write_int(s, key_type); // Key Type + n20_cbor_write_int(s, 1); // Key Type + ++pairs; + n20_cbor_write_map_header(s, pairs); // Map header with the number of pairs +} + +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_open_dice_cwt_write(n20_stream_t *const s, n20_open_dice_cwt_t const *const cwt) { + + uint32_t pairs = 0; + // Write Key Usage + n20_cbor_write_byte_string(s, + (n20_slice_t){.buffer = (uint8_t *)cwt->key_usage, + .size = ((cwt->key_usage[0] | cwt->key_usage[1]) != 0) + ? 0 + : (cwt->key_usage[1] != 0 ? 2 : 1)}); + n20_cbor_write_int(s, -4670553); + ++pairs; // Key Usage + + size_t mark = n20_stream_byte_count(s); + + n20_cose_write_key(s, &cwt->subject_public_key); // Subject Public Key + + n20_cbor_write_header(s, + n20_cbor_type_bytes_e, + n20_stream_byte_count(s) - mark); // Length of the subject public key + n20_cbor_write_int(s, -4670552); // Subject Public Key + ++pairs; // Subject Public Key + + uint8_t mode = (uint8_t)cwt->mode; // Convert mode to uint8_t + + n20_cbor_write_byte_string(s, (n20_slice_t){.buffer = &mode, .size = 1}); + n20_cbor_write_int(s, -4670551); // Mode + ++pairs; + + if (cwt->authority_descriptor.size > 0) { + n20_cbor_write_byte_string(s, cwt->authority_descriptor); + n20_cbor_write_int(s, -4670550); // Authority Descriptor + ++pairs; + } + + if (cwt->authority_hash.size > 0) { + n20_cbor_write_byte_string(s, cwt->authority_hash); + n20_cbor_write_int(s, -4670549); // Authority Hash + ++pairs; + } + + if (cwt->configuration_descriptor.size > 0) { + n20_cbor_write_byte_string(s, cwt->configuration_descriptor); + n20_cbor_write_int(s, -4670548); // Configuration Descriptor + ++pairs; + } + + if (cwt->configuration_hash.size > 0) { + n20_cbor_write_byte_string(s, cwt->configuration_hash); + n20_cbor_write_int(s, -4670547); // Configuration Hash + ++pairs; + } + if (cwt->code_descriptor.size > 0) { + n20_cbor_write_byte_string(s, cwt->code_descriptor); + n20_cbor_write_int(s, -4670546); // Code Descriptor + ++pairs; + } + if (cwt->code_hash.size > 0) { + n20_cbor_write_byte_string(s, cwt->code_hash); + n20_cbor_write_int(s, -4670545); // Code Hash + ++pairs; + } + + if (cwt->subject.size > 0) { + n20_open_dice_write_name_as_hex(s, cwt->subject); + n20_cbor_write_int(s, 2); // Subject + ++pairs; + } + + if (cwt->issuer.size > 0) { + n20_open_dice_write_name_as_hex(s, cwt->issuer); + n20_cbor_write_int(s, 1); // Issuer + ++pairs; + } + n20_cbor_write_map_header(s, pairs); // Write the map header with the number of pairs +} + +n20_slice_t const SIGN_1_CONTEXT_WITH_ARRAY4_HEADER = { + .buffer = (uint8_t *)"\x84\x6aSignature1", + .size = 12, +}; + +n20_slice_t const EMPTY_BYTES_STRING = { + .buffer = (uint8_t *)"\x40", + .size = 1, +}; + +n20_error_t n20_cose_sign1_payload(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t const signing_key, + int32_t signing_key_algorith_id, + void (*payload_callback)(n20_stream_t *s, void *ctx), + void *payload_ctx, + uint8_t *cose_sign1, + size_t *cose_sign1_size) { + n20_stream_t s; + size_t signature_size = 0; + + if (crypto_ctx == NULL) { + return n20_error_missing_crypto_context_e; // Null crypto context + } + if (payload_callback == NULL || payload_ctx == NULL) { + /* Null payload callback or context. */ + return n20_error_missing_callback_function_or_context_e; + } + if (cose_sign1_size == NULL) { + return n20_error_crypto_unexpected_null_size_e; // Null size pointer + } + if (cose_sign1 == NULL && *cose_sign1_size != 0) { + /* Buffer cannot be NULL if size is not zero. */ + return n20_error_crypto_insufficient_buffer_size_e; + } + + n20_slice_t sig_structure[4] = { + SIGN_1_CONTEXT_WITH_ARRAY4_HEADER, + N20_SLICE_NULL, // Placeholder for the protected attributes + EMPTY_BYTES_STRING, // Empty bytestring for external eead + N20_SLICE_NULL, // Placeholder for the payload + }; + + switch (signing_key_algorith_id) { + case -7: // ES256 + case -9: // ESP256 + signature_size = 64; // ECDSA P-256 signature size + break; + case -8: // EdDSA + case -19: // Ed25519 + signature_size = 64; // Ed25519 signature size + break; + case -35: // ES384 + case -51: // ESP384 + signature_size = 96; // ECDSA P-384 signature size + break; + default: + return n20_error_crypto_unknown_algorithm_e; // Unsupported algorithm + } + + uint8_t *signature = cose_sign1 + *cose_sign1_size - signature_size; + + n20_stream_init(&s, cose_sign1, signature - cose_sign1); + + // The byte string header for the signature. + n20_cbor_write_header(&s, n20_cbor_type_bytes_e, signature_size); + + // Mark the end of the payload. + size_t mark = n20_stream_byte_count(&s); + + payload_callback(&s, payload_ctx); + + n20_cbor_write_header(&s, + n20_cbor_type_bytes_e, + n20_stream_byte_count(&s) - mark); // Length of the payload + + sig_structure[3] = (n20_slice_t){ + .buffer = n20_stream_data(&s), + .size = n20_stream_byte_count(&s) - mark, + }; + + // Empty header for unprotected attributes + n20_cbor_write_map_header(&s, 0); + + // Write protected attributes + mark = n20_stream_byte_count(&s); + n20_cbor_write_int(&s, signing_key_algorith_id); // Algorithm identifier + n20_cbor_write_int(&s, 1); // Algorithm identifier label + n20_cbor_write_map_header(&s, 1); // Map header with one pair + // The protected attributes are an encoded CBOR map, so + // a byte string header needs to be added containing the + // encoded map. + n20_cbor_write_header(&s, + n20_cbor_type_bytes_e, + n20_stream_byte_count(&s) - mark); // Length of the protected attributes + + sig_structure[1] = (n20_slice_t){ + .buffer = n20_stream_data(&s), + .size = n20_stream_byte_count(&s) - mark, + }; + + n20_cbor_write_array_header(&s, 4); // Array header with 4 elements + + n20_crypto_gather_list_t sig_structure_gather_list = { + .count = 4, + .list = sig_structure, + }; + + size_t sig_size_in_out = signature_size; + n20_error_t err = crypto_ctx->sign( + crypto_ctx, signing_key, &sig_structure_gather_list, signature, &sig_size_in_out); + if (err != n20_error_ok_e) { + return err; + } + if (sig_size_in_out != signature_size) { + return n20_error_crypto_insufficient_buffer_size_e; + } + + *cose_sign1_size = n20_stream_byte_count(&s) + signature_size; + + return n20_error_ok_e; +} diff --git a/src/core/functionality.c b/src/core/functionality.c new file mode 100644 index 0000000..ff6a1a8 --- /dev/null +++ b/src/core/functionality.c @@ -0,0 +1,1142 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static n20_x509_ext_open_dice_modes_t n20_open_dice_mode_2_x509_mode(n20_open_dice_mode_t mode) { + switch (mode) { + case n20_open_dice_mode_not_configured_e: + return n20_x509_ext_open_dice_not_configured_e; + case n20_open_dice_mode_normal_e: + return n20_x509_ext_open_dice_normal_e; + case n20_open_dice_mode_debug_e: + return n20_x509_ext_open_dice_debug_e; + case n20_open_dice_mode_recovery_e: + return n20_x509_ext_open_dice_recovery_e; + default: + return n20_x509_ext_open_dice_not_configured_e; + } +} + +/** + * @brief Converts n20_open_dice_mode_t to n20_cwt_open_dice_modes_t + * + */ +static n20_cwt_open_dice_modes_t n20_open_dice_mode_2_cwt_mode(n20_open_dice_mode_t mode) { + switch (mode) { + case n20_open_dice_mode_not_configured_e: + return n20_cwt_open_dice_not_configured_e; + case n20_open_dice_mode_normal_e: + return n20_cwt_open_dice_normal_e; + case n20_open_dice_mode_debug_e: + return n20_cwt_open_dice_debug_e; + case n20_open_dice_mode_recovery_e: + return n20_cwt_open_dice_recovery_e; + default: + return n20_cwt_open_dice_not_configured_e; + } +} + +/* + * Buffer holding the utf-8 encoded string "CDI_Attest". + */ +uint8_t const CDI_ATTEST_STR[] = { + 0x43, + 0x44, + 0x49, + 0x5f, + 0x41, + 0x74, + 0x74, + 0x65, + 0x73, + 0x74, +}; +n20_slice_t const CDI_ATTEST_STR_SLICE = { + .buffer = CDI_ATTEST_STR, + .size = 10, +}; + +/** + * @brief Buffer holding the salt used for the asymmetric key derivation. + * + * This buffer is used to derive the asymmetric key pair from the + * CDI secret. The buffer is 64 bytes long and is used as input to + * the KDF function. + */ +uint8_t const ASYM_SALT[] = { + 0x63, 0xb6, 0xa0, 0x4d, 0x2c, 0x07, 0x7f, 0xc1, 0x0f, 0x63, 0x9f, 0x21, 0xda, 0x79, 0x38, 0x44, + 0x35, 0x6c, 0xc2, 0xb0, 0xb4, 0x41, 0xb3, 0xa7, 0x71, 0x24, 0x03, 0x5c, 0x03, 0xf8, 0xe1, 0xbe, + 0x60, 0x35, 0xd3, 0x1f, 0x28, 0x28, 0x21, 0xa7, 0x45, 0x0a, 0x02, 0x22, 0x2a, 0xb1, 0xb3, 0xcf, + 0xf1, 0x67, 0x9b, 0x05, 0xab, 0x1c, 0xa5, 0xd1, 0xaf, 0xfb, 0x78, 0x9c, 0xcd, 0x2b, 0x0b, 0x3b}; +n20_slice_t const ASYM_SALT_SLICE = { + .buffer = ASYM_SALT, + .size = 64, +}; + +/* + * Buffer holding the utf-8 encoded string "Key Pair Attest". + */ +uint8_t const ATTEST_KEY_PAIR_STR[] = { + 0x4b, 0x65, 0x79, 0x20, 0x50, 0x61, 0x69, 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74}; +n20_slice_t const ATTEST_KEY_PAIR_STR_SLICE = { + .buffer = ATTEST_KEY_PAIR_STR, + .size = 15, +}; + +/** + * @brief Buffer holding the salt used for the ID derivation. + * + * This buffer is used to derive the ID from the attestation public key. + & The buffer is 64 bytes long and is used as input to the KDF function. + */ +uint8_t const ID_SALT[] = { + 0xdb, 0xdb, 0xae, 0xbc, 0x80, 0x20, 0xda, 0x9f, 0xf0, 0xdd, 0x5a, 0x24, 0xc8, 0x3a, 0xa5, 0xa5, + 0x42, 0x86, 0xdf, 0xc2, 0x63, 0x03, 0x1e, 0x32, 0x9b, 0x4d, 0xa1, 0x48, 0x43, 0x06, 0x59, 0xfe, + 0x62, 0xcd, 0xb5, 0xb7, 0xe1, 0xe0, 0x0f, 0xc6, 0x80, 0x30, 0x67, 0x11, 0xeb, 0x44, 0x4a, 0xf7, + 0x72, 0x09, 0x35, 0x94, 0x96, 0xfc, 0xff, 0x1d, 0xb9, 0x52, 0x0b, 0xa5, 0x1c, 0x7b, 0x29, 0xea}; + +n20_slice_t const ID_SALT_SLICE = { + .buffer = ID_SALT, + .size = sizeof(ID_SALT), +}; + +/* + * Buffer holding the utf-8 encoded string "ID". + */ +uint8_t const ID_STR[] = { + 0x49, + 0x44, +}; + +n20_slice_t const ID_STR_SLICE = { + .buffer = ID_STR, + .size = 2, +}; + +n20_error_t n20_compress_input(n20_crypto_digest_context_t *crypto_ctx, + n20_open_dice_input_t const *context, + n20_compressed_input_t digest) { + // Check if the crypto context is valid + if (crypto_ctx == NULL) { + return n20_error_missing_crypto_context_e; + } + + uint8_t mode = (uint8_t)context->mode; + + n20_slice_t input_list[] = { + context->code_hash, + context->configuration_hash, + context->authority_hash, + {.buffer = &mode, .size = 1}, + context->hidden, + }; + + n20_crypto_gather_list_t input = { + .count = 5, + .list = &input_list[0], + }; + + size_t digest_size = N20_FUNC_COMPRESSED_INPUT_SIZE; + n20_error_t err = crypto_ctx->digest( + crypto_ctx, N20_FUNC_COMPRESSED_INPUT_ALGORITHM, &input, 1, digest, &digest_size); + if (err != n20_error_ok_e) { + return err; + } + + return n20_error_ok_e; +} + +/** + * @brief Derives a key from the given CDI secret. + * + * This function derives a key from the given CDI secret using + * the given salt and tag. The derived key is returned in the + * given buffer. + * + * @param crypto_ctx The crypto context. + * @param cdi_secret The CDI secret to derive the key from. + * @param derived The derived key. + * @param key_type The type of the derived key. + * @param salt The salt to use for the derivation. + * @param tag The tag to use for the derivation. + * + * @return n20_error_ok_e on success, or an error code on failure. + */ +n20_error_t n20_derive_key(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t cdi_secret, + n20_crypto_key_t *derived, + n20_crypto_key_type_t key_type, + n20_slice_t const salt, + n20_slice_t const tag) { + if (crypto_ctx == NULL) { + return n20_error_missing_crypto_context_e; + } + if (derived == NULL) { + return n20_error_unexpected_null_key_handle_e; + } + + n20_slice_t derivation_context_list[] = {salt, tag}; + + n20_crypto_gather_list_t derivation_context = { + .count = 2, + .list = &derivation_context_list[0], + }; + + return crypto_ctx->kdf(crypto_ctx, cdi_secret, key_type, &derivation_context, derived); +} + +/** + * @brief Derives the next level CDI secret from the given CDI secret. + * + * This function derives the next level CDI secret from the given + * CDI secret using the given salt and tag. The derived key is + * returned in the given buffer. + * + * @param crypto_ctx The crypto context. + * @param current The current CDI secret to derive the key from. + * @param next The derived key. + * @param info The information to use for the derivation. + * + * @return n20_error_ok_e on success, or an error code on failure. + */ +n20_error_t n20_next_level_cdi_attest(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t current, + n20_crypto_key_t *next, + n20_compressed_input_t info) { + + return n20_derive_key(crypto_ctx, + current, + next, + n20_crypto_key_type_cdi_e, + (n20_slice_t){.size = N20_FUNC_COMPRESSED_INPUT_SIZE, .buffer = &info[0]}, + CDI_ATTEST_STR_SLICE); +} + +/** + * @brief Derives an attestation key from the given CDI secret. + * + * This function derives an attestation key from the given CDI + * secret using the given salt and tag. The derived key is returned + * in the given buffer. + * + * @param crypto_ctx The crypto context. + * @param cdi_secret The CDI secret to derive the key from. + * @param derived The derived key. + * @param key_type The type of the derived key. + * + * @return n20_error_ok_e on success, or an error code on failure. + */ +n20_error_t n20_derive_attestation_key(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t cdi_secret, + n20_crypto_key_t *derived, + n20_crypto_key_type_t key_type) { + return n20_derive_key( + crypto_ctx, cdi_secret, derived, key_type, ASYM_SALT_SLICE, ATTEST_KEY_PAIR_STR_SLICE); +} + +/** + * @brief Initializes the algorithm identifier structure. + * + * This function initializes the algorithm identifier structure + * with the given key type. + * + * @param algorithm_identifier The algorithm identifier structure to initialize. + * @param key_type The type of the key. + * + * @return n20_error_ok_e on success, or an error code on failure. + */ +n20_error_t n20_init_algorithm_identifier(n20_x509_algorithm_identifier_t *algorithm_identifier, + n20_crypto_key_type_t key_type) { + + switch (key_type) { + case n20_crypto_key_type_ed25519_e: + algorithm_identifier->oid = &OID_ED25519; + break; + case n20_crypto_key_type_secp256r1_e: + algorithm_identifier->oid = &OID_ECDSA_WITH_SHA256; + break; + case n20_crypto_key_type_secp384r1_e: + algorithm_identifier->oid = &OID_ECDSA_WITH_SHA384; + break; + default: + /* The key type is not supported. */ + return n20_error_crypto_invalid_key_type_e; + } + algorithm_identifier->params.variant = n20_x509_pv_none_e; + algorithm_identifier->params.ec_curve = NULL; + + return n20_error_ok_e; +} + +/** + * @brief Initializes the key info structure. + * + * This function initializes the key info structure with the + * given key type and public key. + * + * @param key_info The key info structure to initialize. + * @param key_type The type of the key. + * @param public_key The public key to use. + * @param public_key_size The size of the public key. + * + * @return n20_error_ok_e on success, or an error code on failure. + */ +n20_error_t n20_init_key_info(n20_x509_public_key_info_t *key_info, + n20_crypto_key_type_t key_type, + uint8_t *public_key, + size_t public_key_size) { + switch (key_type) { + case n20_crypto_key_type_ed25519_e: + key_info->algorithm_identifier.oid = &OID_ED25519; + key_info->algorithm_identifier.params.variant = n20_x509_pv_none_e; + break; + case n20_crypto_key_type_secp256r1_e: + key_info->algorithm_identifier.oid = &OID_EC_PUBLIC_KEY; + key_info->algorithm_identifier.params.variant = n20_x509_pv_ec_curve_e; + key_info->algorithm_identifier.params.ec_curve = &OID_SECP256R1; + break; + case n20_crypto_key_type_secp384r1_e: + key_info->algorithm_identifier.oid = &OID_EC_PUBLIC_KEY; + key_info->algorithm_identifier.params.variant = n20_x509_pv_ec_curve_e; + key_info->algorithm_identifier.params.ec_curve = &OID_SECP384R1; + break; + default: + /* The key type is not supported. */ + return n20_error_crypto_invalid_key_type_e; + } + key_info->public_key_bits = public_key_size * 8; + key_info->public_key = public_key; + + return n20_error_ok_e; +} + +typedef n20_error_t (*n20_signer_callback_t)(void *signer, + n20_slice_t tbs, + uint8_t *signature, + size_t *signature_size); + +n20_error_t n20_signer_callback(void *ctx, + n20_slice_t tbs, + uint8_t *signature, + size_t *signature_size) { + + n20_signer_t *signer = (n20_signer_t *)ctx; + + n20_crypto_gather_list_t tbs_der_gather = { + .count = 1, + .list = &tbs, + }; + + return signer->crypto_ctx->sign( + signer->crypto_ctx, signer->signing_key, &tbs_der_gather, signature, signature_size); +} +/** + * @brief Initializes the X.509 name structure. + * + * This function initializes the X.509 name structure with the + * given name. + * + * @param name The X.509 name structure to initialize. + * @param n The name to use. + */ +void n20_init_x509_name(n20_x509_name_t *name, n20_name_t const *n) { + size_t i = 0; + if (n->country_name.buffer != NULL) { + name->elements[i++] = (n20_x509_rdn_t){&OID_COUNTRY_NAME, .string = n->country_name}; + } + if (n->locality_name.buffer != NULL) { + name->elements[i++] = (n20_x509_rdn_t){&OID_LOCALITY_NAME, .string = n->locality_name}; + } + if (n->organization_name.buffer != NULL) { + name->elements[i++] = + (n20_x509_rdn_t){&OID_ORGANIZATION_NAME, .string = n->organization_name}; + } + if (n->organization_unit_name.buffer != NULL) { + name->elements[i++] = + (n20_x509_rdn_t){&OID_ORGANIZATION_UNIT_NAME, .string = n->organization_unit_name}; + } + if (n->common_name.buffer != NULL) { + name->elements[i++] = (n20_x509_rdn_t){&OID_COMMON_NAME, .string = n->common_name}; + } + if (n->serial_number.buffer != NULL) { + name->elements[i++] = (n20_x509_rdn_t){&OID_SERIAL_NUMBER, .bytes = n->serial_number}; + } + name->element_count = i; +} + +/** + * @brief Prepares the X.509 certificate. + * + * This function prepares the X.509 certificate with the given + * context information and signs it using the given signer. + * + * @param context The context information to use. + * @param signer The signer to use. + * @param issuer_key_type The type of the issuer key. + * @param issuer_name The name of the issuer. + * @param subject_key_type The type of the subject key. + * @param subject_name The name of the subject. + * @param public_key The public key to use. + * @param public_key_size The size of the public key. + * @param attestation_certificate The attestation certificate to use. + * @param attestation_certificate_size The size of the attestation certificate. + * + * @return n20_error_ok_e on success, or an error code on failure. + */ +n20_error_t n20_prepare_x509_cert(n20_open_dice_input_t const *context, + n20_signer_t *signer, + n20_crypto_key_type_t issuer_key_type, + n20_name_t *issuer_name, + n20_crypto_key_type_t subject_key_type, + n20_name_t *subject_name, + uint8_t *public_key, + size_t public_key_size, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size) { + n20_error_t err = n20_error_ok_e; + n20_x509_ext_key_usage_t key_usage = {0}; + + /* Check if this is an ECA certificate (empty context) */ + bool is_eca = (context->code_hash.size == 0 && context->configuration_hash.size == 0 && + context->authority_hash.size == 0); + + if (is_eca) { + /* ECA certificates: end-entity certificates with digital signature and key encipherment */ + N20_X509_KEY_USAGE_SET_DIGITAL_SIGNATURE(&key_usage); + N20_X509_KEY_USAGE_SET_KEY_ENCIPHERMENT(&key_usage); + } else { + /* CDI certificates: CA certificates with certificate signing */ + N20_X509_KEY_USAGE_SET_KEY_CERT_SIGN(&key_usage); + } + + n20_x509_ext_basic_constraints_t basic_constraints = { + .is_ca = is_eca ? 0 : 1, /* ECA is not a CA */ + .has_path_length = false, + }; + + n20_x509_ext_open_dice_input_t open_dice_extension = { + .code_hash = context->code_hash, + .code_descriptor = context->code_descriptor, + .configuration_hash = context->configuration_hash, + .configuration_descriptor = context->configuration_descriptor, + .authority_hash = context->authority_hash, + .authority_descriptor = context->authority_descriptor, + .mode = n20_open_dice_mode_2_x509_mode(context->mode), + .profile_name = context->profile_name, + }; + + /* For ECA certificates, we only include key usage and basic constraints extensions */ + int extension_count = is_eca ? 2 : 3; + + n20_x509_extension_t extensions[3] = { + { + .oid = &OID_OPEN_DICE_INPUT, + .critical = 1, + .content_cb = n20_x509_ext_open_dice_input_content, + .context = &open_dice_extension, + }, + { + .oid = &OID_KEY_USAGE, + .critical = 1, + .content_cb = n20_x509_ext_key_usage_content, + .context = &key_usage, + }, + { + .oid = &OID_BASIC_CONSTRAINTS, + .critical = 1, + .content_cb = n20_x509_ext_basic_constraints_content, + .context = &basic_constraints, + }, + }; + + n20_x509_tbs_t tbs = {0}; + tbs.validity = (n20_x509_validity_t){ + .not_before = N20_STR_NULL, + .not_after = N20_STR_NULL, + }; + + tbs.serial_number = subject_name->serial_number; + + err = n20_init_algorithm_identifier(&tbs.signature_algorithm, issuer_key_type); + if (err != n20_error_ok_e) { + return err; + } + + err = n20_init_key_info( + &tbs.subject_public_key_info, subject_key_type, public_key, public_key_size); + if (err != n20_error_ok_e) { + return err; + } + + n20_init_x509_name(&tbs.issuer_name, issuer_name); + n20_init_x509_name(&tbs.subject_name, subject_name); + + tbs.issuer_unique_id = issuer_name->serial_number; + tbs.subject_unique_id = subject_name->serial_number; + + tbs.extensions = (n20_x509_extensions_t){ + .extensions_count = extension_count, + .extensions = + is_eca ? &extensions[1] : &extensions[0], /* Skip OpenDICE extension for ECA */ + }; + + // Create a new stream for the attestation certificate + n20_stream_t stream; + n20_stream_init(&stream, attestation_certificate, *attestation_certificate_size); + n20_x509_cert_tbs(&stream, &tbs); + if (n20_stream_has_buffer_overflow(&stream) || + n20_stream_has_write_position_overflow(&stream)) { + *attestation_certificate_size = n20_stream_byte_count(&stream); + + return n20_error_insufficient_buffer_size_e; + } + + // Sign the to-be-signed part of the certificate. + uint8_t signature[128]; + size_t signature_size = sizeof(signature); + + err = signer->cb( + signer, + (n20_slice_t){.size = n20_stream_byte_count(&stream), .buffer = n20_stream_data(&stream)}, + signature, + &signature_size); + if (err != n20_error_ok_e) { + return err; + } + + /* Reinitialize the stream. */ + n20_stream_init(&stream, attestation_certificate, *attestation_certificate_size); + n20_x509_t cert = { + .tbs = &tbs, + .signature_algorithm = tbs.signature_algorithm, + .signature_bits = signature_size * 8, + .signature = signature, + }; + + n20_x509_cert(&stream, &cert); + if (n20_stream_has_buffer_overflow(&stream) || + n20_stream_has_write_position_overflow(&stream)) { + *attestation_certificate_size = n20_stream_byte_count(&stream); + return n20_error_insufficient_buffer_size_e; + } + *attestation_certificate_size = n20_stream_byte_count(&stream); + + return n20_error_ok_e; +} + +n20_error_t n20_open_dice_cdi_id(n20_crypto_digest_context_t *digest_ctx, + n20_slice_t const public_key, + n20_cdi_id_t cdi_id) { + n20_error_t rc = digest_ctx->hkdf(digest_ctx, + n20_crypto_digest_algorithm_sha2_512_e, + public_key, + ID_SALT_SLICE, + ID_STR_SLICE, + 20, + &cdi_id[0]); + /* Ensure that the most significant bit is not set so that it + * is a valid positive integer that can be represented as no + * more than 20 bytes in ASN1. + */ + cdi_id[0] &= 0x7F; + return rc; +} + +static void payload_callback_open_dice_cwt(n20_stream_t *s, void *payload_ctx) { + n20_open_dice_cwt_write(s, (n20_open_dice_cwt_t const *)payload_ctx); +} + +/** + * @brief Issues a new attestation certificate. + * + * This function generates a new CDI secret from a CDI or UDS given + * as opaque crypto key handle and context information. It uses the + * crypto context to generate a new CDI secret and then issues a + * new attestation certificate for the given CDI level. + * To that end it uses the derived CDI secret to derive a new + * attestation key pair and formats the attestation certificate + * using the given context information to generate the OpenDICE input + * extension. + * The attestation certificate is then signed using the attestation + * key pair of the given CDI level. + * The attestation certificate is then returned in the given + * buffer. + * The size of the attestation certificate buffer is given as + * pointer which is updated to the actual size of the attestation + * certificate. + * + * Important: Because of the way the attestation certificate is + * rendered, the resulting certificate is not written to the + * beginning of the buffer but to the end. Thus the certificate + * is located at + * `attestation_certificate + in_buffer_size - out_buffer_size`. + * + * The function returns @ref n20_error_ok_e on success, or an error + * code on failure. + */ +n20_error_t n20_opendice_attestation_key_and_certificate( + n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t parent_secret, + n20_crypto_key_t parent_attestation_key, + n20_crypto_key_type_t parent_key_type, + n20_crypto_key_type_t key_type, + n20_open_dice_input_t const *context, + n20_certificate_format_t certificate_format, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size) { + + /* Check if the crypto context is valid. */ + if (crypto_ctx == NULL) { + return n20_error_missing_crypto_context_e; + } + + bool cose = (certificate_format == n20_certificate_format_cose_e); + + n20_compressed_input_t input_digest = {0}; + + n20_error_t err = n20_compress_input(&crypto_ctx->digest_ctx, context, input_digest); + if (err != n20_error_ok_e) { + return err; + } + + n20_cdi_id_t issuer_serial_number = {0}; + + uint8_t public_key_buffer[97]; + + /* Get the public key of the parent attestation key. */ + uint8_t *public_key = &public_key_buffer[1]; + size_t public_key_size = sizeof(public_key_buffer) - 1; + err = crypto_ctx->key_get_public_key( + crypto_ctx, parent_attestation_key, public_key, &public_key_size); + + if (err != n20_error_ok_e) { + return err; + } + + err = n20_open_dice_cdi_id(&crypto_ctx->digest_ctx, + (n20_slice_t){.buffer = public_key, .size = public_key_size}, + issuer_serial_number); + + if (err != n20_error_ok_e) { + return err; + } + + if (!cose && parent_key_type != n20_crypto_key_type_ed25519_e) { + public_key_buffer[0] = 0x04; + public_key = &public_key_buffer[0]; + public_key_size += 1; + } + + n20_crypto_key_t child_secret = NULL; + + err = n20_next_level_cdi_attest(crypto_ctx, parent_secret, &child_secret, input_digest); + if (err != n20_error_ok_e) { + return err; + } + + n20_crypto_key_t child_attestation_key = NULL; + err = n20_derive_attestation_key(crypto_ctx, child_secret, &child_attestation_key, key_type); + + /* Regardless of whether the last call was successful + * the child secret is no longer needed. */ + crypto_ctx->key_free(crypto_ctx, child_secret); + + if (err != n20_error_ok_e) { + return err; + } + + /* Get the public key of the derived key. */ + public_key = &public_key_buffer[1]; + public_key_size = sizeof(public_key_buffer) - 1; + err = crypto_ctx->key_get_public_key( + crypto_ctx, child_attestation_key, public_key, &public_key_size); + + /* Regardless of whether the last call was successful + * the child attestation key is no longer needed. */ + crypto_ctx->key_free(crypto_ctx, child_attestation_key); + + if (err != n20_error_ok_e) { + return err; + } + + n20_cdi_id_t subject_serial_number = {0}; + err = n20_open_dice_cdi_id(&crypto_ctx->digest_ctx, + (n20_slice_t){.buffer = public_key, .size = public_key_size}, + subject_serial_number); + if (err != n20_error_ok_e) { + return err; + } + + if (!cose && key_type != n20_crypto_key_type_ed25519_e) { + public_key_buffer[0] = 0x04; + public_key = &public_key_buffer[0]; + public_key_size += 1; + } + + if (!cose) { + return n20_prepare_x509_cert(context, + &(n20_signer_t){ + .crypto_ctx = crypto_ctx, + .signing_key = parent_attestation_key, + .cb = n20_signer_callback, + }, + parent_key_type, + &(n20_name_t){ + .country_name = N20_STR_C("US"), + .locality_name = N20_STR_C("Scranton"), + .organization_name = N20_STR_C("Test DICE CA"), + .organization_unit_name = N20_STR_NULL, + .common_name = N20_STR_C("DICE Layer 0"), + .serial_number = + { + .buffer = (uint8_t *)issuer_serial_number, + .size = sizeof(issuer_serial_number), + }, + }, + key_type, + &(n20_name_t){ + .country_name = N20_STR_C("US"), + .locality_name = N20_STR_C("Scranton"), + .organization_name = N20_STR_C("Test DICE CA"), + .organization_unit_name = N20_STR_NULL, + .common_name = N20_STR_C("DICE Layer 1"), + .serial_number = + { + .buffer = (uint8_t *)subject_serial_number, + .size = sizeof(subject_serial_number), + }, + }, + public_key, + public_key_size, + attestation_certificate, + attestation_certificate_size); + } else { + n20_open_dice_cwt_t cwt = { + .code_hash = context->code_hash, + .configuration_hash = context->configuration_hash, + .authority_hash = context->authority_hash, + .mode = n20_open_dice_mode_2_cwt_mode(context->mode), + .subject = + { + .buffer = (uint8_t *)subject_serial_number, + .size = sizeof(subject_serial_number), + }, + .issuer = + { + .buffer = (uint8_t *)issuer_serial_number, + .size = sizeof(issuer_serial_number), + }, + .code_descriptor = context->code_descriptor, + .configuration_descriptor = context->configuration_descriptor, + .subject_public_key = + { + .algorithm_id = 0, + .x = N20_SLICE_NULL, + .y = N20_SLICE_NULL, + .d = N20_SLICE_NULL, + }, + }; + + switch (key_type) { + case n20_crypto_key_type_ed25519_e: + cwt.subject_public_key.algorithm_id = -8; + cwt.subject_public_key.x.buffer = public_key; + cwt.subject_public_key.x.size = public_key_size; + break; + case n20_crypto_key_type_secp256r1_e: + cwt.subject_public_key.algorithm_id = -7; + cwt.subject_public_key.x.buffer = public_key; + cwt.subject_public_key.x.size = public_key_size / 2; + cwt.subject_public_key.y.buffer = &public_key[public_key_size / 2]; + cwt.subject_public_key.y.size = public_key_size / 2; + break; + case n20_crypto_key_type_secp384r1_e: + cwt.subject_public_key.algorithm_id = -35; + cwt.subject_public_key.x.buffer = public_key; + cwt.subject_public_key.x.size = public_key_size; + cwt.subject_public_key.y.buffer = &public_key[public_key_size / 2]; + cwt.subject_public_key.y.size = public_key_size / 2; + break; + default: + return n20_error_crypto_invalid_key_type_e; + } + + n20_set_cose_key_ops(&cwt.subject_public_key.key_ops, n20_cose_key_op_sign_e); + n20_set_cose_key_ops(&cwt.subject_public_key.key_ops, n20_cose_key_op_verify_e); + + int32_t signing_key_algorithm_id = 0; + + switch (parent_key_type) { + case n20_crypto_key_type_ed25519_e: + signing_key_algorithm_id = -8; + break; + case n20_crypto_key_type_secp256r1_e: + signing_key_algorithm_id = -7; + break; + case n20_crypto_key_type_secp384r1_e: + signing_key_algorithm_id = -35; + break; + default: + return n20_error_crypto_invalid_key_type_e; + } + + return n20_cose_sign1_payload(crypto_ctx, + parent_attestation_key, + signing_key_algorithm_id, + payload_callback_open_dice_cwt, + &cwt, + attestation_certificate, + attestation_certificate_size); + } +} + +/** + * @brief Issues a new ECA attestation certificate. + * + * This function generates an ECA (Embedded CA) attestation certificate + * using the parent secret to derive an ECA key pair. Unlike CDI certificates, + * ECA certificates are end-entity certificates (not CA certificates) and don't + * derive a new level of secrets. + * + * The ECA key is derived directly from the parent secret using the tag "ECA_KEY". + * The certificate is signed by the parent attestation key and contains the + * ECA public key. + * + * Important: Because of the way the attestation certificate is rendered, + * the resulting certificate is not written to the beginning of the buffer + * but to the end. Thus the certificate is located at + * `attestation_certificate + in_buffer_size - out_buffer_size`. + * + * @param crypto_ctx The crypto context. + * @param parent_secret The parent CDI secret. + * @param parent_attestation_key The parent attestation key for signing. + * @param parent_key_type The type of the parent key. + * @param key_type The type of the ECA key to generate. + * @param certificate_format The format of the certificate (X.509 or COSE). + * @param attestation_certificate Buffer to write the certificate. + * @param attestation_certificate_size In/out parameter for buffer size. + * @return n20_error_ok_e on success, or an error code on failure. + */ +n20_error_t n20_eca_attestation_key_and_certificate(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t parent_secret, + n20_crypto_key_t parent_attestation_key, + n20_crypto_key_type_t parent_key_type, + n20_crypto_key_type_t key_type, + n20_string_slice_t context, + n20_slice_t key_usage, + n20_slice_t challenge, + n20_certificate_format_t certificate_format, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size) { + + /* Check if the crypto context is valid. */ + if (crypto_ctx == NULL) { + return n20_error_missing_crypto_context_e; + } + + bool cose = (certificate_format == n20_certificate_format_cose_e); + + n20_cdi_id_t issuer_serial_number = {0}; + + uint8_t public_key_buffer[97]; + + /* Get the public key of the parent attestation key. */ + uint8_t *public_key = &public_key_buffer[1]; + size_t public_key_size = sizeof(public_key_buffer) - 1; + n20_error_t err = crypto_ctx->key_get_public_key( + crypto_ctx, parent_attestation_key, public_key, &public_key_size); + + if (err != n20_error_ok_e) { + return err; + } + + err = n20_open_dice_cdi_id(&crypto_ctx->digest_ctx, + (n20_slice_t){.buffer = public_key, .size = public_key_size}, + issuer_serial_number); + + if (err != n20_error_ok_e) { + return err; + } + + if (!cose && parent_key_type != n20_crypto_key_type_ed25519_e) { + public_key_buffer[0] = 0x04; + public_key = &public_key_buffer[0]; + public_key_size += 1; + } + + /* Concatenate Context | Key usage | challenge to form ECA_CTX */ + /* Use a reasonable maximum size for the combined input */ + uint8_t eca_ctx[512]; /* Stack allocated buffer */ + size_t eca_ctx_size = 0; + + /* Concatenate Context | Key usage | challenge */ + if (context.size > 0) { + if (eca_ctx_size + context.size > sizeof(eca_ctx)) { + return n20_error_insufficient_buffer_size_e; + } + memcpy(eca_ctx + eca_ctx_size, context.buffer, context.size); + eca_ctx_size += context.size; + } + if (key_usage.size > 0) { + if (eca_ctx_size + key_usage.size > sizeof(eca_ctx)) { + return n20_error_insufficient_buffer_size_e; + } + memcpy(eca_ctx + eca_ctx_size, key_usage.buffer, key_usage.size); + eca_ctx_size += key_usage.size; + } + if (challenge.size > 0) { + if (eca_ctx_size + challenge.size > sizeof(eca_ctx)) { + return n20_error_insufficient_buffer_size_e; + } + memcpy(eca_ctx + eca_ctx_size, challenge.buffer, challenge.size); + eca_ctx_size += challenge.size; + } + + /* Use ECA_CTX as salt for key derivation - n20_derive_key will hash it internally */ + n20_slice_t eca_salt = {.buffer = eca_ctx, .size = eca_ctx_size}; + static uint8_t const ECA_TAG[] = {'E', 'C', 'A', '_', 'K', 'E', 'Y'}; + n20_slice_t eca_tag = {.buffer = (uint8_t *)ECA_TAG, .size = sizeof(ECA_TAG)}; + + /* Derive the ECA attestation key */ + n20_crypto_key_t eca_attestation_key = NULL; + err = n20_derive_key( + crypto_ctx, parent_secret, &eca_attestation_key, key_type, eca_salt, eca_tag); + + if (err != n20_error_ok_e) { + return err; + } + + /* Get the public key of the ECA key. */ + public_key = &public_key_buffer[1]; + public_key_size = sizeof(public_key_buffer) - 1; + err = crypto_ctx->key_get_public_key( + crypto_ctx, eca_attestation_key, public_key, &public_key_size); + + /* Regardless of whether the last call was successful + * the ECA attestation key is no longer needed. */ + crypto_ctx->key_free(crypto_ctx, eca_attestation_key); + + if (err != n20_error_ok_e) { + return err; + } + + n20_cdi_id_t subject_serial_number = {0}; + err = n20_open_dice_cdi_id(&crypto_ctx->digest_ctx, + (n20_slice_t){.buffer = public_key, .size = public_key_size}, + subject_serial_number); + if (err != n20_error_ok_e) { + return err; + } + + if (!cose && key_type != n20_crypto_key_type_ed25519_e) { + public_key_buffer[0] = 0x04; + public_key = &public_key_buffer[0]; + public_key_size += 1; + } + + if (!cose) { + /* Create empty context for ECA certificate (no OpenDICE input extension) */ + n20_open_dice_input_t empty_context = {0}; + + return n20_prepare_x509_cert(&empty_context, + &(n20_signer_t){ + .crypto_ctx = crypto_ctx, + .signing_key = parent_attestation_key, + .cb = n20_signer_callback, + }, + parent_key_type, + &(n20_name_t){ + .country_name = N20_STR_C("US"), + .locality_name = N20_STR_C("Scranton"), + .organization_name = N20_STR_C("Test DICE CA"), + .organization_unit_name = N20_STR_NULL, + .common_name = N20_STR_C("DICE Layer 0"), + .serial_number = + { + .buffer = (uint8_t *)issuer_serial_number, + .size = sizeof(issuer_serial_number), + }, + }, + key_type, + &(n20_name_t){ + .country_name = N20_STR_C("US"), + .locality_name = N20_STR_C("Scranton"), + .organization_name = N20_STR_C("Test DICE ECA"), + .organization_unit_name = N20_STR_NULL, + .common_name = N20_STR_C("DICE ECA Certificate"), + .serial_number = + { + .buffer = (uint8_t *)subject_serial_number, + .size = sizeof(subject_serial_number), + }, + }, + public_key, + public_key_size, + attestation_certificate, + attestation_certificate_size); + } else { + /* For COSE format, create a simpler CWT without OpenDICE claims */ + n20_open_dice_cwt_t cwt = { + .code_hash = N20_SLICE_NULL, + .configuration_hash = N20_SLICE_NULL, + .authority_hash = N20_SLICE_NULL, + .mode = n20_open_dice_mode_2_cwt_mode(n20_open_dice_mode_not_configured_e), + .subject = + { + .buffer = (uint8_t *)subject_serial_number, + .size = sizeof(subject_serial_number), + }, + .issuer = + { + .buffer = (uint8_t *)issuer_serial_number, + .size = sizeof(issuer_serial_number), + }, + }; + + switch (key_type) { + case n20_crypto_key_type_ed25519_e: + cwt.subject_public_key.algorithm_id = -8; + cwt.subject_public_key.x.buffer = public_key; + cwt.subject_public_key.x.size = public_key_size; + break; + case n20_crypto_key_type_secp256r1_e: + cwt.subject_public_key.algorithm_id = -7; + cwt.subject_public_key.x.buffer = public_key; + cwt.subject_public_key.x.size = public_key_size / 2; + cwt.subject_public_key.y.buffer = &public_key[public_key_size / 2]; + cwt.subject_public_key.y.size = public_key_size / 2; + break; + case n20_crypto_key_type_secp384r1_e: + cwt.subject_public_key.algorithm_id = -35; + cwt.subject_public_key.x.buffer = public_key; + cwt.subject_public_key.x.size = public_key_size; + cwt.subject_public_key.y.buffer = &public_key[public_key_size / 2]; + cwt.subject_public_key.y.size = public_key_size / 2; + break; + default: + return n20_error_crypto_invalid_key_type_e; + } + + n20_set_cose_key_ops(&cwt.subject_public_key.key_ops, n20_cose_key_op_sign_e); + n20_set_cose_key_ops(&cwt.subject_public_key.key_ops, n20_cose_key_op_verify_e); + + int32_t signing_key_algorithm_id = 0; + + switch (parent_key_type) { + case n20_crypto_key_type_ed25519_e: + signing_key_algorithm_id = -8; + break; + case n20_crypto_key_type_secp256r1_e: + signing_key_algorithm_id = -7; + break; + case n20_crypto_key_type_secp384r1_e: + signing_key_algorithm_id = -35; + break; + default: + return n20_error_crypto_invalid_key_type_e; + } + + return n20_cose_sign1_payload(crypto_ctx, + parent_attestation_key, + signing_key_algorithm_id, + payload_callback_open_dice_cwt, + &cwt, + attestation_certificate, + attestation_certificate_size); + } +} + +n20_error_t n20_eca_sign_message(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t parent_secret, + n20_crypto_key_type_t key_type, + n20_string_slice_t context, + n20_slice_t key_usage, + n20_slice_t challenge, + n20_slice_t message, + uint8_t *signature, + size_t *signature_size) { + /* Check if the crypto context is valid. */ + if (crypto_ctx == NULL) { + return n20_error_missing_crypto_context_e; + } + + /* Concatenate Context | Key usage | challenge to form ECA_CTX */ + /* Use a reasonable maximum size for the combined input */ + uint8_t eca_ctx[512]; /* Stack allocated buffer */ + size_t eca_ctx_size = 0; + + /* Concatenate Context | Key usage | challenge */ + if (context.size > 0) { + if (eca_ctx_size + context.size > sizeof(eca_ctx)) { + return n20_error_insufficient_buffer_size_e; + } + memcpy(eca_ctx + eca_ctx_size, context.buffer, context.size); + eca_ctx_size += context.size; + } + if (key_usage.size > 0) { + if (eca_ctx_size + key_usage.size > sizeof(eca_ctx)) { + return n20_error_insufficient_buffer_size_e; + } + memcpy(eca_ctx + eca_ctx_size, key_usage.buffer, key_usage.size); + eca_ctx_size += key_usage.size; + } + if (challenge.size > 0) { + if (eca_ctx_size + challenge.size > sizeof(eca_ctx)) { + return n20_error_insufficient_buffer_size_e; + } + memcpy(eca_ctx + eca_ctx_size, challenge.buffer, challenge.size); + eca_ctx_size += challenge.size; + } + + /* Use ECA_CTX as salt for key derivation - n20_derive_key will hash it internally */ + n20_slice_t eca_salt = {.buffer = eca_ctx, .size = eca_ctx_size}; + static uint8_t const ECA_TAG[] = {'E', 'C', 'A', '_', 'K', 'E', 'Y'}; + n20_slice_t eca_tag = {.buffer = (uint8_t *)ECA_TAG, .size = sizeof(ECA_TAG)}; + + /* Derive the ECA signing key */ + n20_crypto_key_t eca_key = NULL; + n20_error_t err = + n20_derive_key(crypto_ctx, parent_secret, &eca_key, key_type, eca_salt, eca_tag); + if (err != n20_error_ok_e) { + return err; + } + + /* Sign the message */ + n20_crypto_gather_list_t message_gather = { + .count = 1, + .list = &message, + }; + + err = crypto_ctx->sign(crypto_ctx, eca_key, &message_gather, signature, signature_size); + + /* Clean up the ECA key */ + crypto_ctx->key_free(crypto_ctx, eca_key); + + return err; +} diff --git a/src/core/oid.c b/src/core/oid.c index a7b4bb1..3a9ae4b 100644 --- a/src/core/oid.c +++ b/src/core/oid.c @@ -15,6 +15,7 @@ */ #include +#include N20_ASN1_DEFINE_OID(OID_RSA_ENCRYPTION, 1, 2, 840, 113549, 1, 1, 1); N20_ASN1_DEFINE_OID(OID_SHA256_WITH_RSA_ENC, 1, 2, 840, 113549, 1, 1, 11); @@ -54,3 +55,19 @@ N20_ASN1_DEFINE_OID(OID_TCG_DICE_TCB_INFO, 2, 23, 133, 5, 4, 1); N20_ASN1_DEFINE_OID(OID_TCG_DICE_MULTI_TCB_INFO, 2, 23, 133, 5, 4, 5); N20_ASN1_DEFINE_OID(OID_TCG_DICE_UEID, 2, 23, 133, 5, 4, 4); N20_ASN1_DEFINE_OID(OID_TCG_DICE_TCB_FRESHNESS, 2, 23, 133, 5, 4, 11); + +bool n20_asn1_oid_equals(n20_asn1_object_identifier_t const *oid1, + n20_asn1_object_identifier_t const *oid2) { + if (oid1 == NULL || oid2 == NULL) { + return oid1 == oid2; // Both NULL is true, one NULL is false. + } + if (oid1->elem_count != oid2->elem_count) { + return false; + } + for (size_t i = 0; i < oid1->elem_count; ++i) { + if (oid1->elements[i] != oid2->elements[i]) { + return false; + } + } + return true; +} diff --git a/src/core/stream.c b/src/core/stream.c index 4bb77eb..46056b6 100644 --- a/src/core/stream.c +++ b/src/core/stream.c @@ -15,6 +15,7 @@ */ #include +#include #include void n20_stream_init(n20_stream_t *s, uint8_t *const buffer, size_t buffer_size) { @@ -81,3 +82,49 @@ void n20_stream_prepend(n20_stream_t *const s, uint8_t const *const src, size_t void n20_stream_put(n20_stream_t *const s, uint8_t const c) { n20_stream_prepend(s, &c, /*src_len=*/1); } + +void n20_istream_init(n20_istream_t *s, uint8_t const *buffer, size_t buffer_size) { + if (s == NULL) return; + s->begin = buffer; + s->size = buffer_size; + s->read_position = 0; + s->buffer_underrun = false; +} + +bool n20_istream_read(n20_istream_t *s, uint8_t *buffer, size_t buffer_size) { + if (s == NULL || s->buffer_underrun) return false; + size_t new_position = s->read_position + buffer_size; + if (new_position > s->size || new_position < s->read_position) { + s->read_position = s->size; + s->buffer_underrun = true; + return false; + } + memcpy(buffer, s->begin + s->read_position, buffer_size); + s->read_position = new_position; + return true; +} + +bool n20_istream_get(n20_istream_t *s, uint8_t *c) { + return n20_istream_read(s, c, /*buffer_size=*/1); +} + +uint8_t const *n20_istream_get_slice(n20_istream_t *s, size_t size) { + if (s == NULL || s->buffer_underrun) return NULL; + size_t new_position = s->read_position + size; + if (new_position > s->size || new_position < s->read_position) { + s->read_position = s->size; + s->buffer_underrun = true; + return NULL; + } + uint8_t const *slice = s->begin + s->read_position; + s->read_position = new_position; + return slice; +} + +bool n20_istream_has_buffer_underrun(n20_istream_t const *s) { + return (s == NULL) || s->buffer_underrun; +} + +size_t n20_istream_read_position(n20_istream_t const *s) { + return (s == NULL) ? 0 : s->read_position; +} diff --git a/src/core/test/asn1.cpp b/src/core/test/asn1.cpp index 29a60c7..157b3ae 100644 --- a/src/core/test/asn1.cpp +++ b/src/core/test/asn1.cpp @@ -296,7 +296,7 @@ TEST_P(IntegerTest, IntegerEncodingBigEndian) { n20_stream_t s; uint8_t buffer[128]; n20_stream_init(&s, buffer, sizeof(buffer)); - n20_asn1_integer(&s, bytes.data(), bytes.size(), false, two_complement, tag_info); + n20_asn1_integer(&s, {bytes.size(), bytes.data()}, false, two_complement, tag_info); ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); ASSERT_EQ(n20_stream_byte_count(&s), expected_patched.size()); std::vector got = std::vector( @@ -317,7 +317,7 @@ TEST_P(IntegerTest, IntegerEncodingLittleEndian) { n20_stream_t s; uint8_t buffer[128]; n20_stream_init(&s, buffer, sizeof(buffer)); - n20_asn1_integer(&s, bytes_reversed.data(), bytes.size(), true, two_complement, tag_info); + n20_asn1_integer(&s, {bytes.size(), bytes_reversed.data()}, true, two_complement, tag_info); ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); ASSERT_EQ(n20_stream_byte_count(&s), expected_patched.size()); std::vector got = std::vector( diff --git a/src/core/test/cbor.cpp b/src/core/test/cbor.cpp new file mode 100644 index 0000000..264da9f --- /dev/null +++ b/src/core/test/cbor.cpp @@ -0,0 +1,711 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +class CborIntegerTestFixture + : public testing::TestWithParam< + std::tuple, std::vector>> {}; + +INSTANTIATE_TEST_CASE_P( + CborIntegerTestInstance, + CborIntegerTestFixture, + testing::Values( + /* CBOR encoding encoding size boundary conditions. */ + std::tuple(UINT64_C(0), std::vector{0x00}), + std::tuple(UINT64_C(1), std::vector{0x01}), + std::tuple(UINT64_C(23), std::vector{0x17}), + std::tuple(UINT64_C(24), std::vector{0x18, 0x18}), + std::tuple(UINT64_C(255), std::vector{0x18, 0xff}), + std::tuple(UINT64_C(256), std::vector{0x19, 0x01, 0x00}), + std::tuple(UINT64_C(0xffff), std::vector{0x19, 0xff, 0xff}), + std::tuple(UINT64_C(0x10000), std::vector{0x1a, 0x00, 0x01, 0x00, 0x00}), + std::tuple(UINT64_C(0xffffffff), std::vector{0x1a, 0xff, 0xff, 0xff, 0xff}), + std::tuple(UINT64_C(0x100000000), + std::vector{0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}), + std::tuple(UINT64_C(0xffffffffffffffff), + std::vector{0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), + /* Repeat the same constants as above but force using the + * the indirection through n20_cbor_write_int. */ + std::tuple(INT64_C(0), std::vector{0x00}), + std::tuple(INT64_C(1), std::vector{0x01}), + std::tuple(INT64_C(23), std::vector{0x17}), + std::tuple(INT64_C(24), std::vector{0x18, 0x18}), + std::tuple(INT64_C(255), std::vector{0x18, 0xff}), + std::tuple(INT64_C(256), std::vector{0x19, 0x01, 0x00}), + std::tuple(INT64_C(0xffff), std::vector{0x19, 0xff, 0xff}), + std::tuple(INT64_C(0x10000), std::vector{0x1a, 0x00, 0x01, 0x00, 0x00}), + std::tuple(INT64_C(0xffffffff), std::vector{0x1a, 0xff, 0xff, 0xff, 0xff}), + std::tuple(INT64_C(0x100000000), + std::vector{0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}), + std::tuple(INT64_MAX, + std::vector{0x1b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), + std::tuple(INT64_C(-1), std::vector{0x20}), + std::tuple(INT64_C(-24), std::vector{0x37}), + std::tuple(INT64_C(-25), std::vector{0x38, 0x18}), + std::tuple(INT64_C(-256), std::vector{0x38, 0xff}), + std::tuple(INT64_C(-257), std::vector{0x39, 0x01, 0x00}), + std::tuple(INT64_C(-65536), std::vector{0x39, 0xff, 0xff}), + std::tuple(INT64_C(-65537), std::vector{0x3a, 0x00, 0x01, 0x00, 0x00}), + std::tuple(INT64_C(-4294967296), std::vector{0x3a, 0xff, 0xff, 0xff, 0xff}), + std::tuple(INT64_C(-4294967297), + std::vector{0x3b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}), + /* This is not the lowest integer that can be represented with + * CBOR major type 1, but it is the lowest that can be represented + * using 64 bits 2s-complement. And thus the limit of the + * integer encoding functions as of now. */ + std::tuple(INT64_MIN, + std::vector{0x3b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), + + /* Known OpenDICE label values. */ + std::tuple(INT64_C(-4670545), std::vector{0x3a, 0x00, 0x47, 0x44, 0x50}), + std::tuple(INT64_C(-4670546), std::vector{0x3a, 0x00, 0x47, 0x44, 0x51}), + std::tuple(INT64_C(-4670547), std::vector{0x3a, 0x00, 0x47, 0x44, 0x52}), + std::tuple(INT64_C(-4670548), std::vector{0x3a, 0x00, 0x47, 0x44, 0x53}), + std::tuple(INT64_C(-4670549), std::vector{0x3a, 0x00, 0x47, 0x44, 0x54}), + std::tuple(INT64_C(-4670550), std::vector{0x3a, 0x00, 0x47, 0x44, 0x55}), + std::tuple(INT64_C(-4670551), std::vector{0x3a, 0x00, 0x47, 0x44, 0x56}), + std::tuple(INT64_C(-4670552), std::vector{0x3a, 0x00, 0x47, 0x44, 0x57}), + std::tuple(INT64_C(-4670553), std::vector{0x3a, 0x00, 0x47, 0x44, 0x58}), + std::tuple(INT64_C(-4670554), std::vector{0x3a, 0x00, 0x47, 0x44, 0x59}))); + +TEST_P(CborIntegerTestFixture, CborIntegerTest) { + auto [integer, encoding] = GetParam(); + + uint8_t buffer[20]; + + n20_stream_t s; + n20_stream_init(&s, &buffer[0], sizeof(buffer)); + + if (std::holds_alternative(integer)) { + n20_cbor_write_uint(&s, std::get(integer)); + } else { + n20_cbor_write_int(&s, std::get(integer)); + } + + ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); + size_t bytes_written = n20_stream_byte_count(&s); + auto got_encoding = std::vector(n20_stream_data(&s), n20_stream_data(&s) + bytes_written); + ASSERT_EQ(got_encoding, encoding); +} + +TEST(CborTests, CborWriteNullTest) { + uint8_t buffer[20]; + + n20_stream_t s; + n20_stream_init(&s, &buffer[0], sizeof(buffer)); + + n20_cbor_write_null(&s); + + ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); + size_t bytes_written = n20_stream_byte_count(&s); + auto got_encoding = std::vector(n20_stream_data(&s), n20_stream_data(&s) + bytes_written); + + ASSERT_EQ(bytes_written, 1); + ASSERT_EQ(got_encoding, std::vector{0xf6}); +} + +TEST(CborTests, CborWriteBoolTest) { + uint8_t buffer[20]; + + n20_stream_t s; + n20_stream_init(&s, &buffer[0], sizeof(buffer)); + + n20_cbor_write_bool(&s, true); + + ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); + size_t bytes_written = n20_stream_byte_count(&s); + auto got_encoding = std::vector(n20_stream_data(&s), n20_stream_data(&s) + bytes_written); + + ASSERT_EQ(bytes_written, 1); + ASSERT_EQ(got_encoding, std::vector{0xf5}); + + n20_stream_init(&s, &buffer[0], sizeof(buffer)); + + n20_cbor_write_bool(&s, false); + + ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); + bytes_written = n20_stream_byte_count(&s); + got_encoding = std::vector(n20_stream_data(&s), n20_stream_data(&s) + bytes_written); + + ASSERT_EQ(bytes_written, 1); + ASSERT_EQ(got_encoding, std::vector{0xf4}); +} + +class CborTagTestFixture + : public testing::TestWithParam>> {}; + +INSTANTIATE_TEST_CASE_P( + CborTagTestInstance, + CborTagTestFixture, + testing::Values( + /* CBOR encoding encoding size boundary conditions. */ + std::tuple(UINT64_C(0), std::vector{0xc0}), + std::tuple(UINT64_C(1), std::vector{0xc1}), + std::tuple(UINT64_C(23), std::vector{0xd7}), + std::tuple(UINT64_C(24), std::vector{0xd8, 0x18}), + std::tuple(UINT64_C(255), std::vector{0xd8, 0xff}), + std::tuple(UINT64_C(256), std::vector{0xd9, 0x01, 0x00}), + std::tuple(UINT64_C(0xffff), std::vector{0xd9, 0xff, 0xff}), + std::tuple(UINT64_C(0x10000), std::vector{0xda, 0x00, 0x01, 0x00, 0x00}), + std::tuple(UINT64_C(0xffffffff), std::vector{0xda, 0xff, 0xff, 0xff, 0xff}), + std::tuple(UINT64_C(0x100000000), + std::vector{0xdb, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}), + std::tuple(UINT64_C(0xffffffffffffffff), + std::vector{0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}))); + +TEST_P(CborTagTestFixture, CborTagTest) { + auto [integer, encoding] = GetParam(); + + uint8_t buffer[20]; + + n20_stream_t s; + n20_stream_init(&s, &buffer[0], sizeof(buffer)); + + n20_cbor_write_tag(&s, integer); + + ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); + size_t bytes_written = n20_stream_byte_count(&s); + auto got_encoding = std::vector(n20_stream_data(&s), n20_stream_data(&s) + bytes_written); + ASSERT_EQ(got_encoding, encoding); +} + +TEST(CborTests, CborWriteByteStringTest) { + uint8_t buffer[20]; + + n20_stream_t s; + n20_stream_init(&s, &buffer[0], sizeof(buffer)); + + uint8_t bytes[] = {0x01, 0x02, 0x03, 0x04}; + n20_cbor_write_byte_string(&s, {.size = sizeof(bytes), .buffer = bytes}); + + ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); + size_t bytes_written = n20_stream_byte_count(&s); + auto got_encoding = std::vector(n20_stream_data(&s), n20_stream_data(&s) + bytes_written); + + auto want_encoding = std::vector{0x44, 0x01, 0x02, 0x03, 0x04}; + + ASSERT_EQ(bytes_written, 5); + ASSERT_EQ(got_encoding, want_encoding); +} + +TEST(CborTests, CborWriteStringTest) { + uint8_t buffer[20]; + + n20_stream_t s; + n20_stream_init(&s, &buffer[0], sizeof(buffer)); + + n20_string_slice_t str = N20_STR_C("Hello"); + n20_cbor_write_text_string(&s, str); + + ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); + size_t bytes_written = n20_stream_byte_count(&s); + auto got_encoding = std::vector(n20_stream_data(&s), n20_stream_data(&s) + bytes_written); + + auto want_encoding = std::vector{0x65, 0x48, 0x65, 0x6c, 0x6c, 0x6f}; + + ASSERT_EQ(bytes_written, 6); + ASSERT_EQ(got_encoding, want_encoding); +} + +class CborArrayHeaderTestFixture + : public testing::TestWithParam>> {}; + +INSTANTIATE_TEST_CASE_P( + CborArrayHeaderTestInstance, + CborArrayHeaderTestFixture, + testing::Values( + /* CBOR encoding encoding size boundary conditions. */ + std::tuple(UINT64_C(0), std::vector{0x80}), + std::tuple(UINT64_C(1), std::vector{0x81}), + std::tuple(UINT64_C(23), std::vector{0x97}), + std::tuple(UINT64_C(24), std::vector{0x98, 0x18}), + std::tuple(UINT64_C(255), std::vector{0x98, 0xff}), + std::tuple(UINT64_C(256), std::vector{0x99, 0x01, 0x00}), + std::tuple(UINT64_C(0xffff), std::vector{0x99, 0xff, 0xff}), + std::tuple(UINT64_C(0x10000), std::vector{0x9a, 0x00, 0x01, 0x00, 0x00}), + std::tuple(UINT64_C(0xffffffff), std::vector{0x9a, 0xff, 0xff, 0xff, 0xff}), + std::tuple(UINT64_C(0x100000000), + std::vector{0x9b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}), + std::tuple(UINT64_C(0xffffffffffffffff), + std::vector{0x9b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}))); + +TEST_P(CborArrayHeaderTestFixture, CborArrayHeaderTest) { + auto [integer, encoding] = GetParam(); + + uint8_t buffer[20]; + + n20_stream_t s; + n20_stream_init(&s, &buffer[0], sizeof(buffer)); + + n20_cbor_write_array_header(&s, integer); + + ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); + size_t bytes_written = n20_stream_byte_count(&s); + auto got_encoding = std::vector(n20_stream_data(&s), n20_stream_data(&s) + bytes_written); + ASSERT_EQ(got_encoding, encoding); +} + +class CborMapHeaderTestFixture + : public testing::TestWithParam>> {}; + +INSTANTIATE_TEST_CASE_P( + CborMapHeaderTestInstance, + CborMapHeaderTestFixture, + testing::Values( + /* CBOR encoding encoding size boundary conditions. */ + std::tuple(UINT64_C(0), std::vector{0xa0}), + std::tuple(UINT64_C(1), std::vector{0xa1}), + std::tuple(UINT64_C(23), std::vector{0xb7}), + std::tuple(UINT64_C(24), std::vector{0xb8, 0x18}), + std::tuple(UINT64_C(255), std::vector{0xb8, 0xff}), + std::tuple(UINT64_C(256), std::vector{0xb9, 0x01, 0x00}), + std::tuple(UINT64_C(0xffff), std::vector{0xb9, 0xff, 0xff}), + std::tuple(UINT64_C(0x10000), std::vector{0xba, 0x00, 0x01, 0x00, 0x00}), + std::tuple(UINT64_C(0xffffffff), std::vector{0xba, 0xff, 0xff, 0xff, 0xff}), + std::tuple(UINT64_C(0x100000000), + std::vector{0xbb, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}), + std::tuple(UINT64_C(0xffffffffffffffff), + std::vector{0xbb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}))); + +TEST_P(CborMapHeaderTestFixture, CborMapHeaderTest) { + auto [integer, encoding] = GetParam(); + + uint8_t buffer[20]; + + n20_stream_t s; + n20_stream_init(&s, &buffer[0], sizeof(buffer)); + + n20_cbor_write_map_header(&s, integer); + + ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); + size_t bytes_written = n20_stream_byte_count(&s); + auto got_encoding = std::vector(n20_stream_data(&s), n20_stream_data(&s) + bytes_written); + ASSERT_EQ(got_encoding, encoding); +} + +class COSEExtOpenDiceInputTest + : public testing::TestWithParam>, + std::optional>, + std::optional>, + std::optional>, + std::optional>, + std::optional>, + n20_cwt_open_dice_modes_t const, + std::optional, + std::vector const>> {}; + +std::vector const CODE_HASH = { + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, +}; + +std::vector const CODE_DESCRIPTOR = {0x63, 0x6f, 0x64, 0x65}; + +std::vector const CONFIGURATION_INLINE = { + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, +}; + +std::vector const CONFIGURATION_HASH = { + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, +}; + +std::vector const CONFIGURATION_DESCRIPTOR = {0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67}; + +std::vector const AUTHORITY_HASH = { + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, +}; + +std::vector const AUTHORITY_DESCRIPTOR = {0x61, 0x75, 0x74, 0x68}; + +// clang-format off +std::vector const EXTENSION_WITH_INLINE_CONFIGURATION = { + // Map header with 10 pairs. + 0xaa, + // issuer (1). + 0x01, + // issuer value + 0x78, 0x28, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35, + 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x30, 0x61, 0x30, 0x62, + 0x30, 0x63, 0x30, 0x64, 0x30, 0x65, 0x30, 0x66, 0x31, 0x30, 0x31, 0x31, + 0x31, 0x32, 0x31, 0x33, 0x31, 0x34, + // subject (2) + 0x02, + // subject value + 0x78, 0x28, 0x31, 0x35, 0x31, 0x36, 0x31, 0x37, 0x31, 0x38, 0x31, 0x39, + 0x31, 0x61, 0x31, 0x62, 0x31, 0x63, 0x31, 0x64, 0x31, 0x65, 0x31, 0x66, + 0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33, 0x32, 0x34, 0x32, 0x35, + 0x32, 0x36, 0x32, 0x37, 0x32, 0x38, + // code hash (-4670545) + 0x3a, 0x00, 0x47, 0x44, 0x50, + // code hash value + 0x58, 0x40, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, + // code descriptor (-4670546) + 0x3a, 0x00, 0x47, 0x44, 0x51, + // code descriptor value ("code") + 0x44, 0x63, 0x6f, 0x64, 0x65, + // configuration descriptor (-4670548) + 0x3a, 0x00, 0x47, 0x44, 0x53, + 0x58, 0x40, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + // authority hash (-4670549) + 0x3a, 0x00, 0x47, 0x44, 0x54, + // authority hash value + 0x58, 0x40, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + // authority descriptor (-4670550) + 0x3a, 0x00, 0x47, 0x44, 0x55, + // authority descriptor value ("auth") + 0x44, 0x61, 0x75, 0x74, 0x68, + // mode (-4670551) + 0x3a, 0x00, 0x47, 0x44, 0x56, + // mode value + 0x41, 0x01, + // Subject public key (-4670552) + 0x3a, 0x00, 0x47, 0x44, 0x57, + // Subject public key value + // byte string header + 0x58, 0x51, + // map header with 6 pairs + 0xa6, + // Key Type (1) + 0x01, + // Key type value (EC2) + 0x02, + // Key Algorithm (3) + 0x03, + // Key Algorithm value (ESP256: -9) + 0x28, + // Cose Key Ops (4) + 0x04, + // Cose Key Ops value (Sign: 1, Verify: 2) + 0x82, 0x01, 0x02, + // Curve type (-1) + 0x20, + // Curve type value (P256: 1) + 0x01, + // X coordinate (-2) + 0x21, + // X coordinate value + 0x58, 0x20, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + // Y coordinate (-3) + 0x22, + // Y coordinate value + 0x58, 0x20, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x39, 0x38, 0x37, 0x36, + 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, + 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, + + // Key Usage (-4670553) + 0x3a, 0x00, 0x47, 0x44, 0x58, + // Key Usage value + 0x40 +}; + +std::vector const EXTENSION_WITH_CONFIGURATION_DESCRIPTOR = { + // Map header with 11 pairs. + 0xab, + // issuer (1). + 0x01, + // issuer value + 0x78, 0x28, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35, + 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x30, 0x61, 0x30, 0x62, + 0x30, 0x63, 0x30, 0x64, 0x30, 0x65, 0x30, 0x66, 0x31, 0x30, 0x31, 0x31, + 0x31, 0x32, 0x31, 0x33, 0x31, 0x34, + // subject (2) + 0x02, + // subject value + 0x78, 0x28, 0x31, 0x35, 0x31, 0x36, 0x31, 0x37, 0x31, 0x38, 0x31, 0x39, + 0x31, 0x61, 0x31, 0x62, 0x31, 0x63, 0x31, 0x64, 0x31, 0x65, 0x31, 0x66, + 0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33, 0x32, 0x34, 0x32, 0x35, + 0x32, 0x36, 0x32, 0x37, 0x32, 0x38, + // code hash (-4670545) + 0x3a, 0x00, 0x47, 0x44, 0x50, + // code hash value + 0x58, 0x40, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, + // code descriptor (-4670546) + 0x3a, 0x00, 0x47, 0x44, 0x51, + // code descriptor value ("code") + 0x44, 0x63, 0x6f, 0x64, 0x65, + // configuration hash (-4670547) + 0x3a, 0x00, 0x47, 0x44, 0x52, + // configuration hash value + 0x58, 0x40, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, + // configuration descriptor (-4670548) + 0x3a, 0x00, 0x47, 0x44, 0x53, + // configuration descriptor value ("config") + 0x46, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + // authority hash (-4670549) + 0x3a, 0x00, 0x47, 0x44, 0x54, + // authority hash value + 0x58, 0x40, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + // authority descriptor (-4670550) + 0x3a, 0x00, 0x47, 0x44, 0x55, + // authority descriptor value ("auth") + 0x44, 0x61, 0x75, 0x74, 0x68, + // mode (-4670551) + 0x3a, 0x00, 0x47, 0x44, 0x56, + // mode value + 0x41, 0x01, + // Subject public key (-4670552) + 0x3a, 0x00, 0x47, 0x44, 0x57, + // Subject public key value + // byte string header + 0x58, 0x51, + // map header with 6 pairs + 0xa6, + // Key Type (1) + 0x01, + // Key type value (EC2) + 0x02, + // Key Algorithm (3) + 0x03, + // Key Algorithm value (ESP256: -9) + 0x28, + // Cose Key Ops (4) + 0x04, + // Cose Key Ops value (Sign: 1, Verify: 2) + 0x82, 0x01, 0x02, + // Curve type (-1) + 0x20, + // Curve type value (P256: 1) + 0x01, + // X coordinate (-2) + 0x21, + // X coordinate value + 0x58, 0x20, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + // Y coordinate (-3) + 0x22, + // Y coordinate value + 0x58, 0x20, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x39, 0x38, 0x37, 0x36, + 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, + 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, + + // Key Usage (-4670553) + 0x3a, 0x00, 0x47, 0x44, 0x58, + // Key Usage value + 0x40 +}; + +std::vector const EXTENSION_WITHOUT_OPTIONALS = { + // Map header with 5 pairs. + 0xa5, + // issuer (1). + 0x01, + // issuer value + 0x78, 0x28, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35, + 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x30, 0x61, 0x30, 0x62, + 0x30, 0x63, 0x30, 0x64, 0x30, 0x65, 0x30, 0x66, 0x31, 0x30, 0x31, 0x31, + 0x31, 0x32, 0x31, 0x33, 0x31, 0x34, + // subject (2) + 0x02, + // subject value + 0x78, 0x28, 0x31, 0x35, 0x31, 0x36, 0x31, 0x37, 0x31, 0x38, 0x31, 0x39, + 0x31, 0x61, 0x31, 0x62, 0x31, 0x63, 0x31, 0x64, 0x31, 0x65, 0x31, 0x66, + 0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33, 0x32, 0x34, 0x32, 0x35, + 0x32, 0x36, 0x32, 0x37, 0x32, 0x38, + // mode (-4670551) + 0x3a, 0x00, 0x47, 0x44, 0x56, + // mode value + 0x41, 0x00, + // Subject public key (-4670552) + 0x3a, 0x00, 0x47, 0x44, 0x57, + // Subject public key value + // byte string header + 0x58, 0x51, + // map header with 6 pairs + 0xa6, + // Key Type (1) + 0x01, + // Key type value (EC2) + 0x02, + // Key Algorithm (3) + 0x03, + // Key Algorithm value (ESP256: -9) + 0x28, + // Cose Key Ops (4) + 0x04, + // Cose Key Ops value (Sign: 1, Verify: 2) + 0x82, 0x01, 0x02, + // Curve type (-1) + 0x20, + // Curve type value (P256: 1) + 0x01, + // X coordinate (-2) + 0x21, + // X coordinate value + 0x58, 0x20, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + // Y coordinate (-3) + 0x22, + // Y coordinate value + 0x58, 0x20, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x39, 0x38, 0x37, 0x36, + 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, + 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, + + // Key Usage (-4670553) + 0x3a, 0x00, 0x47, 0x44, 0x58, + // Key Usage value + 0x40 +}; +// clang-format on + +std::string const AURORA_OPEN_DICE_PROFILE = "Aurora OpenDICE Profile"; + +INSTANTIATE_TEST_CASE_P(OpenDiceInputEncoding, + COSEExtOpenDiceInputTest, + testing::Values(std::tuple(CODE_HASH, + CODE_DESCRIPTOR, + std::nullopt, + CONFIGURATION_INLINE, + AUTHORITY_HASH, + AUTHORITY_DESCRIPTOR, + n20_cwt_open_dice_normal_e, + AURORA_OPEN_DICE_PROFILE, + EXTENSION_WITH_INLINE_CONFIGURATION), + std::tuple(CODE_HASH, + CODE_DESCRIPTOR, + CONFIGURATION_HASH, + CONFIGURATION_DESCRIPTOR, + AUTHORITY_HASH, + AUTHORITY_DESCRIPTOR, + n20_cwt_open_dice_normal_e, + AURORA_OPEN_DICE_PROFILE, + EXTENSION_WITH_CONFIGURATION_DESCRIPTOR), + std::tuple(std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + n20_cwt_open_dice_not_configured_e, + std::nullopt, + EXTENSION_WITHOUT_OPTIONALS))); + +template +inline static n20_slice_t v2slice(T const &v) { + if (v.has_value()) { + return n20_slice_t{v->size(), v->data()}; + } + return N20_SLICE_NULL; +} + +TEST_P(COSEExtOpenDiceInputTest, OpenDiceInputEncoding) { + auto [optional_code_hash, + optional_code_descriptor, + optional_configuration_hash, + optional_configuration_descriptor, + optional_authority_hash, + optional_authority_descriptor, + mode, + optional_profile, + expected] = GetParam(); + + std::vector issuer_data = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14}; + std::vector subject_data = {0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, + 0x23, 0x24, 0x25, 0x26, 0x27, 0x28}; + + n20_open_dice_cwt_t cwt = {}; + cwt.key_usage[0] = 0x01; + cwt.key_usage[1] = 0x00; + cwt.subject_public_key.algorithm_id = -9; // Example algorithm ID + n20_set_cose_key_ops(&cwt.subject_public_key.key_ops, n20_cose_key_op_sign_e); + n20_set_cose_key_ops(&cwt.subject_public_key.key_ops, n20_cose_key_op_verify_e); + cwt.subject_public_key.x = {.size = 32, + .buffer = (uint8_t *)"0123456789abcdef0123456789abcdef"}; + cwt.subject_public_key.y = {.size = 32, + .buffer = (uint8_t *)"fedcba9876543210fedcba9876543210"}; + cwt.subject_public_key.d = {.size = 0, .buffer = nullptr}; // No private key + cwt.issuer = {.size = 20, .buffer = (uint8_t *)issuer_data.data()}; + cwt.subject = {.size = 20, .buffer = (uint8_t *)subject_data.data()}; + cwt.code_hash = v2slice(optional_code_hash); + cwt.code_descriptor = v2slice(optional_code_descriptor); + cwt.configuration_hash = v2slice(optional_configuration_hash); + cwt.configuration_descriptor = v2slice(optional_configuration_descriptor); + cwt.authority_hash = v2slice(optional_authority_hash); + cwt.authority_descriptor = v2slice(optional_authority_descriptor); + cwt.mode = mode; + + n20_stream_t s; + n20_stream_init(&s, nullptr, 0); + n20_open_dice_cwt_write(&s, &cwt); + ASSERT_FALSE(n20_stream_has_write_position_overflow(&s)); + size_t bytes_written = n20_stream_byte_count(&s); + std::vector buffer(bytes_written); + n20_stream_init(&s, buffer.data(), buffer.size()); + n20_open_dice_cwt_write(&s, &cwt); + + ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); + size_t bytes_written_again = n20_stream_byte_count(&s); + ASSERT_EQ(bytes_written_again, bytes_written); + ASSERT_EQ(buffer, expected) << "Expected: " << hex(expected) << ", Got: " << hex(buffer); +} diff --git a/src/core/test/functionality.cpp b/src/core/test/functionality.cpp new file mode 100644 index 0000000..d388e52 --- /dev/null +++ b/src/core/test/functionality.cpp @@ -0,0 +1,260 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "nat20/stream.h" +#include "nat20/types.h" +#include "test_vectors.h" + +uint8_t const test_cdi[] = { + 0xa4, 0x32, 0xb4, 0x34, 0x94, 0x4f, 0x59, 0xcf, 0xdb, 0xf7, 0x04, 0x46, 0x95, 0x9c, 0xee, 0x08, + 0x7f, 0x6b, 0x87, 0x60, 0xd8, 0xef, 0xb4, 0xcf, 0xed, 0xf2, 0xf6, 0x29, 0x33, 0x88, 0xf0, 0x64, + 0xbb, 0xe0, 0x21, 0xf5, 0x87, 0x1c, 0x6c, 0x0c, 0x30, 0x2b, 0x32, 0x4f, 0x4c, 0x44, 0xd1, 0x26, + 0xca, 0x35, 0x6b, 0xc3, 0xc5, 0x0e, 0x17, 0xc6, 0x21, 0xad, 0x1d, 0x32, 0xbd, 0x6e, 0x35, 0x08}; + +/* Sha256 digest of "test code". */ +std::vector test_code_hash = { + 0xcf, 0x48, 0x17, 0xd9, 0x79, 0x3e, 0x92, 0xa0, 0xb0, 0x0b, 0x0a, 0xfc, 0x24, 0x13, 0x54, 0xf9, + 0xa6, 0x49, 0x86, 0x6d, 0xa1, 0xd8, 0x83, 0xc6, 0x04, 0xc0, 0x58, 0x5e, 0xf4, 0x12, 0x9b, 0x82}; + +/* + * This function creates an example code descriptor. + * It is an ASN1 sequence of a name string a version string, + * both encoded as UTF-8 strings and a digest encoded as octetstring. + */ +std::vector make_code_descriptor(std::string const& name, + std::string const& version, + std::vector const& code_hash) { + n20_stream_t s; + uint8_t buffer[1024]; + n20_stream_init(&s, buffer, sizeof(buffer)); + + auto context = std::make_tuple(name, version, code_hash); + + auto cb = [](n20_stream_t* s, void* ctx) -> void { + auto [name, version, code_hash] = *reinterpret_cast(ctx); + n20_slice_t code_hash_slice = {.size = code_hash.size(), + .buffer = (uint8_t*)code_hash.data()}; + + n20_string_slice_t version_slice = {.size = version.length(), .buffer = version.c_str()}; + n20_string_slice_t name_slice = {.size = name.length(), .buffer = name.c_str()}; + n20_asn1_octetstring(s, &code_hash_slice, n20_asn1_tag_info_no_override()); + n20_asn1_utf8_string(s, &version_slice, n20_asn1_tag_info_no_override()); + n20_asn1_utf8_string(s, &name_slice, n20_asn1_tag_info_no_override()); + }; + + n20_asn1_sequence(&s, cb, &context, n20_asn1_tag_info_no_override()); + + EXPECT_FALSE(n20_stream_has_buffer_overflow(&s)); + + return std::vector(n20_stream_data(&s), + n20_stream_data(&s) + n20_stream_byte_count(&s)); +} + +/* + * This function creates an example configuration descriptor. + * The configuration is made up of a frobnication level integer + * a hardcore mode boolean and a preferred pronouns string + * encoded a an ASN1 sequence of integer, boolean, and UTF-8 string. + */ +std::vector make_configuration_descriptor(int frobnication_level, + bool hardcore_mode, + std::string const& preferred_pronouns) { + n20_stream_t s; + uint8_t buffer[1024]; + n20_stream_init(&s, buffer, sizeof(buffer)); + + auto context = std::make_tuple(frobnication_level, hardcore_mode, preferred_pronouns); + + auto cb = [](n20_stream_t* s, void* ctx) -> void { + auto [frobnication_level, hardcore_mode, preferred_pronouns] = + *reinterpret_cast(ctx); + n20_string_slice_t preferred_pronouns_slice = { + .size = preferred_pronouns.length(), + .buffer = preferred_pronouns.c_str(), + }; + n20_asn1_utf8_string(s, &preferred_pronouns_slice, n20_asn1_tag_info_no_override()); + n20_asn1_boolean(s, hardcore_mode, n20_asn1_tag_info_no_override()); + n20_asn1_int64(s, (int64_t)frobnication_level, n20_asn1_tag_info_no_override()); + }; + + n20_asn1_sequence(&s, cb, &context, n20_asn1_tag_info_no_override()); + + EXPECT_FALSE(n20_stream_has_buffer_overflow(&s)); + + return std::vector(n20_stream_data(&s), + n20_stream_data(&s) + n20_stream_byte_count(&s)); +} + +n20_slice_t slice_of_vec(std::vector const& vec) { + return n20_slice_t{.size = vec.size(), .buffer = vec.data()}; +} + +// Test fixture +class FunctionalityTest : public ::testing::TestWithParam>> { + protected: + n20_crypto_context_t* crypto_ctx; + + void SetUp() override { EXPECT_EQ(n20_error_ok_e, n20_crypto_boringssl_open(&crypto_ctx)); } + + void TearDown() override { EXPECT_EQ(n20_error_ok_e, n20_crypto_boringssl_close(crypto_ctx)); } + + n20_crypto_key_t GetCdi() { + n20_slice_t cdi_slice = {sizeof(test_cdi), const_cast(&test_cdi[0])}; + n20_crypto_key_t cdi_key = nullptr; + EXPECT_EQ(n20_error_ok_e, + n20_crypto_boringssl_make_secret(crypto_ctx, &cdi_slice, &cdi_key)); + return cdi_key; + } +}; + +INSTANTIATE_TEST_SUITE_P(FunctionalityTestInstance, + FunctionalityTest, + ::testing::ValuesIn(attestationCertificateTestVectors), + [](testing::TestParamInfo const& info) { + return std::get<0>(info.param); + }); + +TEST_P(FunctionalityTest, TestOpenDiceAttestationCertificate) { + auto [_, parent_key_type, key_type, certificate_format, want_cert] = GetParam(); + + auto key_deleter = [this](void* key) { crypto_ctx->key_free(crypto_ctx, key); }; + + n20_crypto_key_t parent_secret = this->GetCdi(); + auto parent_secret_guard = + std::unique_ptr(parent_secret, key_deleter); + + n20_crypto_key_t parent_attestation_key = nullptr; + ASSERT_NE(parent_secret, nullptr); + auto parent_attestation_key_guard = + std::unique_ptr(parent_attestation_key, key_deleter); + + ASSERT_EQ(n20_error_ok_e, + n20_derive_attestation_key( + crypto_ctx, parent_secret, &parent_attestation_key, parent_key_type)); + ASSERT_NE(parent_attestation_key, nullptr); + + auto code_descriptor = make_code_descriptor("Test DICE", "1.0", test_code_hash); + + n20_slice_t code_descriptor_slice = {.size = code_descriptor.size(), + .buffer = const_cast(code_descriptor.data())}; + + n20_crypto_gather_list_t code_descriptor_gather_list = {1, &code_descriptor_slice}; + + std::vector code_descriptor_hash(32); + size_t code_descriptor_hash_size = code_descriptor_hash.size(); + ASSERT_EQ(n20_error_ok_e, + crypto_ctx->digest_ctx.digest(&crypto_ctx->digest_ctx, + n20_crypto_digest_algorithm_sha2_256_e, + &code_descriptor_gather_list, + 1, + code_descriptor_hash.data(), + &code_descriptor_hash_size)); + + auto configuration_descriptor = make_configuration_descriptor(1, true, "they/them"); + n20_slice_t configuration_descriptor_slice = { + .size = configuration_descriptor.size(), + .buffer = const_cast(configuration_descriptor.data()), + }; + n20_crypto_gather_list_t configuration_descriptor_gather_list = { + 1, &configuration_descriptor_slice}; + + std::vector configuration_descriptor_hash(32); + size_t configuration_descriptor_hash_size = configuration_descriptor_hash.size(); + ASSERT_EQ(n20_error_ok_e, + crypto_ctx->digest_ctx.digest(&crypto_ctx->digest_ctx, + n20_crypto_digest_algorithm_sha2_256_e, + &configuration_descriptor_gather_list, + 1, + configuration_descriptor_hash.data(), + &configuration_descriptor_hash_size)); + ASSERT_EQ(configuration_descriptor_hash_size, 32); + + n20_open_dice_input_t context = { + .code_hash = slice_of_vec(code_descriptor_hash), + .code_descriptor = slice_of_vec(code_descriptor), + .configuration_hash = slice_of_vec(configuration_descriptor_hash), + .configuration_descriptor = slice_of_vec(configuration_descriptor), + .authority_hash = {0, nullptr}, + .authority_descriptor = {0, nullptr}, + .mode = n20_open_dice_mode_debug_e, + .profile_name = N20_STR_C("OpenDICE"), + }; + uint8_t attestation_certificate[2048] = {}; + size_t attestation_certificate_size = sizeof(attestation_certificate); + ASSERT_EQ(n20_error_ok_e, + n20_opendice_attestation_key_and_certificate(crypto_ctx, + parent_secret, + parent_attestation_key, + parent_key_type, + key_type, + &context, + certificate_format, + attestation_certificate, + &attestation_certificate_size)) + << "Expected buffer size: " << attestation_certificate_size; + + auto got_cert = std::vector( + &attestation_certificate[sizeof(attestation_certificate) - attestation_certificate_size], + &attestation_certificate[sizeof(attestation_certificate)]); + + // Currently we are using ECDSA with random nonce (k) for signing with + // secp256r1 and secp384r1 keys, so the signature will not match the + // one in the test vectors. + // We can only compare the certificate content without the signature + // in these cases. For ed25519, the signature is deterministic + // and will match the one in the test vectors. + auto comp_want_cert = want_cert; + auto comp_got_cert = got_cert; + uint8_t signature_offset = 0; + switch (parent_key_type) { + case n20_crypto_key_type_secp256r1_e: + signature_offset = 64; + break; + case n20_crypto_key_type_secp384r1_e: + signature_offset = 96; + break; + default: + break; + } + comp_want_cert.resize(want_cert.size() >= signature_offset ? want_cert.size() - signature_offset + : 0); + comp_got_cert.resize(got_cert.size() >= signature_offset ? got_cert.size() - signature_offset + : 0); + + ASSERT_EQ(comp_want_cert, comp_got_cert) << "\nExpected: \n" + << hexdump(want_cert) << "\n Got: \n" + << hexdump(got_cert); + ASSERT_EQ(want_cert.size(), got_cert.size()); + // Verify the certificate +} diff --git a/src/core/test/oid.cpp b/src/core/test/oid.cpp new file mode 100644 index 0000000..f3d7029 --- /dev/null +++ b/src/core/test/oid.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include +#include + +TEST(N20OidEqualsTest, BothNull) { EXPECT_TRUE(n20_asn1_oid_equals(nullptr, nullptr)); } + +TEST(N20OidEqualsTest, OneNull) { + n20_asn1_object_identifier_t oid = {2, {1, 2}}; + EXPECT_FALSE(n20_asn1_oid_equals(&oid, nullptr)); + EXPECT_FALSE(n20_asn1_oid_equals(nullptr, &oid)); +} + +TEST(N20OidEqualsTest, EqualOids) { + n20_asn1_object_identifier_t oid1 = {3, {1, 2, 3}}; + n20_asn1_object_identifier_t oid2 = {3, {1, 2, 3}}; + EXPECT_TRUE(n20_asn1_oid_equals(&oid1, &oid2)); +} + +TEST(N20OidEqualsTest, DifferentElementCount) { + n20_asn1_object_identifier_t oid1 = {2, {1, 2}}; + n20_asn1_object_identifier_t oid2 = {3, {1, 2, 3}}; + EXPECT_FALSE(n20_asn1_oid_equals(&oid1, &oid2)); +} + +TEST(N20OidEqualsTest, DifferentElements) { + n20_asn1_object_identifier_t oid1 = {3, {1, 2, 3}}; + n20_asn1_object_identifier_t oid2 = {3, {1, 2, 4}}; + EXPECT_FALSE(n20_asn1_oid_equals(&oid1, &oid2)); +} + +TEST(N20OidEqualsTest, LibraryDefinedOidsEqual) { + // OID_RSA_ENCRYPTION is defined in the library and should equal itself + EXPECT_TRUE(n20_asn1_oid_equals(&OID_RSA_ENCRYPTION, &OID_RSA_ENCRYPTION)); +} + +TEST(N20OidEqualsTest, LibraryDefinedOidsNotEqual) { + // OID_RSA_ENCRYPTION and OID_SHA256_WITH_RSA_ENC are different + EXPECT_FALSE(n20_asn1_oid_equals(&OID_RSA_ENCRYPTION, &OID_SHA256_WITH_RSA_ENC)); +} diff --git a/src/core/test/test_data/opendice_attestation_test_vectors.txt b/src/core/test/test_data/opendice_attestation_test_vectors.txt new file mode 100644 index 0000000..9877c49 --- /dev/null +++ b/src/core/test/test_data/opendice_attestation_test_vectors.txt @@ -0,0 +1,66 @@ + +Name: X509_ed25519_ed25519 +pKeyType: ed25519 +keyType: ed25519 +certFormat: X509 +wantCert: 308202dd3082028fa0030201020214025ddfac4887ad5f1ef302a19befb23281d3fcef300506032b6570308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220303131302f06035504051328313836646639356663356165323863633336613164323239346365633835656236376133653261643022180f31393730303130313030303030305a180f39393939313233313233353935395a308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220313131302f0603550405132830323564646661633438383761643566316566333032613139626566623233323831643366636566302a300506032b65700321003302a247e8b2afb784d531789b6f2c0385e7c32468e6900bee18b4b0902a8d6f811500186df95fc5ae28cc36a1d2294cec85eb67a3e2ad821500025ddfac4887ad5f1ef302a19befb23281d3fcefa381e43081e13081bd060a2b06010401d6790201180101ff0481ab3081a8a0220420cedb7b981c5c16a9145f3cbb6931fbc9ab425208d5ab8db82d4d6fc1ff52fa98a136043430320c095465737420444943450c03312e300420cf4817d9793e92a0b00b0afc241354f9a649866da1d883c604c0585ef4129b82a2220420181130b80127a65afcdde4c0f90caf6e691acfc7199e83c03e494e8fc854c1afa315041330110201010101ff0c09746865792f7468656da603020102a70a0c084f70656e44494345300e0603551d0f0101ff040403020204300f0603551d130101ff040530030101ff300506032b6570034100557fa9374ed7657e50c51894de0e7efcd014432f8e4c2cff05f21189b59d23489ff488fa0427d301910af1774e4d7112aa95333d3df88702c3cf6559ba18ac01 + +Name: X509_secp256r1_ed25519 +pKeyType: ed25519 +keyType: secp256r1 +certFormat: X509 +wantCert: 3082030c308202bea003020102021435614105e4c8b5376ae45e399a1bfc0fead71b2d300506032b6570308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220303131302f06035504051328313836646639356663356165323863633336613164323239346365633835656236376133653261643022180f31393730303130313030303030305a180f39393939313233313233353935395a308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220313131302f06035504051328333536313431303565346338623533373661653435653339396131626663306665616437316232643059301306072a8648ce3d020106082a8648ce3d03010703420004c714ef7acbf4384e8669497f43fdf74de81f6d28c0bdbe2604550bc88271b79baff944903d729f37b0d7a968dd32fb9adad961a25942e2267b4b2f4e2bc62adb811500186df95fc5ae28cc36a1d2294cec85eb67a3e2ad82150035614105e4c8b5376ae45e399a1bfc0fead71b2da381e43081e13081bd060a2b06010401d6790201180101ff0481ab3081a8a0220420cedb7b981c5c16a9145f3cbb6931fbc9ab425208d5ab8db82d4d6fc1ff52fa98a136043430320c095465737420444943450c03312e300420cf4817d9793e92a0b00b0afc241354f9a649866da1d883c604c0585ef4129b82a2220420181130b80127a65afcdde4c0f90caf6e691acfc7199e83c03e494e8fc854c1afa315041330110201010101ff0c09746865792f7468656da603020102a70a0c084f70656e44494345300e0603551d0f0101ff040403020204300f0603551d130101ff040530030101ff300506032b6570034100cf0b991ccacb399b0c0da382c5a69a11968609a1bf703082fac78217e12accc692c206fc759a707f3c422cf58756e116930b6a034f87503919e92b72ea5a4608 + +Name: X509_secp384r1_ed25519 +pKeyType: ed25519 +keyType: secp384r1 +certFormat: X509 +wantCert: 30820329308202dba0030201020214213af831ec3cb8f69d86c8ea391de83118358f00300506032b6570308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220303131302f06035504051328313836646639356663356165323863633336613164323239346365633835656236376133653261643022180f31393730303130313030303030305a180f39393939313233313233353935395a308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220313131302f06035504051328323133616638333165633363623866363964383663386561333931646538333131383335386630303076301006072a8648ce3d020106052b810400220362000449d1420f3824737ccb79af981685ee7565fa9384ce7ee0acc920d459fa17463e08c960edab2df330e6ab9955fd2587731077a408f88050165dba687c28f6f92cb6d898a6e95392a82e13692f3ef1a0e2029a06d961bf5a2efca7765787b21dcb811500186df95fc5ae28cc36a1d2294cec85eb67a3e2ad821500213af831ec3cb8f69d86c8ea391de83118358f00a381e43081e13081bd060a2b06010401d6790201180101ff0481ab3081a8a0220420cedb7b981c5c16a9145f3cbb6931fbc9ab425208d5ab8db82d4d6fc1ff52fa98a136043430320c095465737420444943450c03312e300420cf4817d9793e92a0b00b0afc241354f9a649866da1d883c604c0585ef4129b82a2220420181130b80127a65afcdde4c0f90caf6e691acfc7199e83c03e494e8fc854c1afa315041330110201010101ff0c09746865792f7468656da603020102a70a0c084f70656e44494345300e0603551d0f0101ff040403020204300f0603551d130101ff040530030101ff300506032b657003410059c4b79ab1766e3469783cf71e8aff3daebe28d5eb09e2dfac6782fd8709b1ee3b79ff5b560e80698e8afcf1eb9d75d639fc43ddb8c0377ce67fb377d96f200d + +Name: X509_ed25519_secp256r1 +pKeyType: secp256r1 +keyType: ed25519 +certFormat: X509 +wantCert: 308202e730820294a0030201020214025ddfac4887ad5f1ef302a19befb23281d3fcef300a06082a8648ce3d040302308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220303131302f06035504051328356262366236313730643034356163326166336334366531663833393738306139643363383337663022180f31393730303130313030303030305a180f39393939313233313233353935395a308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220313131302f0603550405132830323564646661633438383761643566316566333032613139626566623233323831643366636566302a300506032b65700321003302a247e8b2afb784d531789b6f2c0385e7c32468e6900bee18b4b0902a8d6f8115005bb6b6170d045ac2af3c46e1f839780a9d3c837f821500025ddfac4887ad5f1ef302a19befb23281d3fcefa381e43081e13081bd060a2b06010401d6790201180101ff0481ab3081a8a0220420cedb7b981c5c16a9145f3cbb6931fbc9ab425208d5ab8db82d4d6fc1ff52fa98a136043430320c095465737420444943450c03312e300420cf4817d9793e92a0b00b0afc241354f9a649866da1d883c604c0585ef4129b82a2220420181130b80127a65afcdde4c0f90caf6e691acfc7199e83c03e494e8fc854c1afa315041330110201010101ff0c09746865792f7468656da603020102a70a0c084f70656e44494345300e0603551d0f0101ff040403020204300f0603551d130101ff040530030101ff300a06082a8648ce3d040302034100482f0754ca10a3bdad324110d75c3ad69056144b6d69f3778402fe74b2b45ae4f39ed330f1ab84f7eb1d142c9d98bb77c61dc9a25e158aba7cff832a62700dff + +Name: X509_secp256r1_secp256r1 +pKeyType: secp256r1 +keyType: secp256r1 +certFormat: X509 +wantCert: 30820316308202c3a003020102021435614105e4c8b5376ae45e399a1bfc0fead71b2d300a06082a8648ce3d040302308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220303131302f06035504051328356262366236313730643034356163326166336334366531663833393738306139643363383337663022180f31393730303130313030303030305a180f39393939313233313233353935395a308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220313131302f06035504051328333536313431303565346338623533373661653435653339396131626663306665616437316232643059301306072a8648ce3d020106082a8648ce3d03010703420004c714ef7acbf4384e8669497f43fdf74de81f6d28c0bdbe2604550bc88271b79baff944903d729f37b0d7a968dd32fb9adad961a25942e2267b4b2f4e2bc62adb8115005bb6b6170d045ac2af3c46e1f839780a9d3c837f82150035614105e4c8b5376ae45e399a1bfc0fead71b2da381e43081e13081bd060a2b06010401d6790201180101ff0481ab3081a8a0220420cedb7b981c5c16a9145f3cbb6931fbc9ab425208d5ab8db82d4d6fc1ff52fa98a136043430320c095465737420444943450c03312e300420cf4817d9793e92a0b00b0afc241354f9a649866da1d883c604c0585ef4129b82a2220420181130b80127a65afcdde4c0f90caf6e691acfc7199e83c03e494e8fc854c1afa315041330110201010101ff0c09746865792f7468656da603020102a70a0c084f70656e44494345300e0603551d0f0101ff040403020204300f0603551d130101ff040530030101ff300a06082a8648ce3d040302034100d26b21afc1f58501a19b7b0aa8cc6a1dd8576f54310b9a8e94dbe709d640476b7ec42ef3310b8f0be901f752733bcff44da1b09a193d43b3866b214581b9a38c + +Name: X509_secp384r1_secp256r1 +pKeyType: secp256r1 +keyType: secp384r1 +certFormat: X509 +wantCert: 30820333308202e0a0030201020214213af831ec3cb8f69d86c8ea391de83118358f00300a06082a8648ce3d040302308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220303131302f06035504051328356262366236313730643034356163326166336334366531663833393738306139643363383337663022180f31393730303130313030303030305a180f39393939313233313233353935395a308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220313131302f06035504051328323133616638333165633363623866363964383663386561333931646538333131383335386630303076301006072a8648ce3d020106052b810400220362000449d1420f3824737ccb79af981685ee7565fa9384ce7ee0acc920d459fa17463e08c960edab2df330e6ab9955fd2587731077a408f88050165dba687c28f6f92cb6d898a6e95392a82e13692f3ef1a0e2029a06d961bf5a2efca7765787b21dcb8115005bb6b6170d045ac2af3c46e1f839780a9d3c837f821500213af831ec3cb8f69d86c8ea391de83118358f00a381e43081e13081bd060a2b06010401d6790201180101ff0481ab3081a8a0220420cedb7b981c5c16a9145f3cbb6931fbc9ab425208d5ab8db82d4d6fc1ff52fa98a136043430320c095465737420444943450c03312e300420cf4817d9793e92a0b00b0afc241354f9a649866da1d883c604c0585ef4129b82a2220420181130b80127a65afcdde4c0f90caf6e691acfc7199e83c03e494e8fc854c1afa315041330110201010101ff0c09746865792f7468656da603020102a70a0c084f70656e44494345300e0603551d0f0101ff040403020204300f0603551d130101ff040530030101ff300a06082a8648ce3d040302034100ea2046ca6b31983360b52959ee21ee8ec91ce81a5a921a539083d96bbd91c55c2ab9187164d5797d835d4ea7989baccc3fd666c6397707d9e50e2723f1885f40 + +Name: X509_ed25519_secp384r1 +pKeyType: secp384r1 +keyType: ed25519 +certFormat: X509 +wantCert: 3082030730820294a0030201020214025ddfac4887ad5f1ef302a19befb23281d3fcef300a06082a8648ce3d040303308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220303131302f06035504051328376362653231653739663961353933393864386439666262316662353332396266656138656130643022180f31393730303130313030303030305a180f39393939313233313233353935395a308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220313131302f0603550405132830323564646661633438383761643566316566333032613139626566623233323831643366636566302a300506032b65700321003302a247e8b2afb784d531789b6f2c0385e7c32468e6900bee18b4b0902a8d6f8115007cbe21e79f9a59398d8d9fbb1fb5329bfea8ea0d821500025ddfac4887ad5f1ef302a19befb23281d3fcefa381e43081e13081bd060a2b06010401d6790201180101ff0481ab3081a8a0220420cedb7b981c5c16a9145f3cbb6931fbc9ab425208d5ab8db82d4d6fc1ff52fa98a136043430320c095465737420444943450c03312e300420cf4817d9793e92a0b00b0afc241354f9a649866da1d883c604c0585ef4129b82a2220420181130b80127a65afcdde4c0f90caf6e691acfc7199e83c03e494e8fc854c1afa315041330110201010101ff0c09746865792f7468656da603020102a70a0c084f70656e44494345300e0603551d0f0101ff040403020204300f0603551d130101ff040530030101ff300a06082a8648ce3d040303036100155eff2f3fce11d02e6d4e94216249a7ec6fa716ae4a75afe16efbceddc5aeaab41fd57d2b32dad25333775a7cbe3e75f9a3b94efc0f942a66176f2f0569c35117caba595b18446580a9f9bdbe0c23299eca915070b116c59d360aab35fa6155 + +Name: X509_secp256r1_secp384r1 +pKeyType: secp384r1 +keyType: secp256r1 +certFormat: X509 +wantCert: 30820336308202c3a003020102021435614105e4c8b5376ae45e399a1bfc0fead71b2d300a06082a8648ce3d040303308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220303131302f06035504051328376362653231653739663961353933393864386439666262316662353332396266656138656130643022180f31393730303130313030303030305a180f39393939313233313233353935395a308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220313131302f06035504051328333536313431303565346338623533373661653435653339396131626663306665616437316232643059301306072a8648ce3d020106082a8648ce3d03010703420004c714ef7acbf4384e8669497f43fdf74de81f6d28c0bdbe2604550bc88271b79baff944903d729f37b0d7a968dd32fb9adad961a25942e2267b4b2f4e2bc62adb8115007cbe21e79f9a59398d8d9fbb1fb5329bfea8ea0d82150035614105e4c8b5376ae45e399a1bfc0fead71b2da381e43081e13081bd060a2b06010401d6790201180101ff0481ab3081a8a0220420cedb7b981c5c16a9145f3cbb6931fbc9ab425208d5ab8db82d4d6fc1ff52fa98a136043430320c095465737420444943450c03312e300420cf4817d9793e92a0b00b0afc241354f9a649866da1d883c604c0585ef4129b82a2220420181130b80127a65afcdde4c0f90caf6e691acfc7199e83c03e494e8fc854c1afa315041330110201010101ff0c09746865792f7468656da603020102a70a0c084f70656e44494345300e0603551d0f0101ff040403020204300f0603551d130101ff040530030101ff300a06082a8648ce3d04030303610057f273f52bf742de8e4c7a78b7ba4c964f40d9e9f4362fc2fee5c00d474adf2a760bcd1f8873441fc5e00e1977e2e7bbe5ceb8bb0d6d6b3e6dd26f10556bcd099c645f590024c2379a002bab8bf617c86e7e4713ce67d175096784bbcefd2733 + +Name: X509_secp384r1_secp384r1 +pKeyType: secp384r1 +keyType: secp384r1 +certFormat: X509 +wantCert: 30820353308202e0a0030201020214213af831ec3cb8f69d86c8ea391de83118358f00300a06082a8648ce3d040303308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220303131302f06035504051328376362653231653739663961353933393864386439666262316662353332396266656138656130643022180f31393730303130313030303030305a180f39393939313233313233353935395a308181310b30090603550406130255533111300f06035504071308536372616e746f6e31153013060355040a130c546573742044494345204341311530130603550403130c44494345204c6179657220313131302f06035504051328323133616638333165633363623866363964383663386561333931646538333131383335386630303076301006072a8648ce3d020106052b810400220362000449d1420f3824737ccb79af981685ee7565fa9384ce7ee0acc920d459fa17463e08c960edab2df330e6ab9955fd2587731077a408f88050165dba687c28f6f92cb6d898a6e95392a82e13692f3ef1a0e2029a06d961bf5a2efca7765787b21dcb8115007cbe21e79f9a59398d8d9fbb1fb5329bfea8ea0d821500213af831ec3cb8f69d86c8ea391de83118358f00a381e43081e13081bd060a2b06010401d6790201180101ff0481ab3081a8a0220420cedb7b981c5c16a9145f3cbb6931fbc9ab425208d5ab8db82d4d6fc1ff52fa98a136043430320c095465737420444943450c03312e300420cf4817d9793e92a0b00b0afc241354f9a649866da1d883c604c0585ef4129b82a2220420181130b80127a65afcdde4c0f90caf6e691acfc7199e83c03e494e8fc854c1afa315041330110201010101ff0c09746865792f7468656da603020102a70a0c084f70656e44494345300e0603551d0f0101ff040403020204300f0603551d130101ff040530030101ff300a06082a8648ce3d040303036100d7c4232b56638e7021cf508d69cb646ea63e430b7319c044dd19e171978fb708676b1ee8994b5222785069212e4c957429780ba8b9c944885189b055a1a6d4e2ddecba4d7b6486c46ef8063f7f579fdd7192bdfc47b5e4027d25bb4c497a7d85 + +Name: COSE_ed25519_ed25519 +pKeyType: ed25519 +keyType: ed25519 +certFormat: COSE +wantCert: 8443a10127a059013ca901782831383664663935666335616532386363333661316432323934636563383565623637613365326164027828303235646466616334383837616435663165663330326131396265666232333238316433666365663a004744505820cedb7b981c5c16a9145f3cbb6931fbc9ab425208d5ab8db82d4d6fc1ff52fa983a00474451583430320c095465737420444943450c03312e300420cf4817d9793e92a0b00b0afc241354f9a649866da1d883c604c0585ef4129b823a004744525820181130b80127a65afcdde4c0f90caf6e691acfc7199e83c03e494e8fc854c1af3a004744535330110201010101ff0c09746865792f7468656d3a0047445641023a00474457582ea5010103270482010220062158203302a247e8b2afb784d531789b6f2c0385e7c32468e6900bee18b4b0902a8d6f3a0047445841005840e07ea0117682454e89f63f812dda2e5061632e94bb3fdf0ccb1da06bbd23fb1567f63fe0f292df6ff0c976a9c87e2e2ea40c4e019eee7326a311e7da87e2cb06 + +Name: COSE_secp256r1_secp256r1 +pKeyType: secp256r1 +keyType: secp256r1 +certFormat: COSE +wantCert: 8443a10126a059015fa901782835626236623631373064303435616332616633633436653166383339373830613964336338333766027828333536313431303565346338623533373661653435653339396131626663306665616437316232643a004744505820cedb7b981c5c16a9145f3cbb6931fbc9ab425208d5ab8db82d4d6fc1ff52fa983a00474451583430320c095465737420444943450c03312e300420cf4817d9793e92a0b00b0afc241354f9a649866da1d883c604c0585ef4129b823a004744525820181130b80127a65afcdde4c0f90caf6e691acfc7199e83c03e494e8fc854c1af3a004744535330110201010101ff0c09746865792f7468656d3a0047445641023a004744575851a601020326048201022001215820c714ef7acbf4384e8669497f43fdf74de81f6d28c0bdbe2604550bc88271b79b225820aff944903d729f37b0d7a968dd32fb9adad961a25942e2267b4b2f4e2bc62adb3a0047445841005840d0008574cb56b9e99e383272c4d749f3b381e8f1be000b37a27a8278f7563488cdacd89acec7bfd0dfa469403f9f4e57249bb832bacdb8b64d4a83fa7ee32db7 diff --git a/src/core/test/test_vectors.cpp b/src/core/test/test_vectors.cpp new file mode 100644 index 0000000..6683aaa --- /dev/null +++ b/src/core/test/test_vectors.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include "test_vectors.h" + +#include +#include +#include + +#include + +#include "nat20/functionality.h" + +struct key_type_parser { + static std::optional parse(std::string const& str) { + if (str == "ed25519") { + return n20_crypto_key_type_ed25519_e; + } else if (str == "secp256r1") { + return n20_crypto_key_type_secp256r1_e; + } else if (str == "secp384r1") { + return n20_crypto_key_type_secp384r1_e; + } + return std::nullopt; // Invalid key type + } +}; + +struct certificate_format_parser { + static std::optional parse(std::string const& str) { + if (str == "X509") { + return n20_certificate_format_x509_e; + } else if (str == "COSE") { + return n20_certificate_format_cose_e; + } + return std::nullopt; // Invalid certificate format + } +}; + +DEFINE_FIELD(Name, std::string, string_parser, "Name") +DEFINE_FIELD(ParentKey, n20_crypto_key_type_t, key_type_parser, "pKeyType") +DEFINE_FIELD(Key, n20_crypto_key_type_t, key_type_parser, "keyType") +DEFINE_FIELD(CertificateFormat, n20_certificate_format_t, certificate_format_parser, "certFormat") +DEFINE_FIELD(WantCert, std::vector, hex_string_parser, "wantCert"); + +using AttestationCertificateVectorReader = + TestVectorReader; + +std::vector attestationCertificateTestVectors = + AttestationCertificateVectorReader::read_all_vectors_from_file( + "test_data/core/opendice_attestation_test_vectors.txt"); diff --git a/src/core/test/test_vectors.h b/src/core/test/test_vectors.h new file mode 100644 index 0000000..8e6c139 --- /dev/null +++ b/src/core/test/test_vectors.h @@ -0,0 +1,29 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include +#include +#include + +#include +#include + +extern std::vector>> + attestationCertificateTestVectors; diff --git a/src/core/test/x509.cpp b/src/core/test/x509.cpp index fb7c5a5..6df5a96 100644 --- a/src/core/test/x509.cpp +++ b/src/core/test/x509.cpp @@ -33,10 +33,8 @@ #include #include -#include #include #include -#include #include #define MAKE_PTR(name) using name##_PTR_t = bssl::UniquePtr @@ -61,20 +59,30 @@ uint8_t const test_cdi[] = { 0xbb, 0xe0, 0x21, 0xf5, 0x87, 0x1c, 0x6c, 0x0c, 0x30, 0x2b, 0x32, 0x4f, 0x4c, 0x44, 0xd1, 0x26, 0xca, 0x35, 0x6b, 0xc3, 0xc5, 0x0e, 0x17, 0xc6, 0x21, 0xad, 0x1d, 0x32, 0xbd, 0x6e, 0x35, 0x08}; -uint8_t const test_cdi2[] = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; - class NameTest : public testing::TestWithParam>> { }; +uint8_t const TEST_SERIAL[20] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14}; + n20_x509_name_t NAME_EMPTY = {.element_count = 0, .elements = {}}; n20_x509_name_t NAME_ONE = N20_X509_NAME(N20_X509_RDN(&OID_COUNTRY_NAME, "US")); n20_x509_name_t NAME_TWO = N20_X509_NAME(N20_X509_RDN(&OID_COUNTRY_NAME, "US"), N20_X509_RDN(&OID_LOCALITY_NAME, "Pittsburgh")); n20_x509_name_t NAME_NINE = {.element_count = 9, .elements = {}}; +n20_x509_name_t NAME_WITH_SERIAL = N20_X509_NAME( + N20_X509_RDN(&OID_COUNTRY_NAME, "US"), + N20_X509_RDN(&OID_LOCALITY_NAME, "Scranton"), + N20_X509_RDN(&OID_ORGANIZATION_NAME, "Test DICE CA"), + N20_X509_RDN(&OID_COMMON_NAME, "DICE Layer 00"), + {.type = &OID_SERIAL_NUMBER, + .bytes = {.size = sizeof(TEST_SERIAL), .buffer = const_cast(TEST_SERIAL)}}); +n20_x509_name_t NAME_WITH_NULL_SERIAL = + N20_X509_NAME({.type = &OID_SERIAL_NUMBER, .bytes = {.size = 5, .buffer = nullptr}}); + +n20_x509_name_t NAME_WITH_ZERO_SIZED_SERIAL = + N20_X509_NAME({.type = &OID_SERIAL_NUMBER, + .bytes = {.size = 0, .buffer = reinterpret_cast("ignored")}}); std::vector const ENCODED_NAME_NULL = {0x30, 0x02, 0x05, 0x00}; std::vector const ENCODED_NAME_EMPTY = {0x30, 0x00}; @@ -84,14 +92,31 @@ std::vector const ENCODED_NAME_TWO = { 0x30, 0x22, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x50, 0x69, 0x74, 0x74, 0x73, 0x62, 0x75, 0x72, 0x67, 0x68}; +std::vector const ENCODED_NAME_WITH_SERIAL = { + 0x30, 0x81, 0x82, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, + 0x53, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x08, 0x53, 0x63, 0x72, + 0x61, 0x6e, 0x74, 0x6f, 0x6e, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, + 0x0c, 0x54, 0x65, 0x73, 0x74, 0x20, 0x44, 0x49, 0x43, 0x45, 0x20, 0x43, 0x41, 0x31, 0x16, + 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0d, 0x44, 0x49, 0x43, 0x45, 0x20, 0x4c, + 0x61, 0x79, 0x65, 0x72, 0x20, 0x30, 0x30, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, + 0x05, 0x13, 0x28, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35, 0x30, 0x36, + 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x30, 0x61, 0x30, 0x62, 0x30, 0x63, 0x30, 0x64, 0x30, + 0x65, 0x30, 0x66, 0x31, 0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x33, 0x31, 0x34}; + +std::vector const ENCODED_NAME_WITH_EMPTY_SERIAL = { + 0x30, 0x0b, 0x31, 0x09, 0x30, 0x07, 0x06, 0x03, 0x55, 0x04, 0x05, 0x13, 0x00}; -INSTANTIATE_TEST_CASE_P(X509NameTest, - NameTest, - testing::Values(std::tuple(nullptr, ENCODED_NAME_NULL), - std::tuple(&NAME_EMPTY, ENCODED_NAME_EMPTY), - std::tuple(&NAME_ONE, ENCODED_NAME_ONE), - std::tuple(&NAME_TWO, ENCODED_NAME_TWO), - std::tuple(&NAME_NINE, ENCODED_NAME_NULL))); +INSTANTIATE_TEST_CASE_P( + X509NameTest, + NameTest, + testing::Values(std::tuple(nullptr, ENCODED_NAME_NULL), + std::tuple(&NAME_EMPTY, ENCODED_NAME_EMPTY), + std::tuple(&NAME_ONE, ENCODED_NAME_ONE), + std::tuple(&NAME_TWO, ENCODED_NAME_TWO), + std::tuple(&NAME_NINE, ENCODED_NAME_NULL), + std::tuple(&NAME_WITH_SERIAL, ENCODED_NAME_WITH_SERIAL), + std::tuple(&NAME_WITH_NULL_SERIAL, ENCODED_NAME_WITH_EMPTY_SERIAL), + std::tuple(&NAME_WITH_ZERO_SIZED_SERIAL, ENCODED_NAME_WITH_EMPTY_SERIAL))); // This test tests name encoding. It verifies that slices with one or two well formed // names are encoded correctly as well as the following corner cases: null slice, empty @@ -100,14 +125,14 @@ TEST_P(NameTest, NameEncoding) { auto [p, expected] = GetParam(); n20_stream_t s; - uint8_t buffer[128]; + uint8_t buffer[133]; n20_stream_init(&s, buffer, sizeof(buffer)); n20_x509_name(&s, p); ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); - ASSERT_EQ(n20_stream_byte_count(&s), expected.size()); + EXPECT_EQ(n20_stream_byte_count(&s), expected.size()); std::vector got = std::vector(n20_stream_data(&s), n20_stream_data(&s) + n20_stream_byte_count(&s)); - ASSERT_EQ(expected, got); + ASSERT_EQ(expected, got) << "Got: " << hex(got) << "\nExpected: " << hex(expected); } class ExtensionTest : public testing::TestWithParam< @@ -370,19 +395,21 @@ TEST(CertTBSTest, CertTBSZeroEncoding) { n20_stream_init(&s, buffer, sizeof(buffer)); n20_x509_cert_tbs(&s, &tbs); ASSERT_FALSE(n20_stream_has_buffer_overflow(&s)); - ASSERT_EQ(n20_stream_byte_count(&s), ENCODED_CERT_TBS_ZERO.size()); + EXPECT_EQ(n20_stream_byte_count(&s), ENCODED_CERT_TBS_ZERO.size()); std::vector got = std::vector(n20_stream_data(&s), n20_stream_data(&s) + n20_stream_byte_count(&s)); - ASSERT_EQ(ENCODED_CERT_TBS_ZERO, got); + ASSERT_EQ(ENCODED_CERT_TBS_ZERO, got) + << "Got: " << hex(got) << "\nExpected: " << hex(ENCODED_CERT_TBS_ZERO); } // clang-format off std::vector const ENCODED_CERT_TBS_NONZERO = { - 0x30, 0x81, 0xe5, + 0x30, 0x81, 0xf8, // version 0xa0, 0x03, 0x02, 0x01, 0x02, // serialNumber - 0x02, 0x01, 0x01, + 0x02, 0x14, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // signature 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, // issuer @@ -415,8 +442,11 @@ TEST(CertTBSTest, CertTBSNonzeroEncoding) { 0x40, 0xa2, 0x92, 0xf7, 0x93, 0xde, 0x30, 0xf8, 0x0a, 0x23, 0xa8, 0x31, 0x21, 0x5d, 0xd0, 0x07, 0xd8, 0x63, 0x24, 0x2e, 0xff, 0x68, 0x21, 0x85}; + + uint8_t const serial_number[20] = {0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; n20_x509_tbs_t tbs = { - .serial_number = 1, + .serial_number = {.size = sizeof(serial_number), .buffer = serial_number}, .signature_algorithm = { .oid = &OID_ED25519, @@ -463,7 +493,34 @@ TEST(CertTBSTest, CertTBSNonzeroEncoding) { class CertTest : public testing::TestWithParam> {}; + bool>> { + protected: + n20_crypto_context_t* crypto_ctx; + n20_crypto_key_t cdi; + + void SetUp() override { + EXPECT_EQ(n20_error_ok_e, n20_crypto_boringssl_open(&crypto_ctx)); + n20_slice_t cdi_slice = {sizeof(test_cdi), const_cast(&test_cdi[0])}; + EXPECT_EQ(n20_error_ok_e, n20_crypto_boringssl_make_secret(crypto_ctx, &cdi_slice, &cdi)); + } + + void TearDown() override { + EXPECT_EQ(n20_error_ok_e, crypto_ctx->key_free(crypto_ctx, cdi)); + EXPECT_EQ(n20_error_ok_e, n20_crypto_boringssl_close(crypto_ctx)); + } + + n20_crypto_key_t GetSigningKey(n20_crypto_key_type_t key_type, std::string label) { + n20_crypto_key_t signing_key = nullptr; + n20_slice_t label_slice = {label.size(), reinterpret_cast(label.data())}; + n20_crypto_gather_list_t context = { + .count = 1, + .list = &label_slice, + }; + EXPECT_EQ(n20_error_ok_e, + crypto_ctx->kdf(crypto_ctx, cdi, key_type, &context, &signing_key)); + return signing_key; + } +}; INSTANTIATE_TEST_CASE_P( X509CertTest, @@ -565,13 +622,12 @@ void n20_x509_rs_content(n20_stream_t* const s, void* context) { n20_x509_rs_t const* rs = (n20_x509_rs_t const*)context; n20_asn1_integer(s, - rs->signature + rs->signature_size / 2, - rs->signature_size / 2, + {rs->signature_size / 2, rs->signature + rs->signature_size / 2}, false, false, n20_asn1_tag_info_no_override()); n20_asn1_integer( - s, rs->signature, rs->signature_size / 2, false, false, n20_asn1_tag_info_no_override()); + s, {rs->signature_size / 2, rs->signature}, false, false, n20_asn1_tag_info_no_override()); } void n20_x509_rs(n20_stream_t* const s, n20_x509_rs_t const* const rs) { @@ -585,25 +641,13 @@ TEST_P(CertTest, CertEncoding) { GetParam(); // Create a key with test_cdi. - n20_crypto_context_t* ctx = nullptr; - n20_slice_t cdi_slice{.size = sizeof(test_cdi), .buffer = test_cdi}; - n20_error_t err = n20_crypto_open_boringssl(&ctx, &cdi_slice); - ASSERT_EQ(n20_error_ok_e, err); - - n20_crypto_key_t cdi_key = nullptr; - err = ctx->get_cdi(ctx, &cdi_key); - ASSERT_EQ(n20_error_ok_e, err); - - n20_crypto_gather_list_t empty_context{.count = 0, .list = nullptr}; - - n20_crypto_key_t signing_key = nullptr; - err = ctx->kdf(ctx, cdi_key, key_type, &empty_context, &signing_key); - ASSERT_EQ(n20_error_ok_e, err); + n20_crypto_key_t signing_key = GetSigningKey(key_type, "signing key 1"); uint8_t public_key_buffer[128]; uint8_t* public_key = &public_key_buffer[1]; size_t public_key_size = sizeof(public_key_buffer) - 1; - err = ctx->key_get_public_key(ctx, signing_key, public_key, &public_key_size); + auto err = + crypto_ctx->key_get_public_key(crypto_ctx, signing_key, public_key, &public_key_size); ASSERT_EQ(n20_error_ok_e, err); if (key_type != n20_crypto_key_type_ed25519_e) { @@ -636,8 +680,9 @@ TEST_P(CertTest, CertEncoding) { .context = &basic_constraints, }, }; + uint8_t serial_number = 1; n20_x509_tbs_t tbs = { - .serial_number = 1, + .serial_number = {.size = 1, .buffer = &serial_number}, .signature_algorithm = signature_algorithm, .issuer_name = N20_X509_NAME( N20_X509_RDN(&OID_COUNTRY_NAME, "US"), @@ -683,10 +728,9 @@ TEST_P(CertTest, CertEncoding) { // Sign the to-be-signed part of the certificate. uint8_t signature[128]; size_t signature_size = sizeof(signature); - err = ctx->sign(ctx, signing_key, &tbs_der_gather, signature, &signature_size); + err = crypto_ctx->sign(crypto_ctx, signing_key, &tbs_der_gather, signature, &signature_size); ASSERT_EQ(n20_error_ok_e, err); - ctx->key_free(ctx, signing_key); - n20_crypto_close_boringssl(ctx); + crypto_ctx->key_free(crypto_ctx, signing_key); switch (key_type) { case n20_crypto_key_type_secp256r1_e: @@ -752,24 +796,13 @@ TEST_P(CertTest, CertEncoding) { // Now create a certificate signed with a different key. It is expected to fail the // verification. - // Create a key with test_cdi2. - n20_crypto_context_t* ctx2 = nullptr; - n20_slice_t cdi_slice2{.size = sizeof(test_cdi2), .buffer = test_cdi2}; - err = n20_crypto_open_boringssl(&ctx2, &cdi_slice2); - ASSERT_EQ(n20_error_ok_e, err); - - n20_crypto_key_t cdi_key2 = nullptr; - err = ctx2->get_cdi(ctx2, &cdi_key2); - ASSERT_EQ(n20_error_ok_e, err); - - n20_crypto_key_t signing_key2 = nullptr; - err = ctx2->kdf(ctx2, cdi_key2, key_type, &empty_context, &signing_key2); - ASSERT_EQ(n20_error_ok_e, err); + // Create another signing key. + n20_crypto_key_t signing_key2 = GetSigningKey(key_type, "signing key 2"); uint8_t public_key_buffer2[128]; uint8_t* public_key2 = &public_key_buffer2[1]; size_t public_key_size2 = sizeof(public_key_buffer2) - 1; - err = ctx2->key_get_public_key(ctx2, signing_key2, public_key2, &public_key_size2); + err = crypto_ctx->key_get_public_key(crypto_ctx, signing_key2, public_key2, &public_key_size2); ASSERT_EQ(n20_error_ok_e, err); if (key_type != n20_crypto_key_type_ed25519_e) { @@ -791,10 +824,10 @@ TEST_P(CertTest, CertEncoding) { // Sign the to-be-signed part of the certificate. uint8_t signature2[128]; size_t signature_size2 = sizeof(signature2); - err = ctx2->sign(ctx2, signing_key2, &tbs_der_gather2, signature2, &signature_size2); + err = + crypto_ctx->sign(crypto_ctx, signing_key2, &tbs_der_gather2, signature2, &signature_size2); ASSERT_EQ(n20_error_ok_e, err); - ctx2->key_free(ctx2, signing_key2); - n20_crypto_close_boringssl(ctx2); + crypto_ctx->key_free(crypto_ctx, signing_key2); switch (key_type) { case n20_crypto_key_type_secp256r1_e: diff --git a/src/core/x509.c b/src/core/x509.c index 0124721..a8b5eb0 100644 --- a/src/core/x509.c +++ b/src/core/x509.c @@ -22,14 +22,48 @@ n20_string_slice_t n20_x509_no_expiration = {.buffer = "99991231235959Z", .size = 15}; n20_string_slice_t n20_x509_unix_epoch = {.buffer = "19700101000000Z", .size = 15}; -void n20_x509_rdn_content(n20_stream_t *const s, void *context) { +static uint8_t const nibble2ascii[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + +static void n20_x509_serial_as_hex_content(n20_stream_t *const s, void *ctx) { + n20_slice_t const *slice = (n20_slice_t const *)ctx; + if (slice != NULL && slice->buffer != NULL) { + /* The overflow is intentional. When i becomes SIZE_MAX, + * the loop will terminate naturally even if slice->size is SIZE_MAX. */ + for (size_t i = slice->size - 1; i < slice->size; --i) { + uint8_t c = slice->buffer[i]; + uint8_t str[2] = {nibble2ascii[c >> 4], nibble2ascii[c & 0x0f]}; + // Render the bytes in reverse order. + n20_stream_prepend(s, str, 2); + } + } +} + +static void n20_x509_serial_as_hex_string(n20_stream_t *const s, + n20_slice_t const *const slice, + n20_asn1_tag_info_t const tag_info) { + n20_asn1_header_with_content(s, + N20_ASN1_CLASS_UNIVERSAL, + /*constructed=*/false, + N20_ASN1_TAG_PRINTABLE_STRING, + n20_x509_serial_as_hex_content, + (void *)slice, + tag_info); +} + +static void n20_x509_rdn_content(n20_stream_t *const s, void *context) { n20_x509_rdn_t const *rdn = (n20_x509_rdn_t const *)context; - n20_asn1_printablestring(s, &rdn->value, n20_asn1_tag_info_no_override()); + if (n20_asn1_oid_equals(rdn->type, &OID_SERIAL_NUMBER)) { + // Special case for serial number, which is printed as a hex string. + n20_x509_serial_as_hex_string(s, &rdn->bytes, n20_asn1_tag_info_no_override()); + } else { + n20_asn1_printablestring(s, &rdn->string, n20_asn1_tag_info_no_override()); + } n20_asn1_object_identifier(s, rdn->type, n20_asn1_tag_info_no_override()); } -void n20_x509_rdn(n20_stream_t *const s, n20_x509_rdn_t const *rdn) { +static void n20_x509_rdn(n20_stream_t *const s, n20_x509_rdn_t const *rdn) { size_t mark = n20_stream_byte_count(s); n20_asn1_sequence(s, n20_x509_rdn_content, (void *)rdn, n20_asn1_tag_info_no_override()); n20_asn1_header(s, @@ -39,7 +73,7 @@ void n20_x509_rdn(n20_stream_t *const s, n20_x509_rdn_t const *rdn) { n20_stream_byte_count(s) - mark); } -void n20_x509_name_content(n20_stream_t *const s, void *context) { +static void n20_x509_name_content(n20_stream_t *const s, void *context) { n20_x509_name_t const *name = context; if (name == NULL || name->element_count > N20_X509_NAME_MAX_NAME_ELEMENTS) { n20_asn1_null(s, n20_asn1_tag_info_no_override()); @@ -235,7 +269,19 @@ void n20_x509_cert_tbs_content(n20_stream_t *const s, void *context) { // The following optional fields are not implemented yet. // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL + if (tbs->subject_unique_id.size > 0) { + n20_asn1_bitstring(s, + tbs->subject_unique_id.buffer, + tbs->subject_unique_id.size * 8, + n20_asn1_tag_info_implicit(2)); + } // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL + if (tbs->issuer_unique_id.size > 0) { + n20_asn1_bitstring(s, + tbs->issuer_unique_id.buffer, + tbs->issuer_unique_id.size * 8, + n20_asn1_tag_info_implicit(1)); + } // subjectPublicKeyInfo SubjectPublicKeyInfo n20_x509_public_key_info(s, &tbs->subject_public_key_info); @@ -253,7 +299,16 @@ void n20_x509_cert_tbs_content(n20_stream_t *const s, void *context) { n20_x509_algorithm_identifier(s, &tbs->signature_algorithm); // Serial number - n20_asn1_uint64(s, tbs->serial_number, n20_asn1_tag_info_no_override()); + if (tbs->serial_number.size == 0) { + // If the serial number is empty it is interpreted as zero. + n20_asn1_uint64(s, 0, n20_asn1_tag_info_no_override()); + } else { + n20_asn1_integer(s, + tbs->serial_number, + /*little_endian=*/false, + /*two_complement=*/false, + n20_asn1_tag_info_no_override()); + } // Version 3 (value 2) with explicit tag 0 n20_x509_version_3(s); diff --git a/src/crypto/crypto_boringssl.cpp b/src/crypto/crypto_boringssl.cpp index c9811d8..a59aa91 100644 --- a/src/crypto/crypto_boringssl.cpp +++ b/src/crypto/crypto_boringssl.cpp @@ -71,14 +71,6 @@ struct n20_bssl_evp_pkey : n20_bssl_key_base { virtual ~n20_bssl_evp_pkey() {} }; -struct n20_bssl_context : public n20_crypto_context_t { - n20_bssl_cdi_key cdi; -}; - -static n20_bssl_context* context_cast(n20_crypto_context_t* ctx) { - return static_cast(ctx); -} - static EVP_MD const* digest_enum_to_bssl_evp_md(n20_crypto_digest_algorithm_t alg_in) { switch (alg_in) { case n20_crypto_digest_algorithm_sha2_224_e: @@ -733,6 +725,9 @@ static n20_error_t n20_crypto_boringssl_kdf(struct n20_crypto_context_s* ctx, *key_out = bssl_out; return n20_error_ok_e; } + default: + // Unsupported key type for KDF. + break; } return n20_error_crypto_invalid_key_type_e; @@ -910,22 +905,6 @@ static n20_error_t n20_crypto_boringssl_sign(struct n20_crypto_context_s* ctx, } } -static n20_error_t n20_crypto_boringssl_get_cdi(struct n20_crypto_context_s* ctx, - n20_crypto_key_t* key_out) { - if (ctx == nullptr) { - return n20_error_crypto_invalid_context_e; - } - - if (key_out == nullptr) { - return n20_error_crypto_unexpected_null_key_out_e; - } - - auto bssl_ctx = context_cast(ctx); - *key_out = reinterpret_cast(static_cast(&bssl_ctx->cdi)); - - return n20_error_ok_e; -} - struct BoringsslDeleter { void operator()(void* p) { OPENSSL_free(p); } }; @@ -1033,14 +1012,6 @@ static n20_error_t n20_crypto_boringssl_key_free(struct n20_crypto_context_s* ct auto bssl_key = reinterpret_cast(key_in); - // Every key handle given out by the library must be freed eventually. - // But the key handle for the root secret is owned by the context - // there is nothing to do here in this case. - auto bssl_ctx = context_cast(ctx); - if (bssl_key == static_cast(&bssl_ctx->cdi)) { - return n20_error_ok_e; - } - delete bssl_key; return n20_error_ok_e; @@ -1053,35 +1024,50 @@ static n20_crypto_context_t bssl_ctx{{n20_crypto_boringssl_digest, n20_crypto_boringssl_hkdf_expand}, n20_crypto_boringssl_kdf, n20_crypto_boringssl_sign, - n20_crypto_boringssl_get_cdi, n20_crypto_boringssl_key_get_public_key, n20_crypto_boringssl_key_free}; -extern "C" n20_error_t n20_crypto_open_boringssl(n20_crypto_context_t** ctx, - n20_slice_t const* cdi) { - if (ctx == nullptr || cdi == nullptr || cdi->buffer == nullptr || cdi->size == 0) { +extern "C" n20_error_t n20_crypto_boringssl_open(n20_crypto_context_t** ctx) { + if (ctx == nullptr) { return n20_error_crypto_unexpected_null_e; } - auto new_ctx = std::make_unique(); - if (!new_ctx) { - return n20_error_crypto_no_resources_e; - } + *ctx = &bssl_ctx; - new_ctx->cdi.type = n20_crypto_key_type_cdi_e; - new_ctx->cdi.bits = std::vector(cdi->buffer, cdi->buffer + cdi->size); + return n20_error_ok_e; +} - *ctx = static_cast(new_ctx.release()); - **ctx = bssl_ctx; +extern "C" n20_error_t n20_crypto_boringssl_close(n20_crypto_context_t* ctx) { + if (ctx == nullptr) { + return n20_error_crypto_unexpected_null_e; + } return n20_error_ok_e; } -extern "C" n20_error_t n20_crypto_close_boringssl(n20_crypto_context_t* ctx) { +extern "C" n20_error_t n20_crypto_boringssl_make_secret(struct n20_crypto_context_s* ctx, + n20_slice_t const* secret_in, + n20_crypto_key_t* key_out) { if (ctx == nullptr) { - return n20_error_crypto_unexpected_null_e; + return n20_error_crypto_invalid_context_e; } - auto tbd_context = std::unique_ptr(context_cast(ctx)); + if (secret_in == nullptr || secret_in->buffer == nullptr || secret_in->size == 0) { + return n20_error_crypto_unexpected_null_data_e; + } + + if (key_out == nullptr) { + return n20_error_crypto_unexpected_null_key_out_e; + } + + auto new_key = new n20_bssl_cdi_key(); + if (!new_key) { + return n20_error_crypto_no_resources_e; + } + + new_key->type = n20_crypto_key_type_cdi_e; + new_key->bits = std::vector(secret_in->buffer, secret_in->buffer + secret_in->size); + + *key_out = new_key; return n20_error_ok_e; } diff --git a/src/crypto/test/crypto.cpp b/src/crypto/test/crypto.cpp index b1ddb48..34f5f39 100644 --- a/src/crypto/test/crypto.cpp +++ b/src/crypto/test/crypto.cpp @@ -115,15 +115,21 @@ template class CryptoTestFixture : public ::testing::Test { protected: n20_crypto_context_t* ctx = nullptr; + n20_crypto_key_t cdi = nullptr; public: using impl = T; - void SetUp() override { ASSERT_EQ(n20_error_ok_e, impl::open(&ctx)); } + void SetUp() override { + ASSERT_EQ(n20_error_ok_e, impl::open(&ctx)); + ASSERT_EQ(n20_error_ok_e, impl::get_cdi(ctx, &cdi)); + } void TearDown() override { + ASSERT_EQ(n20_error_ok_e, ctx->key_free(ctx, cdi)); ASSERT_EQ(n20_error_ok_e, impl::close(ctx)); ctx = nullptr; + cdi = nullptr; } }; @@ -926,10 +932,13 @@ static std::vector signature_2_asn1_sequence(std::vector const auto mark = n20_stream_byte_count(&s); // Write S - n20_asn1_integer( - &s, sig.data() + integer_size, integer_size, false, false, n20_asn1_tag_info_no_override()); + n20_asn1_integer(&s, + {integer_size, sig.data() + integer_size}, + false, + false, + n20_asn1_tag_info_no_override()); // Write R - n20_asn1_integer(&s, sig.data(), integer_size, false, false, n20_asn1_tag_info_no_override()); + n20_asn1_integer(&s, {integer_size, sig.data()}, false, false, n20_asn1_tag_info_no_override()); n20_asn1_header(&s, N20_ASN1_CLASS_UNIVERSAL, @@ -1014,10 +1023,6 @@ bool verify(EVP_PKEY_PTR_t const& key, // The third key uses a different context. The signature generated with this // key must not verify against the second. TYPED_TEST_P(CryptoTestFixture, KDFTest) { - n20_crypto_key_t cdi; - - ASSERT_EQ(n20_error_ok_e, this->ctx->get_cdi(this->ctx, &cdi)); - using tc = std::tuple; for (auto [n20_test_name, key_type] : { tc{"ed25519", n20_crypto_key_type_ed25519_e}, @@ -1040,11 +1045,12 @@ TYPED_TEST_P(CryptoTestFixture, KDFTest) { // certain that the derived keys were indeed the same. n20_crypto_key_t derived_key_sign; N20_ASSERT_EQ(n20_error_ok_e, - this->ctx->kdf(this->ctx, cdi, key_type, &context, &derived_key_sign)); + this->ctx->kdf(this->ctx, this->cdi, key_type, &context, &derived_key_sign)); n20_crypto_key_t derived_key_verify; - N20_ASSERT_EQ(n20_error_ok_e, - this->ctx->kdf(this->ctx, cdi, key_type, &context, &derived_key_verify)); + N20_ASSERT_EQ( + n20_error_ok_e, + this->ctx->kdf(this->ctx, this->cdi, key_type, &context, &derived_key_verify)); // ##### Sign the message. ######### n20_slice_t message_buffers[] = { @@ -1072,7 +1078,7 @@ TYPED_TEST_P(CryptoTestFixture, KDFTest) { context_buffers[1].size = 0; n20_crypto_key_t derived_key_other; N20_ASSERT_EQ(n20_error_ok_e, - this->ctx->kdf(this->ctx, cdi, key_type, &context, &derived_key_other)); + this->ctx->kdf(this->ctx, this->cdi, key_type, &context, &derived_key_other)); // 96 is large enough for all implemented algorithms. So // no need to do the query dance again. @@ -1144,20 +1150,9 @@ TYPED_TEST_P(CryptoTestFixture, KDFTest) { N20_ASSERT_EQ(n20_error_ok_e, this->ctx->key_free(this->ctx, derived_key_verify)); N20_ASSERT_EQ(n20_error_ok_e, this->ctx->key_free(this->ctx, derived_key_other)); } - ASSERT_EQ(n20_error_ok_e, this->ctx->key_free(this->ctx, cdi)); -} - -TYPED_TEST_P(CryptoTestFixture, GetCDIErrorsTest) { - ASSERT_EQ(n20_error_crypto_invalid_context_e, this->ctx->get_cdi(nullptr, nullptr)); - - ASSERT_EQ(n20_error_crypto_unexpected_null_key_out_e, this->ctx->get_cdi(this->ctx, nullptr)); } TYPED_TEST_P(CryptoTestFixture, KDFErrorsTest) { - n20_crypto_key_t cdi; - - ASSERT_EQ(n20_error_ok_e, this->ctx->get_cdi(this->ctx, &cdi)); - using tc = std::tuple; for (auto [n20_test_name, key_type] : { tc{"cdi", n20_crypto_key_type_cdi_e}, @@ -1182,21 +1177,24 @@ TYPED_TEST_P(CryptoTestFixture, KDFErrorsTest) { n20_crypto_key_t invalid_key = nullptr; N20_ASSERT_EQ( n20_error_ok_e, - this->ctx->kdf(this->ctx, cdi, n20_crypto_key_type_ed25519_e, &context, &invalid_key)); + this->ctx->kdf( + this->ctx, this->cdi, n20_crypto_key_type_ed25519_e, &context, &invalid_key)); N20_ASSERT_EQ(n20_error_crypto_invalid_key_e, this->ctx->kdf(this->ctx, invalid_key, key_type, nullptr, nullptr)); N20_ASSERT_EQ(n20_error_ok_e, this->ctx->key_free(this->ctx, invalid_key)); - N20_ASSERT_EQ(n20_error_ok_e, - this->ctx->kdf( - this->ctx, cdi, n20_crypto_key_type_secp256r1_e, &context, &invalid_key)); + N20_ASSERT_EQ( + n20_error_ok_e, + this->ctx->kdf( + this->ctx, this->cdi, n20_crypto_key_type_secp256r1_e, &context, &invalid_key)); N20_ASSERT_EQ(n20_error_crypto_invalid_key_e, this->ctx->kdf(this->ctx, invalid_key, key_type, nullptr, nullptr)); N20_ASSERT_EQ(n20_error_ok_e, this->ctx->key_free(this->ctx, invalid_key)); - N20_ASSERT_EQ(n20_error_ok_e, - this->ctx->kdf( - this->ctx, cdi, n20_crypto_key_type_secp384r1_e, &context, &invalid_key)); + N20_ASSERT_EQ( + n20_error_ok_e, + this->ctx->kdf( + this->ctx, this->cdi, n20_crypto_key_type_secp384r1_e, &context, &invalid_key)); N20_ASSERT_EQ(n20_error_crypto_invalid_key_e, this->ctx->kdf(this->ctx, invalid_key, key_type, nullptr, nullptr)); N20_ASSERT_EQ(n20_error_ok_e, this->ctx->key_free(this->ctx, invalid_key)); @@ -1204,24 +1202,24 @@ TYPED_TEST_P(CryptoTestFixture, KDFErrorsTest) { // Must return n20_error_crypto_unexpected_null_key_out_e if no buffer is // given to return the derived key. N20_ASSERT_EQ(n20_error_crypto_unexpected_null_key_out_e, - this->ctx->kdf(this->ctx, cdi, key_type, nullptr, nullptr)); + this->ctx->kdf(this->ctx, this->cdi, key_type, nullptr, nullptr)); n20_crypto_key_t key_out = nullptr; N20_ASSERT_EQ(n20_error_crypto_unexpected_null_data_e, - this->ctx->kdf(this->ctx, cdi, key_type, nullptr, &key_out)); + this->ctx->kdf(this->ctx, this->cdi, key_type, nullptr, &key_out)); // Must return n20_error_crypto_unexpected_null_list_e if the gather list // pointer is NULL. n20_crypto_gather_list_t invalid_context = {1, nullptr}; N20_ASSERT_EQ(n20_error_crypto_unexpected_null_list_e, - this->ctx->kdf(this->ctx, cdi, key_type, &invalid_context, &key_out)); + this->ctx->kdf(this->ctx, this->cdi, key_type, &invalid_context, &key_out)); n20_slice_t invalid_context_buffers[] = { {3, nullptr}, }; invalid_context.list = invalid_context_buffers; N20_ASSERT_EQ(n20_error_crypto_unexpected_null_slice_e, - this->ctx->kdf(this->ctx, cdi, key_type, &invalid_context, &key_out)); + this->ctx->kdf(this->ctx, this->cdi, key_type, &invalid_context, &key_out)); } n20_crypto_key_t out_key = nullptr; @@ -1231,16 +1229,10 @@ TYPED_TEST_P(CryptoTestFixture, KDFErrorsTest) { n20_crypto_gather_list_t context = {1, context_buffers}; ASSERT_EQ(n20_error_crypto_invalid_key_type_e, - this->ctx->kdf(this->ctx, cdi, (n20_crypto_key_type_t)-1, &context, &out_key)); - - ASSERT_EQ(n20_error_ok_e, this->ctx->key_free(this->ctx, cdi)); + this->ctx->kdf(this->ctx, this->cdi, (n20_crypto_key_type_t)-1, &context, &out_key)); } TYPED_TEST_P(CryptoTestFixture, SignErrorsTest) { - n20_crypto_key_t cdi; - - ASSERT_EQ(n20_error_ok_e, this->ctx->get_cdi(this->ctx, &cdi)); - ASSERT_EQ(n20_error_crypto_invalid_context_e, this->ctx->sign(nullptr, nullptr, nullptr, nullptr, nullptr)); @@ -1248,11 +1240,11 @@ TYPED_TEST_P(CryptoTestFixture, SignErrorsTest) { this->ctx->sign(this->ctx, nullptr, nullptr, nullptr, nullptr)); ASSERT_EQ(n20_error_crypto_unexpected_null_size_e, - this->ctx->sign(this->ctx, cdi, nullptr, nullptr, nullptr)); + this->ctx->sign(this->ctx, this->cdi, nullptr, nullptr, nullptr)); size_t signature_size = 0; ASSERT_EQ(n20_error_crypto_invalid_key_e, - this->ctx->sign(this->ctx, cdi, nullptr, nullptr, &signature_size)); + this->ctx->sign(this->ctx, this->cdi, nullptr, nullptr, &signature_size)); using tc = std::tuple; for (auto [n20_test_name, key_type, want_signature_size] : { @@ -1267,7 +1259,7 @@ TYPED_TEST_P(CryptoTestFixture, SignErrorsTest) { n20_crypto_gather_list_t context = {1, context_buffers}; n20_crypto_key_t signing_key = nullptr; N20_ASSERT_EQ(n20_error_ok_e, - this->ctx->kdf(this->ctx, cdi, key_type, &context, &signing_key)); + this->ctx->kdf(this->ctx, this->cdi, key_type, &context, &signing_key)); // Must return n20_error_crypto_insufficient_buffer_size_e if out buffer is NULL. signature_size = 30000; @@ -1303,15 +1295,9 @@ TYPED_TEST_P(CryptoTestFixture, SignErrorsTest) { n20_error_crypto_unexpected_null_slice_e, this->ctx->sign(this->ctx, signing_key, &message, signature_buffer, &signature_size)); } - - ASSERT_EQ(n20_error_ok_e, this->ctx->key_free(this->ctx, cdi)); } TYPED_TEST_P(CryptoTestFixture, GetPublicKeyErrorsTest) { - n20_crypto_key_t cdi; - - ASSERT_EQ(n20_error_ok_e, this->ctx->get_cdi(this->ctx, &cdi)); - ASSERT_EQ(n20_error_crypto_invalid_context_e, this->ctx->key_get_public_key(nullptr, nullptr, nullptr, nullptr)); @@ -1319,11 +1305,11 @@ TYPED_TEST_P(CryptoTestFixture, GetPublicKeyErrorsTest) { this->ctx->key_get_public_key(this->ctx, nullptr, nullptr, nullptr)); ASSERT_EQ(n20_error_crypto_unexpected_null_size_e, - this->ctx->key_get_public_key(this->ctx, cdi, nullptr, nullptr)); + this->ctx->key_get_public_key(this->ctx, this->cdi, nullptr, nullptr)); size_t public_key_size = 0; ASSERT_EQ(n20_error_crypto_invalid_key_e, - this->ctx->key_get_public_key(this->ctx, cdi, nullptr, &public_key_size)); + this->ctx->key_get_public_key(this->ctx, this->cdi, nullptr, &public_key_size)); using tc = std::tuple; for (auto [n20_test_name, key_type, want_key_size] : { @@ -1336,7 +1322,8 @@ TYPED_TEST_P(CryptoTestFixture, GetPublicKeyErrorsTest) { char const context_str[] = "public key errors test context"; n20_slice_t context_buffers[] = {sizeof(context_str) - 1, (uint8_t* const)&context_str[0]}; n20_crypto_gather_list_t context = {1, context_buffers}; - N20_ASSERT_EQ(n20_error_ok_e, this->ctx->kdf(this->ctx, cdi, key_type, &context, &key)); + N20_ASSERT_EQ(n20_error_ok_e, + this->ctx->kdf(this->ctx, this->cdi, key_type, &context, &key)); // Must return n20_error_crypto_insufficient_buffer_size_e if public_key_out // is nullptr. @@ -1372,8 +1359,6 @@ TYPED_TEST_P(CryptoTestFixture, GetPublicKeyErrorsTest) { N20_ASSERT_EQ(n20_error_ok_e, this->ctx->key_free(this->ctx, key)); } - - ASSERT_EQ(n20_error_ok_e, this->ctx->key_free(this->ctx, cdi)); } TYPED_TEST_P(CryptoTestFixture, KeyFreeErrorsTest) { @@ -1402,7 +1387,6 @@ REGISTER_TYPED_TEST_SUITE_P(CryptoDigestFixture, REGISTER_TYPED_TEST_SUITE_P(CryptoTestFixture, OpenClose, KDFTest, - GetCDIErrorsTest, KDFErrorsTest, SignErrorsTest, GetPublicKeyErrorsTest, diff --git a/src/crypto/test/crypto_boringssl.cpp b/src/crypto/test/crypto_boringssl.cpp index 24ae96b..e3028ef 100644 --- a/src/crypto/test/crypto_boringssl.cpp +++ b/src/crypto/test/crypto_boringssl.cpp @@ -19,14 +19,15 @@ #include #include #include +#include +#include +#include #include #include #include #include -#include "openssl/base.h" - uint8_t const test_cdi[] = { 0xa4, 0x32, 0xb4, 0x34, 0x94, 0x4f, 0x59, 0xcf, 0xdb, 0xf7, 0x04, 0x46, 0x95, 0x9c, 0xee, 0x08, 0x7f, 0x6b, 0x87, 0x60, 0xd8, 0xef, 0xb4, 0xcf, 0xed, 0xf2, 0xf6, 0x29, 0x33, 0x88, 0xf0, 0x64, @@ -34,11 +35,15 @@ uint8_t const test_cdi[] = { 0xca, 0x35, 0x6b, 0xc3, 0xc5, 0x0e, 0x17, 0xc6, 0x21, 0xad, 0x1d, 0x32, 0xbd, 0x6e, 0x35, 0x08}; n20_error_t CryptoImplBSSL::open(n20_crypto_context_t** ctx) { - n20_slice_t cdi = {sizeof(test_cdi), const_cast(&test_cdi[0])}; - return n20_crypto_open_boringssl(ctx, &cdi); + return n20_crypto_boringssl_open(ctx); } n20_error_t CryptoImplBSSL::close(n20_crypto_context_t* ctx) { - return n20_crypto_close_boringssl(ctx); + return n20_crypto_boringssl_close(ctx); +} + +n20_error_t CryptoImplBSSL::get_cdi(n20_crypto_context_t* ctx, n20_crypto_key_t* out_cdi) { + n20_slice_t cdi = {sizeof(test_cdi), const_cast(&test_cdi[0])}; + return n20_crypto_boringssl_make_secret(ctx, &cdi, out_cdi); } /* @@ -327,3 +332,32 @@ TEST_P(RFC6979KGenerationTestP384, Test_rfc6979_k_P_384_generation) { ASSERT_EQ(0, BN_cmp(k.get(), got_k)) << BN_bn2hex(got_k); } + +TEST(CryptoBoringsslTest, TestMakeSecretErrors) { + n20_crypto_context_t* ctx = nullptr; + + ASSERT_EQ(n20_error_ok_e, n20_crypto_boringssl_open(&ctx)); + + ASSERT_EQ(n20_error_crypto_invalid_context_e, + n20_crypto_boringssl_make_secret(nullptr, nullptr, nullptr)); + + ASSERT_EQ(n20_error_crypto_unexpected_null_data_e, + n20_crypto_boringssl_make_secret(ctx, nullptr, nullptr)); + + n20_slice_t cdi_slice = {0, const_cast(test_cdi)}; + + ASSERT_EQ(n20_error_crypto_unexpected_null_data_e, + n20_crypto_boringssl_make_secret(ctx, &cdi_slice, nullptr)); + + cdi_slice.buffer = nullptr; + cdi_slice.size = sizeof(test_cdi); + + ASSERT_EQ(n20_error_crypto_unexpected_null_data_e, + n20_crypto_boringssl_make_secret(ctx, &cdi_slice, nullptr)); + + cdi_slice.buffer = const_cast(test_cdi); + ASSERT_EQ(n20_error_crypto_unexpected_null_key_out_e, + n20_crypto_boringssl_make_secret(ctx, &cdi_slice, nullptr)); + + ASSERT_EQ(n20_error_ok_e, n20_crypto_boringssl_close(ctx)); +} \ No newline at end of file diff --git a/src/crypto/test/crypto_boringssl.h b/src/crypto/test/crypto_boringssl.h index 6e4cb0c..8a1f0f5 100644 --- a/src/crypto/test/crypto_boringssl.h +++ b/src/crypto/test/crypto_boringssl.h @@ -21,4 +21,5 @@ struct CryptoImplBSSL { static n20_error_t open(n20_crypto_context_t** ctx); static n20_error_t close(n20_crypto_context_t* ctx); + static n20_error_t get_cdi(n20_crypto_context_t* ctx, n20_crypto_key_t* out_cdi); }; diff --git a/src/service/client_stateless.c b/src/service/client_stateless.c new file mode 100644 index 0000000..4c37cdc --- /dev/null +++ b/src/service/client_stateless.c @@ -0,0 +1,799 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void print_usage(char const *prog) { + fprintf(stderr, + "Usage: %s -r -p -k " + " " + "-c -C -g -G -a -A " + " -m " + " -h \n" + "-k -P -n -o \n" + "-f -x -u -l -M \n", + prog); +} + +int parse_key_type(char const *str) { + if (strcmp(str, "ed25519") == 0) return n20_crypto_key_type_ed25519_e; + if (strcmp(str, "p256") == 0) return n20_crypto_key_type_secp256r1_e; + if (strcmp(str, "p384") == 0) return n20_crypto_key_type_secp384r1_e; + return n20_crypto_key_type_none_e; +} + +int parse_request_type(char const *str) { + if (strcmp(str, "promote") == 0) return n20_msg_request_type_promote_e; + if (strcmp(str, "cdi-cert") == 0) return n20_msg_request_type_issue_cdi_cert_e; + if (strcmp(str, "eca-cert") == 0) return n20_msg_request_type_issue_eca_cert_e; + if (strcmp(str, "eca-sign") == 0) return n20_msg_request_type_eca_sign_e; + return n20_msg_request_type_none_e; +} + +int parse_mode(char const *str) { + if (strcmp(str, "not-configured") == 0) return n20_open_dice_mode_not_configured_e; + if (strcmp(str, "normal") == 0) return n20_open_dice_mode_normal_e; + if (strcmp(str, "debug") == 0) return n20_open_dice_mode_debug_e; + if (strcmp(str, "recovery") == 0) return n20_open_dice_mode_recovery_e; + return n20_open_dice_mode_not_configured_e; +} + +int parse_output_format(char const *str) { + if (strcmp(str, "x509") == 0) return n20_certificate_format_x509_e; + if (strcmp(str, "cose") == 0) return n20_certificate_format_cose_e; + return n20_certificate_format_none_e; +} + +char nibble2bits(uint8_t nibble) { + nibble -= 0x30; // Convert ASCII to numeric value + if (nibble <= 9) return nibble; + nibble &= 0xDF; // Convert to uppercase + nibble -= 7; // Adjust for A-F + if (nibble < 0x10) return nibble; + return -1; +} + +int hex_string_to_bytes_in_place(char *hex) { + size_t len = strlen(hex); + uint8_t *out_pos = (uint8_t *)hex; + size_t pos = 0; + if ((len & 1) != 0) { + // Odd length, assume leading zero + *out_pos++ = nibble2bits(hex[0]); + pos++; + } + + while (pos < len) { + uint8_t high = nibble2bits(hex[pos++]); + uint8_t low = nibble2bits(hex[pos++]); + if (high < 0 || low < 0) { + return -1; // Invalid hex character + } + *out_pos++ = (high << 4) | low; + } + + return out_pos - (uint8_t *)hex; // Return number of bytes written +} + +static n20_crypto_digest_context_t *digest_ctx = NULL; + +int main(int argc, char *argv[]) { + + n20_error_t err = n20_crypto_nat20_open(&digest_ctx); + if (err != n20_error_ok_e) { + fprintf(stderr, "Failed to open digest context: %d\n", err); + return -1; + } + + int opt; + n20_msg_request_t request = {0}; + char const *output_file = NULL; + char const *shortopts = "r:p:k:c:C:g:G:a:A:m:h:i:P:n:o:f:x:u:l:M:"; + + opt = getopt(argc, argv, shortopts); + if (opt != 'r') { + fprintf(stderr, "Request type (-r) is required as first flag\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + request.request_type = parse_request_type(optarg); + if (request.request_type == n20_msg_request_type_none_e) { + fprintf(stderr, "Invalid request type: %s\n", optarg); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + switch (request.request_type) { + case n20_msg_request_type_promote_e: + while ((opt = getopt(argc, argv, shortopts)) != -1) { + switch (opt) { + case 'i': + request.payload.promote.compressed_context.buffer = (uint8_t *)optarg; + break; + default: + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + } + + if (request.payload.promote.compressed_context.buffer) { + int bytes_written = hex_string_to_bytes_in_place( + (char *)request.payload.promote.compressed_context.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for compressed input: %s\n", optarg); + exit(EXIT_FAILURE); + } + request.payload.promote.compressed_context.size = bytes_written; + } + + break; + case n20_msg_request_type_issue_cdi_cert_e: + while ((opt = getopt(argc, argv, shortopts)) != -1) { + switch (opt) { + case 'r': + request.request_type = parse_request_type(optarg); + break; + case 'p': + request.payload.issue_cdi_cert.parent_key_type = parse_key_type(optarg); + break; + case 'k': + request.payload.issue_cdi_cert.key_type = parse_key_type(optarg); + break; + case 'c': + request.payload.issue_cdi_cert.next_context.code_hash.buffer = + (uint8_t *)optarg; + break; + case 'C': + request.payload.issue_cdi_cert.next_context.code_descriptor.buffer = + (uint8_t *)optarg; + break; + case 'g': + request.payload.issue_cdi_cert.next_context.configuration_hash.buffer = + (uint8_t *)optarg; + break; + case 'G': + request.payload.issue_cdi_cert.next_context.configuration_descriptor + .buffer = (uint8_t *)optarg; + break; + case 'a': + request.payload.issue_cdi_cert.next_context.authority_hash.buffer = + (uint8_t *)optarg; + break; + case 'A': + request.payload.issue_cdi_cert.next_context.authority_descriptor.buffer = + (uint8_t *)optarg; + break; + case 'm': + request.payload.issue_cdi_cert.next_context.mode = parse_mode(optarg); + break; + case 'h': + request.payload.issue_cdi_cert.next_context.hidden.buffer = + (uint8_t *)optarg; + break; + case 'P': + request.payload.issue_cdi_cert.next_context.profile_name.buffer = optarg; + request.payload.issue_cdi_cert.next_context.profile_name.size = + strlen(optarg); + break; + case 'n': + if (request.payload.issue_cdi_cert.parent_path_length < + N20_STATELESS_MAX_PATH_LENGTH) { + request.payload.issue_cdi_cert + .parent_path[request.payload.issue_cdi_cert.parent_path_length] + .buffer = (uint8_t *)optarg; + request.payload.issue_cdi_cert.parent_path_length++; + } else { + fprintf(stderr, "Parent path size exceeds maximum length\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + break; + case 'o': + output_file = optarg; + break; + case 'f': + request.payload.issue_cdi_cert.certificate_format = + parse_output_format(optarg); + break; + default: + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + } + if (request.payload.issue_cdi_cert.key_type == n20_crypto_key_type_none_e) { + fprintf(stderr, "Invalid parent key type: %s\n", optarg); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + if (request.payload.issue_cdi_cert.key_type == n20_crypto_key_type_none_e) { + fprintf(stderr, "Invalid key type: %s\n", optarg); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + // Convert hex strings to byte arrays + if (request.payload.issue_cdi_cert.next_context.code_hash.buffer) { + int bytes_written = hex_string_to_bytes_in_place( + (char *)request.payload.issue_cdi_cert.next_context.code_hash.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for code_hash: %s\n", optarg); + exit(EXIT_FAILURE); + } + request.payload.issue_cdi_cert.next_context.code_hash.size = bytes_written; + } + + if (request.payload.issue_cdi_cert.next_context.code_descriptor.buffer) { + int bytes_written = hex_string_to_bytes_in_place( + (char *)request.payload.issue_cdi_cert.next_context.code_descriptor.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for code_desc: %s\n", optarg); + exit(EXIT_FAILURE); + } + request.payload.issue_cdi_cert.next_context.code_descriptor.size = bytes_written; + } + if (request.payload.issue_cdi_cert.next_context.configuration_hash.buffer) { + int bytes_written = hex_string_to_bytes_in_place( + (char *)request.payload.issue_cdi_cert.next_context.configuration_hash.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for configuration_hash: %s\n", optarg); + exit(EXIT_FAILURE); + } + request.payload.issue_cdi_cert.next_context.configuration_hash.size = bytes_written; + } + if (request.payload.issue_cdi_cert.next_context.configuration_descriptor.buffer) { + int bytes_written = + hex_string_to_bytes_in_place((char *)request.payload.issue_cdi_cert.next_context + .configuration_descriptor.buffer); + if (bytes_written < 0) { + fprintf( + stderr, "Invalid hex string for configuration_descriptor: %s\n", optarg); + exit(EXIT_FAILURE); + } + request.payload.issue_cdi_cert.next_context.configuration_descriptor.size = + bytes_written; + } + if (request.payload.issue_cdi_cert.next_context.authority_hash.buffer) { + int bytes_written = hex_string_to_bytes_in_place( + (char *)request.payload.issue_cdi_cert.next_context.authority_hash.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for authority_hash: %s\n", optarg); + exit(EXIT_FAILURE); + } + request.payload.issue_cdi_cert.next_context.authority_hash.size = bytes_written; + } + if (request.payload.issue_cdi_cert.next_context.authority_descriptor.buffer) { + int bytes_written = hex_string_to_bytes_in_place( + (char *) + request.payload.issue_cdi_cert.next_context.authority_descriptor.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for authority_descriptor: %s\n", optarg); + exit(EXIT_FAILURE); + } + request.payload.issue_cdi_cert.next_context.authority_descriptor.size = + bytes_written; + } + if (request.payload.issue_cdi_cert.next_context.hidden.buffer) { + int bytes_written = hex_string_to_bytes_in_place( + (char *)request.payload.issue_cdi_cert.next_context.hidden.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for hidden: %s\n", optarg); + exit(EXIT_FAILURE); + } + request.payload.issue_cdi_cert.next_context.hidden.size = bytes_written; + } + + for (size_t i = 0; i < request.payload.issue_cdi_cert.parent_path_length; ++i) { + int bytes_written = hex_string_to_bytes_in_place( + (char *)request.payload.issue_cdi_cert.parent_path[i].buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for parent path element: %s\n", optarg); + exit(EXIT_FAILURE); + } + request.payload.issue_cdi_cert.parent_path[i].size = bytes_written; + } + + if (request.payload.issue_cdi_cert.certificate_format == + n20_certificate_format_none_e) { + fprintf(stderr, "Invalid certificate format: %s\n", optarg); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + break; + case n20_msg_request_type_issue_eca_cert_e: + while ((opt = getopt(argc, argv, shortopts)) != -1) { + switch (opt) { + case 'r': + request.request_type = parse_request_type(optarg); + break; + case 'p': + request.payload.issue_eca_cert.parent_key_type = parse_key_type(optarg); + break; + case 'k': + request.payload.issue_eca_cert.key_type = parse_key_type(optarg); + break; + case 'n': + if (request.payload.issue_eca_cert.parent_path_length < + N20_STATELESS_MAX_PATH_LENGTH) { + request.payload.issue_eca_cert + .parent_path[request.payload.issue_eca_cert.parent_path_length] + .buffer = (uint8_t *)optarg; + request.payload.issue_eca_cert.parent_path_length++; + } else { + fprintf(stderr, "Parent path size exceeds maximum length\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + break; + case 'o': + output_file = optarg; + break; + case 'f': + request.payload.issue_eca_cert.certificate_format = + parse_output_format(optarg); + break; + case 'x': /* context */ + request.payload.issue_eca_cert.context.buffer = (uint8_t *)optarg; + request.payload.issue_eca_cert.context.size = strlen(optarg); + break; + case 'u': /* key_usage */ + request.payload.issue_eca_cert.key_usage.buffer = (uint8_t *)optarg; + request.payload.issue_eca_cert.key_usage.size = strlen(optarg); + break; + case 'l': /* challenge */ + request.payload.issue_eca_cert.challenge.buffer = (uint8_t *)optarg; + request.payload.issue_eca_cert.challenge.size = strlen(optarg); + break; + default: + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + } + if (request.payload.issue_eca_cert.parent_key_type == n20_crypto_key_type_none_e) { + fprintf(stderr, "Invalid parent key type\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + if (request.payload.issue_eca_cert.key_type == n20_crypto_key_type_none_e) { + fprintf(stderr, "Invalid key type\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + // Convert hex strings to byte arrays for parent path + for (size_t i = 0; i < request.payload.issue_eca_cert.parent_path_length; ++i) { + int bytes_written = hex_string_to_bytes_in_place( + (char *)request.payload.issue_eca_cert.parent_path[i].buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for parent path element: %s\n", optarg); + exit(EXIT_FAILURE); + } + request.payload.issue_eca_cert.parent_path[i].size = bytes_written; + } + + // Convert key_usage from hex string to bytes if provided + if (request.payload.issue_eca_cert.key_usage.buffer != NULL) { + int bytes_written = hex_string_to_bytes_in_place( + (char *)request.payload.issue_eca_cert.key_usage.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for key_usage\n"); + exit(EXIT_FAILURE); + } + request.payload.issue_eca_cert.key_usage.size = bytes_written; + } + + // Convert challenge from hex string to bytes if provided + if (request.payload.issue_eca_cert.challenge.buffer != NULL) { + int bytes_written = hex_string_to_bytes_in_place( + (char *)request.payload.issue_eca_cert.challenge.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for challenge\n"); + exit(EXIT_FAILURE); + } + request.payload.issue_eca_cert.challenge.size = bytes_written; + } + + if (request.payload.issue_eca_cert.certificate_format == + n20_certificate_format_none_e) { + fprintf(stderr, "Invalid certificate format: %s\n", optarg); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + break; + case n20_msg_request_type_eca_sign_e: + while ((opt = getopt(argc, argv, shortopts)) != -1) { + switch (opt) { + case 'r': + request.request_type = parse_request_type(optarg); + break; + case 'p': + request.payload.eca_sign.parent_key_type = parse_key_type(optarg); + break; + case 'k': + request.payload.eca_sign.key_type = parse_key_type(optarg); + break; + case 'n': + if (request.payload.eca_sign.parent_path_length < + N20_STATELESS_MAX_PATH_LENGTH) { + request.payload.eca_sign + .parent_path[request.payload.eca_sign.parent_path_length] + .buffer = (uint8_t *)optarg; + request.payload.eca_sign.parent_path_length++; + } else { + fprintf(stderr, "Parent path size exceeds maximum length\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + break; + case 'o': + output_file = optarg; + break; + case 'x': /* context */ + request.payload.eca_sign.context.buffer = (uint8_t *)optarg; + request.payload.eca_sign.context.size = strlen(optarg); + break; + case 'u': /* key_usage */ + request.payload.eca_sign.key_usage.buffer = (uint8_t *)optarg; + request.payload.eca_sign.key_usage.size = strlen(optarg); + break; + case 'l': /* challenge */ + request.payload.eca_sign.challenge.buffer = (uint8_t *)optarg; + request.payload.eca_sign.challenge.size = strlen(optarg); + break; + case 'M': /* message */ + request.payload.eca_sign.message.buffer = (uint8_t *)optarg; + request.payload.eca_sign.message.size = strlen(optarg); + break; + default: + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + } + if (request.payload.eca_sign.parent_key_type == n20_crypto_key_type_none_e) { + fprintf(stderr, "Invalid parent key type\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + if (request.payload.eca_sign.key_type == n20_crypto_key_type_none_e) { + fprintf(stderr, "Invalid key type\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + // Convert hex strings to byte arrays for parent path + for (size_t i = 0; i < request.payload.eca_sign.parent_path_length; ++i) { + int bytes_written = hex_string_to_bytes_in_place( + (char *)request.payload.eca_sign.parent_path[i].buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for parent path element: %s\n", optarg); + exit(EXIT_FAILURE); + } + request.payload.eca_sign.parent_path[i].size = bytes_written; + } + + // Convert key_usage from hex string to bytes if provided + if (request.payload.eca_sign.key_usage.buffer != NULL) { + int bytes_written = + hex_string_to_bytes_in_place((char *)request.payload.eca_sign.key_usage.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for key_usage\n"); + exit(EXIT_FAILURE); + } + request.payload.eca_sign.key_usage.size = bytes_written; + } + + // Convert challenge from hex string to bytes if provided + if (request.payload.eca_sign.challenge.buffer != NULL) { + int bytes_written = + hex_string_to_bytes_in_place((char *)request.payload.eca_sign.challenge.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for challenge\n"); + exit(EXIT_FAILURE); + } + request.payload.eca_sign.challenge.size = bytes_written; + } + + // Convert message from hex string to bytes + if (request.payload.eca_sign.message.buffer != NULL) { + int bytes_written = + hex_string_to_bytes_in_place((char *)request.payload.eca_sign.message.buffer); + if (bytes_written < 0) { + fprintf(stderr, "Invalid hex string for message\n"); + exit(EXIT_FAILURE); + } + request.payload.eca_sign.message.size = bytes_written; + } + break; + default: + fprintf(stderr, "Unsupported request type: %d\n", request.request_type); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + uint8_t msg_buffer[1024]; + + size_t msg_size = sizeof(msg_buffer); + + err = n20_msg_request_write(&request, msg_buffer, &msg_size); + if (err != n20_error_ok_e) { + fprintf(stderr, "Failed to write request: %d\n", err); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + // Connect to the service + int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (socket_fd < 0) { + perror("socket"); + exit(EXIT_FAILURE); + } + + struct sockaddr_un addr = {0}; + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, "/tmp/gnostic_n20_service.sock", sizeof(addr.sun_path) - 1); + int rc = connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rc < 0) { + perror("connect"); + close(socket_fd); + exit(EXIT_FAILURE); + } + + uint8_t *msg_begin = &msg_buffer[sizeof(msg_buffer) - msg_size]; + + // Send the request based on the request type + if (send(socket_fd, msg_begin, msg_size, 0) < 0) { + perror("send"); + close(socket_fd); + exit(EXIT_FAILURE); + } + + // Read the response + uint8_t response_buffer[1024]; + ssize_t bytes_received = recv(socket_fd, response_buffer, sizeof(response_buffer), 0); + if (bytes_received < 0) { + perror("recv"); + close(socket_fd); + exit(EXIT_FAILURE); + } + + n20_slice_t response_slice = { + .buffer = response_buffer, + .size = (size_t)bytes_received, + }; + + switch (request.request_type) { + case n20_msg_request_type_promote_e: { + n20_msg_error_response_t response; + err = n20_msg_error_response_read(&response, response_slice); + if (err != n20_error_ok_e) { + fprintf(stderr, "Failed to read promote response: %d\n", err); + close(socket_fd); + exit(EXIT_FAILURE); + } + if (response.error_code != n20_error_ok_e) { + fprintf(stderr, + "Promote request failed with error: %d (0x%x\n", + response.error_code, + response.error_code); + close(socket_fd); + exit(EXIT_FAILURE); + } + printf("Promote request successful\n"); + break; + } + case n20_msg_request_type_issue_cdi_cert_e: { + n20_compressed_input_t next_compressed_input; + + err = n20_compress_input( + digest_ctx, &request.payload.issue_cdi_cert.next_context, next_compressed_input); + if (err != n20_error_ok_e) { + fprintf(stderr, "Failed to compress input: %d\n", err); + close(socket_fd); + exit(EXIT_FAILURE); + } + + n20_msg_issue_cdi_cert_response_t response; + err = n20_msg_issue_cdi_cert_response_read(&response, response_slice); + if (err != n20_error_ok_e) { + fprintf(stderr, "Failed to read CDI cert response: %d\n", err); + close(socket_fd); + exit(EXIT_FAILURE); + } + if (response.error_code != n20_error_ok_e) { + fprintf(stderr, "CDI cert request failed with error: %d\n", response.error_code); + close(socket_fd); + exit(EXIT_FAILURE); + } + printf("CDI cert request successful, certificate size: %zu\n", + response.certificate.size); + + if (output_file) { + FILE *file = fopen(output_file, "wb"); + if (!file) { + perror("fopen"); + close(socket_fd); + exit(EXIT_FAILURE); + } + size_t written = + fwrite(response.certificate.buffer, 1, response.certificate.size, file); + if (written != response.certificate.size) { + fprintf(stderr, "Failed to write full certificate to file\n"); + fclose(file); + close(socket_fd); + exit(EXIT_FAILURE); + } + fclose(file); + printf("Certificate written to %s\n", output_file); + } else { + printf("Certificate data: "); + for (size_t i = 0; i < response.certificate.size; ++i) { + printf("%02x", response.certificate.buffer[i]); + } + printf("\n"); + } + // Write the compressed input to standard output + printf("Compressed input: "); + for (size_t i = 0; i < sizeof(next_compressed_input); ++i) { + printf("%02x", next_compressed_input[i]); + } + printf("\n"); + + break; + } + case n20_msg_request_type_issue_eca_cert_e: { + // Debug: print raw response + printf("Raw response (%zu bytes): ", response_slice.size); + for (size_t i = 0; i < response_slice.size && i < 32; ++i) { + printf("%02x", response_slice.buffer[i]); + } + if (response_slice.size > 32) printf("..."); + printf("\n"); + + // First try to read as an error response + n20_msg_error_response_t error_response; + err = n20_msg_error_response_read(&error_response, response_slice); + if (err == n20_error_ok_e && error_response.error_code != n20_error_ok_e) { + fprintf(stderr, + "ECA cert request failed with server error: %d\n", + error_response.error_code); + close(socket_fd); + exit(EXIT_FAILURE); + } + + // If not an error response, try to read as certificate response + n20_msg_issue_eca_cert_response_t response; + err = n20_msg_issue_eca_cert_response_read(&response, response_slice); + if (err != n20_error_ok_e) { + fprintf(stderr, "Failed to read ECA cert response: %d\n", err); + close(socket_fd); + exit(EXIT_FAILURE); + } + if (response.error_code != n20_error_ok_e) { + fprintf(stderr, "ECA cert request failed with error: %d\n", response.error_code); + close(socket_fd); + exit(EXIT_FAILURE); + } + printf("ECA cert request successful, certificate size: %zu\n", + response.certificate.size); + + if (output_file) { + FILE *file = fopen(output_file, "wb"); + if (!file) { + perror("fopen"); + close(socket_fd); + exit(EXIT_FAILURE); + } + size_t written = + fwrite(response.certificate.buffer, 1, response.certificate.size, file); + if (written != response.certificate.size) { + fprintf(stderr, "Failed to write full certificate to file\n"); + fclose(file); + close(socket_fd); + exit(EXIT_FAILURE); + } + fclose(file); + printf("Certificate written to %s\n", output_file); + } else { + printf("Certificate data: "); + for (size_t i = 0; i < response.certificate.size; ++i) { + printf("%02x", response.certificate.buffer[i]); + } + printf("\n"); + } + + break; + } + case n20_msg_request_type_eca_sign_e: { + // First try to read as an error response + n20_msg_error_response_t error_response; + err = n20_msg_error_response_read(&error_response, response_slice); + if (err == n20_error_ok_e && error_response.error_code != n20_error_ok_e) { + fprintf(stderr, + "ECA sign request failed with server error: %d\n", + error_response.error_code); + close(socket_fd); + exit(EXIT_FAILURE); + } + + // If not an error response, try to read as sign response + n20_msg_eca_sign_response_t response; + err = n20_msg_eca_sign_response_read(&response, response_slice); + if (err != n20_error_ok_e) { + fprintf(stderr, "Failed to read ECA sign response: %d\n", err); + close(socket_fd); + exit(EXIT_FAILURE); + } + if (response.error_code != n20_error_ok_e) { + fprintf(stderr, "ECA sign request failed with error: %d\n", response.error_code); + close(socket_fd); + exit(EXIT_FAILURE); + } + printf("ECA sign request successful, signature size: %zu\n", response.signature.size); + + if (output_file) { + FILE *file = fopen(output_file, "wb"); + if (!file) { + perror("fopen"); + close(socket_fd); + exit(EXIT_FAILURE); + } + size_t written = + fwrite(response.signature.buffer, 1, response.signature.size, file); + if (written != response.signature.size) { + fprintf(stderr, "Failed to write full signature to file\n"); + fclose(file); + close(socket_fd); + exit(EXIT_FAILURE); + } + fclose(file); + printf("Signature written to %s\n", output_file); + } else { + printf("Signature data: "); + for (size_t i = 0; i < response.signature.size; ++i) { + printf("%02x", response.signature.buffer[i]); + } + printf("\n"); + } + + break; + } + default: + fprintf(stderr, "Unknown request type in response\n"); + close(socket_fd); + exit(EXIT_FAILURE); + } + + return 0; +} diff --git a/src/service/gnostic.c b/src/service/gnostic.c new file mode 100644 index 0000000..b7d2b1a --- /dev/null +++ b/src/service/gnostic.c @@ -0,0 +1,263 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void n20_gnostic_init(n20_gnostic_node_state_t *node_state, + n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t client_min_cdis[N20_GNOSTIC_MAX_CLIENT_SLOTS]) { + if (node_state == NULL) { + return; + } + + node_state->crypto_context = crypto_ctx; + + if (client_min_cdis == NULL) { + // If no client CDIs are provided, initialize them to NULL. + for (size_t i = 0; i < N20_GNOSTIC_MAX_CLIENT_SLOTS; ++i) { + node_state->client_slots[i].min_cdi = NULL; + } + return; + } + + // If client CDIs are provided, copy them into the client slots. + for (size_t i = 0; i < N20_GNOSTIC_MAX_CLIENT_SLOTS; ++i) { + node_state->client_slots[i].min_cdi = client_min_cdis[i]; + } +} + +n20_error_t n20_gnostic_promote(n20_gnostic_node_state_t *node_state, + size_t client_slot_index, + n20_compressed_input_t compressed_context) { + if (node_state == NULL) { + return n20_error_missing_node_state_e; + } + + if (client_slot_index >= N20_GNOSTIC_MAX_CLIENT_SLOTS) { + return n20_error_client_slot_index_out_of_range_e; + } + + if (node_state->client_slots[client_slot_index].min_cdi == NULL) { + return n20_error_client_slot_empty_e; + } + + n20_crypto_key_t *client_min_cdi = &node_state->client_slots[client_slot_index].min_cdi; + n20_crypto_key_t next = NULL; + + n20_error_t error = n20_next_level_cdi_attest( + node_state->crypto_context, *client_min_cdi, &next, compressed_context); + if (error != n20_error_ok_e) { + return error; + } + + error = node_state->crypto_context->key_free(node_state->crypto_context, *client_min_cdi); + if (error != n20_error_ok_e) { + return error; + } + + // Promote the given CDI to the specified client slot. + *client_min_cdi = next; + return n20_error_ok_e; +} + +static n20_error_t n20_resolve_path(n20_crypto_context_t *crypto_ctx, + n20_crypto_key_t parent_secret, + n20_compressed_input_t *parent_path, + size_t parent_path_size, + n20_crypto_key_t *resolved_key) { + n20_crypto_key_t current_secret = parent_secret; + + if (parent_path_size == 0) { + *resolved_key = current_secret; + return n20_error_ok_e; + } + + size_t i = 0; + + n20_crypto_key_t next = NULL; + n20_error_t error = + n20_next_level_cdi_attest(crypto_ctx, current_secret, &next, parent_path[i]); + if (error != n20_error_ok_e) { + return error; + } + current_secret = next; + ++i; + + while (i < parent_path_size) { + error = n20_next_level_cdi_attest(crypto_ctx, current_secret, &next, parent_path[i]); + crypto_ctx->key_free(crypto_ctx, current_secret); + if (error != n20_error_ok_e) { + return error; + } + current_secret = next; + ++i; + } + + *resolved_key = current_secret; + return n20_error_ok_e; +} + +n20_error_t n20_gnostic_issue_cdi_certificate(n20_gnostic_node_state_t *node_state, + size_t client_slot_index, + n20_crypto_key_type_t parent_key_type, + n20_crypto_key_type_t key_type, + n20_compressed_input_t *parent_path, + size_t parent_path_size, + n20_open_dice_input_t const *next_context, + n20_certificate_format_t certificate_format, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size) { + + if (node_state == NULL || node_state->crypto_context == NULL) { + return n20_error_missing_crypto_context_e; + } + + n20_crypto_key_t parent_secret = node_state->client_slots[client_slot_index].min_cdi; + + n20_error_t error = n20_resolve_path( + node_state->crypto_context, parent_secret, parent_path, parent_path_size, &parent_secret); + if (error != n20_error_ok_e) { + return error; + } + + n20_crypto_key_t parent_attestation_key = NULL; + + error = n20_derive_attestation_key( + node_state->crypto_context, parent_secret, &parent_attestation_key, parent_key_type); + if (error != n20_error_ok_e) { + goto out; + } + + error = n20_opendice_attestation_key_and_certificate(node_state->crypto_context, + parent_secret, + parent_attestation_key, + parent_key_type, + key_type, + next_context, + certificate_format, + attestation_certificate, + attestation_certificate_size); + + node_state->crypto_context->key_free(node_state->crypto_context, parent_attestation_key); + +out: + if (parent_path_size > 0) { + node_state->crypto_context->key_free(node_state->crypto_context, parent_secret); + } + return error; +} + +n20_error_t n20_gnostic_issue_eca_certificate(n20_gnostic_node_state_t *node_state, + size_t client_slot_index, + n20_crypto_key_type_t parent_key_type, + n20_crypto_key_type_t key_type, + n20_compressed_input_t *parent_path, + size_t parent_path_size, + n20_string_slice_t context, + n20_slice_t key_usage, + n20_slice_t challenge, + n20_certificate_format_t certificate_format, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size) { + + if (node_state == NULL || node_state->crypto_context == NULL) { + return n20_error_missing_crypto_context_e; + } + + n20_crypto_key_t parent_secret = node_state->client_slots[client_slot_index].min_cdi; + + n20_error_t error = n20_resolve_path( + node_state->crypto_context, parent_secret, parent_path, parent_path_size, &parent_secret); + if (error != n20_error_ok_e) { + return error; + } + + n20_crypto_key_t parent_attestation_key = NULL; + + error = n20_derive_attestation_key( + node_state->crypto_context, parent_secret, &parent_attestation_key, parent_key_type); + if (error != n20_error_ok_e) { + goto out; + } + + error = n20_eca_attestation_key_and_certificate(node_state->crypto_context, + parent_secret, + parent_attestation_key, + parent_key_type, + key_type, + context, + key_usage, + challenge, + certificate_format, + attestation_certificate, + attestation_certificate_size); + + node_state->crypto_context->key_free(node_state->crypto_context, parent_attestation_key); + +out: + if (parent_path_size > 0) { + node_state->crypto_context->key_free(node_state->crypto_context, parent_secret); + } + return error; +} + +n20_error_t n20_gnostic_eca_sign(n20_gnostic_node_state_t *node_state, + size_t client_slot_index, + n20_crypto_key_type_t key_type, + n20_compressed_input_t *parent_path, + size_t parent_path_size, + n20_string_slice_t context, + n20_slice_t key_usage, + n20_slice_t challenge, + n20_slice_t message, + uint8_t *signature, + size_t *signature_size) { + + if (node_state == NULL || node_state->crypto_context == NULL) { + return n20_error_missing_crypto_context_e; + } + + n20_crypto_key_t parent_secret = node_state->client_slots[client_slot_index].min_cdi; + + n20_error_t error = n20_resolve_path( + node_state->crypto_context, parent_secret, parent_path, parent_path_size, &parent_secret); + if (error != n20_error_ok_e) { + return error; + } + + error = n20_eca_sign_message(node_state->crypto_context, + parent_secret, + key_type, + context, + key_usage, + challenge, + message, + signature, + signature_size); + + if (parent_path_size > 0) { + node_state->crypto_context->key_free(node_state->crypto_context, parent_secret); + } + return error; +} diff --git a/src/service/gnostic_main.c b/src/service/gnostic_main.c new file mode 100644 index 0000000..303e2f4 --- /dev/null +++ b/src/service/gnostic_main.c @@ -0,0 +1,597 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SOCKET_PATH "/tmp/gnostic_n20_service.sock" +#define BACKLOG 1 + +n20_error_t init_gnostic_node_with_bssl_crypto(n20_gnostic_node_state_t *node_state) { + + if (node_state == NULL) { + return n20_error_missing_node_state_e; + } + + *node_state = (n20_gnostic_node_state_t){0}; + + // OpenSSL BSSL crypto context initialization. + n20_crypto_context_t *crypto_bssl_context; + n20_crypto_boringssl_open(&crypto_bssl_context); + if (crypto_bssl_context == NULL) { + return n20_error_crypto_no_resources_e; + } + + n20_slice_t info = {.size = 18, .buffer = (uint8_t *)"example_info_value"}; + + n20_slice_t salt = {.size = 18, .buffer = (uint8_t *)"example_salt_value"}; + + n20_slice_t ikm = {.size = 22, .buffer = (uint8_t *)"example_uds_passphrase"}; + + uint8_t uds[32] = {0}; // Example UDS passphrase buffer. + + n20_error_t rc = crypto_bssl_context->digest_ctx.hkdf(&crypto_bssl_context->digest_ctx, + n20_crypto_digest_algorithm_sha2_256_e, + ikm, + salt, + info, + 32, + uds); + if (rc != n20_error_ok_e) { + n20_crypto_boringssl_close(crypto_bssl_context); + return rc; + } + + n20_slice_t uds_slice = {.size = sizeof(uds), .buffer = uds}; + + n20_crypto_key_t uds_handle = NULL; + + rc = n20_crypto_boringssl_make_secret(crypto_bssl_context, &uds_slice, &uds_handle); + if (rc != n20_error_ok_e) { + n20_crypto_boringssl_close(crypto_bssl_context); + return rc; + } + + n20_crypto_key_t client_min_cdis[N20_GNOSTIC_MAX_CLIENT_SLOTS] = {0}; + client_min_cdis[0] = uds_handle; // Example: Use the UDS as the first client CDI. + + n20_gnostic_init(node_state, crypto_bssl_context, client_min_cdis); + + return n20_error_ok_e; +} + +static n20_error_t export_uds_public_key_as_cose(n20_gnostic_node_state_t *node_state, + n20_crypto_key_type_t key_type, + uint8_t *response_buffer, + size_t *response_size_in_out) { + if (node_state == NULL || response_buffer == NULL || response_size_in_out == NULL) { + return n20_error_insufficient_buffer_size_e; + } + + // Get the UDS public key from the node state. + n20_crypto_key_t uds_public_key = node_state->client_slots[0].min_cdi; + + n20_crypto_key_t derived_key = NULL; + + n20_error_t error = n20_derive_attestation_key( + node_state->crypto_context, uds_public_key, &derived_key, key_type); + if (error != n20_error_ok_e) { + return error; + } + + uint8_t public_key_buffer[97]; + uint8_t *public_key = &public_key_buffer[1]; + size_t public_key_size = sizeof(public_key_buffer) - 1; + + error = node_state->crypto_context->key_get_public_key( + node_state->crypto_context, derived_key, public_key, &public_key_size); + node_state->crypto_context->key_free(node_state->crypto_context, derived_key); + if (error != n20_error_ok_e) { + return error; + } + + n20_cose_key_t cose_key = {0}; + + switch (key_type) { + case n20_crypto_key_type_ed25519_e: + cose_key.algorithm_id = -8; + cose_key.x.buffer = public_key; + cose_key.x.size = public_key_size; + break; + case n20_crypto_key_type_secp256r1_e: + cose_key.algorithm_id = -7; + cose_key.x.buffer = public_key; + cose_key.x.size = public_key_size / 2; + cose_key.y.buffer = &public_key[public_key_size / 2]; + cose_key.y.size = public_key_size / 2; + break; + case n20_crypto_key_type_secp384r1_e: + cose_key.algorithm_id = -35; + cose_key.x.buffer = public_key; + cose_key.x.size = public_key_size; + cose_key.y.buffer = &public_key[public_key_size / 2]; + cose_key.y.size = public_key_size / 2; + break; + default: + return n20_error_crypto_invalid_key_type_e; + } + + n20_set_cose_key_ops(&cose_key.key_ops, n20_cose_key_op_sign_e); + n20_set_cose_key_ops(&cose_key.key_ops, n20_cose_key_op_verify_e); + + n20_stream_t stream; + n20_stream_init(&stream, response_buffer, *response_size_in_out); + + // Write the COSE key to the response buffer. + n20_cose_write_key(&stream, &cose_key); + + if (n20_stream_has_write_position_overflow(&stream)) { + // If the write position overflows, we need to return an error. + return n20_error_write_position_overflow_e; + } + + *response_size_in_out = n20_stream_byte_count(&stream); + + if (n20_stream_has_buffer_overflow(&stream)) { + return n20_error_insufficient_buffer_size_e; + } + + return n20_error_ok_e; +} + +static n20_gnostic_node_state_t service_node_state = {0}; + +n20_error_t dispatch_promote_request(n20_gnostic_node_state_t *node_state, + uint8_t *response_buffer, + size_t *response_size_in_out, + n20_msg_promote_request_t *request, + size_t client_index) { + + if (request->compressed_context.size != sizeof(n20_compressed_input_t)) { + // Handle error: invalid compressed context size + return n20_error_incompatible_compressed_input_size_e; + } + + n20_error_t rc = n20_gnostic_promote( + node_state, client_index, (uint8_t *)request->compressed_context.buffer); + if (rc != n20_error_ok_e) { + // Handle error: promotion failed + return rc; + } + + // Prepare the response message. + n20_msg_error_response_t response = { + .error_code = n20_error_ok_e, + }; + return n20_msg_error_response_write(&response, response_buffer, response_size_in_out); + if (rc != n20_error_ok_e) { + // Handle error: failed to write response + return rc; + } +} + +n20_error_t dispatch_issue_cdi_cert_request(n20_gnostic_node_state_t *node_state, + uint8_t *response_buffer, + size_t *response_size_in_out, + n20_msg_issue_cdi_cert_request_t *request, + size_t client_index) { + + n20_compressed_input_t parent_path[N20_STATELESS_MAX_PATH_LENGTH]; + size_t parent_path_size = request->parent_path_length; + + if (parent_path_size > N20_STATELESS_MAX_PATH_LENGTH) { + // Handle error: parent path size exceeds maximum + return n20_error_parent_path_size_exceeds_max_e; + } + + for (size_t i = 0; i < parent_path_size; ++i) { + if (request->parent_path[i].size != sizeof(n20_compressed_input_t)) { + // Handle error: invalid parent path size + return n20_error_incompatible_compressed_input_size_e; + } + memcpy(&parent_path[i], request->parent_path[i].buffer, sizeof(n20_compressed_input_t)); + } + + size_t const total_buffer_size = *response_size_in_out; + + n20_error_t rc = n20_gnostic_issue_cdi_certificate(node_state, + client_index, + request->parent_key_type, + request->key_type, + parent_path, + parent_path_size, + &request->next_context, + request->certificate_format, + response_buffer, + response_size_in_out); + if (rc != n20_error_ok_e) { + // Handle error: issuing CDI certificate failed + return rc; + } + + if (*response_size_in_out > total_buffer_size) { + // Handle error: response size exceeds buffer size + return n20_error_insufficient_buffer_size_e; + } + + n20_stream_t s; + n20_stream_init(&s, response_buffer, total_buffer_size - *response_size_in_out); + + n20_cbor_write_header(&s, n20_cbor_type_bytes_e, *response_size_in_out); + n20_cbor_write_int(&s, 2); + n20_cbor_write_map_header(&s, 1); + + if (n20_stream_has_buffer_overflow(&s)) { + return n20_error_insufficient_buffer_size_e; + } + + *response_size_in_out += n20_stream_byte_count(&s); + + return n20_error_ok_e; +} + +n20_error_t dispatch_issue_eca_cert_request(n20_gnostic_node_state_t *node_state, + uint8_t *response_buffer, + size_t *response_size_in_out, + n20_msg_issue_eca_cert_request_t *request, + size_t client_index) { + + n20_compressed_input_t parent_path[N20_STATELESS_MAX_PATH_LENGTH]; + size_t parent_path_size = request->parent_path_length; + + if (parent_path_size > N20_STATELESS_MAX_PATH_LENGTH) { + // Handle error: parent path size exceeds maximum + return n20_error_parent_path_size_exceeds_max_e; + } + + for (size_t i = 0; i < parent_path_size; ++i) { + if (request->parent_path[i].size != sizeof(n20_compressed_input_t)) { + // Handle error: invalid parent path size + return n20_error_incompatible_compressed_input_size_e; + } + memcpy(&parent_path[i], request->parent_path[i].buffer, sizeof(n20_compressed_input_t)); + } + + size_t const total_buffer_size = *response_size_in_out; + + n20_error_t rc = n20_gnostic_issue_eca_certificate(node_state, + client_index, + request->parent_key_type, + request->key_type, + parent_path, + parent_path_size, + request->context, + request->key_usage, + request->challenge, + request->certificate_format, + response_buffer, + response_size_in_out); + if (rc != n20_error_ok_e) { + // Handle error: issuing ECA certificate failed + return rc; + } + + if (*response_size_in_out > total_buffer_size) { + // Handle error: response size exceeds buffer size + return n20_error_insufficient_buffer_size_e; + } + + n20_stream_t s; + n20_stream_init(&s, response_buffer, total_buffer_size - *response_size_in_out); + + n20_cbor_write_header(&s, n20_cbor_type_bytes_e, *response_size_in_out); + n20_cbor_write_int(&s, 2); + n20_cbor_write_map_header(&s, 1); + + if (n20_stream_has_buffer_overflow(&s)) { + return n20_error_insufficient_buffer_size_e; + } + + *response_size_in_out += n20_stream_byte_count(&s); + + return n20_error_ok_e; +} + +n20_error_t dispatch_eca_sign_request(n20_gnostic_node_state_t *node_state, + uint8_t *response_buffer, + size_t *response_size_in_out, + n20_msg_eca_sign_request_t *request, + size_t client_index) { + + n20_compressed_input_t parent_path[N20_STATELESS_MAX_PATH_LENGTH]; + size_t parent_path_size = request->parent_path_length; + + if (parent_path_size > N20_STATELESS_MAX_PATH_LENGTH) { + // Handle error: parent path size exceeds maximum + return n20_error_parent_path_size_exceeds_max_e; + } + + for (size_t i = 0; i < parent_path_size; ++i) { + if (request->parent_path[i].size != sizeof(n20_compressed_input_t)) { + // Handle error: invalid parent path size + return n20_error_incompatible_compressed_input_size_e; + } + memcpy(&parent_path[i], request->parent_path[i].buffer, sizeof(n20_compressed_input_t)); + } + + size_t const total_buffer_size = *response_size_in_out; + + n20_error_t rc = n20_gnostic_eca_sign(node_state, + client_index, + request->key_type, + parent_path, + parent_path_size, + request->context, + request->key_usage, + request->challenge, + request->message, + response_buffer, + response_size_in_out); + if (rc != n20_error_ok_e) { + // Handle error: signing failed + return rc; + } + + if (*response_size_in_out > total_buffer_size) { + // Handle error: response size exceeds buffer size + return n20_error_insufficient_buffer_size_e; + } + + // Prepare the response message + // The signature is at the beginning of response_buffer + n20_msg_eca_sign_response_t response = { + .error_code = n20_error_ok_e, + .signature = {.buffer = response_buffer, .size = *response_size_in_out}}; + + *response_size_in_out = total_buffer_size; + return n20_msg_eca_sign_response_write(&response, response_buffer, response_size_in_out); +} + +n20_error_t dispatch_message(n20_gnostic_node_state_t *node_state, + uint8_t *response_buffer, + size_t *response_size_in_out, + n20_slice_t message, + size_t client_index) { + + // Print message in hex format for debugging. + printf("Received message: "); + for (size_t i = 0; i < message.size; ++i) { + printf("%02x", message.buffer[i]); + } + printf("\n"); + + n20_msg_request_t request; + n20_error_t error = n20_msg_request_read(&request, message); + if (error != n20_error_ok_e) { + // Handle error: failed to read message + printf( + "Dispatching message of type: %d failed with error: %d\n", request.request_type, error); + return error; + } + + // Example message dispatch function. + // This function would handle incoming messages and perform actions + // based on the message type. + printf("Dispatching message of type: %d\n", request.request_type); + + size_t const total_buffer_size = *response_size_in_out; + + switch (request.request_type) { + case n20_msg_request_type_promote_e: + error = dispatch_promote_request(node_state, + response_buffer, + response_size_in_out, + &request.payload.promote, + client_index); + break; + case n20_msg_request_type_issue_cdi_cert_e: + error = dispatch_issue_cdi_cert_request(node_state, + response_buffer, + response_size_in_out, + &request.payload.issue_cdi_cert, + client_index); + break; + case n20_msg_request_type_issue_eca_cert_e: + error = dispatch_issue_eca_cert_request(node_state, + response_buffer, + response_size_in_out, + &request.payload.issue_eca_cert, + client_index); + break; + case n20_msg_request_type_eca_sign_e: + error = dispatch_eca_sign_request(node_state, + response_buffer, + response_size_in_out, + &request.payload.eca_sign, + client_index); + break; + default: + // Handle unknown request type + error = n20_error_unrecognized_request_type_e; + break; + } + if (error != n20_error_ok_e) { + // Prepare an error response. + n20_msg_error_response_t error_response = {.error_code = error}; + *response_size_in_out = total_buffer_size; // Reset response size. + return n20_msg_error_response_write(&error_response, response_buffer, response_size_in_out); + } + return n20_error_ok_e; +} + +static void print_uds_public_keys(n20_gnostic_node_state_t *node_state) { + if (node_state == NULL) { + printf("Node state is NULL\n"); + return; + } + + uint8_t key_formatting_buffer[256]; + size_t key_formatting_size = sizeof(key_formatting_buffer); + + n20_error_t error = export_uds_public_key_as_cose( + node_state, n20_crypto_key_type_ed25519_e, key_formatting_buffer, &key_formatting_size); + if (error != n20_error_ok_e) { + printf("Failed to export UDS public key as COSE: %d\n", error); + return; + } + + printf("UDS Public Key ED25519 (COSE format): "); + for (size_t i = 0; i < key_formatting_size; ++i) { + printf("%02x", + key_formatting_buffer[sizeof(key_formatting_buffer) - key_formatting_size + i]); + } + printf("\n"); + + key_formatting_size = sizeof(key_formatting_buffer); + error = export_uds_public_key_as_cose( + node_state, n20_crypto_key_type_secp256r1_e, key_formatting_buffer, &key_formatting_size); + if (error != n20_error_ok_e) { + printf("Failed to export UDS public key as COSE: %d\n", error); + return; + } + + printf("UDS Public Key P256 (COSE format): "); + for (size_t i = 0; i < key_formatting_size; ++i) { + printf("%02x", + key_formatting_buffer[sizeof(key_formatting_buffer) - key_formatting_size + i]); + } + printf("\n"); + + key_formatting_size = sizeof(key_formatting_buffer); + error = export_uds_public_key_as_cose( + node_state, n20_crypto_key_type_secp384r1_e, key_formatting_buffer, &key_formatting_size); + if (error != n20_error_ok_e) { + printf("Failed to export UDS public key as COSE: %d\n", error); + return; + } + + printf("UDS Public Key P384 (COSE format): "); + for (size_t i = 0; i < key_formatting_size; ++i) { + printf("%02x", + key_formatting_buffer[sizeof(key_formatting_buffer) - key_formatting_size + i]); + } + printf("\n"); +} + +int main(void /*int argc, char *argv[]*/) { + + // Initialize the gnostic node state and crypto context. + init_gnostic_node_with_bssl_crypto(&service_node_state); + + // Print the UDS public keys in COSE format. + print_uds_public_keys(&service_node_state); + + int server_fd; + struct sockaddr_un addr; + + // Create socket + server_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_fd == -1) { + perror("socket"); + exit(EXIT_FAILURE); + } + + // Remove any existing socket file + unlink(SOCKET_PATH); + + // Set up the address structure + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1); + + // Bind the socket + if (bind(server_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) { + perror("bind"); + close(server_fd); + exit(EXIT_FAILURE); + } + + // Listen for connections + if (listen(server_fd, BACKLOG) == -1) { + perror("listen"); + close(server_fd); + exit(EXIT_FAILURE); + } + + printf("Listening on %s\n", SOCKET_PATH); + + bool run = true; + + while (run) { + // Accept loop (example, not handling clients here) + int client_fd = accept(server_fd, NULL, NULL); + if (client_fd == -1) { + perror("accept"); + close(server_fd); + exit(EXIT_FAILURE); + } + printf("Client connected\n"); + uint8_t buffer[1024]; + uint8_t response_buffer[1024]; + size_t response_size = sizeof(buffer); + n20_slice_t message = {.size = 0, .buffer = buffer}; + // Read message from client + ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer)); + if (bytes_read < 0) { + perror("read"); + close(client_fd); + continue; + } else if (bytes_read == 0) { + printf("Client disconnected\n"); + close(client_fd); + continue; + } + message.size = (size_t)bytes_read; + printf("Received message of size: %zu\n", message.size); + // Dispatch the message + n20_error_t error = + dispatch_message(&service_node_state, response_buffer, &response_size, message, 0); + if (error != n20_error_ok_e) { + printf("Error dispatching message: %d\n", error); + close(client_fd); + continue; + } + // Write response back to client + // The response is written to the end of the response_buffer. + ssize_t bytes_written = write( + client_fd, &response_buffer[sizeof(response_buffer) - response_size], response_size); + if (bytes_written < 0) { + perror("write"); + } + } + + // Cleanup + close(server_fd); + unlink(SOCKET_PATH); + + return 0; +} diff --git a/src/service/messages.c b/src/service/messages.c new file mode 100644 index 0000000..f9c7eac --- /dev/null +++ b/src/service/messages.c @@ -0,0 +1,959 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +n20_error_t n20_msg_read_map_with_int_key(n20_istream_t *istream, + n20_error_t (*cb)(n20_istream_t *istream, + int64_t key, + void *context), + void *context) { + n20_cbor_type_t cbor_type; + uint64_t map_size; + uint64_t cbor_value; + + if (!n20_read_cbor_header(istream, &cbor_type, &map_size) || cbor_type != n20_cbor_type_map_e) { + // The map must be a CBOR map. + return n20_error_unexpected_message_structure_e; + } + + for (uint64_t i = 0; i < map_size; ++i) { + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + (cbor_type != n20_cbor_type_uint_e && cbor_type != n20_cbor_type_nint_e)) { + // The key must be an integer (either unsigned or negative). + n20_cbor_skip_item(istream); + continue; + } + int64_t key = + cbor_type == n20_cbor_type_nint_e ? -1 - (int64_t)cbor_value : (int64_t)cbor_value; + n20_error_t error = cb(istream, key, context); + if (error != n20_error_ok_e) { + // If the callback returns an error, propagate it. + return error; + } + } + + return n20_error_ok_e; +} + +n20_error_t n20_msg_promote_request_read_cb(n20_istream_t *istream, int64_t key, void *context) { + n20_msg_promote_request_t *request = (n20_msg_promote_request_t *)context; + n20_cbor_type_t cbor_type; + uint64_t cbor_value; + + switch (key) { + case 1: // compressed_context + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The compressed context must be a byte string. + return n20_error_unexpected_message_structure_e; + } + request->compressed_context.size = cbor_value; + request->compressed_context.buffer = n20_istream_get_slice(istream, cbor_value); + break; + default: + // Skip unknown keys. + return n20_cbor_skip_item(istream) ? n20_error_ok_e + : n20_error_unexpected_message_structure_e; + } + + return n20_error_ok_e; +} + +n20_error_t n20_msg_promote_request_read(n20_istream_t *istream, + n20_msg_promote_request_t *request) { + + return n20_msg_read_map_with_int_key(istream, n20_msg_promote_request_read_cb, request); +} + +n20_error_t n20_msg_open_dice_input_read_cb(n20_istream_t *istream, int64_t key, void *context) { + n20_open_dice_input_t *input = (n20_open_dice_input_t *)context; + n20_cbor_type_t cbor_type; + uint64_t cbor_value; + + switch (key) { + case 1: // code_hash + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The code hash must be a byte string. + return n20_error_unexpected_message_structure_e; + } + input->code_hash.size = cbor_value; + input->code_hash.buffer = n20_istream_get_slice(istream, cbor_value); + break; + case 2: // code_descriptor + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The code descriptor must be a byte string. + return n20_error_unexpected_message_structure_e; + } + input->code_descriptor.size = cbor_value; + input->code_descriptor.buffer = n20_istream_get_slice(istream, cbor_value); + break; + case 3: // configuration_hash + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The configuration hash must be a byte string. + return n20_error_unexpected_message_structure_e; + } + input->configuration_hash.size = cbor_value; + input->configuration_hash.buffer = n20_istream_get_slice(istream, cbor_value); + break; + case 4: // configuration_descriptor + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The configuration descriptor must be a byte string. + return n20_error_unexpected_message_structure_e; + } + input->configuration_descriptor.size = cbor_value; + input->configuration_descriptor.buffer = n20_istream_get_slice(istream, cbor_value); + break; + case 5: // authority_hash + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The authority hash must be a byte string. + return n20_error_unexpected_message_structure_e; + } + input->authority_hash.size = cbor_value; + input->authority_hash.buffer = n20_istream_get_slice(istream, cbor_value); + break; + case 6: // authority_descriptor + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The authority descriptor must be a byte string. + return n20_error_unexpected_message_structure_e; + } + input->authority_descriptor.size = cbor_value; + input->authority_descriptor.buffer = n20_istream_get_slice(istream, cbor_value); + break; + case 7: // mode + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The mode must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + if (cbor_value >= n20_open_dice_mode_max_e) { + // The mode value is out of range. + return n20_error_unexpected_message_structure_e; + } + input->mode = (n20_open_dice_mode_t)cbor_value; + break; + case 8: // hidden + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The hidden field must be a byte string. + return n20_error_unexpected_message_structure_e; + } + input->hidden.size = cbor_value; + input->hidden.buffer = n20_istream_get_slice(istream, cbor_value); + break; + case 9: // profile_name + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_string_e) { + // The profile name must be a string. + return n20_error_unexpected_message_structure_e; + } + input->profile_name.size = cbor_value; + input->profile_name.buffer = (char const *)n20_istream_get_slice(istream, cbor_value); + break; + default: + // Skip unknown keys. + n20_cbor_skip_item(istream); + } + return n20_error_ok_e; +} + +n20_error_t n20_msg_open_dice_input_read(n20_istream_t *istream, void *context) { + n20_open_dice_input_t *input = (n20_open_dice_input_t *)context; + + *input = (n20_open_dice_input_t){ + .code_hash = N20_SLICE_NULL, + .code_descriptor = N20_SLICE_NULL, + .configuration_hash = N20_SLICE_NULL, + .configuration_descriptor = N20_SLICE_NULL, + .authority_hash = N20_SLICE_NULL, + .authority_descriptor = N20_SLICE_NULL, + .mode = n20_open_dice_mode_not_configured_e, + .hidden = N20_SLICE_NULL, + .profile_name = N20_STR_NULL, + }; + + n20_msg_read_map_with_int_key(istream, n20_msg_open_dice_input_read_cb, input); + + return n20_error_ok_e; +} + +n20_error_t n20_msg_compressed_context_array_read(n20_istream_t *istream, + n20_slice_t *compressed_context, + size_t *path_length_in_out) { + n20_cbor_type_t cbor_type; + uint64_t cbor_value; + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_array_e) { + // The compressed context must be an array. + return n20_error_unexpected_message_structure_e; + } + + if (cbor_value > *path_length_in_out) { + // The path length exceeds the maximum allowed. + return n20_error_parent_path_size_exceeds_max_e; + } + + *path_length_in_out = cbor_value; + + for (uint64_t i = 0; i < *path_length_in_out; ++i) { + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // Each item in the array must be a byte string. + return n20_error_unexpected_message_structure_e; + } + + compressed_context[i].size = cbor_value; + compressed_context[i].buffer = n20_istream_get_slice(istream, cbor_value); + } + + return n20_error_ok_e; +} + +n20_error_t n20_msg_issue_cdi_cert_request_read_cb(n20_istream_t *istream, + int64_t key, + void *context) { + n20_msg_issue_cdi_cert_request_t *request = (n20_msg_issue_cdi_cert_request_t *)context; + n20_cbor_type_t cbor_type; + uint64_t cbor_value; + n20_error_t error; + + switch (key) { + case 1: // parent_key_type + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The parent key type must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + request->parent_key_type = (n20_crypto_key_type_t)cbor_value; + break; + case 2: // key_type + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The key type must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + request->key_type = (n20_crypto_key_type_t)cbor_value; + break; + case 3: // context + error = n20_msg_open_dice_input_read(istream, &request->next_context); + if (error != n20_error_ok_e) { + return error; + } + break; + case 4: // parent path + request->parent_path_length = N20_STATELESS_MAX_PATH_LENGTH; + error = n20_msg_compressed_context_array_read( + istream, request->parent_path, &request->parent_path_length); + if (error != n20_error_ok_e) { + return error; + } + break; + case 5: // certificate format + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The certificate format must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + request->certificate_format = (n20_certificate_format_t)cbor_value; + break; + default: + // Skip unknown keys. + return n20_cbor_skip_item(istream) ? n20_error_ok_e + : n20_error_unexpected_message_structure_e; + } + + return n20_error_ok_e; +} + +n20_error_t n20_msg_issue_cdi_cert_request_read(n20_istream_t *istream, + n20_msg_issue_cdi_cert_request_t *request) { + + request->parent_path_length = 0; + request->parent_key_type = n20_crypto_key_type_none_e; + request->key_type = n20_crypto_key_type_none_e; + request->next_context = (n20_open_dice_input_t){0}; + + return n20_msg_read_map_with_int_key(istream, n20_msg_issue_cdi_cert_request_read_cb, request); +} + +n20_error_t n20_msg_issue_eca_cert_request_read_cb(n20_istream_t *istream, + int64_t key, + void *context) { + n20_msg_issue_eca_cert_request_t *request = (n20_msg_issue_eca_cert_request_t *)context; + n20_cbor_type_t cbor_type; + uint64_t cbor_value; + n20_error_t error; + + switch (key) { + case 1: // parent_key_type + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The parent key type must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + request->parent_key_type = (n20_crypto_key_type_t)cbor_value; + break; + case 2: // key_type + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The key type must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + request->key_type = (n20_crypto_key_type_t)cbor_value; + break; + case 3: // parent path + request->parent_path_length = N20_STATELESS_MAX_PATH_LENGTH; + error = n20_msg_compressed_context_array_read( + istream, request->parent_path, &request->parent_path_length); + if (error != n20_error_ok_e) { + return error; + } + break; + case 4: // certificate format + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The certificate format must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + request->certificate_format = (n20_certificate_format_t)cbor_value; + break; + case 5: // context + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_string_e) { + // The context must be a text string. + return n20_error_unexpected_message_structure_e; + } + request->context.size = cbor_value; + request->context.buffer = (char const *)n20_istream_get_slice(istream, cbor_value); + break; + case 6: // key_usage + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The key usage must be a byte string. + return n20_error_unexpected_message_structure_e; + } + request->key_usage.size = cbor_value; + request->key_usage.buffer = n20_istream_get_slice(istream, cbor_value); + break; + case 7: // challenge + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The challenge must be a byte string. + return n20_error_unexpected_message_structure_e; + } + request->challenge.size = cbor_value; + request->challenge.buffer = n20_istream_get_slice(istream, cbor_value); + break; + default: + // Skip unknown keys. + return n20_cbor_skip_item(istream) ? n20_error_ok_e + : n20_error_unexpected_message_structure_e; + } + + return n20_error_ok_e; +} + +n20_error_t n20_msg_issue_eca_cert_request_read(n20_istream_t *istream, + n20_msg_issue_eca_cert_request_t *request) { + + request->parent_path_length = 0; + request->parent_key_type = n20_crypto_key_type_none_e; + request->key_type = n20_crypto_key_type_none_e; + request->certificate_format = n20_certificate_format_none_e; + request->context = N20_STR_NULL; + request->key_usage = N20_SLICE_NULL; + request->challenge = N20_SLICE_NULL; + + return n20_msg_read_map_with_int_key(istream, n20_msg_issue_eca_cert_request_read_cb, request); +} + +n20_error_t n20_msg_eca_sign_request_read_cb(n20_istream_t *istream, int64_t key, void *context) { + n20_msg_eca_sign_request_t *request = (n20_msg_eca_sign_request_t *)context; + n20_cbor_type_t cbor_type; + uint64_t cbor_value; + n20_error_t error; + + switch (key) { + case 1: // parent_key_type + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The parent key type must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + request->parent_key_type = (n20_crypto_key_type_t)cbor_value; + break; + case 2: // key_type + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The key type must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + request->key_type = (n20_crypto_key_type_t)cbor_value; + break; + case 3: // parent_path + error = n20_msg_compressed_context_array_read( + istream, request->parent_path, &request->parent_path_length); + if (error != n20_error_ok_e) { + return error; + } + break; + case 5: // context + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_string_e) { + // The context must be a text string. + return n20_error_unexpected_message_structure_e; + } + request->context.size = cbor_value; + request->context.buffer = (char const *)n20_istream_get_slice(istream, cbor_value); + break; + case 6: // key_usage + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The key usage must be a byte string. + return n20_error_unexpected_message_structure_e; + } + request->key_usage.size = cbor_value; + request->key_usage.buffer = n20_istream_get_slice(istream, cbor_value); + break; + case 7: // challenge + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The challenge must be a byte string. + return n20_error_unexpected_message_structure_e; + } + request->challenge.size = cbor_value; + request->challenge.buffer = n20_istream_get_slice(istream, cbor_value); + break; + case 8: // message + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The message must be a byte string. + return n20_error_unexpected_message_structure_e; + } + request->message.size = cbor_value; + request->message.buffer = n20_istream_get_slice(istream, cbor_value); + break; + default: + // Skip unknown keys. + return n20_cbor_skip_item(istream) ? n20_error_ok_e + : n20_error_unexpected_message_structure_e; + } + + return n20_error_ok_e; +} + +n20_error_t n20_msg_eca_sign_request_read(n20_istream_t *istream, + n20_msg_eca_sign_request_t *request) { + + request->parent_path_length = 0; + request->parent_key_type = n20_crypto_key_type_none_e; + request->key_type = n20_crypto_key_type_none_e; + request->context = N20_STR_NULL; + request->key_usage = N20_SLICE_NULL; + request->challenge = N20_SLICE_NULL; + request->message = N20_SLICE_NULL; + + return n20_msg_read_map_with_int_key(istream, n20_msg_eca_sign_request_read_cb, request); +} + +n20_error_t n20_msg_request_read(n20_msg_request_t *request, n20_slice_t const msg_buffer) { + + n20_istream_t istream; + n20_istream_init(&istream, msg_buffer.buffer, msg_buffer.size); + + n20_cbor_type_t cbor_type; + uint64_t cbor_value; + + if (!n20_read_cbor_header(&istream, &cbor_type, &cbor_value)) { + return n20_error_unexpected_message_structure_e; + } + + if (cbor_type != n20_cbor_type_array_e || cbor_value != 2) { + // The request must be an array of length 2. + return n20_error_unexpected_message_structure_e; + } + + // Read the first element, which is the request type. + if (!n20_read_cbor_header(&istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + return n20_error_unexpected_message_structure_e; + } + + request->request_type = (n20_msg_request_type_t)cbor_value; + + switch (cbor_value) { + case n20_msg_request_type_promote_e: + return n20_msg_promote_request_read(&istream, &request->payload.promote); + case n20_msg_request_type_issue_cdi_cert_e: + return n20_msg_issue_cdi_cert_request_read(&istream, &request->payload.issue_cdi_cert); + case n20_msg_request_type_issue_eca_cert_e: + return n20_msg_issue_eca_cert_request_read(&istream, &request->payload.issue_eca_cert); + case n20_msg_request_type_eca_sign_e: + return n20_msg_eca_sign_request_read(&istream, &request->payload.eca_sign); + default: + return n20_error_unrecognized_request_type_e; + } +} + +void n20_msg_promote_request_write(n20_stream_t *stream, n20_msg_promote_request_t const *request) { + n20_cbor_write_byte_string(stream, request->compressed_context); + n20_cbor_write_int(stream, 1); // Key for compressed_context + n20_cbor_write_map_header(stream, 1); // The request is a map with one key-value pair. +} + +void n20_msg_open_dice_input_write(n20_stream_t *stream, n20_open_dice_input_t const *input) { + int pairs = 0; + + if (input->profile_name.size != 0) { + n20_cbor_write_text_string(stream, input->profile_name); + n20_cbor_write_int(stream, 9); // Key for profile_name + ++pairs; + } + + if (input->hidden.size != 0) { + n20_cbor_write_byte_string(stream, input->hidden); + n20_cbor_write_int(stream, 8); // Key for hidden + ++pairs; + } + + if (input->mode != n20_open_dice_mode_not_configured_e) { + n20_cbor_write_int(stream, (uint64_t)input->mode); + n20_cbor_write_int(stream, 7); // Key for mode + ++pairs; + } + + if (input->authority_descriptor.size != 0) { + n20_cbor_write_byte_string(stream, input->authority_descriptor); + n20_cbor_write_int(stream, 6); // Key for authority_descriptor + ++pairs; + } + if (input->authority_hash.size != 0) { + n20_cbor_write_byte_string(stream, input->authority_hash); + n20_cbor_write_int(stream, 5); // Key for authority_hash + ++pairs; + } + if (input->configuration_descriptor.size != 0) { + n20_cbor_write_byte_string(stream, input->configuration_descriptor); + n20_cbor_write_int(stream, 4); // Key for configuration_descriptor + ++pairs; + } + if (input->configuration_hash.size != 0) { + n20_cbor_write_byte_string(stream, input->configuration_hash); + n20_cbor_write_int(stream, 3); // Key for configuration_hash + ++pairs; + } + if (input->code_descriptor.size != 0) { + n20_cbor_write_byte_string(stream, input->code_descriptor); + n20_cbor_write_int(stream, 2); // Key for code_descriptor + ++pairs; + } + + if (input->code_hash.size != 0) { + n20_cbor_write_byte_string(stream, input->code_hash); + n20_cbor_write_int(stream, 1); // Key for code_hash + ++pairs; + } + n20_cbor_write_map_header(stream, pairs); // The request is a map with nine key-value pairs. +} + +void n20_msg_issue_cdi_cert_request_write(n20_stream_t *s, + n20_msg_issue_cdi_cert_request_t const *request) { + int pairs = 4; + + n20_cbor_write_int(s, (uint64_t)request->certificate_format); // certificate format + n20_cbor_write_int(s, 5); // Key for certificate format + + if (request->parent_path_length > 0) { + size_t i = request->parent_path_length; + do { + --i; + n20_cbor_write_byte_string(s, request->parent_path[i]); + } while (i != 0); + n20_cbor_write_array_header(s, request->parent_path_length); + n20_cbor_write_int(s, 4); // Key for parent_path + ++pairs; + } + + n20_msg_open_dice_input_write(s, &request->next_context); + n20_cbor_write_int(s, 3); // Key for dice input + + n20_cbor_write_int(s, (uint64_t)request->key_type); // key type + n20_cbor_write_int(s, 2); // Key for key_type + + n20_cbor_write_int(s, (uint64_t)request->parent_key_type); // parent key type + n20_cbor_write_int(s, 1); // Key for parent_key_type + n20_cbor_write_map_header(s, pairs); // The request is a map with four key-value pairs. +} + +void n20_msg_issue_eca_cert_request_write(n20_stream_t *s, + n20_msg_issue_eca_cert_request_t const *request) { + int pairs = 3; + + // Count additional optional fields + if (request->challenge.size > 0) ++pairs; + if (request->key_usage.size > 0) ++pairs; + if (request->context.size > 0) ++pairs; + if (request->parent_path_length > 0) ++pairs; + + // Write fields in reverse order (because of reverse stream) + if (request->challenge.size > 0) { + n20_cbor_write_byte_string(s, request->challenge); + n20_cbor_write_int(s, 7); // Key for challenge + } + + if (request->key_usage.size > 0) { + n20_cbor_write_byte_string(s, request->key_usage); + n20_cbor_write_int(s, 6); // Key for key_usage + } + + if (request->context.size > 0) { + n20_cbor_write_text_string(s, request->context); + n20_cbor_write_int(s, 5); // Key for context + } + + n20_cbor_write_int(s, (uint64_t)request->certificate_format); // certificate format + n20_cbor_write_int(s, 4); // Key for certificate format + + if (request->parent_path_length > 0) { + size_t i = request->parent_path_length; + do { + --i; + n20_cbor_write_byte_string(s, request->parent_path[i]); + } while (i != 0); + n20_cbor_write_array_header(s, request->parent_path_length); + n20_cbor_write_int(s, 3); // Key for parent_path + } + + n20_cbor_write_int(s, (uint64_t)request->key_type); // key type + n20_cbor_write_int(s, 2); // Key for key_type + + n20_cbor_write_int(s, (uint64_t)request->parent_key_type); // parent key type + n20_cbor_write_int(s, 1); // Key for parent_key_type + n20_cbor_write_map_header(s, pairs); // The request is a map with pairs key-value pairs. +} + +void n20_msg_eca_sign_request_write(n20_stream_t *s, n20_msg_eca_sign_request_t const *request) { + int pairs = 3; // parent_key_type, key_type, message + + // Count additional optional fields + if (request->challenge.size > 0) ++pairs; + if (request->key_usage.size > 0) ++pairs; + if (request->context.size > 0) ++pairs; + if (request->parent_path_length > 0) ++pairs; + + // Write fields in reverse order (because of reverse stream) + n20_cbor_write_byte_string(s, request->message); + n20_cbor_write_int(s, 8); // Key for message + + if (request->challenge.size > 0) { + n20_cbor_write_byte_string(s, request->challenge); + n20_cbor_write_int(s, 7); // Key for challenge + } + + if (request->key_usage.size > 0) { + n20_cbor_write_byte_string(s, request->key_usage); + n20_cbor_write_int(s, 6); // Key for key_usage + } + + if (request->context.size > 0) { + n20_cbor_write_text_string(s, request->context); + n20_cbor_write_int(s, 5); // Key for context + } + + if (request->parent_path_length > 0) { + size_t i = request->parent_path_length; + do { + --i; + n20_cbor_write_byte_string(s, request->parent_path[i]); + } while (i != 0); + n20_cbor_write_array_header(s, request->parent_path_length); + n20_cbor_write_int(s, 3); // Key for parent_path + } + + n20_cbor_write_int(s, (uint64_t)request->key_type); // key type + n20_cbor_write_int(s, 2); // Key for key_type + + n20_cbor_write_int(s, (uint64_t)request->parent_key_type); // parent key type + n20_cbor_write_int(s, 1); // Key for parent_key_type + n20_cbor_write_map_header(s, pairs); // The request is a map with pairs key-value pairs. +} + +n20_error_t n20_msg_request_write(n20_msg_request_t const *request, + uint8_t *buffer, + size_t *buffer_size) { + n20_stream_t stream; + n20_stream_init(&stream, buffer, *buffer_size); + + switch (request->request_type) { + case n20_msg_request_type_promote_e: + n20_msg_promote_request_write(&stream, &request->payload.promote); + break; + case n20_msg_request_type_issue_cdi_cert_e: + n20_msg_issue_cdi_cert_request_write(&stream, &request->payload.issue_cdi_cert); + break; + case n20_msg_request_type_issue_eca_cert_e: + n20_msg_issue_eca_cert_request_write(&stream, &request->payload.issue_eca_cert); + break; + case n20_msg_request_type_eca_sign_e: + n20_msg_eca_sign_request_write(&stream, &request->payload.eca_sign); + break; + default: + return n20_error_unrecognized_request_type_e; + } + + n20_cbor_write_uint(&stream, (uint64_t)request->request_type); + n20_cbor_write_array_header(&stream, 2); // The request is an array of two elements. + + if (n20_stream_has_write_position_overflow(&stream)) { + return n20_error_write_position_overflow_e; + } + + *buffer_size = n20_stream_byte_count(&stream); + + return n20_stream_has_buffer_overflow(&stream) ? n20_error_insufficient_buffer_size_e + : n20_error_ok_e; +} + +n20_error_t n20_msg_issue_cdi_cert_response_read_cb(n20_istream_t *istream, + int64_t key, + void *context) { + n20_msg_issue_cdi_cert_response_t *response = (n20_msg_issue_cdi_cert_response_t *)context; + n20_cbor_type_t cbor_type; + uint64_t cbor_value; + + switch (key) { + case 1: // error_code + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The error code must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + response->error_code = (n20_error_t)cbor_value; + break; + case 2: // certificate + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The certificate must be a byte string. + return n20_error_unexpected_message_structure_e; + } + response->certificate.size = cbor_value; + response->certificate.buffer = n20_istream_get_slice(istream, cbor_value); + break; + default: + // Skip unknown keys. + return n20_cbor_skip_item(istream) ? n20_error_ok_e + : n20_error_unexpected_message_structure_e; + } + + return n20_error_ok_e; +} + +n20_error_t n20_msg_issue_cdi_cert_response_read(n20_msg_issue_cdi_cert_response_t *response, + n20_slice_t buffer) { + n20_istream_t istream; + n20_istream_init(&istream, buffer.buffer, buffer.size); + + response->error_code = n20_error_ok_e; + response->certificate.size = 0; + response->certificate.buffer = NULL; + + return n20_msg_read_map_with_int_key( + &istream, n20_msg_issue_cdi_cert_response_read_cb, response); +} + +n20_error_t n20_msg_issue_cdi_cert_response_write(n20_msg_issue_cdi_cert_response_t const *response, + uint8_t *buffer, + size_t *const buffer_size_in_out) { + n20_stream_t stream; + + if (buffer_size_in_out == NULL) { + return n20_error_unexpected_null_buffer_size_e; + } + + n20_stream_init(&stream, buffer, *buffer_size_in_out); + + if (response->error_code != n20_error_ok_e) { + // If there is an error, we only write the error code and skip the certificate. + n20_cbor_write_uint(&stream, (uint64_t)response->error_code); + n20_cbor_write_uint(&stream, 1); // Key for error_code + } else { + n20_cbor_write_byte_string(&stream, response->certificate); + n20_cbor_write_uint(&stream, 2); // Key for certificate + } + + n20_cbor_write_map_header(&stream, 1); + + if (n20_stream_has_write_position_overflow(&stream)) { + return n20_error_write_position_overflow_e; + } + + *buffer_size_in_out = n20_stream_byte_count(&stream); + + return n20_stream_has_buffer_overflow(&stream) ? n20_error_insufficient_buffer_size_e + : n20_error_ok_e; +} + +n20_error_t n20_msg_error_response_read_cb(n20_istream_t *istream, int64_t key, void *context) { + n20_msg_error_response_t *response = (n20_msg_error_response_t *)context; + n20_cbor_type_t cbor_type; + uint64_t cbor_value; + + switch (key) { + case 1: // error_code + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The error code must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + response->error_code = (n20_error_t)cbor_value; + break; + default: + // Skip unknown keys. + return n20_cbor_skip_item(istream) ? n20_error_ok_e + : n20_error_unexpected_message_structure_e; + } + + return n20_error_ok_e; +} + +n20_error_t n20_msg_error_response_read(n20_msg_error_response_t *response, + n20_slice_t const buffer) { + n20_istream_t istream; + n20_istream_init(&istream, buffer.buffer, buffer.size); + + response->error_code = n20_error_ok_e; + + return n20_msg_read_map_with_int_key(&istream, n20_msg_error_response_read_cb, response); +} + +n20_error_t n20_msg_error_response_write(n20_msg_error_response_t const *response, + uint8_t *buffer, + size_t *const buffer_size_in_out) { + n20_stream_t stream; + int pairs = 0; + + if (buffer_size_in_out == NULL) { + return n20_error_unexpected_null_buffer_size_e; + } + + n20_stream_init(&stream, buffer, *buffer_size_in_out); + + if (response->error_code != n20_error_ok_e) { + // If there is an error, we only write the error code and skip the certificate. + n20_cbor_write_uint(&stream, (uint64_t)response->error_code); + n20_cbor_write_uint(&stream, 1); // Key for error_code + pairs = 1; + } + + n20_cbor_write_map_header(&stream, pairs); + + if (n20_stream_has_write_position_overflow(&stream)) { + return n20_error_write_position_overflow_e; + } + + *buffer_size_in_out = n20_stream_byte_count(&stream); + + return n20_stream_has_buffer_overflow(&stream) ? n20_error_insufficient_buffer_size_e + : n20_error_ok_e; +} + +n20_error_t n20_msg_eca_sign_response_read_cb(n20_istream_t *istream, int64_t key, void *context) { + n20_msg_eca_sign_response_t *response = (n20_msg_eca_sign_response_t *)context; + n20_cbor_type_t cbor_type; + uint64_t cbor_value; + + switch (key) { + case 1: // error_code + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_uint_e) { + // The error code must be an unsigned integer. + return n20_error_unexpected_message_structure_e; + } + response->error_code = (n20_error_t)cbor_value; + break; + case 2: // signature + if (!n20_read_cbor_header(istream, &cbor_type, &cbor_value) || + cbor_type != n20_cbor_type_bytes_e) { + // The signature must be a byte string. + return n20_error_unexpected_message_structure_e; + } + response->signature.size = cbor_value; + response->signature.buffer = n20_istream_get_slice(istream, cbor_value); + break; + default: + // Skip unknown keys. + return n20_cbor_skip_item(istream) ? n20_error_ok_e + : n20_error_unexpected_message_structure_e; + } + + return n20_error_ok_e; +} + +n20_error_t n20_msg_eca_sign_response_read(n20_msg_eca_sign_response_t *response, + n20_slice_t buffer) { + n20_istream_t istream; + n20_istream_init(&istream, buffer.buffer, buffer.size); + + response->error_code = n20_error_ok_e; + response->signature.size = 0; + response->signature.buffer = NULL; + + return n20_msg_read_map_with_int_key(&istream, n20_msg_eca_sign_response_read_cb, response); +} + +n20_error_t n20_msg_eca_sign_response_write(n20_msg_eca_sign_response_t const *response, + uint8_t *buffer, + size_t *const buffer_size_in_out) { + n20_stream_t stream; + + if (buffer_size_in_out == NULL) { + return n20_error_unexpected_null_buffer_size_e; + } + + n20_stream_init(&stream, buffer, *buffer_size_in_out); + + if (response->error_code != n20_error_ok_e) { + // If there is an error, we only write the error code and skip the signature. + n20_cbor_write_uint(&stream, (uint64_t)response->error_code); + n20_cbor_write_uint(&stream, 1); // Key for error_code + } else { + n20_cbor_write_byte_string(&stream, response->signature); + n20_cbor_write_uint(&stream, 2); // Key for signature + } + + n20_cbor_write_map_header(&stream, 1); + + if (n20_stream_has_write_position_overflow(&stream)) { + return n20_error_write_position_overflow_e; + } + + *buffer_size_in_out = n20_stream_byte_count(&stream); + + return n20_stream_has_buffer_overflow(&stream) ? n20_error_insufficient_buffer_size_e + : n20_error_ok_e; +} diff --git a/src/service/messages.cddl b/src/service/messages.cddl new file mode 100644 index 0000000..78c6844 --- /dev/null +++ b/src/service/messages.cddl @@ -0,0 +1,97 @@ +; Copyright 2025 Aurora Operations, Inc. +; +; 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. + +N20_Error = ( + 1 : &N20_ErrorCode +) + +N20_ErrorCode = ( + OK : 0, + MISSING_CRYPTO_CONTEXT : 1, + INSUFFICIENT_BUFFER_SIZE : 3, + UNEXPECTED_NULL_BUFFER_SIZE : 4, + UNEXPECTED_NULL_KEY_HANDLE : 5, + MISSING_NODE_STATE : 6, + CLIENT_SLOT_INDEX_OUT_OF_RANGE : 7, + CLIENT_SLOT_EMPTY : 8, + PARENT_PATH_SIZE_EXCEEDS_MAX : 9, + MISSING_PARENT_SERVICE : 10, + MISSING_CALLBACK_FUNCTION_OR_CONTEXT : 11, + CRYPTO_INVALID_CONTEXT : 0x1001, + CRYPTO_UNEXPECTED_NULL_KEY_IN : 0x1002, + CRYPTO_UNEXPECTED_NULL_KEY_OUT : 0x1003, + CRYPTO_UNEXPECTED_NULL_SIZE : 0x1004, + CRYPTO_UNEXPECTED_NULL_DATA : 0x1005, + CRYPTO_UNEXPECTED_NULL_LIST : 0x1006, + CRYPTO_UNEXPECTED_NULL_SLICE : 0x1007, + CRYPTO_NOT_IMPLEMENTED : 0x1008, + CRYPTO_UNKNOWN_ALGORITHM : 0x1009, + CRYPTO_INVALID_KEY : 0x100a, + CRYPTO_INVALID_KEY_TYPE : 0x100b, + CRYPTO_INSUFFICIENT_BUFFER_SIZE : 0x100c, + CRYPTO_UNEXPECTED_NULL : 0x100d, + CRYPTO_NO_RESOURCES : 0x100e, + CRYPTO_IMPLEMENTATION_SPECIFIC : 0x100f, + CRYPTO_UNEXPECTED_NULL_OUTPUT_BUFFER : 0x1010, +) + +N20_ResponsePayload = ( + ISSUE_CDI_CERT : { + 1 => bstr, ; certificate + } +) + +N20_Response = { + ? 1 => &N20_Error, ; error code + ? 2 => &N20_ResponsePayload +} + +N20_RequestType = ( + PROMOTE : 0x01, + ISSUE_CDI_CERT : 0x02, +) + +N20_Context = { + ? 1 => bstr, ; code hash + ? 2 => bstr, ; code descriptor + ? 3 => bstr, ; configuration hash + ? 4 => bstr, ; configuration descriptor + ? 5 => bstr, ; authority hash + ? 6 => bstr, ; authority descriptor + ? 7 => bstr, ; mode + ? 8 => tstr, ; profile name +} + +N20_CompressedContext = { + 1 => bstr, ; compressed context +} + +N20_CompressedContextPath = [ + *N20_CompressedContext, ; list of compressed contexts +] + +N20_RequestPayload = ( + PROMOTE : { + 1 => N20_CompressedContext, ; compressed context, + }, + ISSUE_CDI_CERT : { + 1 => N20_Context, + 2 => N20_CompressedContextPath, ; parent path, + } +) + +N20_Request = { + 1 => &N20_RequestType + 2 => &N20_RequestPayload +} diff --git a/src/service/proxy.c b/src/service/proxy.c new file mode 100644 index 0000000..7b89811 --- /dev/null +++ b/src/service/proxy.c @@ -0,0 +1,130 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void n20_proxy_init(n20_proxy_node_state_t *node_state, + n20_crypto_digest_context_t *crypto_ctx, + n20_compressed_input_t client_identity[N20_PROXY_MAX_CLIENT_SLOTS]) { + if (node_state == NULL) { + return; + } + + node_state->crypto_digest_context = crypto_ctx; + + if (client_identity == NULL) { + // If no client CDIs are provided, initialize them to NULL. + for (size_t i = 0; i < N20_PROXY_MAX_CLIENT_SLOTS; ++i) { + node_state->client_slots[i].parent_path_size = 0; + } + return; + } + + // If client CDIs are provided, copy them into the client slots. + for (size_t i = 0; i < N20_PROXY_MAX_CLIENT_SLOTS; ++i) { + memcpy(&node_state->client_slots[i].parent_path[0], + client_identity[i], + sizeof(n20_compressed_input_t)); + node_state->client_slots[i].parent_path_size = 1; + } +} + +n20_error_t n20_proxy_promote(n20_proxy_node_state_t *node_state, + size_t client_slot_index, + n20_compressed_input_t compressed_context) { + if (node_state == NULL) { + return n20_error_missing_node_state_e; + } + + if (client_slot_index >= N20_PROXY_MAX_CLIENT_SLOTS) { + return n20_error_client_slot_index_out_of_range_e; + } + + if (node_state->client_slots[client_slot_index].parent_path_size == 0) { + // If the client slot is empty, we cannot promote. + return n20_error_client_slot_empty_e; + } + + if (node_state->client_slots[client_slot_index].parent_path_size >= + N20_PROXY_MAX_PARENT_PATH_SIZE) { + // If the parent path size exceeds the maximum, we cannot promote. + return n20_error_parent_path_size_exceeds_max_e; + } + + size_t parent_path_size = node_state->client_slots[client_slot_index].parent_path_size; + n20_compressed_input_t(*parent_path)[] = + &node_state->client_slots[client_slot_index].parent_path; + memcpy((*parent_path)[parent_path_size], compressed_context, sizeof(n20_compressed_input_t)); + + node_state->client_slots[client_slot_index].parent_path_size += 1; + + return n20_error_ok_e; +} + +n20_error_t n20_proxy_issue_cdi_certificate(n20_proxy_node_state_t *node_state, + size_t client_slot_index, + n20_crypto_key_type_t parent_key_type, + n20_crypto_key_type_t key_type, + n20_compressed_input_t *parent_path, + size_t parent_path_size, + n20_open_dice_input_t const *next_context, + uint8_t *attestation_certificate, + size_t *attestation_certificate_size) { + + if (node_state == NULL) { + return n20_error_missing_node_state_e; + } + + n20_parent_service_t *parent_service = node_state->parent_service; + if (parent_service == NULL) { + return n20_error_missing_parent_service_e; + } + size_t client_parent_path_size = node_state->client_slots[client_slot_index].parent_path_size; + + size_t total_parent_path_size = client_parent_path_size + parent_path_size; + + /* Overflow check. */ + if (total_parent_path_size < client_parent_path_size) { + return n20_error_parent_path_size_exceeds_max_e; + } + + /* Parent path limit check. */ + if (total_parent_path_size > N20_PROXY_MAX_PARENT_PATH_SIZE) { + return n20_error_parent_path_size_exceeds_max_e; + } + + memcpy(&node_state->client_slots[client_slot_index].parent_path[client_parent_path_size], + parent_path, + parent_path_size * sizeof(n20_compressed_input_t)); + + return parent_service->issue_cdi_certificate( + parent_service, + parent_key_type, + key_type, + (n20_compressed_input_t const *)&node_state->client_slots[client_slot_index].parent_path[0], + total_parent_path_size, + next_context, + attestation_certificate, + attestation_certificate_size); +} diff --git a/src/service/service.c b/src/service/service.c new file mode 100644 index 0000000..ff81375 --- /dev/null +++ b/src/service/service.c @@ -0,0 +1,21 @@ +/* + * Copyright 2025 Aurora Operations, Inc. + * + * 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. + */ + +#include + +typedef struct n20_service_context_s { + n20_crypto_context_t crypto_ctx; +} n20_service_context_t;