diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml index bebe244..b93d4c1 100644 --- a/bootloader/Cargo.toml +++ b/bootloader/Cargo.toml @@ -15,6 +15,7 @@ cortex-m-rt = "*" cortex-m-rt-macros = "*" panic-abort = "0.3.2" rtt-target = { version = "*", features = ["cortex-m"] } +tock-registers = { version = "0.6.0", features = ["no_std_unit_tests"] } [profile.dev] panic = "abort" diff --git a/bootloader/src/bitfields.rs b/bootloader/src/bitfields.rs new file mode 100644 index 0000000..5a6dc92 --- /dev/null +++ b/bootloader/src/bitfields.rs @@ -0,0 +1,118 @@ +// Copyright 2020-2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::register_bitfields; + +register_bitfields! [u32, + // Generic or shared bitfields + pub Task [ + ENABLE OFFSET(0) NUMBITS(1) + ], + + pub Byte [ + VALUE OFFSET(0) NUMBITS(8) + ], + + pub Busy [ + /// Asserted when AES_BUSY or DES_BUSY or HASH_BUSY are asserted or when the DIN FIFO is not empty + BUSY OFFSET(0) NUMBITS(1) [ + Ready = 0, + Busy = 1 + ] + ], + + // CC_CTL register bitfields + pub CryptoMode [ + /// Determines the active cryptographic engine + MODE OFFSET(0) NUMBITS(5) [ + Bypass = 0, + Aes = 1, + AesToHash = 2, + AesAndHash = 3, + Des = 4, + DesToHash = 5, + DesAndHash = 6, + Hash = 7, + AesMacAndBypass = 9, + AesToHashAndDout = 10 + ] + ], + + // HOST_RGF register bitfields + pub Interrupts [ + /// This interrupt is asserted when all data was delivered to DIN buffer from SRAM + SRAM_TO_DIN OFFSET(4) NUMBITS(1), + /// This interrupt is asserted when all data was delivered to SRAM buffer from DOUT + DOUT_TO_SRAM OFFSET(5) NUMBITS(1), + /// This interrupt is asserted when all data was delivered to DIN buffer from memory + MEM_TO_DIN OFFSET(6) NUMBITS(1), + /// This interrupt is asserted when all data was delivered to memory buffer from DOUT + DOUT_TO_MEM OFFSET(7) NUMBITS(1), + AXI_ERROR OFFSET(8) NUMBITS(1), + /// The PKA end of operation interrupt status + PKA_EXP OFFSET(9) NUMBITS(1), + /// The RNG interrupt status + RNG OFFSET(10) NUMBITS(1), + /// The GPR interrupt status + SYM_DMA_COMPLETED OFFSET(11) NUMBITS(1) + ], + + pub RgfEndianness [ + /// DOUT write endianness + DOUT_WR_BG OFFSET(3) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ], + /// DIN write endianness + DIN_RD_BG OFFSET(7) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ], + /// DOUT write word endianness + DOUT_WR_WBG OFFSET(11) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ], + /// DIN write word endianness + DIN_RD_WBG OFFSET(15) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ] + ], + + // DIN and DOUT register bitfields + pub LliWord1 [ + /// Total number of bytes to read using DMA in this entry + BYTES_NUM OFFSET(0) NUMBITS(30), + /// Indicates the first LLI entry + FIRST OFFSET(30) NUMBITS(1), + /// Indicates the last LLI entry + LAST OFFSET(31) NUMBITS(1) + ], + + pub HashControl [ + // bit 2 is reserved but to simplify the logic we include it in the bitfield. + MODE OFFSET(0) NUMBITS(4) [ + MD5 = 0, + SHA1 = 1, + SHA256 = 2, + SHA224 = 10 + ] + ], + + pub PaddingConfig [ + /// Enable Padding generation. must be reset upon completion of padding. + DO_PAD OFFSET(2) NUMBITS(1) + ] +]; diff --git a/bootloader/src/crypto_cell.rs b/bootloader/src/crypto_cell.rs new file mode 100644 index 0000000..7cbd01e --- /dev/null +++ b/bootloader/src/crypto_cell.rs @@ -0,0 +1,283 @@ +// Copyright 2019-2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! CryptoCell 310 +//! +//! Author +//! ------------------- +//! +//! * Author: Jean-Michel Picod +//! * Date: October 1 2019 + +use super::bitfields; +use super::registers::{CryptoCellRegisters, NordicCC310Registers}; +use super::static_ref::StaticRef; +use core::cell::Cell; +#[cfg(debug_assertions)] +use rtt_target::rprintln; + +const SHA256_INIT_VALUE: [u32; 8] = [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, +]; + +#[derive(Copy, Clone)] +enum DigestAlgorithm { + Sha256 = 2, +} + +#[derive(Copy, Clone)] +enum OperationMode { + Idle, + Hash, +} + +pub struct CryptoCell310 { + registers: StaticRef, + power: StaticRef, + current_op: Cell, + + hash_ctx: Cell<[u32; 8]>, + hash_total_size: Cell, +} + +const CC310_BASE: StaticRef = + unsafe { StaticRef::new(0x5002B000 as *const CryptoCellRegisters) }; +const CC310_POWER: StaticRef = + unsafe { StaticRef::new(0x5002A500 as *const NordicCC310Registers) }; + +// Identification "signature" for CryptoCell. According to the documentation, the value +// held by this register is a fixed value, used by Host driver to verify CryptoCell presence +// at this address. +// This value was read from a CryptoCell-310 on a nRF52840-dongle kit. +const CC310_SIGNATURE: u32 = 0x20E00000; + +impl CryptoCell310 { + /// Creates a new instance of cryptocell state. + pub const fn new() -> Self { + CryptoCell310 { + registers: CC310_BASE, + power: CC310_POWER, + current_op: Cell::new(OperationMode::Idle), + + hash_ctx: Cell::new(SHA256_INIT_VALUE), + hash_total_size: Cell::new(0), + } + } + + fn enable(&self) { + self.power.enable.write(bitfields::Task::ENABLE::SET); + for _i in 1..10 { + let read_signature = self.registers.host_rgf.signature.get(); + if read_signature != CC310_SIGNATURE { + #[cfg(debug_assertions)] + rprintln!( + "[loop {}] Invalid CC310 signature. Expected {}, got {}\n", + _i, + CC310_SIGNATURE, + read_signature + ); + } else { + break; + } + } + if self.registers.host_rgf.signature.get() != CC310_SIGNATURE { + panic!("Failed to initialize CC310"); + } + // Make sure everything is set to little endian + self.registers.host_rgf.endian.write( + bitfields::RgfEndianness::DOUT_WR_BG::LittleEndian + + bitfields::RgfEndianness::DIN_RD_BG::LittleEndian + + bitfields::RgfEndianness::DOUT_WR_WBG::LittleEndian + + bitfields::RgfEndianness::DIN_RD_WBG::LittleEndian, + ); + // Always start the clock for DMA engine. It's too hard to keep + // track of which submodule needs DMA otherwise. + self.registers + .misc + .dma_clk_enable + .write(bitfields::Task::ENABLE::SET); + self.registers.host_rgf.interrupt_mask.write( + bitfields::Interrupts::SRAM_TO_DIN::CLEAR + + bitfields::Interrupts::DOUT_TO_SRAM::CLEAR + + bitfields::Interrupts::MEM_TO_DIN::CLEAR + + bitfields::Interrupts::DOUT_TO_MEM::CLEAR + + bitfields::Interrupts::AXI_ERROR::SET + + bitfields::Interrupts::PKA_EXP::SET + + bitfields::Interrupts::RNG::SET + + bitfields::Interrupts::SYM_DMA_COMPLETED::CLEAR, + ); + } + + fn disable(&self) { + self.registers.host_rgf.interrupt_mask.set(0); + self.power.enable.write(bitfields::Task::ENABLE::CLEAR); + self.registers + .misc + .dma_clk_enable + .write(bitfields::Task::ENABLE::CLEAR); + } + + fn clear_data(&self) { + let mut ctx = self.hash_ctx.get(); + ctx.iter_mut().for_each(|b| *b = 0); + self.hash_ctx.set(ctx); + self.hash_total_size.set(0); + } + + /// Adds data to the current hash computation. + /// + /// You have to know in advance if is this is going to be the last block, and indicate that + /// correctly. Sizes of chunks before the last need to be multiples of 64. + pub fn update(&self, data: &[u8], is_last_block: bool) { + // Start CryptoCell + self.enable(); + + while self.registers.ctrl.hash_busy.is_set(bitfields::Busy::BUSY) {} + while self + .registers + .ctrl + .crypto_busy + .is_set(bitfields::Busy::BUSY) + {} + while self + .registers + .din + .mem_dma_busy + .is_set(bitfields::Busy::BUSY) + {} + + // Start HASH module and configure it + self.current_op.set(OperationMode::Hash); + self.registers + .misc + .hash_clk_enable + .write(bitfields::Task::ENABLE::SET); + self.registers + .ctrl + .crypto_ctl + .write(bitfields::CryptoMode::MODE::Hash); + self.registers + .hash + .padding + .write(bitfields::Task::ENABLE::SET); + let size = self.hash_total_size.get(); + self.registers.hash.hash_len_lsb.set(size as u32); + self.registers + .hash + .hash_len_msb + .set(size.wrapping_shr(32) as u32); + self.registers + .hash + .control + .set(DigestAlgorithm::Sha256 as u32); + + // Digest must be set backwards because writing to HASH[0] + // starts computation + let mut digest = self.hash_ctx.get(); + for i in (0..digest.len()).rev() { + self.registers.hash.hash[i].set(digest[i]); + } + while self.registers.ctrl.hash_busy.is_set(bitfields::Busy::BUSY) {} + + // Process data + if !data.is_empty() { + if is_last_block { + self.registers + .hash + .auto_hw_padding + .write(bitfields::Task::ENABLE::SET); + } + self.registers.din.src_lli_word0.set(data.as_ptr() as u32); + self.registers + .din + .src_lli_word1 + .write(bitfields::LliWord1::BYTES_NUM.val(data.len() as u32)); + while !self + .registers + .host_rgf + .interrupts + .is_set(bitfields::Interrupts::MEM_TO_DIN) + {} + self.registers + .host_rgf + .interrupt_clear + .write(bitfields::Interrupts::MEM_TO_DIN::SET); + } else { + // use DO_PAD to complete padding of previous operation + self.registers + .hash + .pad_config + .write(bitfields::PaddingConfig::DO_PAD::SET); + } + while self + .registers + .ctrl + .crypto_busy + .is_set(bitfields::Busy::BUSY) + {} + while self + .registers + .din + .mem_dma_busy + .is_set(bitfields::Busy::BUSY) + {} + + // Update context and total size + for i in (0..digest.len()).rev() { + digest[i] = self.registers.hash.hash[i].get(); + } + self.hash_ctx.set(digest); + let new_size: u64 = ((self.registers.hash.hash_len_msb.get() as u64) << 32) + + (self.registers.hash.hash_len_lsb.get() as u64); + self.hash_total_size.set(new_size); + + // Disable HASH module + self.registers + .hash + .padding + .write(bitfields::Task::ENABLE::SET); + self.registers + .hash + .auto_hw_padding + .write(bitfields::Task::ENABLE::CLEAR); + self.registers + .hash + .pad_config + .write(bitfields::PaddingConfig::DO_PAD::CLEAR); + while self + .registers + .ctrl + .crypto_busy + .is_set(bitfields::Busy::BUSY) + {} + self.registers + .misc + .hash_clk_enable + .write(bitfields::Task::ENABLE::CLEAR); + + self.disable(); + } + + /// Clears the data for potential reuse, and returns the result. + pub fn finalize_and_clear(&self) -> [u8; 32] { + use byteorder::{BigEndian, ByteOrder}; + let words = self.hash_ctx.get(); + let mut bytes = [0u8; 32]; + for (i, word) in words.iter().enumerate() { + BigEndian::write_u32(&mut bytes[4 * i..4 * i + 4], *word); + } + self.clear_data(); + bytes + } +} diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs index 51bd03d..b14e976 100644 --- a/bootloader/src/main.rs +++ b/bootloader/src/main.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Google LLC +// Copyright 2021-2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,6 +15,11 @@ #![no_main] #![no_std] +mod bitfields; +mod crypto_cell; +mod registers; +mod static_ref; + extern crate cortex_m; extern crate cortex_m_rt as rt; @@ -69,7 +74,7 @@ struct BootPartition { } impl BootPartition { - const _FIRMWARE_LENGTH: usize = 0x00040000; + const FIRMWARE_LENGTH: usize = 0x00040000; /// Reads the metadata, returns the timestamp if all checks pass. pub fn read_timestamp(&self) -> Result { @@ -93,18 +98,40 @@ impl BootPartition { Ok(metadata.timestamp) } - /// Placeholder for the SHA256 implementation. + /// Computes the SHA256 of metadata information and partition data. /// - /// TODO implemented in next PR - /// Without it, the bootloader will never boot anything. - fn compute_upgrade_hash(&self, _metadata_page: &[u8]) -> [u8; 32] { - [0; 32] + /// Assumes that firmware address and length are divisible by the page size. + /// This is the hardware implementation on the cryptocell. + #[allow(clippy::assertions_on_constants)] + fn compute_upgrade_hash(&self, metadata_page: &[u8]) -> [u8; 32] { + debug_assert!(self.firmware_address % PAGE_SIZE == 0); + debug_assert!(BootPartition::FIRMWARE_LENGTH % PAGE_SIZE == 0); + let cc310 = crypto_cell::CryptoCell310::new(); + for page_offset in (0..BootPartition::FIRMWARE_LENGTH).step_by(PAGE_SIZE) { + let page = unsafe { read_page(self.firmware_address + page_offset) }; + cc310.update(&page, false); + } + cc310.update(&metadata_page[32..Metadata::DATA_LEN], true); + cc310.finalize_and_clear() } /// Jump to the firmware. pub fn boot(&self) -> ! { let address = self.firmware_address; + // Clear any pending Cryptocell interrupt in NVIC + let peripherals = cortex_m::Peripherals::take().unwrap(); + unsafe { + // We could only clear cryptocell interrupts, but let's clean up before booting. + // Example code to clear more specifically: + // const CC310_IRQ: u16 = 42; + // peripherals.NVIC.icpr[usize::from(CC310_IRQ / 32)].write(1 << (CC310_IRQ % 32)); + peripherals.NVIC.icer[0].write(0xffff_ffff); + peripherals.NVIC.icpr[0].write(0xffff_ffff); + peripherals.NVIC.icer[1].write(0xffff_ffff); + peripherals.NVIC.icpr[1].write(0xffff_ffff); + } + #[cfg(debug_assertions)] rprintln!("Boot jump to {:08X}", address); let address_pointer = address as *const u32; diff --git a/bootloader/src/registers.rs b/bootloader/src/registers.rs new file mode 100644 index 0000000..17725dc --- /dev/null +++ b/bootloader/src/registers.rs @@ -0,0 +1,139 @@ +// Copyright 2020-2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::bitfields::{ + Busy, CryptoMode, HashControl, Interrupts, LliWord1, PaddingConfig, RgfEndianness, Task, +}; +use tock_registers::{ + register_structs, + registers::{ReadOnly, ReadWrite, WriteOnly}, +}; + +register_structs! { + pub CryptoCellControlRegisters { + /// Defines the cryptographic flow + (0x0000 => pub crypto_ctl: WriteOnly), + (0x0004 => _reserved0), + /// This register is set whent the cryptographic core is busy + (0x0010 => pub crypto_busy: ReadOnly), + (0x0014 => _reserved1), + /// This register is set when the Hash engine is busy + (0x001C => pub hash_busy: ReadOnly), + (0x0020 => @END), + } +} + +register_structs! { + pub CryptoCellDinRegisters { + (0x0000 => _reserved0), + /// Indicates whether memoty (AXI) source DMA (DIN) is busy + (0x0020 => pub mem_dma_busy: ReadOnly), + (0x0024 => _reserved1), + /// This register is used in direct LLI mode - holds the location of the data source + /// in the memory (AXI) + (0x0028 => pub src_lli_word0: WriteOnly), + /// This register is used in direct LLI mode - holds the number of bytes to be read + /// from the memory (AXI). + /// Writing to this register triggers the DMA. + (0x002C => pub src_lli_word1: WriteOnly), + (0x0030 => @END), + } +} + +register_structs! { + pub CryptoCellHashRegisters { + /// Write initial hash value or read final hash value + (0x0000 => pub hash: [ReadWrite; 9]), + (0x0024 => _reserved0), + /// HW padding automatically activated by engine. + /// For the special case of ZERO bytes data vector this register should not be used! instead use HASH_PAD_CFG + (0x0044 => pub auto_hw_padding: WriteOnly), + (0x0048 => _reserved1), + /// Selects which HASH mode to run + (0x0180 => pub control: ReadWrite), + /// This register enables the hash hw padding. + (0x0184 => pub padding: ReadWrite), + /// HASH_PAD_CFG Register. + (0x0188 => pub pad_config: ReadWrite), + /// This register hold the length of current hash operation + (0x018C => pub hash_len_lsb: ReadWrite), + /// This register hold the length of current hash operation + (0x0190 => pub hash_len_msb: ReadWrite), + (0x0194 => @END), + } +} + +register_structs! { + pub CryptoCellHostRgfRegisters { + /// The Interrupt Request register. + /// Each bit of this register holds the interrupt status of a single interrupt source. + (0x0000 => pub interrupts: ReadOnly), + /// The Interrupt Mask register. Each bit of this register holds the mask of a single + /// interrupt source. + (0x0004 => pub interrupt_mask: ReadWrite), + /// Interrupt Clear Register + (0x0008 => pub interrupt_clear: WriteOnly), + /// This register defines the endianness of the Host-accessible registers. + (0x000C => pub endian: ReadWrite), + (0x0010 => _reserved0), + /// This register holds the CryptoCell product signature. + (0x0024 => pub signature: ReadOnly), + (0x0028 => @END), + } +} + +register_structs! { + pub CryptoCellMiscRegisters { + (0x0000 => _reserved0), + /// The HASH clock enable register + (0x0018 => pub hash_clk_enable: ReadWrite), + /// The PKA clock enable register + (0x001C => _reserved1), + /// The DMA clock enable register + (0x0020 => pub dma_clk_enable: ReadWrite), + /// the CryptoCell clocks' status register + (0x0024 => @END), + } +} + +register_structs! { + pub NordicCC310Registers { + (0x0000 => pub enable: ReadWrite), + (0x0004 => @END), + }, + + pub CryptoCellRegisters { + (0x0000 => _reserved0), + /// HASH registers + /// - Base address: 0x0640 + (0x0640 => pub hash: CryptoCellHashRegisters), + (0x07D4 => _reserved1), + /// Misc registers + /// - Base address: 0x0800 + (0x0800 => pub misc: CryptoCellMiscRegisters), + (0x0824 => _reserved2), + /// CryptoCell control registers + /// - Base address: 0x0900 + (0x0900 => pub ctrl: CryptoCellControlRegisters), + (0x0920 => _reserved3), + /// HOST_RGF registers + /// - Base address: 0x0A00 + (0x0A00 => pub host_rgf: CryptoCellHostRgfRegisters), + (0x0A28 => _reserved4), + /// DIN registers + /// - Base address: 0x0C00 + (0x0C00 => pub din: CryptoCellDinRegisters), + (0x0C30 => @END), + } +} diff --git a/bootloader/src/static_ref.rs b/bootloader/src/static_ref.rs new file mode 100644 index 0000000..4fc27f4 --- /dev/null +++ b/bootloader/src/static_ref.rs @@ -0,0 +1,46 @@ +//! Wrapper type for safe pointers to static memory. +//! +//! Imported from: +//! https://github.com/tock/tock/blob/master/kernel/src/utilities/static_ref.rs + +use core::ops::Deref; + +/// A pointer to statically allocated mutable data such as memory mapped I/O +/// registers. +/// +/// This is a simple wrapper around a raw pointer that encapsulates an unsafe +/// dereference in a safe manner. It serve the role of creating a `&'static T` +/// given a raw address and acts similarly to `extern` definitions, except +/// `StaticRef` is subject to module and crate boundaries, while `extern` +/// definitions can be imported anywhere. +#[derive(Debug)] +pub struct StaticRef { + ptr: *const T, +} + +impl StaticRef { + /// Create a new `StaticRef` from a raw pointer + /// + /// ## Safety + /// + /// Callers must pass in a reference to statically allocated memory which + /// does not overlap with other values. + pub const unsafe fn new(ptr: *const T) -> StaticRef { + StaticRef { ptr } + } +} + +impl Clone for StaticRef { + fn clone(&self) -> Self { + StaticRef { ptr: self.ptr } + } +} + +impl Copy for StaticRef {} + +impl Deref for StaticRef { + type Target = T; + fn deref(&self) -> &'static T { + unsafe { &*self.ptr } + } +}