Skip to content

Commit

Permalink
[rom_ext_e2e] Add an ownership transfer test
Browse files Browse the repository at this point in the history
1. Create a library of helper functions for facilitating ownership
   transfer tests.
2. Create a basic ownership transfer test that transfers chip ownership
   from the default `fake` test owner to a `dummy` owner.

Signed-off-by: Chris Frantz <cfrantz@google.com>
  • Loading branch information
cfrantz committed Aug 30, 2024
1 parent 5ecb3fb commit 0ad99c9
Show file tree
Hide file tree
Showing 11 changed files with 317 additions and 8 deletions.
8 changes: 5 additions & 3 deletions quality/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -174,19 +174,21 @@ RUST_TARGETS = [
"//sw/host/cryptotest:cryptotest_parser_test",
"//sw/host/hsmtool:hsmlib",
"//sw/host/hsmtool:hsmlib_test",
"//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/tock/uart:uart",
"//sw/host/tests/xmodem:lrzsz_test",
"//sw/host/tests/xmodem:xmodem",
Expand Down
51 changes: 51 additions & 0 deletions sw/device/silicon_creator/rom_ext/e2e/ownership/BUILD
Original file line number Diff line number Diff line change
@@ -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",
],
)
1 change: 1 addition & 0 deletions sw/device/silicon_creator/rom_ext/e2e/verified_boot/BUILD
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 2 additions & 2 deletions sw/host/opentitanlib/src/chip/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<UnlockMode>,
Expand Down Expand Up @@ -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<u64>,
Expand Down
7 changes: 7 additions & 0 deletions sw/host/opentitanlib/src/crypto/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@ impl TryFrom<&EcdsaPublicKey> for EcdsaRawPublicKey {
}
}

impl TryFrom<EcdsaPublicKey> for EcdsaRawPublicKey {
type Error = Error;
fn try_from(v: EcdsaPublicKey) -> Result<Self, Self::Error> {
EcdsaRawPublicKey::try_from(&v)
}
}

impl FromStr for EcdsaRawPublicKey {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Expand Down
7 changes: 7 additions & 0 deletions sw/host/opentitanlib/src/crypto/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,13 @@ impl TryFrom<&RsaPublicKey> for RsaRawPublicKey {
}
}

impl TryFrom<RsaPublicKey> for RsaRawPublicKey {
type Error = Error;
fn try_from(v: RsaPublicKey) -> Result<Self, Self::Error> {
RsaRawPublicKey::try_from(&v)
}
}

impl FromStr for RsaRawPublicKey {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Expand Down
4 changes: 2 additions & 2 deletions sw/host/opentitanlib/src/ownership/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
5 changes: 4 additions & 1 deletion sw/host/opentitanlib/src/rescue/xmodem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?})"
);
}
}
}
Expand Down
33 changes: 33 additions & 0 deletions sw/host/tests/ownership/BUILD
Original file line number Diff line number Diff line change
@@ -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",
],
)
121 changes: 121 additions & 0 deletions sw/host/tests/ownership/transfer_lib.rs
Original file line number Diff line number Diff line change
@@ -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<BootLog> {
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(())
}
84 changes: 84 additions & 0 deletions sw/host/tests/ownership/transfer_test.rs
Original file line number Diff line number Diff line change
@@ -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::<u32>()?;

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::<u32>()?;
assert_eq!(transfers0 + 1, transfers1);
Ok(())
}

0 comments on commit 0ad99c9

Please sign in to comment.