From 270e0da303d86c0c3b1661ac0ce249aef4cb10e9 Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Thu, 21 Nov 2024 13:59:25 -0800 Subject: [PATCH] [provisioning] Encrypt the RMA unlock token 1. During provisioning, encrypt the RMA unlock token for long term storage in a devices database. 2. Support generating a random RMA unlock token. 3. Add fake RSA keys to support encrypting the token during provisioning test flows. Signed-off-by: Chris Frantz --- .../manuf/base/provisioning_inputs.bzl | 1 + .../silicon_creator/manuf/keys/fake/BUILD | 1 + .../silicon_creator/manuf/keys/fake/README.md | 19 ++++++++ .../keys/fake/rma_unlock_enc_rsa3072.der | Bin 0 -> 1794 bytes .../keys/fake/rma_unlock_enc_rsa3072.pub.der | Bin 0 -> 422 bytes sw/host/provisioning/ft/BUILD | 2 + sw/host/provisioning/ft/src/main.rs | 27 +++++++++-- .../orchestrator/configs/skus/sival.hjson | 1 + sw/host/provisioning/orchestrator/src/BUILD | 1 + .../orchestrator/src/orchestrator.py | 1 - .../provisioning/orchestrator/src/ot_dut.py | 3 ++ .../orchestrator/src/sku_config.py | 1 + sw/host/provisioning/util_lib/BUILD | 3 ++ sw/host/provisioning/util_lib/src/lib.rs | 42 +++++++++++++++++- 14 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 sw/device/silicon_creator/manuf/keys/fake/rma_unlock_enc_rsa3072.der create mode 100644 sw/device/silicon_creator/manuf/keys/fake/rma_unlock_enc_rsa3072.pub.der 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 0000000000000000000000000000000000000000..66b49d0aee13567913dc64228805ba415599e8bb GIT binary patch literal 1794 zcmV+d2mSakf(HHq0RS)!1_>&LNQUt=rDo?(Y z?cSyh3}rmJqZ@&JT`A+`^%-}tGm~UTuy<|fl}HT|f(CN$g3qV}1FJlqFc}54Kj-Y{ z76>92YuKfwNZZkVC<=Ano*>uF`1H3L5mbX~$S>=bOCsKQHbOF%5A{As+Gd2?v-O{L%~!3(l5yCMFt%X8qj5or^fPQJndP zsq#JDkHY!iHO(Ntpe7l&gyb!gugAoPs6CjpK1*16i2z~(hpyo;nwO)!qkA;AI)<9g zlK@h5Ls7cSHrJ+snF0d=009Dm0e}Q)e0_lZ(eMqBTqU{)9c0S0&^BpMMuXr$hym=R zA9&=E(JBlvA_<@r_q!Prb-CT zK(_RnJM~kG{SC^66NF9e;|;nFSM^m7o-?(^<$ki0QRxNTBE+$kx$npSeP`$(%{FRD z_eA)U*J~?W^>gKB>PZyA#2R`!A^)Uj0!2stWvr22` zvm=2Uytic79+FrHyE@JWan{c!k(Kyv;+ov8eELJ{R`>wOPa|G5_*t?!3$`-uID)SR zfFu=+Tqx>3d4o53EDrO|9CGcRQ!}+q{^Fpk28s5-eor6e*M)Uq<$tdw2^jS4R-kP> zZJ#budIPk$ab;2Y_w9#b4rp!e(WDaqLZROwpJ=W`ow#OH61A4t@w6L+xwp|qWrg@N;!I}k>g!gMRMz=@`D{rj2lSJxAb9dS`?T91Ab zpPK}EowF$WD9Q)rB|Gj&tUq)i8R4<(df7Xs%|t;48%OLD=6XZ&dbZ5%g4?lgUDl&j zlg)!TLmvXkAzbu9h?D`GYuH12SY`Kyek9A-TwSwS&8A!XioJ&oiC+$A`px3yWmnn? zwPT(&AVNR(_Ay-$hiyOEUbg~)!2r8VKk_REL|mO8Q3`yS_6#|4x#p===b~RTt^SSI zt)e*k4Y2T`Aym(;gA?)}lCgacWkNiAcPG!4!1;X4#ZnTWmG6^aiYJ4@o})99eQ^bs zGTDp4j0X7vd|0|nlO6v>S*YUCY*FC}1C8}}W{vy@vH+IOaCP3iW&r{ipZr)klYtgA zyUJMl@+R&vgl-MqOIrix!Pj&4EF|koaxHcFZ605}^WB%IQq{O2vA(kt(LoICdpr}I z@-uOXZrK8Xz+qlh(vuZ0@=J!5sW7U4n4v)DZoUGcA+NNpKb5s9y!h!>4<|cavD$$2 zIj?vnQZTH?s1<{dwdl-#O=<|&M%j|;aRW?%f+0;rn|5#*@^L4f!*;l2 ze%t=!D!)>d&{+^C(3s~=>gVQEh51er+7CCQ;T#rEg&8UjMHy?5uE$kT5$MzkYHRTm zz7@z1wSfHk0b68!39IJKE*>i#Zdw^wtD~MvKs*O1pi<i-b5pt=2)jbal@+-4k1jHz^>QtIrYK9O0z*oKOPK^W z99!Kqbt7L$hP6)2UHzKL96g%T?gr!|%_nG}l-5U{ob2n4!?2PaZ|D{Va?tRyv!R3w ztk%8lO&Lt`Yh z0d!yYVXaUH^xo{F&gE^8w}Y<*yDvn?qFQ5z8s z`TCDBZ9iB)FYXXA4RK5%9`W@F2bdcC(hLm?&X#s2CK`oi{oKQyi#WGYocV^S@;%*; z!uj7d%^<#@CKhMLZk08(^A QQM$}F*QSA)0s{d60s1Gv&;S4c literal 0 HcmV?d00001 diff --git a/sw/host/provisioning/ft/BUILD b/sw/host/provisioning/ft/BUILD index 81eda4e25946d..bd874d86c372e 100644 --- a/sw/host/provisioning/ft/BUILD +++ b/sw/host/provisioning/ft/BUILD @@ -23,9 +23,11 @@ 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", + "@crate_index//:zerocopy", "@lowrisc_serde_annotate//serde_annotate", ], ) diff --git a/sw/host/provisioning/ft/src/main.rs b/sw/host/provisioning/ft/src/main.rs index b6ee114ffad7f..f56874cdd3bf1 100644 --- a/sw/host/provisioning/ft/src/main.rs +++ b/sw/host/provisioning/ft/src/main.rs @@ -11,6 +11,8 @@ use clap::{Args, Parser}; use elliptic_curve::pkcs8::DecodePrivateKey; use elliptic_curve::SecretKey; use p256::NistP256; +use zerocopy::AsBytes; + use cert_lib::{CaConfig, CaKey, CaKeyType}; use ft_lib::{ @@ -23,7 +25,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 +49,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 +74,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 +125,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())?) +}