Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

minimum self-signed DICE #698

Merged
merged 4 commits into from
Sep 2, 2022
Merged

minimum self-signed DICE #698

merged 4 commits into from
Sep 2, 2022

Conversation

flihp
Copy link
Contributor

@flihp flihp commented Aug 9, 2022

This PR adds a minimal, self-signed DeviceId and Alias cert creation process to stage0. The majority of the functionality is collected in a library that provides a types for various artifacts (Cdi, CdiL0, AliasOkm etc), functions for their derivation, and an interface to build the DeviceId and Alias certs. The artifacts created by stage0 (Alias seed, Alias cert & DeviceId cert) are then passed through RAM to a demonstration / test task in hubris. This task extracts the relevant artifacts, checks signatures on the certs and dumps the FWID out to a ringbuf.

NOTE: The demonstration task should be removed from this PR before merging as it serves no useful purpose beyond demo / debug.

@flihp flihp requested a review from labbott August 9, 2022 16:43
chips/lpc55/chip.toml Show resolved Hide resolved
lib/dice/src/handoff.rs Outdated Show resolved Hide resolved
lib/dice/src/lib.rs Outdated Show resolved Hide resolved
lib/dice/src/lib.rs Outdated Show resolved Hide resolved
stage0/src/dice.rs Outdated Show resolved Hide resolved
stage0/src/dice.rs Outdated Show resolved Hide resolved
lib/dice/src/deviceid_cert_tmpl.rs Outdated Show resolved Hide resolved
task/dice-data/src/main.rs Outdated Show resolved Hide resolved
@flihp flihp force-pushed the dice-self-min branch 2 times, most recently from 43d36a9 to 24f50bb Compare August 16, 2022 16:57
@flihp
Copy link
Contributor Author

flihp commented Aug 16, 2022

NOTE: In addressing review comments I noticed that I hadn't enabled dice in the app toml until the commit with the demo task (that should not be merged). This should have been included in the stage0.toml in bbd3260. This has been fixed and now the dice feature is enabled by default. If DICE is disabled on an lpc55 executing this code then none of the artifacts are generated and stage0 kicks off the hubris image like it always has.

lib/dice/src/cert.rs Outdated Show resolved Hide resolved

// This memory is the USB peripheral SRAM that's 0x4000 bytes long. Changes
// to this address must be coordinated with the [dice_*] tables in
// chips/lpc55/chips.toml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build.rs could generate these from the config.

Copy link
Contributor Author

@flihp flihp Aug 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spent a bit of time looking at this and while I agree in general it would require adding some plumbing to xtask. The data we would need is in the 'chip' key from the top level toml table but AFAIK the currently available methods for getting at the app toml don't expose it:

  • build_util::config exposes the app config that includes tables under [config]
  • build_util::task_config exposes tables under the [task_name] table

That's not to say it can't be done, but AFAIK doing so would require a new interface in build_util and some plumbing in xtask to expose the chip key / file path. This is definitely something that should be added but maybe it can wait till after this PR is merged? #731

lib/dice/src/handoff.rs Outdated Show resolved Hide resolved
lib/dice/src/handoff.rs Outdated Show resolved Hide resolved
@flihp flihp force-pushed the dice-self-min branch 4 times, most recently from 47998cd to 78d82c3 Compare August 24, 2022 17:56
@@ -46,6 +46,12 @@ size = 4096
address = 0x4003A000
size = 4096

# this is the start of the USB SRAM AHB peripheral (0x4000 bytes total)
# we appropriate this SRAM for passing DICE artifacts
[dice_alias]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer that peripherals, especially those defined in the chip file, reflect the actual peripheral rather than our usage of it. In this case, renaming to usb_sram and extending to the full size.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally I'd agree with you here but eventually we'll be transferring other collections of artifacts across this boundary. In a future where we're transferring credentials to multiple recipients / hubris tasks we'll want guarantees that each task is only able to access the appropriate credentials. AFAIK, by dividing the USB SRAM into regions in the chip.toml we're able to grant access to each region using the existing mechanisms in the kernel. I wrote this up in RFD 282: https://rfd.shared.oxide.computer/rfd/0282#_ram

Use of the USB SRAM was a choice of convenience and so if the issue is with this memory being associated with a peripheral we could select any sufficiently large & unused region of memory for this purpose. This would still require that we list it in the chip.toml file as separate memory regions but it would keep us from labeling the USB SRAM as anything but what it is.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use SRAM 4 instead (0x20040000-0x20043FFF)? That seems less weird than using spare USB SRAM, and isn't otherwise used in the system.

(I'm not sure if we'd need to jump through any hoops at startup to turn it on, or whether it's on immediately)

lib/dice/src/cert.rs Outdated Show resolved Hide resolved
lib/dice/src/cert.rs Outdated Show resolved Hide resolved
lib/dice/src/cert.rs Outdated Show resolved Hide resolved
lib/dice/src/cert.rs Outdated Show resolved Hide resolved
lib/dice/src/lib.rs Outdated Show resolved Hide resolved
stage0/src/dice.rs Outdated Show resolved Hide resolved
lib/dice/src/lib.rs Outdated Show resolved Hide resolved
stage0/src/dice.rs Outdated Show resolved Hide resolved
stage0/src/image_header.rs Show resolved Hide resolved
@flihp flihp force-pushed the dice-self-min branch 9 times, most recently from 6bb2a73 to fce77ef Compare August 30, 2022 18:07
&self.as_bytes()[Self::PUB_START..Self::PUB_END]
}

fn set_pub(mut self, pubkey: &[u8; PUBLICKEY_SERIALIZED_LENGTH]) -> Self
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: in cases where we have a _LENGTH constant, could we remove the _END and use *_START + *LENGTH instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could. This case is a bit weird since this _LENGTH is coming from a different library. But either way _START, _END, _LENGTH stuff from the *_tmpl.rs is mostly generated at this point so the _LENGTH values in these files are just calculated values from _START and _END behind the scenes.

#[allow(dead_code)]
pub const SIZE: usize = 608;
#[allow(dead_code)]
pub const SERIAL_NUMBER_START: usize = 15;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: these could be more compactly expressed as ranges, e.g.

pub const SERIAL_NUMBER_RANGE: core::ops::Range<usize> = 15..16;

Doing so would reduce the potential for copy-paste errors!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL core::ops::Range exists. The code that generates these tmpl files can't go into the hubris tree cleanly since I'm using a bunch of external tools to do ASN.1 / x509 crimes but I'll queue this change up for the code gen. This may also help w/ your previous point about _LENGTH values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fn as_bytes(&self) -> &[u8];
fn as_mut_bytes(&mut self) -> &mut [u8];

const SERIAL_NUMBER_START: usize;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same comment here about using core::ops::Range<usize>)

const SERIAL_NUMBER_START: usize;
const SERIAL_NUMBER_END: usize;

fn get_serial_number(&self) -> u8 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional:

If you want to get fancy here, you could add a helper function of the form

    fn read_range<'a, T>(&'a self, r: core::ops::Range<usize>) -> T
    where T: TryFrom<&'a [u8]>
    {
        self.as_bytes()[r].try_into().unwrap_lite()
    }

Then, get_serial_number would become

fn get_serial_number(&self) -> u8 {
    u8::from_be_bytes(self.read_range(Self::SERIAL_NUMBER_RANGE))
}

(and similarly, the other getters would be a little less verbose)

lib/dice/src/cert.rs Outdated Show resolved Hide resolved
task/dice-data/src/main.rs Outdated Show resolved Hide resolved
.set_issuer_sn(dname_sn)
.set_subject_sn(dname_sn)
.set_pub(keypair.public.as_bytes())
.sign(keypair)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vague architectural vibes (no need to take action): the API would be more robust if we split up the intermediate vs signed certificate, e.g. if we had CertBuilder::sign(self, keypair: &Keypair) -> Cert

This would protect us from editing the certificate after it was signed. I'm not exactly sure how this would work with the generics – maybe the Cert type would be an associated type on CertBuilder, and it would store the various fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely like the idea of minimizing potential mistakes. I'm not sure how the generics would work either though. The two cert types have different fields (only one, but still different) so we'd need a different builder for each cert type?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't notice that there was a difference in the Wall Of Fields. In that case, we'd need separate builder types for each one (which might even let us kill the Cert trait entirely?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will probably cause everything in the Cert trait to move into another trait for the builders. Just moves things around.

0x88, 0x35, 0x50, 0x0E,
];

// Each time this library is built the notBefore time in the validity
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see anything in this library dealing with notBefore. Is this a TODO?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had initially set this up such that the cert templates were generated at build time. I pulled this out because it had weird dependencies on external tools used to create the x.509 structures. In constructing the templates I was initially setting the notBefore field with the system time before I realized that this would break reproducible builds. This can be done in such a way it won't break reproducible builds but it requires support from xtask. I'll make a ticket and add a TODO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lib/dice/src/handoff.rs Show resolved Hide resolved
// 1) The associated memory region has been enabled / turned on if
// necessary. This happens in the constructor / 'turn_on' function.
// 2) The function call is made by code in stage0.
let dst: &mut [u8] = unsafe { &mut *dst_ptr };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let dst: &mut [u8] = unsafe { &mut *dst_ptr };
let dst: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(ALIAS_START as *mut u8, AliasData::MAX_SIZE) };

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also check that AliasData::MAX_SIZE fits in the dice_alias peripheral's range. That should be a compile-time check of some sort.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// conditions are met:
// 1) The associated memory region has been enabled / turned on if
// necessary. This happens in the constructor / 'turn_on' function.
// 2) The function call is made by code in stage0.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels worth clarifying in the safety comment that this is safe even if these conditions aren't met since it will cause a fault.

// necessary. This should be done by code in stage0.
// 2) The task making the call has been granted access to the memory
// region by the kernel.
let src: &[u8] = unsafe { &*src_ptr };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let src: &[u8] = unsafe { &*src_ptr };
let src: &[u8] = unsafe { core::slice::from_raw_parts(ALIAS_START as *const u8, AliasData::MAX_SIZE) };

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about checking that AliasData::MAX_SIZE is less than or equal to dice_alias peripheral size.

// 1) The associated memory region has been enabled / turned on if
// necessary. This should be done by code in stage0.
// 2) The task making the call has been granted access to the memory
// region by the kernel.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about noting that this is safe even if these conditions are violated as it will cause a fault.

Comment on lines 61 to 65
if self.0.iter().all(|&w| w == 0) {
true
} else {
false
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.0.iter().all(|&w| w == 0) {
true
} else {
false
}
self.0.iter().all(|&w| w == 0)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oof 🤣


for (dst, src) in cdi
.chunks_exact_mut(mem::size_of::<u32>())
.zip(cdi_reg.0.as_ref())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

impl SeedBuf on CdiReg instead of cdi_reg.0.as_ref()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CdiReg is a slice of u32s not bytes like the SeedBufs. The type exists pretty much exclusively to ensure the registers that it wraps are zeroed when it's dropped.

pub fn run(image: &Image) {
let cdi = match Cdi::from_reg() {
Some(cdi) => cdi,
None => return,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the CDI isn't available, this is going to skip turning on the USB SRAM. That means that any task that attempts to access the DICE artifacts in stage1 will fault when they try to access it. Probably better to enable the USB SRAM and write out a well-known pattern indicating that DICE was disabled in stage0 so that can reported upstream.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This came up elsewhere in the review and I 💯 agree. For now the well-known pattern is 0's but I'll do some testing to see how well this works in practice.

The current cert implementation is limited to the creation of a self
signed cert for the DeviceId & the Alias cert. Cert creation is as
simple as possible with a template for each cert (DER) that is populated
with fixed length data like the platform serial number, certificate
serial number, public key & signature. Future work will address
programatic generation of these templates at build time to replace the
currently static ones.
The memory used in this commit is SRAM intended for use by USB. The
choice of using this memory region was mostly driven by convenience and
pivoting to a different range should be straight forward. The size of
this region was chosen based on the estimated size of the data passed.
The DeviceId and Alias cert are each ~600 bytes and the seed required to
reconstruct the Alias key is 32 bytes. All of this fits comfortably in
2k.
…ials.

This commit collects the DICE CDI from the NXP ROM, creates an ed25519
key pair from it (effectively the DeviceId) and uses this key to create
a self-signed DeviceId cert. The hash of the hubris image that will be
launched by stage0 is then hashed and the output used to generate the
Alias credentials (used for attestation). Finally the keying material
used to create the Alias key, and the cert chain from the Alias back to
the DeviceId are written to memory for use in an attestation exchange by
a future hubris task.

If run on an lpc55 with DICE disabled (the default config) none of the
DICE artifacts will be created and hubris will be booted as usual.
@flihp flihp merged commit 58dd4d3 into oxidecomputer:master Sep 2, 2022
FawazTirmizi pushed a commit to rivosinc/hubris that referenced this pull request Oct 3, 2022
* Update lpc55_support for compatibility with latest zeroize crate.

* lib-dice: Add new library with types for DICE artifacts and certs.

The current cert implementation is limited to the creation of a self
signed cert for the DeviceId & the Alias cert. Cert creation is as
simple as possible with a template for each cert (DER) that is populated
with fixed length data like the platform serial number, certificate
serial number, public key & signature. Future work will address
programatic generation of these templates at build time to replace the
currently static ones.

* lpc55/chip.toml: Add memory regions for DICE artifact handoff.

The memory used in this commit is SRAM intended for use by USB. The
choice of using this memory region was mostly driven by convenience and
pivoting to a different range should be straight forward. The size of
this region was chosen based on the estimated size of the data passed.
The DeviceId and Alias cert are each ~600 bytes and the seed required to
reconstruct the Alias key is 32 bytes. All of this fits comfortably in
2k.

* stage0: Use the dice library to implement minimal self-signed credentials.

This commit collects the DICE CDI from the NXP ROM, creates an ed25519
key pair from it (effectively the DeviceId) and uses this key to create
a self-signed DeviceId cert. The hash of the hubris image that will be
launched by stage0 is then hashed and the output used to generate the
Alias credentials (used for attestation). Finally the keying material
used to create the Alias key, and the cert chain from the Alias back to
the DeviceId are written to memory for use in an attestation exchange by
a future hubris task.

If run on an lpc55 with DICE disabled (the default config) none of the
DICE artifacts will be created and hubris will be booted as usual.
FawazTirmizi pushed a commit to rivosinc/hubris that referenced this pull request Oct 3, 2022
* Update lpc55_support for compatibility with latest zeroize crate.

* lib-dice: Add new library with types for DICE artifacts and certs.

The current cert implementation is limited to the creation of a self
signed cert for the DeviceId & the Alias cert. Cert creation is as
simple as possible with a template for each cert (DER) that is populated
with fixed length data like the platform serial number, certificate
serial number, public key & signature. Future work will address
programatic generation of these templates at build time to replace the
currently static ones.

* lpc55/chip.toml: Add memory regions for DICE artifact handoff.

The memory used in this commit is SRAM intended for use by USB. The
choice of using this memory region was mostly driven by convenience and
pivoting to a different range should be straight forward. The size of
this region was chosen based on the estimated size of the data passed.
The DeviceId and Alias cert are each ~600 bytes and the seed required to
reconstruct the Alias key is 32 bytes. All of this fits comfortably in
2k.

* stage0: Use the dice library to implement minimal self-signed credentials.

This commit collects the DICE CDI from the NXP ROM, creates an ed25519
key pair from it (effectively the DeviceId) and uses this key to create
a self-signed DeviceId cert. The hash of the hubris image that will be
launched by stage0 is then hashed and the output used to generate the
Alias credentials (used for attestation). Finally the keying material
used to create the Alias key, and the cert chain from the Alias back to
the DeviceId are written to memory for use in an attestation exchange by
a future hubris task.

If run on an lpc55 with DICE disabled (the default config) none of the
DICE artifacts will be created and hubris will be booted as usual.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants