Skip to content

Commit

Permalink
[provisioning] Encrypt the RMA unlock token
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
cfrantz committed Nov 25, 2024
1 parent 040c235 commit d7fc4f7
Show file tree
Hide file tree
Showing 14 changed files with 93 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions sw/device/silicon_creator/manuf/keys/fake/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ filegroup(
":ca_config.json",
":dice_ca.pem",
":ext_ca.pem",
":rma_unlock_enc_rsa3072.pub.der",
":sk.pkcs8.der",
],
)
19 changes: 19 additions & 0 deletions sw/device/silicon_creator/manuf/keys/fake/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions sw/host/provisioning/ft/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
25 changes: 21 additions & 4 deletions sw/host/provisioning/ft/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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<String>,

/// LC state to transition to from TEST_UNLOCKED*.
#[arg(long, value_parser = DifLcCtrlState::parse_lc_state_str)]
Expand All @@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions sw/host/provisioning/orchestrator/configs/skus/sival.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
1 change: 1 addition & 0 deletions sw/host/provisioning/orchestrator/src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 0 additions & 1 deletion sw/host/provisioning/orchestrator/src/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
)
Expand Down
3 changes: 3 additions & 0 deletions sw/host/provisioning/orchestrator/src/ot_dut.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down
1 change: 1 addition & 0 deletions sw/host/provisioning/orchestrator/src/sku_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions sw/host/provisioning/util_lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)
42 changes: 41 additions & 1 deletion sw/host/provisioning/util_lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<const N: usize>(hex_str: &str) -> Result<ArrayVec<u32, N>> {
let hex_str_no_sep = hex_str.replace('_', "");
Expand Down Expand Up @@ -53,3 +62,34 @@ pub fn hash_lc_token(input: &[u8]) -> Result<ArrayVec<u64, 2>> {
})
.collect::<ArrayVec<u64, 2>>())
}

fn _random_data<RNG>(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<const N: usize>() -> Result<ArrayVec<u32, N>> {
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<Path>) -> Result<RsaPublicKey> {
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<Vec<u8>> {
Ok(Pkcs1v15Encrypt.encrypt(&mut OsRng, pub_key, token.as_bytes())?)
}

0 comments on commit d7fc4f7

Please sign in to comment.