diff --git a/sw/device/silicon_creator/manuf/base/provisioning_inputs.bzl b/sw/device/silicon_creator/manuf/base/provisioning_inputs.bzl index 0a9249284ca9e..6796f73d2874a 100644 --- a/sw/device/silicon_creator/manuf/base/provisioning_inputs.bzl +++ b/sw/device/silicon_creator/manuf/base/provisioning_inputs.bzl @@ -93,6 +93,7 @@ CP_PROVISIONING_INPUTS = _DEVICE_ID_AND_TEST_TOKENS + """ FT_PROVISIONING_INPUTS = _DEVICE_ID_AND_TEST_TOKENS + """ --target-mission-mode-lc-state="prod" --rma-unlock-token="0x01234567_89abcdef_01234567_89abcdef" + --token-encrypt-key-der-file="sw/device/silicon_creator/manuf/keys/fake/rma_unlock_enc_rsa3072.pub.der" --rom-ext-measurement="0x11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111" --owner-manifest-measurement="0x22222222_22222222_22222222_22222222_22222222_22222222_22222222_22222222" --owner-measurement="0x33333333_33333333_33333333_33333333_33333333_33333333_33333333_33333333" diff --git a/sw/device/silicon_creator/manuf/keys/fake/BUILD b/sw/device/silicon_creator/manuf/keys/fake/BUILD index 7eccb6c57b855..d8f5f06e6eb04 100644 --- a/sw/device/silicon_creator/manuf/keys/fake/BUILD +++ b/sw/device/silicon_creator/manuf/keys/fake/BUILD @@ -12,6 +12,7 @@ filegroup( ":ca_config.json", ":dice_ca.pem", ":ext_ca.pem", + ":rma_unlock_enc_rsa3072.pub.der", ":sk.pkcs8.der", ], ) diff --git a/sw/device/silicon_creator/manuf/keys/fake/README.md b/sw/device/silicon_creator/manuf/keys/fake/README.md index 06c217b5e0771..5861db653aff6 100644 --- a/sw/device/silicon_creator/manuf/keys/fake/README.md +++ b/sw/device/silicon_creator/manuf/keys/fake/README.md @@ -45,3 +45,22 @@ $ openssl x509 -req -in ca.csr -signkey sk.pem -out ca.pem -days 3650 \ # Examine the generated certificate: $ openssl x509 -in ca.pem -text ``` + +# Generating the RMA unlock token encryption keypair with OpenSSL + +The RMA unlock token encryption keypair is an RSA-3072 key used to encrypt the +RMA unlock token generated during provisioning. + +The fake keys (used for testing) in this subdirectory were generated with `openssl`. + +``` +### Generate the RSA keypair: +$ openssl genrsa -out rma_unlock_enc_rsa3072.pem 3072 + +### Extract the public key to a separate file: +$ openssl rsa -in rma_unlock_enc_rsa3072.pem -pubout -out rma_unlock_enc_rsa3072.pub.pem + +### Convert the PEM files to DER files: +$ openssl rsa -in rma_unlock_enc_rsa3072.pem -outform der -out rma_unlock_enc_rsa3072..der +$ openssl rsa -pubin -in rma_unlock_enc_rsa3072.pub.pem -outform der -out rma_unlock_enc_rsa3072.pub.der +``` diff --git a/sw/device/silicon_creator/manuf/keys/fake/rma_unlock_enc_rsa3072.der b/sw/device/silicon_creator/manuf/keys/fake/rma_unlock_enc_rsa3072.der new file mode 100644 index 0000000000000..66b49d0aee135 Binary files /dev/null and b/sw/device/silicon_creator/manuf/keys/fake/rma_unlock_enc_rsa3072.der differ diff --git a/sw/device/silicon_creator/manuf/keys/fake/rma_unlock_enc_rsa3072.pub.der b/sw/device/silicon_creator/manuf/keys/fake/rma_unlock_enc_rsa3072.pub.der new file mode 100644 index 0000000000000..f91d52bb1d2bd Binary files /dev/null and b/sw/device/silicon_creator/manuf/keys/fake/rma_unlock_enc_rsa3072.pub.der differ diff --git a/sw/host/provisioning/ft/BUILD b/sw/host/provisioning/ft/BUILD index 81eda4e25946d..dc3a12d7532ff 100644 --- a/sw/host/provisioning/ft/BUILD +++ b/sw/host/provisioning/ft/BUILD @@ -23,6 +23,7 @@ package(default_visibility = ["//visibility:public"]) "@crate_index//:anyhow", "@crate_index//:clap", "@crate_index//:elliptic-curve", + "@crate_index//:hex", "@crate_index//:humantime", "@crate_index//:log", "@crate_index//:p256", diff --git a/sw/host/provisioning/ft/src/main.rs b/sw/host/provisioning/ft/src/main.rs index b6ee114ffad7f..25e3225c298ce 100644 --- a/sw/host/provisioning/ft/src/main.rs +++ b/sw/host/provisioning/ft/src/main.rs @@ -23,7 +23,10 @@ use opentitanlib::test_utils::init::InitializeTest; use opentitanlib::test_utils::lc::read_lc_state; use opentitanlib::test_utils::load_sram_program::SramProgramParams; use ujson_lib::provisioning_data::{ManufCertgenInputs, ManufFtIndividualizeData}; -use util_lib::{hex_string_to_u32_arrayvec, hex_string_to_u8_arrayvec}; +use util_lib::{ + encrypt_token, hex_string_to_u32_arrayvec, hex_string_to_u8_arrayvec, load_rsa_public_key, + random_token, +}; /// Provisioning data command-line parameters. #[derive(Debug, Args, Clone)] @@ -44,7 +47,7 @@ pub struct ManufFtProvisioningDataInput { /// RMA unlock token; a 128-bit hex string. #[arg(long)] - pub rma_unlock_token: String, + pub rma_unlock_token: Option, /// LC state to transition to from TEST_UNLOCKED*. #[arg(long, value_parser = DifLcCtrlState::parse_lc_state_str)] @@ -69,6 +72,10 @@ pub struct ManufFtProvisioningDataInput { /// Security version the Owner image to be loaded onto the device. #[arg(long, default_value = "0")] pub owner_security_version: u32, + + /// Token Encryption public key (RSA) DER file path. + #[arg(long)] + token_encrypt_key_der_file: PathBuf, } #[derive(Debug, Parser)] @@ -116,8 +123,18 @@ fn main() -> Result<()> { hex_string_to_u32_arrayvec::<4>(opts.provisioning_data.test_unlock_token.as_str())?; let _test_exit_token = hex_string_to_u32_arrayvec::<4>(opts.provisioning_data.test_exit_token.as_str())?; - let rma_unlock_token = - hex_string_to_u32_arrayvec::<4>(opts.provisioning_data.rma_unlock_token.as_str())?; + let rma_unlock_token = if let Some(token) = &opts.provisioning_data.rma_unlock_token { + hex_string_to_u32_arrayvec::<4>(token.as_str())? + } else { + random_token::<4>()? + }; + let token_encrypt_key = + load_rsa_public_key(&opts.provisioning_data.token_encrypt_key_der_file)?; + let encrypted_rma_unlock_token = encrypt_token(&token_encrypt_key, &rma_unlock_token)?; + log::info!( + "Encrypted rma_unlock_token = {}", + hex::encode(&encrypted_rma_unlock_token) + ); // Parse and prepare individualization ujson data payload. let _ft_individualize_data_in = ManufFtIndividualizeData { diff --git a/sw/host/provisioning/orchestrator/configs/skus/sival.hjson b/sw/host/provisioning/orchestrator/configs/skus/sival.hjson index 13ff890e192ba..e2a387f4436d7 100644 --- a/sw/host/provisioning/orchestrator/configs/skus/sival.hjson +++ b/sw/host/provisioning/orchestrator/configs/skus/sival.hjson @@ -22,4 +22,5 @@ key_type: "Raw", key_id: "0xfe584ae7_53790cfd_8601a312_fb32d3c1_b822d112" } + token_encrypt_key: "sw/device/silicon_creator/manuf/keys/fake/rma_unlock_enc_rsa3072.pub.der" } diff --git a/sw/host/provisioning/orchestrator/src/BUILD b/sw/host/provisioning/orchestrator/src/BUILD index ec715edee75f4..958648c5f72f3 100644 --- a/sw/host/provisioning/orchestrator/src/BUILD +++ b/sw/host/provisioning/orchestrator/src/BUILD @@ -76,6 +76,7 @@ py_binary( "//sw/device/silicon_creator/manuf/base:sram_ft_individualize_all", "//sw/device/silicon_creator/manuf/keys/fake:dice_ca.pem", "//sw/device/silicon_creator/manuf/keys/fake:ext_ca.pem", + "//sw/device/silicon_creator/manuf/keys/fake:rma_unlock_enc_rsa3072.pub.der", "//sw/device/silicon_creator/manuf/keys/fake:sk.pkcs8.der", "//sw/host/provisioning/cp", "//sw/host/provisioning/ft:ft_all", diff --git a/sw/host/provisioning/orchestrator/src/orchestrator.py b/sw/host/provisioning/orchestrator/src/orchestrator.py index 46a1754157a3f..73cb2fcc81c12 100644 --- a/sw/host/provisioning/orchestrator/src/orchestrator.py +++ b/sw/host/provisioning/orchestrator/src/orchestrator.py @@ -92,7 +92,6 @@ def main(args_in): ) parser.add_argument( "--rma-unlock-token", - required=True, type=parse_hexstring_to_int, help="Raw RMA token to inject into OTP SECRET2 partition.", ) diff --git a/sw/host/provisioning/orchestrator/src/ot_dut.py b/sw/host/provisioning/orchestrator/src/ot_dut.py index 655544d4be687..bd848dd451d7b 100644 --- a/sw/host/provisioning/orchestrator/src/ot_dut.py +++ b/sw/host/provisioning/orchestrator/src/ot_dut.py @@ -171,7 +171,10 @@ def run_ft(self) -> None: --rom-ext-security-version="0" \ --owner-security-version="0" \ --ca-config={ca_config_file.name} \ + --token-encrypt-key-der-file={self.sku_config.token_encrypt_key} \ """ + if self.rma_unlock_token is not None: + cmd += f'--rma-unlock-token="{format_hex(self.rma_unlock_token, width=32)}" \\\n' # Get user confirmation before running command. logging.info(f"Running command: {cmd}") diff --git a/sw/host/provisioning/orchestrator/src/sku_config.py b/sw/host/provisioning/orchestrator/src/sku_config.py index 12857d3a5c1e9..adae1f5f4f344 100644 --- a/sw/host/provisioning/orchestrator/src/sku_config.py +++ b/sw/host/provisioning/orchestrator/src/sku_config.py @@ -24,6 +24,7 @@ class SkuConfig: target_lc_state: str # valid: must be in ["dev", "prod", "prod_end"] dice_ca: OrderedDict # valid: see CaConfig ext_ca: OrderedDict # valid: see CaConfig + token_encrypt_key: str def __post_init__(self): # Load CA configs. diff --git a/sw/host/provisioning/util_lib/BUILD b/sw/host/provisioning/util_lib/BUILD index ee929d54c9ae3..2f5f04c1c74ec 100644 --- a/sw/host/provisioning/util_lib/BUILD +++ b/sw/host/provisioning/util_lib/BUILD @@ -13,6 +13,9 @@ rust_library( "@crate_index//:anyhow", "@crate_index//:arrayvec", "@crate_index//:hex", + "@crate_index//:rand", + "@crate_index//:rsa", "@crate_index//:tiny-keccak", + "@crate_index//:zerocopy", ], ) diff --git a/sw/host/provisioning/util_lib/src/lib.rs b/sw/host/provisioning/util_lib/src/lib.rs index c2fbf949410a3..0cb18e9de9818 100644 --- a/sw/host/provisioning/util_lib/src/lib.rs +++ b/sw/host/provisioning/util_lib/src/lib.rs @@ -2,10 +2,19 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -use anyhow::Result; +use anyhow::{Context, Result}; use arrayvec::ArrayVec; use hex::decode; +use rand::rngs::OsRng; +use rand::{CryptoRng, Rng}; +use rsa::pkcs1::DecodeRsaPublicKey; +use rsa::pkcs1v15::Pkcs1v15Encrypt; +use rsa::pkcs8::DecodePublicKey; +use rsa::traits::PaddingScheme; +use rsa::RsaPublicKey; +use std::path::Path; use tiny_keccak::{CShake, Hasher}; +use zerocopy::AsBytes; pub fn hex_string_to_u32_arrayvec(hex_str: &str) -> Result> { let hex_str_no_sep = hex_str.replace('_', ""); @@ -53,3 +62,34 @@ pub fn hash_lc_token(input: &[u8]) -> Result> { }) .collect::>()) } + +fn _random_data(rng: &mut RNG, data: &mut [u32]) -> Result<()> +where + RNG: Rng + CryptoRng, +{ + rng.try_fill(data)?; + Ok(()) +} + +/// Generates a random token using a CSPRNG. +pub fn random_token() -> Result> { + let mut data = [0u32; N]; + _random_data(&mut OsRng, &mut data)?; + Ok(ArrayVec::from(data)) +} + +/// Loads a DER-encoded RSA public key. +pub fn load_rsa_public_key(path: impl AsRef) -> Result { + let path = path.as_ref(); + match DecodeRsaPublicKey::read_pkcs1_der_file(path) + .with_context(|| format!("read PKCS#1 der {path:?}")) + { + Ok(key) => Ok(key), + Err(e) => Ok(DecodePublicKey::read_public_key_der_file(path) + .with_context(|| format!("read PKCS#8 der {path:?} (previous error: {e})"))?), + } +} + +pub fn encrypt_token(pub_key: &RsaPublicKey, token: &[u32]) -> Result> { + Ok(Pkcs1v15Encrypt.encrypt(&mut OsRng, pub_key, token.as_bytes())?) +}