Skip to content

Commit

Permalink
[rom_ext_e2e] Check the attestation certificates
Browse files Browse the repository at this point in the history
Examine the attestation certificates and check the measurements
published in the DiceTcbInfo extensions.

Signed-off-by: Chris Frantz <[email protected]>
  • Loading branch information
cfrantz committed Oct 16, 2024
1 parent 14ab823 commit d490c39
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 3 deletions.
36 changes: 36 additions & 0 deletions sw/device/silicon_creator/rom_ext/e2e/attestation/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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",
"CLEAR_KEY_SET",
"DEFAULT_TEST_FAILURE_MSG",
"DEFAULT_TEST_SUCCESS_MSG",
"EARLGREY_TEST_ENVS",
"fpga_params",
"opentitan_test",
)

package(default_visibility = ["//visibility:public"])

opentitan_test(
name = "print_certs_test",
srcs = ["print_certs.c"],
exec_env = {
"//hw/top_earlgrey:fpga_hyper310_rom_ext": None,
},
fpga = fpga_params(
test_cmd = """
--bootstrap={firmware}
""",
test_harness = "//sw/host/tests/attestation:attestation_test",
),
deps = [
"//sw/device/lib/base:status",
"//sw/device/lib/runtime:log",
"//sw/device/lib/runtime:print",
"//sw/device/lib/testing/test_framework:ottf_main",
"//sw/device/silicon_creator/lib/drivers:flash_ctrl",
],
)
82 changes: 82 additions & 0 deletions sw/device/silicon_creator/rom_ext/e2e/attestation/print_certs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include "sw/device/lib/base/status.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/runtime/print.h"
#include "sw/device/lib/testing/test_framework/ottf_main.h"
#include "sw/device/silicon_creator/lib/drivers/flash_ctrl.h"

OTTF_DEFINE_TEST_CONFIG();

const char base64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

void base64_encode(char *dest, const uint8_t *data, int32_t len) {
*dest = '\0';
for (int32_t i = 0; len > 0; i += 3, len -= 3) {
// clang-format off
uint32_t val = (uint32_t)(data[i] << 16 |
(len > 1 ? data[i + 1] << 8 : 0) |
(len > 2 ? data[i + 2] : 0));
// clang-format on
*dest++ = base64[(val >> 18) & 0x3f];
*dest++ = base64[(val >> 12) & 0x3f];
*dest++ = len > 1 ? base64[(val >> 6) & 0x3f] : '=';
*dest++ = len > 2 ? base64[(val >> 0) & 0x3f] : '=';
*dest = '\0';
}
}

status_t print_cert(char *dest, const flash_ctrl_info_page_t *info_page) {
uint8_t data[2048];
TRY(flash_ctrl_info_read(info_page, 0, sizeof(data) / sizeof(uint32_t),
data));

int16_t len = (int16_t)(data[2] << 8 | data[3]);
if (len == -1) {
*dest = '\0';
return OK_STATUS();
}
len += 4;
base64_encode(dest, data, len);
return OK_STATUS();
}

status_t print_owner_block(char *dest,
const flash_ctrl_info_page_t *info_page) {
uint8_t data[2048];
TRY(flash_ctrl_info_read(info_page, 0, sizeof(data) / sizeof(uint32_t),
data));
base64_encode(dest, data, sizeof(data));
return OK_STATUS();
}

status_t print_certs(void) {
char buf[3072];
TRY(print_cert(buf, &kFlashCtrlInfoPageUdsCertificate));
LOG_INFO("UDS: %s", buf);

TRY(print_cert(buf, &kFlashCtrlInfoPageCdi0Certificate));
LOG_INFO("CDI_0: %s", buf);

TRY(print_cert(buf, &kFlashCtrlInfoPageCdi1Certificate));
LOG_INFO("CDI_1: %s", buf);

TRY(print_owner_block(buf, &kFlashCtrlInfoPageOwnerSlot0));
LOG_INFO("OWNER_PAGE_0: %s", buf);

TRY(print_owner_block(buf, &kFlashCtrlInfoPageOwnerSlot1));
LOG_INFO("OWNER_PAGE_1: %s", buf);

return OK_STATUS();
}

bool test_main(void) {
status_t sts = print_certs();
if (status_err(sts)) {
LOG_ERROR("print_certs: %r", sts);
}
return status_ok(sts);
}
16 changes: 16 additions & 0 deletions sw/host/opentitanlib/src/image/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,22 @@ impl Image {
}
}

impl SubImage<'_> {
/// Operates on the signed region of the image.
pub fn map_signed_region<F, R>(&self, f: F) -> Result<R>
where
F: FnOnce(&[u8]) -> R,
{
Ok(f(&self.data[offset_of!(Manifest, usage_constraints)
..self.manifest.signed_region_end as usize]))
}

/// Compute the SHA256 digest for the signed portion of the `Image`.
pub fn compute_digest(&self) -> Result<sha256::Sha256Digest> {
self.map_signed_region(|v| sha256::sha256(v))
}
}

impl ImageAssembler {
/// Creates an `ImageAssembler` with a given `size` and mirroring parameters.
pub fn with_params(size: usize, mirrored: bool) -> Self {
Expand Down
6 changes: 3 additions & 3 deletions sw/host/opentitanlib/src/ownership/owner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ impl Default for OwnerBlock {
}

impl OwnerBlock {
const SIZE: usize = 2048;
const DATA_SIZE: usize = 1536;
const SIGNATURE_OFFSET: usize = 1952;
pub const SIZE: usize = 2048;
pub const DATA_SIZE: usize = 1536;
pub const SIGNATURE_OFFSET: usize = 1952;
// The not present value must be reflected in the TlvTag::NotPresent value.
const NOT_PRESENT: u8 = 0x5a;

Expand Down
25 changes: 25 additions & 0 deletions sw/host/tests/attestation/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 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_binary(
name = "attestation_test",
srcs = ["attestation_test.rs"],
deps = [
"//sw/host/opentitanlib",
"//sw/host/ot_certs",
"@crate_index//:anyhow",
"@crate_index//:base64ct",
"@crate_index//:clap",
"@crate_index//:humantime",
"@crate_index//:log",
"@crate_index//:num-bigint-dig",
"@crate_index//:regex",
"@crate_index//:serde_json",
],
)
132 changes: 132 additions & 0 deletions sw/host/tests/attestation/attestation_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// 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 base64ct::{Base64, Decoder};
use clap::Parser;
use num_bigint_dig::BigUint;
use regex::Regex;
use std::time::Duration;

use opentitanlib::app::TransportWrapper;
use opentitanlib::crypto::sha256;
use opentitanlib::image::image::Image;
use opentitanlib::ownership::OwnerBlock;
use opentitanlib::test_utils::init::InitializeTest;
use opentitanlib::uart::console::UartConsole;
use opentitanlib::util::file::FromReader;

use ot_certs::template::{CertificateExtension, Value};
use ot_certs::x509;

#[derive(Debug, Parser)]
struct Opts {
#[command(flatten)]
init: InitializeTest,

/// Console receive timeout.
#[arg(long, value_parser = humantime::parse_duration, default_value = "10s")]
timeout: Duration,
}

// A helper trait for extracting data out of the `Value` type.
trait GetValue<T> {
fn get_value(&self) -> &T;
}

impl<T: std::fmt::Debug> GetValue<T> for Value<T> {
fn get_value(&self) -> &T {
match self {
Value::Variable(_) => panic!("Not expecting a Variable: {self:?}"),
Value::Literal(x) => x,
}
}
}

impl<T: std::fmt::Debug> GetValue<T> for Option<Value<T>> {
fn get_value(&self) -> &T {
match self {
None => panic!("Not expecting Option::None"),
Some(Value::Variable(_)) => panic!("Not expecting a Variable: {self:?}"),
Some(Value::Literal(x)) => x,
}
}
}

fn get_base64_blob(haystack: &str, rx: &str) -> Result<Vec<u8>> {
let rx = Regex::new(rx)?;
let encoded = rx
.captures(haystack)
.ok_or(anyhow!("Encoded certificate not found"))?;
let mut bin = Vec::new();
if !encoded[1].is_empty() {
let mut decoder = Decoder::<Base64>::new(encoded[1].as_bytes())?;
decoder.decode_to_end(&mut bin)?;
}
Ok(bin)
}

fn attestation_test(opts: &Opts, transport: &TransportWrapper) -> Result<()> {
let uart = transport.uart("console")?;
let capture = UartConsole::wait_for(
&*uart,
r"(?msR)Running.*PASS!$|FAIL!$|BFV:([0-9A-Fa-f]{8})$",
opts.timeout,
)?;

let _uds_bin = get_base64_blob(&capture[0], r"(?msR)UDS: (.*?)$")?;
let cdi0_bin = get_base64_blob(&capture[0], r"(?msR)CDI_0: (.*?)$")?;
let cdi1_bin = get_base64_blob(&capture[0], r"(?msR)CDI_1: (.*?)$")?;
let owner_page_0 = get_base64_blob(&capture[0], r"(?msR)OWNER_PAGE_0: (.*?)$")?;
let owner_page_1 = get_base64_blob(&capture[0], r"(?msR)OWNER_PAGE_1: (.*?)$")?;

// TODO: check UDS certificate.
let cdi0 = x509::parse_certificate(&cdi0_bin)?;
let cdi1 = x509::parse_certificate(&cdi1_bin)?;

// TODO: verify signature chain from CDI_1 to CDI_0 to UDS.

let image = Image::read_from_file(opts.init.bootstrap.bootstrap.as_deref().unwrap())?;

// TODO: determine the correct endianness for expressing these measurements.
let measurements = image
.subimages()?
.iter()
.map(|s| s.compute_digest().unwrap().to_le_bytes())
.collect::<Vec<_>>();
let owner_measurements = [
// The owner page digests should not include the signature or seal fields.
sha256::sha256(&owner_page_0[0..OwnerBlock::SIGNATURE_OFFSET]).to_le_bytes(),
sha256::sha256(&owner_page_1[0..OwnerBlock::SIGNATURE_OFFSET]).to_le_bytes(),
];

let CertificateExtension::DiceTcbInfo(dice) = &cdi0.private_extensions[0];
log::info!("Checking CDI_0 (ROM_EXT) DICE Extension: {dice:#?}");
assert_eq!(dice.model.get_value(), "ROM_EXT");
assert_eq!(dice.vendor.get_value(), "OpenTitan");
assert_eq!(dice.layer.get_value(), &BigUint::from(1u8));
let fw_ids = dice.fw_ids.as_ref().expect("list of fw_ids");
assert_eq!(fw_ids.len(), 1);
assert_eq!(fw_ids[0].digest.get_value(), &measurements[0]);

let CertificateExtension::DiceTcbInfo(dice) = &cdi1.private_extensions[0];
log::info!("Checking CDI_1 (Owner) DICE Extension: {dice:#?}");
assert_eq!(dice.model.get_value(), "Owner");
assert_eq!(dice.vendor.get_value(), "OpenTitan");
assert_eq!(dice.layer.get_value(), &BigUint::from(2u8));
let fw_ids = dice.fw_ids.as_ref().expect("list of fw_ids");
assert_eq!(fw_ids.len(), 2);
assert_eq!(fw_ids[0].digest.get_value(), &measurements[1]);
assert_eq!(fw_ids[1].digest.get_value(), &owner_measurements[0]);
Ok(())
}

fn main() -> Result<()> {
let opts = Opts::parse();
opts.init.init_logging();
let transport = opts.init.init_target()?;
attestation_test(&opts, &transport)?;
Ok(())
}

0 comments on commit d490c39

Please sign in to comment.