Moves vendor commands into TockEnv (#614)
This move changes the Env trait: It removes all functionality that is used only in vendor commands (`FirmwareProtection`, `UpgradeStorage`) and adds a function to call when parsing CBOR commands. The abstraction necessary to test these commands is instead realized through compile flags. The mock upgrade storage is active when compiled for std for example.
This commit is contained in:
198
src/env/tock/storage.rs
vendored
198
src/env/tock/storage.rs
vendored
@@ -12,20 +12,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::storage_helper::{find_slice, is_aligned, ModRange, Partition};
|
||||
use super::upgrade_helper::{
|
||||
check_metadata, parse_metadata_hash, parse_metadata_version, METADATA_SIGN_OFFSET,
|
||||
};
|
||||
use super::TockEnv;
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::vec::Vec;
|
||||
use arrayref::array_ref;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use core::cell::Cell;
|
||||
use crypto::sha256::Sha256;
|
||||
use crypto::{ecdsa, Hash256};
|
||||
use libtock_core::{callback, syscalls};
|
||||
use opensk::api::upgrade_storage::helper::{find_slice, is_aligned, ModRange, Partition};
|
||||
use opensk::api::upgrade_storage::UpgradeStorage;
|
||||
use opensk::api::crypto::sha256::Sha256;
|
||||
use opensk::env::Sha;
|
||||
use persistent_store::{Storage, StorageError, StorageIndex, StorageResult};
|
||||
|
||||
const DRIVER_NUMBER: usize = 0x50003;
|
||||
const METADATA_SIGN_OFFSET: usize = 0x800;
|
||||
|
||||
const UPGRADE_PUBLIC_KEY: &[u8; 65] =
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_upgrade_pubkey.bin"));
|
||||
@@ -337,7 +337,7 @@ impl TockUpgradeStorage {
|
||||
/// Checks if the metadata's hash matches the partition's content.
|
||||
fn check_partition_hash(&self, metadata: &[u8]) -> StorageResult<()> {
|
||||
let start_address = self.metadata.start() + METADATA_SIGN_OFFSET;
|
||||
let mut hasher = Sha256::new();
|
||||
let mut hasher = Sha::<TockEnv>::new();
|
||||
for range in self.partition.ranges_from(start_address) {
|
||||
let partition_slice = unsafe { read_slice(range.start(), range.length()) };
|
||||
// The hash implementation handles this in chunks, so no memory issues.
|
||||
@@ -349,10 +349,8 @@ impl TockUpgradeStorage {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeStorage for TockUpgradeStorage {
|
||||
fn write_bundle(&mut self, offset: usize, data: Vec<u8>) -> StorageResult<()> {
|
||||
pub fn write_bundle(&mut self, offset: usize, data: Vec<u8>) -> StorageResult<()> {
|
||||
if data.is_empty() {
|
||||
return Err(StorageError::OutOfBounds);
|
||||
}
|
||||
@@ -363,7 +361,7 @@ impl UpgradeStorage for TockUpgradeStorage {
|
||||
let write_range = ModRange::new(address, data.len());
|
||||
if self.contains_metadata(&write_range)? {
|
||||
let new_metadata = &data[self.metadata.start() - address..][..self.metadata.length()];
|
||||
check_metadata(self, UPGRADE_PUBLIC_KEY, new_metadata)?;
|
||||
check_metadata::<TockEnv>(self, UPGRADE_PUBLIC_KEY, new_metadata)?;
|
||||
}
|
||||
|
||||
// Erases all pages that have their first byte in the write range.
|
||||
@@ -384,11 +382,11 @@ impl UpgradeStorage for TockUpgradeStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bundle_identifier(&self) -> u32 {
|
||||
pub fn bundle_identifier(&self) -> u32 {
|
||||
self.identifier
|
||||
}
|
||||
|
||||
fn running_firmware_version(&self) -> u64 {
|
||||
pub fn running_firmware_version(&self) -> u64 {
|
||||
let running_metadata = unsafe {
|
||||
read_slice(
|
||||
self.running_metadata.start(),
|
||||
@@ -398,175 +396,3 @@ impl UpgradeStorage for TockUpgradeStorage {
|
||||
parse_metadata_version(running_metadata)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the metadata of an upgrade, and checks its correctness.
|
||||
///
|
||||
/// The metadata is a page starting with:
|
||||
/// - 32 B upgrade hash (SHA256)
|
||||
/// - 64 B signature,
|
||||
/// that are not signed over. The second part is included in the signature with
|
||||
/// - 8 B version and
|
||||
/// - 4 B partition address in little endian encoding
|
||||
/// written at METADATA_SIGN_OFFSET.
|
||||
///
|
||||
/// Checks signature correctness against the hash, and whether the partition offset matches.
|
||||
/// Whether the hash matches the partition content is not tested here!
|
||||
fn check_metadata(
|
||||
upgrade_locations: &impl UpgradeStorage,
|
||||
public_key_bytes: &[u8],
|
||||
metadata: &[u8],
|
||||
) -> StorageResult<()> {
|
||||
const METADATA_LEN: usize = 0x1000;
|
||||
if metadata.len() != METADATA_LEN {
|
||||
return Err(StorageError::CustomError);
|
||||
}
|
||||
|
||||
let version = parse_metadata_version(metadata);
|
||||
if version < upgrade_locations.running_firmware_version() {
|
||||
return Err(StorageError::CustomError);
|
||||
}
|
||||
|
||||
let metadata_address = LittleEndian::read_u32(&metadata[METADATA_SIGN_OFFSET + 8..][..4]);
|
||||
if metadata_address != upgrade_locations.bundle_identifier() {
|
||||
return Err(StorageError::CustomError);
|
||||
}
|
||||
|
||||
verify_signature(
|
||||
array_ref!(metadata, 32, 64),
|
||||
public_key_bytes,
|
||||
parse_metadata_hash(metadata),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the metadata, returns the hash.
|
||||
fn parse_metadata_hash(data: &[u8]) -> &[u8; 32] {
|
||||
array_ref!(data, 0, 32)
|
||||
}
|
||||
|
||||
/// Parses the metadata, returns the firmware version.
|
||||
fn parse_metadata_version(data: &[u8]) -> u64 {
|
||||
LittleEndian::read_u64(&data[METADATA_SIGN_OFFSET..][..8])
|
||||
}
|
||||
|
||||
/// Verifies the signature over the given hash.
|
||||
///
|
||||
/// The public key is COSE encoded, and the hash is a SHA256.
|
||||
fn verify_signature(
|
||||
signature_bytes: &[u8; 64],
|
||||
public_key_bytes: &[u8],
|
||||
signed_hash: &[u8; 32],
|
||||
) -> StorageResult<()> {
|
||||
let signature =
|
||||
ecdsa::Signature::from_bytes(signature_bytes).ok_or(StorageError::CustomError)?;
|
||||
let public_key = ecdsa::PubKey::from_bytes_uncompressed(public_key_bytes)
|
||||
.ok_or(StorageError::CustomError)?;
|
||||
if !public_key.verify_hash_vartime(signed_hash, &signature) {
|
||||
return Err(StorageError::CustomError);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use opensk::env::test::TestEnv;
|
||||
use opensk::env::Env;
|
||||
|
||||
#[test]
|
||||
fn test_check_metadata() {
|
||||
let mut env = TestEnv::default();
|
||||
let private_key = crypto::ecdsa::SecKey::gensk(env.rng());
|
||||
let upgrade_locations = env.upgrade_storage().unwrap();
|
||||
|
||||
const METADATA_LEN: usize = 0x1000;
|
||||
const METADATA_SIGN_OFFSET: usize = 0x800;
|
||||
let mut metadata = vec![0xFF; METADATA_LEN];
|
||||
LittleEndian::write_u32(&mut metadata[METADATA_SIGN_OFFSET + 8..][..4], 0x60000);
|
||||
|
||||
let mut signed_over_data = metadata[METADATA_SIGN_OFFSET..].to_vec();
|
||||
signed_over_data.extend(&[0xFF; 0x20000]);
|
||||
let signed_hash = Sha256::hash(&signed_over_data);
|
||||
|
||||
metadata[..32].copy_from_slice(&signed_hash);
|
||||
let signature = private_key.sign_rfc6979::<Sha256>(&signed_over_data);
|
||||
let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
|
||||
signature.to_bytes(&mut signature_bytes);
|
||||
metadata[32..96].copy_from_slice(&signature_bytes);
|
||||
|
||||
let public_key = private_key.genpk();
|
||||
let mut public_key_bytes = [0; 65];
|
||||
public_key.to_bytes_uncompressed(&mut public_key_bytes);
|
||||
|
||||
assert_eq!(
|
||||
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
// Manipulating the partition address fails.
|
||||
metadata[METADATA_SIGN_OFFSET + 8] = 0x88;
|
||||
assert_eq!(
|
||||
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
|
||||
Err(StorageError::CustomError)
|
||||
);
|
||||
metadata[METADATA_SIGN_OFFSET + 8] = 0x00;
|
||||
// Wrong metadata length fails.
|
||||
assert_eq!(
|
||||
check_metadata(
|
||||
upgrade_locations,
|
||||
&public_key_bytes,
|
||||
&metadata[..METADATA_LEN - 1]
|
||||
),
|
||||
Err(StorageError::CustomError)
|
||||
);
|
||||
// Manipulating the hash fails.
|
||||
metadata[0] ^= 0x01;
|
||||
assert_eq!(
|
||||
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
|
||||
Err(StorageError::CustomError)
|
||||
);
|
||||
metadata[0] ^= 0x01;
|
||||
// Manipulating the signature fails.
|
||||
metadata[32] ^= 0x01;
|
||||
assert_eq!(
|
||||
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
|
||||
Err(StorageError::CustomError)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_signature() {
|
||||
let mut env = TestEnv::default();
|
||||
let private_key = crypto::ecdsa::SecKey::gensk(env.rng());
|
||||
let message = [0x44; 64];
|
||||
let signed_hash = Sha256::hash(&message);
|
||||
let signature = private_key.sign_rfc6979::<Sha256>(&message);
|
||||
|
||||
let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
|
||||
signature.to_bytes(&mut signature_bytes);
|
||||
|
||||
let public_key = private_key.genpk();
|
||||
let mut public_key_bytes = [0; 65];
|
||||
public_key.to_bytes_uncompressed(&mut public_key_bytes);
|
||||
|
||||
assert_eq!(
|
||||
verify_signature(&signature_bytes, &public_key_bytes, &signed_hash),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
verify_signature(&signature_bytes, &public_key_bytes, &[0x55; 32]),
|
||||
Err(StorageError::CustomError)
|
||||
);
|
||||
public_key_bytes[0] ^= 0x01;
|
||||
assert_eq!(
|
||||
verify_signature(&signature_bytes, &public_key_bytes, &signed_hash),
|
||||
Err(StorageError::CustomError)
|
||||
);
|
||||
public_key_bytes[0] ^= 0x01;
|
||||
signature_bytes[0] ^= 0x01;
|
||||
assert_eq!(
|
||||
verify_signature(&signature_bytes, &public_key_bytes, &signed_hash),
|
||||
Err(StorageError::CustomError)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user