diff --git a/quality/BUILD.bazel b/quality/BUILD.bazel index ced3e05c7b1e0..21688a9bceb3f 100644 --- a/quality/BUILD.bazel +++ b/quality/BUILD.bazel @@ -173,19 +173,21 @@ RUST_TARGETS = [ "//sw/host/hsmtool:hsmlib", "//sw/host/hsmtool:hsmlib_test", "//sw/host/hsmtool/acorn:acorn", - "//sw/host/ot_certs:ot_certs", - "//sw/host/ot_certs:ot_certs_test", "//sw/host/opentitanlib:opentitanlib", "//sw/host/opentitanlib:opentitanlib_test", "//sw/host/opentitansession:opentitansession", "//sw/host/opentitantool:opentitantool", + "//sw/host/ot_certs:ot_certs", + "//sw/host/ot_certs:ot_certs_test", "//sw/host/tests/chip/gpio:gpio", "//sw/host/tests/chip/power_virus:power_virus", + "//sw/host/tests/chip/spi_device:spi_passthru", + "//sw/host/tests/ownership:transfer_lib", + "//sw/host/tests/ownership:transfer_test", "//sw/host/tests/rom/e2e_bootstrap_disabled:e2e_bootstrap_disabled", "//sw/host/tests/rom/e2e_bootstrap_entry:e2e_bootstrap_entry", "//sw/host/tests/rom/e2e_chip_specific_startup:e2e_chip_specific_startup", "//sw/host/tests/rom/sw_strap_value:sw_strap_value", - "//sw/host/tests/chip/spi_device:spi_passthru", "//sw/host/tests/xmodem:lrzsz_test", "//sw/host/tests/xmodem:xmodem", "//sw/host/sphincsplus:sphincsplus", diff --git a/sw/device/silicon_creator/rom_ext/e2e/ownership/BUILD b/sw/device/silicon_creator/rom_ext/e2e/ownership/BUILD new file mode 100644 index 0000000000000..700054bac1a84 --- /dev/null +++ b/sw/device/silicon_creator/rom_ext/e2e/ownership/BUILD @@ -0,0 +1,51 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +load( + "//rules/opentitan:defs.bzl", + "fpga_params", + "opentitan_test", +) + +package(default_visibility = ["//visibility:public"]) + +opentitan_test( + name = "ownership_transfer_test", + srcs = ["//sw/device/silicon_creator/rom_ext/e2e/verified_boot:boot_test"], + exec_env = { + "//hw/top_earlgrey:fpga_hyper310_rom_ext": None, + }, + fpga = fpga_params( + # This test doesn't change OTP, but it modifies the ownership INFO + # pages, so we need to clear the bitstream after the test, which is + # what the `changes_otp` parameter actually does. + changes_otp = True, + data = [ + "//sw/device/silicon_creator/lib/ownership/keys/dummy:activate_key", + "//sw/device/silicon_creator/lib/ownership/keys/dummy:app_prod_pub", + "//sw/device/silicon_creator/lib/ownership/keys/dummy:owner_key", + "//sw/device/silicon_creator/lib/ownership/keys/dummy:unlock_key", + "//sw/device/silicon_creator/lib/ownership/keys/fake:unlock_key", + ], + test_cmd = """ + --clear-bitstream + --bootstrap={firmware} + --unlock-key=$(location //sw/device/silicon_creator/lib/ownership/keys/fake:unlock_key) + --next-owner-key=$(location //sw/device/silicon_creator/lib/ownership/keys/dummy:owner_key) + --next-unlock-key=$(location //sw/device/silicon_creator/lib/ownership/keys/dummy:unlock_key) + --next-activate-key=$(location //sw/device/silicon_creator/lib/ownership/keys/dummy:activate_key) + --next-application-key=$(location //sw/device/silicon_creator/lib/ownership/keys/dummy:app_prod_pub) + """, + test_harness = "//sw/host/tests/ownership:transfer_test", + ), + rsa_key = { + "//sw/device/silicon_creator/lib/ownership/keys/dummy:app_prod": "app_prod", + }, + deps = [ + "//sw/device/lib/base:status", + "//sw/device/lib/testing/test_framework:ottf_main", + "//sw/device/silicon_creator/lib:boot_log", + "//sw/device/silicon_creator/lib/drivers:retention_sram", + ], +) diff --git a/sw/device/silicon_creator/rom_ext/e2e/verified_boot/BUILD b/sw/device/silicon_creator/rom_ext/e2e/verified_boot/BUILD index 0616c75df464a..2a091cf71abb3 100644 --- a/sw/device/silicon_creator/rom_ext/e2e/verified_boot/BUILD +++ b/sw/device/silicon_creator/rom_ext/e2e/verified_boot/BUILD @@ -1,6 +1,7 @@ # Copyright lowRISC contributors (OpenTitan project). # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 + load( "//rules/opentitan:defs.bzl", "DEFAULT_TEST_FAILURE_MSG", diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD index 14893395888cd..8583e428df238 100644 --- a/sw/host/opentitanlib/BUILD +++ b/sw/host/opentitanlib/BUILD @@ -82,8 +82,8 @@ rust_library( "src/chip/boot_log.rs", "src/chip/boot_svc.rs", "src/chip/helper.rs", - "src/chip/rom_error.rs", "src/chip/mod.rs", + "src/chip/rom_error.rs", "src/console/mod.rs", "src/console/spi.rs", "src/crypto/ecdsa.rs", diff --git a/sw/host/opentitanlib/src/chip/helper.rs b/sw/host/opentitanlib/src/chip/helper.rs index 0c43dc1e41e67..cf4e3717272a9 100644 --- a/sw/host/opentitanlib/src/chip/helper.rs +++ b/sw/host/opentitanlib/src/chip/helper.rs @@ -11,7 +11,7 @@ use std::fs::File; use std::io::Read; use std::path::PathBuf; -#[derive(Debug, Args)] +#[derive(Debug, Default, Args)] pub struct OwnershipUnlockParams { #[arg(long, value_enum, help = "Requested unlock mode")] pub mode: Option, @@ -63,7 +63,7 @@ impl OwnershipUnlockParams { } } -#[derive(Debug, Args)] +#[derive(Debug, Default, Args)] pub struct OwnershipActivateParams { #[arg(long, value_parser = u64::from_str, help="Current ROM_EXT nonce")] pub nonce: Option, diff --git a/sw/host/opentitanlib/src/crypto/ecdsa.rs b/sw/host/opentitanlib/src/crypto/ecdsa.rs index 08558428db616..358c9e66dbbbd 100644 --- a/sw/host/opentitanlib/src/crypto/ecdsa.rs +++ b/sw/host/opentitanlib/src/crypto/ecdsa.rs @@ -226,6 +226,13 @@ impl TryFrom<&EcdsaPublicKey> for EcdsaRawPublicKey { } } +impl TryFrom for EcdsaRawPublicKey { + type Error = Error; + fn try_from(v: EcdsaPublicKey) -> Result { + EcdsaRawPublicKey::try_from(&v) + } +} + impl FromStr for EcdsaRawPublicKey { type Err = Error; fn from_str(s: &str) -> Result { diff --git a/sw/host/opentitanlib/src/crypto/rsa.rs b/sw/host/opentitanlib/src/crypto/rsa.rs index 41509cd177b8b..d1ae75df9f202 100644 --- a/sw/host/opentitanlib/src/crypto/rsa.rs +++ b/sw/host/opentitanlib/src/crypto/rsa.rs @@ -273,6 +273,13 @@ impl TryFrom<&RsaPublicKey> for RsaRawPublicKey { } } +impl TryFrom for RsaRawPublicKey { + type Error = Error; + fn try_from(v: RsaPublicKey) -> Result { + RsaRawPublicKey::try_from(&v) + } +} + impl FromStr for RsaRawPublicKey { type Err = Error; fn from_str(s: &str) -> Result { diff --git a/sw/host/opentitanlib/src/ownership/mod.rs b/sw/host/opentitanlib/src/ownership/mod.rs index 41fc3030ddc5b..9c9840db15bd5 100644 --- a/sw/host/opentitanlib/src/ownership/mod.rs +++ b/sw/host/opentitanlib/src/ownership/mod.rs @@ -12,6 +12,6 @@ mod rescue; pub use application_key::{ApplicationKeyDomain, OwnerApplicationKey}; pub use flash::{FlashFlags, OwnerFlashConfig, OwnerFlashRegion}; pub use flash_info::{OwnerFlashInfoConfig, OwnerInfoPage}; -pub use misc::{OwnershipKeyAlg, TlvHeader, TlvTag}; -pub use owner::{OwnerBlock, SramExecMode}; +pub use misc::{KeyMaterial, OwnershipKeyAlg, TlvHeader, TlvTag}; +pub use owner::{OwnerBlock, OwnerConfigItem, SramExecMode}; pub use rescue::{OwnerRescueConfig, RescueType}; diff --git a/sw/host/opentitanlib/src/rescue/xmodem.rs b/sw/host/opentitanlib/src/rescue/xmodem.rs index 696313572af1b..ba415a3a50d86 100644 --- a/sw/host/opentitanlib/src/rescue/xmodem.rs +++ b/sw/host/opentitanlib/src/rescue/xmodem.rs @@ -97,7 +97,10 @@ impl Xmodem { } } _ => { - log::info!("Unknown byte received while waiting for XMODEM start: {ch:#x?}"); + let p = ch as char; + log::info!( + "Unknown byte received while waiting for XMODEM start: {p:?} ({ch:#x?})" + ); } } } diff --git a/sw/host/tests/ownership/BUILD b/sw/host/tests/ownership/BUILD new file mode 100644 index 0000000000000..a67277d607e43 --- /dev/null +++ b/sw/host/tests/ownership/BUILD @@ -0,0 +1,33 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library") +load("//rules:ujson.bzl", "ujson_rust") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "transfer_lib", + srcs = ["transfer_lib.rs"], + deps = [ + "//sw/host/opentitanlib", + "@crate_index//:anyhow", + "@crate_index//:log", + ], +) + +rust_binary( + name = "transfer_test", + srcs = [ + "transfer_test.rs", + ], + deps = [ + ":transfer_lib", + "//sw/host/opentitanlib", + "@crate_index//:anyhow", + "@crate_index//:clap", + "@crate_index//:humantime", + "@crate_index//:log", + ], +) diff --git a/sw/host/tests/ownership/transfer_lib.rs b/sw/host/tests/ownership/transfer_lib.rs new file mode 100644 index 0000000000000..914993800fc1b --- /dev/null +++ b/sw/host/tests/ownership/transfer_lib.rs @@ -0,0 +1,121 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#![allow(clippy::bool_assert_comparison)] +use anyhow::{anyhow, Result}; +use opentitanlib::app::TransportWrapper; +use opentitanlib::chip::boot_log::BootLog; +use opentitanlib::chip::boot_svc::{Message, UnlockMode}; +use opentitanlib::chip::helper::{OwnershipActivateParams, OwnershipUnlockParams}; +use opentitanlib::chip::rom_error::RomError; +use opentitanlib::crypto::ecdsa::EcdsaPrivateKey; +use opentitanlib::crypto::rsa::RsaPublicKey; +use opentitanlib::ownership::{ + ApplicationKeyDomain, KeyMaterial, OwnerApplicationKey, OwnerBlock, OwnerConfigItem, + OwnershipKeyAlg, +}; +use opentitanlib::rescue::serial::RescueSerial; + +use std::path::Path; + +/// Gets the BootLog. +pub fn get_boot_log(transport: &TransportWrapper, rescue: &RescueSerial) -> Result { + rescue.enter(transport, /*reset=*/ true)?; + rescue.get_boot_log() +} + +/// Prepares an UnlockOwnership command, sends it to the chip and gets the response. +pub fn ownership_unlock( + transport: &TransportWrapper, + rescue: &RescueSerial, + mode: UnlockMode, + nonce: u64, + unlock_key: &Path, + next_owner: Option<&Path>, +) -> Result<()> { + let unlock = OwnershipUnlockParams { + mode: Some(mode), + nonce: Some(nonce), + next_owner: next_owner.map(|p| p.into()), + sign: Some(unlock_key.into()), + ..Default::default() + } + .apply_to(Option::<&mut std::fs::File>::None)?; + + rescue.enter(transport, /*reset=*/ true)?; + rescue.ownership_unlock(unlock)?; + rescue.enter(transport, /*reset=*/ false)?; + let result = rescue.get_boot_svc()?; + match &result.message { + Message::OwnershipUnlockResponse(r) if r.status == RomError::Ok => Ok(()), + _ => Err(anyhow!("Unexpected response: {result:x?}")), + } +} + +/// Prepares an UnlockOwnership command (with UnlockMode::Any), sends it to the chip and gets the response. +pub fn ownership_unlock_any( + transport: &TransportWrapper, + rescue: &RescueSerial, + nonce: u64, + unlock_key: &Path, +) -> Result<()> { + ownership_unlock(transport, rescue, UnlockMode::Any, nonce, unlock_key, None) +} + +/// Prepares an OwnershipActivate command, sends it to the chip and gets the response. +pub fn ownership_activate( + transport: &TransportWrapper, + rescue: &RescueSerial, + nonce: u64, + activate_key: &Path, +) -> Result<()> { + let activate = OwnershipActivateParams { + nonce: Some(nonce), + sign: Some(activate_key.into()), + ..Default::default() + } + .apply_to(Option::<&mut std::fs::File>::None)?; + + rescue.enter(transport, /*reset=*/ true)?; + rescue.ownership_activate(activate)?; + rescue.enter(transport, /*reset=*/ false)?; + let result = rescue.get_boot_svc()?; + match &result.message { + Message::OwnershipActivateResponse(r) if r.status == RomError::Ok => Ok(()), + _ => Err(anyhow!("Unexpected response: {result:x?}")), + } +} + +/// Prepares an OwnerBlock and sends it to the chip. +pub fn create_owner( + transport: &TransportWrapper, + rescue: &RescueSerial, + owner_key: &Path, + activate_key: &Path, + unlock_key: &Path, + app_key: &Path, +) -> Result<()> { + let owner_key = EcdsaPrivateKey::load(owner_key)?; + let activate_key = EcdsaPrivateKey::load(activate_key)?; + let unlock_key = EcdsaPrivateKey::load(unlock_key)?; + let app_key = RsaPublicKey::from_pkcs1_der_file(app_key)?; + let mut owner = OwnerBlock { + owner_key: KeyMaterial::Ecdsa(owner_key.public_key().try_into()?), + activate_key: KeyMaterial::Ecdsa(activate_key.public_key().try_into()?), + unlock_key: KeyMaterial::Ecdsa(unlock_key.public_key().try_into()?), + data: vec![OwnerConfigItem::ApplicationKey(OwnerApplicationKey { + key_alg: OwnershipKeyAlg::Rsa, + key_domain: ApplicationKeyDomain::Prod, + key: KeyMaterial::Rsa(app_key.try_into()?), + ..Default::default() + })], + ..Default::default() + }; + owner.sign(&owner_key)?; + let mut owner_config = Vec::new(); + owner.write(&mut owner_config)?; + rescue.enter(transport, /*reset=*/ true)?; + rescue.set_owner_config(&owner_config)?; + Ok(()) +} diff --git a/sw/host/tests/ownership/transfer_test.rs b/sw/host/tests/ownership/transfer_test.rs new file mode 100644 index 0000000000000..3b7ec5ee10a2b --- /dev/null +++ b/sw/host/tests/ownership/transfer_test.rs @@ -0,0 +1,84 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#![allow(clippy::bool_assert_comparison)] +use anyhow::Result; +use clap::Parser; +use std::path::PathBuf; +use std::rc::Rc; +use std::time::Duration; + +use opentitanlib::rescue::serial::RescueSerial; +use opentitanlib::test_utils::init::InitializeTest; +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, help = "Unlock private key (ECDSA P256)")] + unlock_key: PathBuf, + #[arg(long, help = "Next Owner private key (ECDSA P256)")] + next_owner_key: PathBuf, + #[arg(long, help = "Next Owner activate private key (ECDSA P256)")] + next_activate_key: PathBuf, + #[arg(long, help = "Next Owner unlock private key (ECDSA P256)")] + next_unlock_key: PathBuf, + #[arg(long, help = "Next Owner's application public key (RSA3K)")] + next_application_key: PathBuf, +} + +fn main() -> Result<()> { + let opts = Opts::parse(); + opts.init.init_logging(); + + let transport = opts.init.init_target()?; + let uart = transport.uart("console")?; + let rescue = RescueSerial::new(Rc::clone(&uart)); + + let data = transfer_lib::get_boot_log(&transport, &rescue)?; + transfer_lib::ownership_unlock_any(&transport, &rescue, data.rom_ext_nonce, &opts.unlock_key)?; + + transfer_lib::create_owner( + &transport, + &rescue, + &opts.next_owner_key, + &opts.next_activate_key, + &opts.next_unlock_key, + &opts.next_application_key, + )?; + + // At this point, the device should be unlocked and should have accepted the owner + // configuration. Owner code should run and report the state as `UANY`. + transport.reset_target(Duration::from_millis(50), /*clear_uart=*/ true)?; + let capture = UartConsole::wait_for( + &*uart, + r"(?msR)ownership_state = UANY$.*ownership_transfers = (\d+)$.*PASS!$", + opts.timeout, + )?; + let transfers0 = capture[1].parse::()?; + + let data = transfer_lib::get_boot_log(&transport, &rescue)?; + transfer_lib::ownership_activate( + &transport, + &rescue, + data.rom_ext_nonce, + &opts.next_activate_key, + )?; + + // After the activate command, the device should report the ownership state as `OWND`. + transport.reset_target(Duration::from_millis(50), /*clear_uart=*/ true)?; + let capture = UartConsole::wait_for( + &*uart, + r"(?msR)ownership_state = OWND$.*ownership_transfers = (\d+)$.*PASS!$", + opts.timeout, + )?; + let transfers1 = capture[1].parse::()?; + assert_eq!(transfers0 + 1, transfers1); + Ok(()) +}