diff --git a/sw/device/tests/crypto/cryptotest/firmware/BUILD b/sw/device/tests/crypto/cryptotest/firmware/BUILD index f1d7a12845e8a2..ba3614b4c05c1f 100644 --- a/sw/device/tests/crypto/cryptotest/firmware/BUILD +++ b/sw/device/tests/crypto/cryptotest/firmware/BUILD @@ -28,6 +28,25 @@ cc_library( ], ) +cc_library( + name = "ecdsa", + srcs = ["ecdsa.c"], + hdrs = ["ecdsa.h"], + deps = [ + "//sw/device/lib/base:memory", + "//sw/device/lib/base:status", + "//sw/device/lib/crypto/impl:ecc", + "//sw/device/lib/crypto/impl/ecc:p256_common", + "//sw/device/lib/crypto/impl:integrity", + "//sw/device/lib/crypto/impl:keyblob", + "//sw/device/lib/crypto/include:datatypes", + "//sw/device/lib/runtime:log", + "//sw/device/lib/testing/test_framework:ujson_ottf", + "//sw/device/lib/ujson", + "//sw/device/tests/crypto/cryptotest/json:ecdsa_commands", + ], +) + cc_library( name = "aes_sca", srcs = ["aes_sca.c"], @@ -138,6 +157,7 @@ opentitan_binary( ], deps = [ ":aes", + ":ecdsa", ":aes_sca", ":ibex_fi", ":kmac_sca", diff --git a/sw/device/tests/crypto/cryptotest/firmware/ecdsa.c b/sw/device/tests/crypto/cryptotest/firmware/ecdsa.c new file mode 100644 index 00000000000000..e32138c16a676b --- /dev/null +++ b/sw/device/tests/crypto/cryptotest/firmware/ecdsa.c @@ -0,0 +1,137 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/base/memory.h" +#include "sw/device/lib/base/status.h" +#include "sw/device/lib/crypto/impl/ecc/p256_common.h" +#include "sw/device/lib/crypto/impl/integrity.h" +#include "sw/device/lib/crypto/impl/keyblob.h" +#include "sw/device/lib/crypto/include/datatypes.h" +#include "sw/device/lib/crypto/include/ecc.h" +#include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/testing/test_framework/ujson_ottf.h" +#include "sw/device/lib/ujson/ujson.h" +#include "sw/device/tests/crypto/cryptotest/json/ecdsa_commands.h" + +status_t handle_ecdsa_block(ujson_t *uj) { + // Declare ECDSA parameter ujson deserializer types + cryptotest_ecdsa_operation_t uj_op; + cryptotest_ecdsa_hash_alg_t uj_hash_alg; + cryptotest_ecdsa_curve_t uj_curve; + cryptotest_ecdsa_message_t uj_message; + cryptotest_ecdsa_signature_t uj_signature; + cryptotest_ecdsa_coordinate_t uj_qx; + cryptotest_ecdsa_coordinate_t uj_qy; + + // Deserialize ujson byte stream into ECDSA parameters + TRY(ujson_deserialize_cryptotest_ecdsa_operation_t(uj, &uj_op)); + TRY(ujson_deserialize_cryptotest_ecdsa_hash_alg_t(uj, &uj_hash_alg)); + TRY(ujson_deserialize_cryptotest_ecdsa_curve_t(uj, &uj_curve)); + TRY(ujson_deserialize_cryptotest_ecdsa_message_t(uj, &uj_message)); + TRY(ujson_deserialize_cryptotest_ecdsa_signature_t(uj, &uj_signature)); + TRY(ujson_deserialize_cryptotest_ecdsa_coordinate_t(uj, &uj_qx)); + TRY(ujson_deserialize_cryptotest_ecdsa_coordinate_t(uj, &uj_qy)); + + otcrypto_ecc_curve_type_t curve_type; + otcrypto_unblinded_key_t public_key; + p256_point_t pub_p256; + + switch (uj_curve) { + case kCryptotestEcdsaCurveP256: + curve_type = kOtcryptoEccCurveTypeNistP256; + memset(pub_p256.x, 0, kP256CoordWords * 4); + memcpy(pub_p256.x, uj_qx.coordinate, uj_qx.coordinate_len); + memset(pub_p256.y, 0, kP256CoordWords * 4); + memcpy(pub_p256.y, uj_qy.coordinate, uj_qy.coordinate_len); + public_key.key_mode = kOtcryptoKeyModeEcdsa; + public_key.key_length = sizeof(p256_point_t); + public_key.key = (uint32_t *)&pub_p256; + break; + default: + LOG_ERROR("Unsupported ECC curve: %d", uj_curve); + return INVALID_ARGUMENT(); + } + public_key.checksum = integrity_unblinded_checksum(&public_key); + + otcrypto_ecc_curve_t elliptic_curve = { + .curve_type = curve_type, + // NULL because we use a named curve + .domain_parameter = NULL, + }; + otcrypto_hash_mode_t mode; + switch (uj_hash_alg) { + case kCryptotestEcdsaHashAlgSha256: + mode = kOtcryptoHashModeSha256; + break; + case kCryptotestEcdsaHashAlgSha512: + mode = kOtcryptoHashModeSha512; + break; + case kCryptotestEcdsaHashAlgSha3_256: + mode = kOtcryptoHashModeSha3_256; + break; + case kCryptotestEcdsaHashAlgSha3_512: + mode = kOtcryptoHashModeSha3_512; + break; + default: + LOG_ERROR("Unrecognized ECDSA hash mode: %d", uj_hash_alg); + return INVALID_ARGUMENT(); + } + uint8_t message_buf[ECDSA_CMD_MAX_MESSAGE_BYTES]; + memcpy(message_buf, uj_message.input, uj_message.input_len); + const otcrypto_hash_digest_t message_digest = { + .mode = mode, + .len = uj_message.input_len / 4, // 32-bit words + .data = (uint32_t *)message_buf, + }; + uint8_t signature_buf[ECDSA_CMD_MAX_SIGNATURE_BYTES]; + switch (uj_op) { + case kCryptotestEcdsaOperationVerify: { + memcpy(signature_buf, uj_signature.signature, uj_signature.signature_len); + otcrypto_const_word32_buf_t signature = { + .data = (uint32_t *)signature_buf, + .len = uj_signature.signature_len, + }; + hardened_bool_t verification_result = kHardenedBoolFalse; + otcrypto_status_t status = + otcrypto_ecdsa_verify(&public_key, &message_digest, signature, + &elliptic_curve, &verification_result); + if (status.value != kOk) { + return INTERNAL(status.value); + } + cryptotest_ecdsa_verify_output_t uj_output; + switch (verification_result) { + case kHardenedBoolFalse: + uj_output = kCryptotestEcdsaVerifyOutputFailure; + break; + case kHardenedBoolTrue: + uj_output = kCryptotestEcdsaVerifyOutputSuccess; + break; + default: + LOG_ERROR("Unexpected result value from otcrypto_ecdsa_verify: %d", + verification_result); + return INTERNAL(); + } + RESP_OK(ujson_serialize_cryptotest_ecdsa_verify_output_t, uj, &uj_output); + break; + } + default: + LOG_ERROR("Unrecognized ECDSA operation: %d", uj_op); + return INVALID_ARGUMENT(); + } + return OK_STATUS(0); +} + +status_t handle_ecdsa(ujson_t *uj) { + ecdsa_subcommand_t cmd; + TRY(ujson_deserialize_ecdsa_subcommand_t(uj, &cmd)); + switch (cmd) { + case kEcdsaSubcommandEcdsaBlock: + return handle_ecdsa_block(uj); + break; + default: + LOG_ERROR("Unrecognized ECDSA subcommand: %d", cmd); + return INVALID_ARGUMENT(); + } + return OK_STATUS(0); +} diff --git a/sw/device/tests/crypto/cryptotest/firmware/ecdsa.h b/sw/device/tests/crypto/cryptotest/firmware/ecdsa.h new file mode 100644 index 00000000000000..288b9d88a4ebf1 --- /dev/null +++ b/sw/device/tests/crypto/cryptotest/firmware/ecdsa.h @@ -0,0 +1,14 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_SW_DEVICE_TESTS_CRYPTO_CRYPTOTEST_FIRMWARE_ECDSA_H_ +#define OPENTITAN_SW_DEVICE_TESTS_CRYPTO_CRYPTOTEST_FIRMWARE_ECDSA_H_ + +#include "sw/device/lib/base/status.h" +#include "sw/device/lib/ujson/ujson.h" + +status_t handle_ecdsa_block(ujson_t *uj); +status_t handle_ecdsa(ujson_t *uj); + +#endif // OPENTITAN_SW_DEVICE_TESTS_CRYPTO_CRYPTOTEST_FIRMWARE_ECDSA_H_ diff --git a/sw/device/tests/crypto/cryptotest/json/BUILD b/sw/device/tests/crypto/cryptotest/json/BUILD index 01466fd2492e5c..b478af57c86adf 100644 --- a/sw/device/tests/crypto/cryptotest/json/BUILD +++ b/sw/device/tests/crypto/cryptotest/json/BUILD @@ -18,6 +18,13 @@ cc_library( deps = ["//sw/device/lib/ujson"], ) +cc_library( + name = "ecdsa_commands", + srcs = ["ecdsa_commands.c"], + hdrs = ["ecdsa_commands.h"], + deps = ["//sw/device/lib/ujson"], +) + cc_library( name = "aes_sca_commands", srcs = ["aes_sca_commands.c"], diff --git a/sw/device/tests/crypto/cryptotest/json/commands.h b/sw/device/tests/crypto/cryptotest/json/commands.h index 0338a5b9deeb3e..29d5e44be76e86 100644 --- a/sw/device/tests/crypto/cryptotest/json/commands.h +++ b/sw/device/tests/crypto/cryptotest/json/commands.h @@ -13,6 +13,7 @@ extern "C" { #define COMMAND(_, value) \ value(_, Aes) \ + value(_, Ecdsa) \ value(_, AesSca) \ value(_, IbexFi) \ value(_, KmacSca) \ diff --git a/sw/device/tests/crypto/cryptotest/json/ecdsa_commands.c b/sw/device/tests/crypto/cryptotest/json/ecdsa_commands.c new file mode 100644 index 00000000000000..b0e14d41403f8d --- /dev/null +++ b/sw/device/tests/crypto/cryptotest/json/ecdsa_commands.c @@ -0,0 +1,6 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#define UJSON_SERDE_IMPL 1 +#include "ecdsa_commands.h" diff --git a/sw/device/tests/crypto/cryptotest/json/ecdsa_commands.h b/sw/device/tests/crypto/cryptotest/json/ecdsa_commands.h new file mode 100644 index 00000000000000..01d9196ab8b9bd --- /dev/null +++ b/sw/device/tests/crypto/cryptotest/json/ecdsa_commands.h @@ -0,0 +1,62 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_SW_DEVICE_TESTS_CRYPTO_CRYPTOTEST_JSON_ECDSA_COMMANDS_H_ +#define OPENTITAN_SW_DEVICE_TESTS_CRYPTO_CRYPTOTEST_JSON_ECDSA_COMMANDS_H_ +#include "sw/device/lib/ujson/ujson_derive.h" +#ifdef __cplusplus +extern "C" { +#endif + +#define ECDSA_CMD_MAX_MESSAGE_BYTES 20 +#define ECDSA_CMD_MAX_SIGNATURE_BYTES 72 +#define ECDSA_CMD_MAX_COORDINATE_BYTES 33 + +// clang-format off + +#define ECDSA_SUBCOMMAND(_, value) \ + value(_, EcdsaBlock) +UJSON_SERDE_ENUM(EcdsaSubcommand, ecdsa_subcommand_t, ECDSA_SUBCOMMAND); + +#define ECDSA_OPERATION(_, value) \ + value(_, Verify) +UJSON_SERDE_ENUM(CryptotestEcdsaOperation, cryptotest_ecdsa_operation_t, ECDSA_OPERATION); + +#define ECDSA_HASH_ALG(_, value) \ + value(_, Sha256) \ + value(_, Sha512) \ + value(_, Sha3_256) \ + value(_, Sha3_512) +UJSON_SERDE_ENUM(CryptotestEcdsaHashAlg, cryptotest_ecdsa_hash_alg_t, ECDSA_HASH_ALG); + +#define ECDSA_MESSAGE(field, string) \ + field(input, uint8_t, ECDSA_CMD_MAX_MESSAGE_BYTES) \ + field(input_len, size_t) +UJSON_SERDE_STRUCT(CryptotestEcdsaMessage, cryptotest_ecdsa_message_t, ECDSA_MESSAGE); + +#define ECDSA_SIGNATURE(field, string) \ + field(signature, uint8_t, ECDSA_CMD_MAX_SIGNATURE_BYTES) \ + field(signature_len, size_t) +UJSON_SERDE_STRUCT(CryptotestEcdsaSignature, cryptotest_ecdsa_signature_t, ECDSA_SIGNATURE); + +#define ECDSA_CURVE(_, value) \ + value(_, P256) +UJSON_SERDE_ENUM(CryptotestEcdsaCurve, cryptotest_ecdsa_curve_t, ECDSA_CURVE); + +#define ECDSA_COORDINATE(field, string) \ + field(coordinate, uint8_t, ECDSA_CMD_MAX_COORDINATE_BYTES) \ + field(coordinate_len, size_t) +UJSON_SERDE_STRUCT(CryptotestEcdsaCoordinate, cryptotest_ecdsa_coordinate_t, ECDSA_COORDINATE); + +#define ECDSA_VERIFY_OUTPUT(_, value) \ + value(_, Success) \ + value(_, Failure) +UJSON_SERDE_ENUM(CryptotestEcdsaVerifyOutput, cryptotest_ecdsa_verify_output_t, ECDSA_VERIFY_OUTPUT); + +// clang-format on + +#ifdef __cplusplus +} +#endif +#endif // OPENTITAN_SW_DEVICE_TESTS_CRYPTO_CRYPTOTEST_JSON_ECDSA_COMMANDS_H_ diff --git a/sw/host/cryptotest/ujson_lib/BUILD b/sw/host/cryptotest/ujson_lib/BUILD index 7b2644e25361bc..ce5106ab348b41 100644 --- a/sw/host/cryptotest/ujson_lib/BUILD +++ b/sw/host/cryptotest/ujson_lib/BUILD @@ -17,20 +17,28 @@ ujson_rust( srcs = ["//sw/device/tests/crypto/cryptotest/json:aes_commands"], ) +ujson_rust( + name = "ecdsa_commands_rust", + srcs = ["//sw/device/tests/crypto/cryptotest/json:ecdsa_commands"], +) + rust_library( name = "cryptotest_commands", srcs = [ "src/aes_commands.rs", + "src/ecdsa_commands.rs", "src/commands.rs", "src/lib.rs", ], compile_data = [ ":commands_rust", ":aes_commands_rust", + ":ecdsa_commands_rust", ], rustc_env = { "commands_loc": "$(execpath :commands_rust)", "aes_commands_loc": "$(execpath :aes_commands_rust)", + "ecdsa_commands_loc": "$(execpath :ecdsa_commands_rust)", }, deps = [ "//sw/host/opentitanlib", diff --git a/sw/host/cryptotest/ujson_lib/src/ecdsa_commands.rs b/sw/host/cryptotest/ujson_lib/src/ecdsa_commands.rs new file mode 100644 index 00000000000000..f3648388feb5fd --- /dev/null +++ b/sw/host/cryptotest/ujson_lib/src/ecdsa_commands.rs @@ -0,0 +1,4 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +include!(env!("ecdsa_commands_loc")); diff --git a/sw/host/cryptotest/ujson_lib/src/lib.rs b/sw/host/cryptotest/ujson_lib/src/lib.rs index 1ef1b0dde481ea..ac0662642d22ad 100644 --- a/sw/host/cryptotest/ujson_lib/src/lib.rs +++ b/sw/host/cryptotest/ujson_lib/src/lib.rs @@ -2,4 +2,5 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 pub mod aes_commands; +pub mod ecdsa_commands; pub mod commands; diff --git a/sw/host/tests/crypto/ecdsa_kat/BUILD b/sw/host/tests/crypto/ecdsa_kat/BUILD new file mode 100644 index 00000000000000..2b3babafbe1285 --- /dev/null +++ b/sw/host/tests/crypto/ecdsa_kat/BUILD @@ -0,0 +1,25 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +load("//rules:ujson.bzl", "ujson_rust") +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_binary( + name = "harness", + srcs = ["src/main.rs"], + deps = [ + "//sw/host/cryptotest/ujson_lib:cryptotest_commands", + "//sw/host/opentitanlib", + "@crate_index//:anyhow", + "@crate_index//:arrayvec", + "@crate_index//:clap", + "@crate_index//:humantime", + "@crate_index//:num-bigint-dig", + "@crate_index//:log", + "@crate_index//:serde", + "@crate_index//:serde_json", + ], +) diff --git a/sw/host/tests/crypto/ecdsa_kat/src/main.rs b/sw/host/tests/crypto/ecdsa_kat/src/main.rs new file mode 100644 index 00000000000000..eb0d9cbd39f96b --- /dev/null +++ b/sw/host/tests/crypto/ecdsa_kat/src/main.rs @@ -0,0 +1,153 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use arrayvec::ArrayVec; +use clap::Parser; +use num_bigint_dig::BigUint; +use std::fs; +use std::time::Duration; + +use serde::Deserialize; + +use cryptotest_commands::ecdsa_commands::{ + EcdsaSubcommand, CryptotestEcdsaOperation, CryptotestEcdsaHashAlg, CryptotestEcdsaCurve, CryptotestEcdsaMessage, CryptotestEcdsaSignature, CryptotestEcdsaCoordinate, CryptotestEcdsaVerifyOutput, +}; +use cryptotest_commands::commands::CryptotestCommand; + +use opentitanlib::app::TransportWrapper; +use opentitanlib::execute_test; +use opentitanlib::test_utils::init::InitializeTest; +use opentitanlib::test_utils::rpc::{UartRecv, UartSend}; +use opentitanlib::uart::console::UartConsole; + +#[derive(Debug, Parser)] +struct Opts { + #[command(flatten)] + init: InitializeTest, + + // Console receive timeout. + #[arg(long, value_parser = humantime::parse_duration, default_value = "10s")] + timeout: Duration, + + #[arg(long, num_args = 1..)] + ecdsa_json: Vec, +} + +#[derive(Debug, Deserialize)] +struct EcdsaTestCase { + algorithm: String, + operation: String, + curve: String, + hash_alg: String, + message: Vec, + qx: BigUint, + qy: BigUint, + // Signature + s: Vec, + result: bool, +} + +const ECDSA_CMD_MAX_MESSAGE_BYTES: usize = 20; +const ECDSA_CMD_MAX_SIGNATURE_BYTES: usize = 72; +const ECDSA_CMD_MAX_COORDINATE_BYTES: usize = 33; + +fn run_ecdsa_testcase( + test_case: &EcdsaTestCase, + opts: &Opts, + transport: &TransportWrapper, +) -> Result<()> { + let uart = transport.uart("console")?; + + assert_eq!(test_case.algorithm.as_str(), "ecdsa"); + assert!(test_case.message.len() <= ECDSA_CMD_MAX_MESSAGE_BYTES, "ECDSA message was too long for device firmware configuration"); + assert!(test_case.s.len() <= ECDSA_CMD_MAX_SIGNATURE_BYTES, "ECDSA signature was too long for device firmware configuration"); + let qx: Vec = test_case.qx.to_bytes_le(); + let qy: Vec = test_case.qy.to_bytes_le(); + assert!(qx.len() <= ECDSA_CMD_MAX_COORDINATE_BYTES, "ECDSA qx value was too long for device firmware configuration"); + assert!(qy.len() <= ECDSA_CMD_MAX_COORDINATE_BYTES, "ECDSA qy value was too long for device firmware configuration"); + + CryptotestCommand::Ecdsa.send(&*uart)?; + EcdsaSubcommand::EcdsaBlock.send(&*uart)?; + + match test_case.operation.as_str() { + "encrypt" => CryptotestEcdsaOperation::Verify.send(&*uart)?, + _ => panic!("Invalid ECDSA operation"), + } + + match test_case.hash_alg.as_str() { + "Sha256" => CryptotestEcdsaHashAlg::Sha256, + "Sha512" => CryptotestEcdsaHashAlg::Sha512, + "Sha3_256" => CryptotestEcdsaHashAlg::Sha3_256, + "Sha3_512" => CryptotestEcdsaHashAlg::Sha3_512, + _ => panic!("Invalid ECDSA hash mode"), + }.send(&*uart)?; + + match test_case.curve.as_str() { + "p256" => CryptotestEcdsaCurve::P256, + _ => panic!("Invalid ECDSA curve name"), + }.send(&*uart)?; + + let mut message: ArrayVec = ArrayVec::new(); + message.try_extend_from_slice(&test_case.message)?; + + CryptotestEcdsaMessage { + input: message, + input_len: test_case.message.len() + }.send(&*uart)?; + + // `unwrap()` operations are safe here because we checked the sizes above. + CryptotestEcdsaSignature { + signature: ArrayVec::try_from(test_case.s.as_slice()).unwrap(), + signature_len: test_case.s.len() + }.send(&*uart)?; + + CryptotestEcdsaCoordinate { + coordinate: ArrayVec::try_from(qx.as_slice()).unwrap(), + coordinate_len: qx.len(), + }.send(&*uart)?; + + CryptotestEcdsaCoordinate { + coordinate: ArrayVec::try_from(qy.as_slice()).unwrap(), + coordinate_len: qy.len(), + }.send(&*uart)?; + + let ecdsa_output = CryptotestEcdsaVerifyOutput::recv(&*uart, opts.timeout, false)?; + let success = match ecdsa_output { + CryptotestEcdsaVerifyOutput::Success => true, + CryptotestEcdsaVerifyOutput::Failure => false, + CryptotestEcdsaVerifyOutput::IntValue(i) => panic!("Invalid ECDSA verify result: {}", i), + }; + assert_eq!(success, test_case.result, "ECDSA returned incorrect verification status"); + Ok(()) +} + +fn test_ecdsa(opts: &Opts, transport: &TransportWrapper) -> Result<()> { + let uart = transport.uart("console")?; + uart.set_flow_control(true)?; + let _ = UartConsole::wait_for(&*uart, r"Running [^\r\n]*", opts.timeout)?; + + let mut test_counter = 0u32; + let test_vector_files = &opts.ecdsa_json; + for file in test_vector_files { + let raw_json = fs::read_to_string(file)?; + let ecdsa_tests: Vec = serde_json::from_str(&raw_json)?; + + for ecdsa_test in &ecdsa_tests { + test_counter += 1; + log::info!("Test counter: {}", test_counter); + run_ecdsa_testcase(ecdsa_test, opts, transport)?; + } + } + Ok(()) +} + +fn main() -> Result<()> { + let opts = Opts::parse(); + opts.init.init_logging(); + + let transport = opts.init.init_target()?; + execute_test!(test_ecdsa, &opts, &transport); + Ok(()) +}