New metadata format (#539)
* new metadata format is used * Update bootloader/src/main.rs Co-authored-by: ztoked <zhalvorsen@google.com> * splits the metadata signed and unsigned parts evenly * fixes pylint Co-authored-by: ztoked <zhalvorsen@google.com>
This commit is contained in:
@@ -34,6 +34,7 @@ use rtt_target::{rprintln, rtt_init_print};
|
|||||||
|
|
||||||
/// Size of a flash page in bytes.
|
/// Size of a flash page in bytes.
|
||||||
const PAGE_SIZE: usize = 0x1000;
|
const PAGE_SIZE: usize = 0x1000;
|
||||||
|
const METADATA_SIGN_OFFSET: usize = 0x800;
|
||||||
|
|
||||||
/// A flash page.
|
/// A flash page.
|
||||||
type Page = [u8; PAGE_SIZE];
|
type Page = [u8; PAGE_SIZE];
|
||||||
@@ -48,21 +49,19 @@ unsafe fn read_page(address: usize) -> Page {
|
|||||||
/// Parsed metadata for a firmware partition.
|
/// Parsed metadata for a firmware partition.
|
||||||
struct Metadata {
|
struct Metadata {
|
||||||
checksum: [u8; 32],
|
checksum: [u8; 32],
|
||||||
timestamp: u32,
|
_signature: [u8; 64],
|
||||||
|
version: u64,
|
||||||
address: u32,
|
address: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Metadata {
|
|
||||||
pub const DATA_LEN: usize = 40;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads the metadata from a flash page.
|
/// Reads the metadata from a flash page.
|
||||||
impl From<Page> for Metadata {
|
impl From<Page> for Metadata {
|
||||||
fn from(page: Page) -> Self {
|
fn from(page: Page) -> Self {
|
||||||
Metadata {
|
Metadata {
|
||||||
checksum: page[0..32].try_into().unwrap(),
|
checksum: page[0..32].try_into().unwrap(),
|
||||||
timestamp: LittleEndian::read_u32(&page[32..36]),
|
_signature: page[32..96].try_into().unwrap(),
|
||||||
address: LittleEndian::read_u32(&page[36..Metadata::DATA_LEN]),
|
version: LittleEndian::read_u64(&page[METADATA_SIGN_OFFSET..][..8]),
|
||||||
|
address: LittleEndian::read_u32(&page[METADATA_SIGN_OFFSET + 8..][..4]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,15 +75,15 @@ struct BootPartition {
|
|||||||
impl BootPartition {
|
impl BootPartition {
|
||||||
const FIRMWARE_LENGTH: usize = 0x00040000;
|
const FIRMWARE_LENGTH: usize = 0x00040000;
|
||||||
|
|
||||||
/// Reads the metadata, returns the timestamp if all checks pass.
|
/// Reads the metadata, returns the firmware version if all checks pass.
|
||||||
pub fn read_timestamp(&self) -> Result<u32, ()> {
|
pub fn read_version(&self) -> Result<u64, ()> {
|
||||||
let metadata_page = unsafe { read_page(self.metadata_address) };
|
let metadata_page = unsafe { read_page(self.metadata_address) };
|
||||||
let hash_value = self.compute_upgrade_hash(&metadata_page);
|
let hash_value = self.compute_upgrade_hash(&metadata_page);
|
||||||
let metadata = Metadata::from(metadata_page);
|
let metadata = Metadata::from(metadata_page);
|
||||||
if self.firmware_address != metadata.address as usize {
|
if self.firmware_address != metadata.address as usize {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
rprintln!(
|
rprintln!(
|
||||||
"Firmware address mismatch: expected 0x{:08X}, metadata 0x{:08X}",
|
"Partition address mismatch: expected 0x{:08X}, metadata 0x{:08X}",
|
||||||
self.firmware_address,
|
self.firmware_address,
|
||||||
metadata.address as usize
|
metadata.address as usize
|
||||||
);
|
);
|
||||||
@@ -95,7 +94,7 @@ impl BootPartition {
|
|||||||
rprintln!("Hash mismatch");
|
rprintln!("Hash mismatch");
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
Ok(metadata.timestamp)
|
Ok(metadata.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the SHA256 of metadata information and partition data.
|
/// Computes the SHA256 of metadata information and partition data.
|
||||||
@@ -107,11 +106,14 @@ impl BootPartition {
|
|||||||
debug_assert!(self.firmware_address % PAGE_SIZE == 0);
|
debug_assert!(self.firmware_address % PAGE_SIZE == 0);
|
||||||
debug_assert!(BootPartition::FIRMWARE_LENGTH % PAGE_SIZE == 0);
|
debug_assert!(BootPartition::FIRMWARE_LENGTH % PAGE_SIZE == 0);
|
||||||
let cc310 = crypto_cell::CryptoCell310::new();
|
let cc310 = crypto_cell::CryptoCell310::new();
|
||||||
|
cc310.update(&metadata_page[METADATA_SIGN_OFFSET..], false);
|
||||||
for page_offset in (0..BootPartition::FIRMWARE_LENGTH).step_by(PAGE_SIZE) {
|
for page_offset in (0..BootPartition::FIRMWARE_LENGTH).step_by(PAGE_SIZE) {
|
||||||
let page = unsafe { read_page(self.firmware_address + page_offset) };
|
let page = unsafe { read_page(self.firmware_address + page_offset) };
|
||||||
cc310.update(&page, false);
|
cc310.update(
|
||||||
|
&page,
|
||||||
|
page_offset + PAGE_SIZE == BootPartition::FIRMWARE_LENGTH,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
cc310.update(&metadata_page[32..Metadata::DATA_LEN], true);
|
|
||||||
cc310.finalize_and_clear()
|
cc310.finalize_and_clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,12 +158,12 @@ fn main() -> ! {
|
|||||||
};
|
};
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
rprintln!("Reading partition A");
|
rprintln!("Reading partition A");
|
||||||
let timestamp_a = partition_a.read_timestamp();
|
let version_a = partition_a.read_version();
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
rprintln!("Reading partition B");
|
rprintln!("Reading partition B");
|
||||||
let timestamp_b = partition_b.read_timestamp();
|
let version_b = partition_b.read_version();
|
||||||
|
|
||||||
match (timestamp_a, timestamp_b) {
|
match (version_a, version_b) {
|
||||||
(Ok(t1), Ok(t2)) => {
|
(Ok(t1), Ok(t2)) => {
|
||||||
if t1 >= t2 {
|
if t1 >= t2 {
|
||||||
partition_a.boot()
|
partition_a.boot()
|
||||||
|
|||||||
22
deploy.py
22
deploy.py
@@ -38,7 +38,7 @@ from tockloader import tockloader as loader
|
|||||||
from tockloader.exceptions import TockLoaderException
|
from tockloader.exceptions import TockLoaderException
|
||||||
|
|
||||||
import tools.configure
|
import tools.configure
|
||||||
from tools.deploy_partition import create_metadata, pad_to
|
from tools.deploy_partition import create_metadata, load_priv_key, pad_to
|
||||||
|
|
||||||
PROGRAMMERS = frozenset(("jlink", "openocd", "pyocd", "nordicdfu", "none"))
|
PROGRAMMERS = frozenset(("jlink", "openocd", "pyocd", "nordicdfu", "none"))
|
||||||
|
|
||||||
@@ -622,7 +622,9 @@ class OpenSKInstaller:
|
|||||||
# The kernel is already padded when read.
|
# The kernel is already padded when read.
|
||||||
firmware_image = kernel + pad_to(app, app_size)
|
firmware_image = kernel + pad_to(app, app_size)
|
||||||
|
|
||||||
metadata = create_metadata(firmware_image, board_props.kernel_address)
|
priv_key = load_priv_key(self.args.upgrade_priv_key)
|
||||||
|
metadata = create_metadata(firmware_image, board_props.kernel_address,
|
||||||
|
self.args.version, priv_key)
|
||||||
if self.args.verbose_build:
|
if self.args.verbose_build:
|
||||||
info(f"Metadata bytes: {metadata}")
|
info(f"Metadata bytes: {metadata}")
|
||||||
|
|
||||||
@@ -1131,6 +1133,22 @@ if __name__ == "__main__":
|
|||||||
help=("Don't check that patches are in sync with their submodules."),
|
help=("Don't check that patches are in sync with their submodules."),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
main_parser.add_argument(
|
||||||
|
"--private-key",
|
||||||
|
type=str,
|
||||||
|
default="crypto_data/opensk_upgrade.key",
|
||||||
|
dest="upgrade_priv_key",
|
||||||
|
help=("PEM file for signing the firmware."),
|
||||||
|
)
|
||||||
|
|
||||||
|
main_parser.add_argument(
|
||||||
|
"--version",
|
||||||
|
type=int,
|
||||||
|
default=-1,
|
||||||
|
dest="version",
|
||||||
|
help=("Firmware version that is built."),
|
||||||
|
)
|
||||||
|
|
||||||
main_parser.set_defaults(features=["with_ctap1"])
|
main_parser.set_defaults(features=["with_ctap1"])
|
||||||
|
|
||||||
# Start parsing to know if we're going to list things or not.
|
# Start parsing to know if we're going to list things or not.
|
||||||
|
|||||||
@@ -55,15 +55,15 @@ There are variants of the board that introduce A/B partitions for upgrading the
|
|||||||
firmware. You can bootstrap an upgradable board using one of the two commands:
|
firmware. You can bootstrap an upgradable board using one of the two commands:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./deploy.py --board=nrf52840dk_opensk_a --opensk
|
./deploy.py --board=nrf52840dk_opensk_a --opensk --version=0
|
||||||
./deploy.py --board=nrf52840dk_opensk_b --opensk
|
./deploy.py --board=nrf52840dk_opensk_b --opensk --version=0
|
||||||
```
|
```
|
||||||
|
|
||||||
Afterwards, you can upgrade the other partition with
|
Afterwards, you can upgrade the other partition with
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./tools/perform_upgrade.sh nrf52840dk_opensk_b
|
./tools/perform_upgrade.sh nrf52840dk_opensk_b --version=1
|
||||||
./tools/perform_upgrade.sh nrf52840dk_opensk_a
|
./tools/perform_upgrade.sh nrf52840dk_opensk_a --version=1
|
||||||
```
|
```
|
||||||
|
|
||||||
respectively. You can only upgrade the partition that is not currently running,
|
respectively. You can only upgrade the partition that is not currently running,
|
||||||
@@ -75,6 +75,6 @@ If you deploy with `--vendor-hid`, also add this flag to `perform_upgrade.sh`,
|
|||||||
for example:
|
for example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./deploy.py --board=nrf52840dk_opensk_a --opensk --vendor-hid
|
./deploy.py --board=nrf52840dk_opensk_a --opensk --version=0 --vendor-hid
|
||||||
./tools/perform_upgrade.sh nrf52840dk_opensk_b --vendor-hid
|
./tools/perform_upgrade.sh nrf52840dk_opensk_b --version=1 --vendor-hid
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -15,11 +15,10 @@
|
|||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
|
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
|
||||||
extract_unsigned, ok_or_missing, ClientPinSubCommand, ConfigSubCommand, ConfigSubCommandParams,
|
extract_unsigned, ok_or_missing, ClientPinSubCommand, ConfigSubCommand, ConfigSubCommandParams,
|
||||||
CoseKey, CoseSignature, CredentialManagementSubCommand,
|
CoseKey, CredentialManagementSubCommand, CredentialManagementSubCommandParameters,
|
||||||
CredentialManagementSubCommandParameters, GetAssertionExtensions, GetAssertionOptions,
|
GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions, MakeCredentialOptions,
|
||||||
MakeCredentialExtensions, MakeCredentialOptions, PinUvAuthProtocol,
|
PinUvAuthProtocol, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
|
||||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity,
|
PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, SetMinPinLengthParams,
|
||||||
PublicKeyCredentialUserEntity, SetMinPinLengthParams,
|
|
||||||
};
|
};
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use super::{cbor_read, key_material};
|
use super::{cbor_read, key_material};
|
||||||
@@ -596,7 +595,6 @@ pub struct AuthenticatorVendorUpgradeParameters {
|
|||||||
pub address: Option<usize>,
|
pub address: Option<usize>,
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
pub hash: Vec<u8>,
|
pub hash: Vec<u8>,
|
||||||
pub signature: Option<CoseSignature>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<cbor::Value> for AuthenticatorVendorUpgradeParameters {
|
impl TryFrom<cbor::Value> for AuthenticatorVendorUpgradeParameters {
|
||||||
@@ -608,7 +606,6 @@ impl TryFrom<cbor::Value> for AuthenticatorVendorUpgradeParameters {
|
|||||||
0x01 => address,
|
0x01 => address,
|
||||||
0x02 => data,
|
0x02 => data,
|
||||||
0x03 => hash,
|
0x03 => hash,
|
||||||
0x04 => signature,
|
|
||||||
} = extract_map(cbor_value)?;
|
} = extract_map(cbor_value)?;
|
||||||
}
|
}
|
||||||
let address = address
|
let address = address
|
||||||
@@ -617,12 +614,10 @@ impl TryFrom<cbor::Value> for AuthenticatorVendorUpgradeParameters {
|
|||||||
.map(|u| u as usize);
|
.map(|u| u as usize);
|
||||||
let data = extract_byte_string(ok_or_missing(data)?)?;
|
let data = extract_byte_string(ok_or_missing(data)?)?;
|
||||||
let hash = extract_byte_string(ok_or_missing(hash)?)?;
|
let hash = extract_byte_string(ok_or_missing(hash)?)?;
|
||||||
let signature = signature.map(CoseSignature::try_from).transpose()?;
|
|
||||||
Ok(AuthenticatorVendorUpgradeParameters {
|
Ok(AuthenticatorVendorUpgradeParameters {
|
||||||
address,
|
address,
|
||||||
data,
|
data,
|
||||||
hash,
|
hash,
|
||||||
signature,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -631,7 +626,7 @@ impl TryFrom<cbor::Value> for AuthenticatorVendorUpgradeParameters {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::super::data_formats::{
|
use super::super::data_formats::{
|
||||||
AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialType,
|
AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialType,
|
||||||
PublicKeyCredentialUserEntity, SignatureAlgorithm,
|
PublicKeyCredentialUserEntity,
|
||||||
};
|
};
|
||||||
use super::super::ES256_CRED_PARAM;
|
use super::super::ES256_CRED_PARAM;
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -1096,10 +1091,6 @@ mod test {
|
|||||||
let cbor_value = cbor_map! {
|
let cbor_value = cbor_map! {
|
||||||
0x02 => [0xFF; 0x100],
|
0x02 => [0xFF; 0x100],
|
||||||
0x03 => [0x44; 32],
|
0x03 => [0x44; 32],
|
||||||
0x04 => cbor_map! {
|
|
||||||
"alg" => -7,
|
|
||||||
"signature" => [0x55; 64],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
AuthenticatorVendorUpgradeParameters::try_from(cbor_value),
|
AuthenticatorVendorUpgradeParameters::try_from(cbor_value),
|
||||||
@@ -1107,14 +1098,10 @@ mod test {
|
|||||||
address: None,
|
address: None,
|
||||||
data: vec![0xFF; 0x100],
|
data: vec![0xFF; 0x100],
|
||||||
hash: vec![0x44; 32],
|
hash: vec![0x44; 32],
|
||||||
signature: Some(CoseSignature {
|
|
||||||
algorithm: SignatureAlgorithm::Es256,
|
|
||||||
bytes: [0x55; 64],
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Valid without signature
|
// Valid with address
|
||||||
let cbor_value = cbor_map! {
|
let cbor_value = cbor_map! {
|
||||||
0x01 => 0x1000,
|
0x01 => 0x1000,
|
||||||
0x02 => [0xFF; 0x100],
|
0x02 => [0xFF; 0x100],
|
||||||
@@ -1126,7 +1113,6 @@ mod test {
|
|||||||
address: Some(0x1000),
|
address: Some(0x1000),
|
||||||
data: vec![0xFF; 0x100],
|
data: vec![0xFF; 0x100],
|
||||||
hash: vec![0x44; 32],
|
hash: vec![0x44; 32],
|
||||||
signature: None,
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -912,53 +912,6 @@ impl TryFrom<CoseKey> for ecdsa::PubKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data structure for receiving a signature.
|
|
||||||
///
|
|
||||||
/// See https://datatracker.ietf.org/doc/html/rfc8152#appendix-C.1.1 for reference.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct CoseSignature {
|
|
||||||
pub algorithm: SignatureAlgorithm,
|
|
||||||
pub bytes: [u8; ecdsa::Signature::BYTES_LENGTH],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<cbor::Value> for CoseSignature {
|
|
||||||
type Error = Ctap2StatusCode;
|
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
|
||||||
destructure_cbor_map! {
|
|
||||||
let {
|
|
||||||
"alg" => algorithm,
|
|
||||||
"signature" => bytes,
|
|
||||||
} = extract_map(cbor_value)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let algorithm = SignatureAlgorithm::try_from(ok_or_missing(algorithm)?)?;
|
|
||||||
let bytes = extract_byte_string(ok_or_missing(bytes)?)?;
|
|
||||||
if bytes.len() != ecdsa::Signature::BYTES_LENGTH {
|
|
||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(CoseSignature {
|
|
||||||
algorithm,
|
|
||||||
bytes: *array_ref![bytes.as_slice(), 0, ecdsa::Signature::BYTES_LENGTH],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<CoseSignature> for ecdsa::Signature {
|
|
||||||
type Error = Ctap2StatusCode;
|
|
||||||
|
|
||||||
fn try_from(cose_signature: CoseSignature) -> Result<Self, Ctap2StatusCode> {
|
|
||||||
match cose_signature.algorithm {
|
|
||||||
SignatureAlgorithm::Es256 => ecdsa::Signature::from_bytes(&cose_signature.bytes)
|
|
||||||
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
SignatureAlgorithm::Eddsa => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
|
|
||||||
SignatureAlgorithm::Unknown => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
|
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
|
||||||
pub enum PinUvAuthProtocol {
|
pub enum PinUvAuthProtocol {
|
||||||
@@ -1292,7 +1245,6 @@ mod test {
|
|||||||
cbor_array, cbor_bool, cbor_bytes, cbor_bytes_lit, cbor_false, cbor_int, cbor_null,
|
cbor_array, cbor_bool, cbor_bytes, cbor_bytes_lit, cbor_false, cbor_int, cbor_null,
|
||||||
cbor_text, cbor_unsigned,
|
cbor_text, cbor_unsigned,
|
||||||
};
|
};
|
||||||
use crypto::sha256::Sha256;
|
|
||||||
use rng256::Rng256;
|
use rng256::Rng256;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2003,64 +1955,6 @@ mod test {
|
|||||||
assert_eq!(cose_key.algorithm, ES256_ALGORITHM);
|
assert_eq!(cose_key.algorithm, ES256_ALGORITHM);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_into_cose_signature() {
|
|
||||||
let mut env = TestEnv::new();
|
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(env.rng());
|
|
||||||
let dummy_signature = sk.sign_rfc6979::<Sha256>(&[]);
|
|
||||||
let mut bytes = [0; ecdsa::Signature::BYTES_LENGTH];
|
|
||||||
dummy_signature.to_bytes(&mut bytes);
|
|
||||||
let cbor_value = cbor_map! {
|
|
||||||
"alg" => ES256_ALGORITHM,
|
|
||||||
"signature" => bytes,
|
|
||||||
};
|
|
||||||
let cose_signature = CoseSignature::try_from(cbor_value).unwrap();
|
|
||||||
let created_signature = crypto::ecdsa::Signature::try_from(cose_signature).unwrap();
|
|
||||||
let mut created_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
|
|
||||||
created_signature.to_bytes(&mut created_bytes);
|
|
||||||
assert_eq!(bytes[..], created_bytes[..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cose_signature_wrong_algorithm() {
|
|
||||||
let mut env = TestEnv::new();
|
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(env.rng());
|
|
||||||
let dummy_signature = sk.sign_rfc6979::<Sha256>(&[]);
|
|
||||||
let mut bytes = [0; ecdsa::Signature::BYTES_LENGTH];
|
|
||||||
dummy_signature.to_bytes(&mut bytes);
|
|
||||||
let cbor_value = cbor_map! {
|
|
||||||
"alg" => -1, // unused algorithm
|
|
||||||
"signature" => bytes,
|
|
||||||
};
|
|
||||||
let cose_signature = CoseSignature::try_from(cbor_value).unwrap();
|
|
||||||
let created_signature = crypto::ecdsa::Signature::try_from(cose_signature);
|
|
||||||
// Can not compare directly, since ecdsa::Signature does not implement Debug.
|
|
||||||
assert_eq!(
|
|
||||||
created_signature.err(),
|
|
||||||
Some(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cose_signature_wrong_signature_length() {
|
|
||||||
let cbor_value = cbor_map! {
|
|
||||||
"alg" => ES256_ALGORITHM,
|
|
||||||
"signature" => [0; ecdsa::Signature::BYTES_LENGTH - 1],
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
CoseSignature::try_from(cbor_value),
|
|
||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
|
||||||
);
|
|
||||||
let cbor_value = cbor_map! {
|
|
||||||
"alg" => ES256_ALGORITHM,
|
|
||||||
"signature" => [0; ecdsa::Signature::BYTES_LENGTH + 1],
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
CoseSignature::try_from(cbor_value),
|
|
||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_pin_uv_auth_protocol() {
|
fn test_from_pin_uv_auth_protocol() {
|
||||||
let cbor_protocol: cbor::Value = cbor_int!(0x01);
|
let cbor_protocol: cbor::Value = cbor_int!(0x01);
|
||||||
|
|||||||
175
src/ctap/mod.rs
175
src/ctap/mod.rs
@@ -47,11 +47,10 @@ use self::credential_id::{
|
|||||||
use self::credential_management::process_credential_management;
|
use self::credential_management::process_credential_management;
|
||||||
use self::crypto_wrapper::PrivateKey;
|
use self::crypto_wrapper::PrivateKey;
|
||||||
use self::data_formats::{
|
use self::data_formats::{
|
||||||
AuthenticatorTransport, CoseKey, CoseSignature, CredentialProtectionPolicy,
|
AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, EnterpriseAttestationMode,
|
||||||
EnterpriseAttestationMode, GetAssertionExtensions, PackedAttestationStatement,
|
GetAssertionExtensions, PackedAttestationStatement, PinUvAuthProtocol,
|
||||||
PinUvAuthProtocol, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
|
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialSource,
|
||||||
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
|
PublicKeyCredentialType, PublicKeyCredentialUserEntity, SignatureAlgorithm,
|
||||||
SignatureAlgorithm,
|
|
||||||
};
|
};
|
||||||
use self::hid::{ChannelID, CtapHid, CtapHidCommand, KeepaliveStatus, ProcessedPacket};
|
use self::hid::{ChannelID, CtapHid, CtapHidCommand, KeepaliveStatus, ProcessedPacket};
|
||||||
use self::large_blobs::LargeBlobs;
|
use self::large_blobs::LargeBlobs;
|
||||||
@@ -77,7 +76,7 @@ use alloc::string::{String, ToString};
|
|||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
||||||
use core::convert::TryFrom;
|
use core::convert::TryFrom;
|
||||||
use crypto::hmac::hmac_256;
|
use crypto::hmac::hmac_256;
|
||||||
use crypto::sha256::Sha256;
|
use crypto::sha256::Sha256;
|
||||||
@@ -212,45 +211,61 @@ fn truncate_to_char_boundary(s: &str, mut max: usize) -> &str {
|
|||||||
|
|
||||||
/// Parses the metadata of an upgrade, and checks its correctness.
|
/// Parses the metadata of an upgrade, and checks its correctness.
|
||||||
///
|
///
|
||||||
/// Returns the hash over the upgrade, including partition and some metadata.
|
/// The metadata is a page starting with:
|
||||||
/// The metadata consists of:
|
/// - 32 B upgrade hash (SHA256)
|
||||||
/// - 32B upgrade hash (SHA256)
|
/// - 64 B signature,
|
||||||
/// - 4B timestamp (little endian encoding)
|
/// that are not signed over. The second part is included in the signature with
|
||||||
/// - 4B partition address (little endian encoding)
|
/// - 8 B version and
|
||||||
/// The upgrade hash is computed over the firmware image and all metadata,
|
/// - 4 B partition address in little endian encoding
|
||||||
/// except the hash itself.
|
/// written at METADATA_SIGN_OFFSET.
|
||||||
|
///
|
||||||
|
/// Checks hash and signature correctness, and whether the partition offset matches.
|
||||||
fn parse_metadata(
|
fn parse_metadata(
|
||||||
upgrade_locations: &impl UpgradeStorage,
|
upgrade_locations: &impl UpgradeStorage,
|
||||||
|
public_key_bytes: &[u8],
|
||||||
metadata: &[u8],
|
metadata: &[u8],
|
||||||
) -> Result<[u8; 32], Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
const METADATA_LEN: usize = 40;
|
const METADATA_LEN: usize = 0x1000;
|
||||||
|
const METADATA_SIGN_OFFSET: usize = 0x800;
|
||||||
if metadata.len() != METADATA_LEN {
|
if metadata.len() != METADATA_LEN {
|
||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let metadata_address = LittleEndian::read_u32(&metadata[METADATA_SIGN_OFFSET + 8..][..4]);
|
||||||
|
if metadata_address as usize != upgrade_locations.partition_address() {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
// The hash implementation handles this in chunks, so no memory issues.
|
// The hash implementation handles this in chunks, so no memory issues.
|
||||||
let partition_slice = upgrade_locations
|
let partition_slice = upgrade_locations
|
||||||
.read_partition(0, upgrade_locations.partition_length())
|
.read_partition(0, upgrade_locations.partition_length())
|
||||||
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(&metadata[METADATA_SIGN_OFFSET..]);
|
||||||
hasher.update(partition_slice);
|
hasher.update(partition_slice);
|
||||||
hasher.update(&metadata[32..METADATA_LEN]);
|
|
||||||
let computed_hash = hasher.finalize();
|
let computed_hash = hasher.finalize();
|
||||||
if &computed_hash != array_ref!(metadata, 0, 32) {
|
if &computed_hash != array_ref!(metadata, 0, 32) {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE);
|
return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE);
|
||||||
}
|
}
|
||||||
Ok(computed_hash)
|
|
||||||
|
verify_signature(
|
||||||
|
array_ref!(metadata, 32, 64),
|
||||||
|
public_key_bytes,
|
||||||
|
&computed_hash,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies the signature over the given hash.
|
/// Verifies the signature over the given hash.
|
||||||
///
|
///
|
||||||
/// The public key is COSE encoded, and the hash is a SHA256.
|
/// The public key is COSE encoded, and the hash is a SHA256.
|
||||||
fn verify_signature(
|
fn verify_signature(
|
||||||
signature: Option<CoseSignature>,
|
signature_bytes: &[u8; 64],
|
||||||
public_key_bytes: &[u8],
|
public_key_bytes: &[u8],
|
||||||
signed_hash: &[u8; 32],
|
signed_hash: &[u8; 32],
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
let signature =
|
let signature = ecdsa::Signature::from_bytes(signature_bytes)
|
||||||
ecdsa::Signature::try_from(signature.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?)?;
|
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?;
|
||||||
let cbor_public_key = cbor_read(public_key_bytes)?;
|
let cbor_public_key = cbor_read(public_key_bytes)?;
|
||||||
let cose_key = CoseKey::try_from(cbor_public_key)?;
|
let cose_key = CoseKey::try_from(cbor_public_key)?;
|
||||||
let public_key = ecdsa::PubKey::try_from(cose_key)?;
|
let public_key = ecdsa::PubKey::try_from(cose_key)?;
|
||||||
@@ -1454,7 +1469,6 @@ impl CtapState {
|
|||||||
address,
|
address,
|
||||||
data,
|
data,
|
||||||
hash,
|
hash,
|
||||||
signature,
|
|
||||||
} = params;
|
} = params;
|
||||||
let upgrade_locations = env
|
let upgrade_locations = env
|
||||||
.upgrade_storage()
|
.upgrade_storage()
|
||||||
@@ -1468,9 +1482,7 @@ impl CtapState {
|
|||||||
.map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?
|
.map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?
|
||||||
} else {
|
} else {
|
||||||
// Compares the hash inside the metadata to the actual hash.
|
// Compares the hash inside the metadata to the actual hash.
|
||||||
let upgrade_hash = parse_metadata(upgrade_locations, &data)?;
|
parse_metadata(upgrade_locations, key_material::UPGRADE_PUBLIC_KEY, &data)?;
|
||||||
// Only signed firmware images may be fully written.
|
|
||||||
verify_signature(signature, key_material::UPGRADE_PUBLIC_KEY, &upgrade_hash)?;
|
|
||||||
// Write the metadata page after verifying that its hash is signed.
|
// Write the metadata page after verifying that its hash is signed.
|
||||||
upgrade_locations
|
upgrade_locations
|
||||||
.write_metadata(&data)
|
.write_metadata(&data)
|
||||||
@@ -3456,37 +3468,64 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_parse_metadata() {
|
fn test_parse_metadata() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
// The test buffer starts fully erased with 0xFF bytes.
|
let private_key = crypto::ecdsa::SecKey::gensk(env.rng());
|
||||||
// The compiler issues an incorrect warning.
|
let upgrade_locations = env.upgrade_storage().unwrap();
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut 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 partition_length = upgrade_locations.partition_length();
|
||||||
|
let mut signed_over_data = metadata[METADATA_SIGN_OFFSET..].to_vec();
|
||||||
|
signed_over_data.extend(
|
||||||
|
upgrade_locations
|
||||||
|
.read_partition(0, partition_length)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
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 = vec![];
|
||||||
|
cbor_write(
|
||||||
|
cbor::Value::from(CoseKey::from(public_key)),
|
||||||
|
&mut public_key_bytes,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Partition of 0x40000 bytes and 8 bytes metadata are hashed.
|
|
||||||
let hashed_data = vec![0xFF; 0x40000 + 8];
|
|
||||||
let expected_hash = Sha256::hash(&hashed_data);
|
|
||||||
let mut metadata = vec![0xFF; 40];
|
|
||||||
metadata[..32].copy_from_slice(&expected_hash);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_metadata(upgrade_locations, &metadata),
|
parse_metadata(upgrade_locations, &public_key_bytes, &metadata),
|
||||||
Ok(expected_hash)
|
Ok(())
|
||||||
);
|
);
|
||||||
|
|
||||||
// Any manipulation of data fails.
|
// Any manipulation of data fails.
|
||||||
metadata[32] = 0x88;
|
metadata[METADATA_SIGN_OFFSET] = 0x88;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_metadata(upgrade_locations, &metadata),
|
parse_metadata(upgrade_locations, &public_key_bytes, &metadata),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
||||||
);
|
);
|
||||||
metadata[32] = 0xFF;
|
metadata[METADATA_SIGN_OFFSET] = 0xFF;
|
||||||
metadata[0] ^= 0x01;
|
metadata[0] ^= 0x01;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_metadata(upgrade_locations, &metadata),
|
parse_metadata(upgrade_locations, &public_key_bytes, &metadata),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
||||||
);
|
);
|
||||||
metadata[0] ^= 0x01;
|
metadata[0] ^= 0x01;
|
||||||
|
metadata[32] ^= 0x01;
|
||||||
|
assert_eq!(
|
||||||
|
parse_metadata(upgrade_locations, &public_key_bytes, &metadata),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
||||||
|
);
|
||||||
|
metadata[32] ^= 0x01;
|
||||||
upgrade_locations.write_partition(0, &[0x88; 1]).unwrap();
|
upgrade_locations.write_partition(0, &[0x88; 1]).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_metadata(upgrade_locations, &metadata),
|
parse_metadata(upgrade_locations, &public_key_bytes, &metadata),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -3501,10 +3540,6 @@ mod test {
|
|||||||
|
|
||||||
let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
|
let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
|
||||||
signature.to_bytes(&mut signature_bytes);
|
signature.to_bytes(&mut signature_bytes);
|
||||||
let cose_signature = CoseSignature {
|
|
||||||
algorithm: SignatureAlgorithm::Es256,
|
|
||||||
bytes: signature_bytes,
|
|
||||||
};
|
|
||||||
|
|
||||||
let public_key = private_key.genpk();
|
let public_key = private_key.genpk();
|
||||||
let mut public_key_bytes = vec![];
|
let mut public_key_bytes = vec![];
|
||||||
@@ -3515,34 +3550,22 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
verify_signature(
|
verify_signature(&signature_bytes, &public_key_bytes, &signed_hash),
|
||||||
Some(cose_signature.clone()),
|
|
||||||
&public_key_bytes,
|
|
||||||
&signed_hash
|
|
||||||
),
|
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
verify_signature(Some(cose_signature.clone()), &public_key_bytes, &[0x55; 32]),
|
verify_signature(&signature_bytes, &public_key_bytes, &[0x55; 32]),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
||||||
);
|
);
|
||||||
public_key_bytes[0] ^= 0x01;
|
public_key_bytes[0] ^= 0x01;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
verify_signature(Some(cose_signature), &public_key_bytes, &signed_hash),
|
verify_signature(&signature_bytes, &public_key_bytes, &signed_hash),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)
|
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)
|
||||||
);
|
);
|
||||||
public_key_bytes[0] ^= 0x01;
|
public_key_bytes[0] ^= 0x01;
|
||||||
assert_eq!(
|
|
||||||
verify_signature(None, &public_key_bytes, &signed_hash),
|
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
|
|
||||||
);
|
|
||||||
signature_bytes[0] ^= 0x01;
|
signature_bytes[0] ^= 0x01;
|
||||||
let cose_signature = CoseSignature {
|
|
||||||
algorithm: SignatureAlgorithm::Es256,
|
|
||||||
bytes: signature_bytes,
|
|
||||||
};
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
verify_signature(Some(cose_signature), &public_key_bytes, &signed_hash),
|
verify_signature(&signature_bytes, &public_key_bytes, &signed_hash),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -3555,38 +3578,37 @@ mod test {
|
|||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let private_key = crypto::ecdsa::SecKey::gensk(env.rng());
|
let private_key = crypto::ecdsa::SecKey::gensk(env.rng());
|
||||||
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
|
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
|
||||||
const METADATA_LEN: usize = 40;
|
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 data = vec![0xFF; 0x1000];
|
let data = vec![0xFF; 0x1000];
|
||||||
let hash = Sha256::hash(&data).to_vec();
|
let hash = Sha256::hash(&data).to_vec();
|
||||||
let upgrade_locations = env.upgrade_storage().unwrap();
|
let upgrade_locations = env.upgrade_storage().unwrap();
|
||||||
let partition_length = upgrade_locations.partition_length();
|
let partition_length = upgrade_locations.partition_length();
|
||||||
let mut signed_over_data = upgrade_locations
|
let mut signed_over_data = metadata[METADATA_SIGN_OFFSET..].to_vec();
|
||||||
|
signed_over_data.extend(
|
||||||
|
upgrade_locations
|
||||||
.read_partition(0, partition_length)
|
.read_partition(0, partition_length)
|
||||||
.unwrap()
|
.unwrap(),
|
||||||
.to_vec();
|
);
|
||||||
signed_over_data.extend(&[0xFF; METADATA_LEN - 32]);
|
|
||||||
let signed_hash = Sha256::hash(&signed_over_data);
|
let signed_hash = Sha256::hash(&signed_over_data);
|
||||||
let mut metadata = vec![0xFF; METADATA_LEN];
|
|
||||||
metadata[..32].copy_from_slice(&signed_hash);
|
|
||||||
let metadata_hash = Sha256::hash(&metadata).to_vec();
|
|
||||||
|
|
||||||
|
metadata[..32].copy_from_slice(&signed_hash);
|
||||||
let signature = private_key.sign_rfc6979::<Sha256>(&signed_over_data);
|
let signature = private_key.sign_rfc6979::<Sha256>(&signed_over_data);
|
||||||
let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
|
let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
|
||||||
signature.to_bytes(&mut signature_bytes);
|
signature.to_bytes(&mut signature_bytes);
|
||||||
let cose_signature = CoseSignature {
|
metadata[32..96].copy_from_slice(&signature_bytes);
|
||||||
algorithm: SignatureAlgorithm::Es256,
|
let metadata_hash = Sha256::hash(&metadata).to_vec();
|
||||||
bytes: signature_bytes,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write to partition and metadata.
|
// Write to partition.
|
||||||
let response = ctap_state.process_vendor_upgrade(
|
let response = ctap_state.process_vendor_upgrade(
|
||||||
&mut env,
|
&mut env,
|
||||||
AuthenticatorVendorUpgradeParameters {
|
AuthenticatorVendorUpgradeParameters {
|
||||||
address: Some(0x20000),
|
address: Some(0x20000),
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
hash: hash.clone(),
|
hash: hash.clone(),
|
||||||
signature: None,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(response, Ok(ResponseData::AuthenticatorVendorUpgrade));
|
assert_eq!(response, Ok(ResponseData::AuthenticatorVendorUpgrade));
|
||||||
@@ -3599,7 +3621,6 @@ mod test {
|
|||||||
address: None,
|
address: None,
|
||||||
data: metadata.clone(),
|
data: metadata.clone(),
|
||||||
hash: metadata_hash.clone(),
|
hash: metadata_hash.clone(),
|
||||||
signature: Some(cose_signature.clone()),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(response, Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE));
|
assert_eq!(response, Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE));
|
||||||
@@ -3611,7 +3632,6 @@ mod test {
|
|||||||
address: None,
|
address: None,
|
||||||
data: metadata[..METADATA_LEN - 1].to_vec(),
|
data: metadata[..METADATA_LEN - 1].to_vec(),
|
||||||
hash: metadata_hash,
|
hash: metadata_hash,
|
||||||
signature: Some(cose_signature),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER));
|
assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER));
|
||||||
@@ -3623,7 +3643,6 @@ mod test {
|
|||||||
address: Some(0x40000),
|
address: Some(0x40000),
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
hash,
|
hash,
|
||||||
signature: None,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER));
|
assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER));
|
||||||
@@ -3635,7 +3654,6 @@ mod test {
|
|||||||
address: Some(0x20000),
|
address: Some(0x20000),
|
||||||
data,
|
data,
|
||||||
hash: [0xEE; 32].to_vec(),
|
hash: [0xEE; 32].to_vec(),
|
||||||
signature: None,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(response, Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE));
|
assert_eq!(response, Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE));
|
||||||
@@ -3655,7 +3673,6 @@ mod test {
|
|||||||
address: Some(0),
|
address: Some(0),
|
||||||
data,
|
data,
|
||||||
hash,
|
hash,
|
||||||
signature: None,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND));
|
assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND));
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from __future__ import division
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import datetime
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
@@ -44,6 +43,7 @@ OPENSK_VID_PID = (0x1915, 0x521F)
|
|||||||
OPENSK_VENDOR_UPGRADE = 0x42
|
OPENSK_VENDOR_UPGRADE = 0x42
|
||||||
OPENSK_VENDOR_UPGRADE_INFO = 0x43
|
OPENSK_VENDOR_UPGRADE_INFO = 0x43
|
||||||
PAGE_SIZE = 0x1000
|
PAGE_SIZE = 0x1000
|
||||||
|
METADATA_SIGN_OFFSET = 0x800
|
||||||
KERNEL_SIZE = 0x20000
|
KERNEL_SIZE = 0x20000
|
||||||
APP_SIZE = 0x20000
|
APP_SIZE = 0x20000
|
||||||
PARTITION_ADDRESS = {
|
PARTITION_ADDRESS = {
|
||||||
@@ -54,7 +54,15 @@ ES256_ALGORITHM = -7
|
|||||||
ARCH = "thumbv7em-none-eabi"
|
ARCH = "thumbv7em-none-eabi"
|
||||||
|
|
||||||
|
|
||||||
def create_metadata(firmware_image: bytes, partition_address: int) -> bytes:
|
def hash_message(message: bytes) -> bytes:
|
||||||
|
"""Uses SHA256 to hash a message."""
|
||||||
|
sha256_hash = hashlib.sha256()
|
||||||
|
sha256_hash.update(message)
|
||||||
|
return sha256_hash.digest()
|
||||||
|
|
||||||
|
|
||||||
|
def create_metadata(firmware_image: bytes, partition_address: int, version: int,
|
||||||
|
priv_key: Any) -> bytes:
|
||||||
"""Creates the matching metadata for the given firmware.
|
"""Creates the matching metadata for the given firmware.
|
||||||
|
|
||||||
The metadata consists of a timestamp, the expected address and a hash of
|
The metadata consists of a timestamp, the expected address and a hash of
|
||||||
@@ -65,25 +73,26 @@ def create_metadata(firmware_image: bytes, partition_address: int) -> bytes:
|
|||||||
partition_address: The address to be written as a metadata property.
|
partition_address: The address to be written as a metadata property.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A byte array consisting of 32B hash, 4B timestamp and 4B partition address
|
A byte array of page size, consisting of
|
||||||
in little endian encoding.
|
- 32 B hash,
|
||||||
|
- 64 B signature,
|
||||||
|
at the beginning and
|
||||||
|
- 8 B version and
|
||||||
|
- 4 B partition address in little endian encoding
|
||||||
|
after METADATA_SIGN_OFFSET. All other bytes are 0xFF.
|
||||||
"""
|
"""
|
||||||
t = datetime.datetime.utcnow().timestamp()
|
if version < 0 or version >= 2**63:
|
||||||
timestamp = struct.pack("<I", int(t))
|
fatal("The version must fit into an unsigned integer with 63 bit.\n"
|
||||||
|
"Please pass it using --version")
|
||||||
|
version_bytes = struct.pack("<Q", version)
|
||||||
partition_start = struct.pack("<I", partition_address)
|
partition_start = struct.pack("<I", partition_address)
|
||||||
sha256_hash = hashlib.sha256()
|
# Prefix sizes that are a multiple of 64 suit our bootloader's SHA.
|
||||||
sha256_hash.update(firmware_image)
|
signed_metadata = pad_to(version_bytes + partition_start,
|
||||||
sha256_hash.update(timestamp)
|
PAGE_SIZE - METADATA_SIGN_OFFSET)
|
||||||
sha256_hash.update(partition_start)
|
signed_data = signed_metadata + firmware_image
|
||||||
checksum = sha256_hash.digest()
|
checksum = hash_message(signed_data)
|
||||||
return checksum + timestamp + partition_start
|
signature = sign_firmware(signed_data, priv_key)
|
||||||
|
return pad_to(checksum + signature, METADATA_SIGN_OFFSET) + signed_metadata
|
||||||
|
|
||||||
def hash_message(message: bytes) -> bytes:
|
|
||||||
"""Uses SHA256 to hash a message."""
|
|
||||||
sha256_hash = hashlib.sha256()
|
|
||||||
sha256_hash.update(message)
|
|
||||||
return sha256_hash.digest()
|
|
||||||
|
|
||||||
|
|
||||||
def check_info(partition_address: int, authenticator: Any):
|
def check_info(partition_address: int, authenticator: Any):
|
||||||
@@ -95,7 +104,8 @@ def check_info(partition_address: int, authenticator: Any):
|
|||||||
data={},
|
data={},
|
||||||
)
|
)
|
||||||
if result[0x01] != partition_address:
|
if result[0x01] != partition_address:
|
||||||
fatal("Identifiers do not match.")
|
fatal(f"Identifiers do not match, received 0x{result[0x01]:0x}, "
|
||||||
|
f"expected 0x{partition_address:0x}.")
|
||||||
except ctap.CtapError as ex:
|
except ctap.CtapError as ex:
|
||||||
fatal(f"Failed to read OpenSK upgrade info (error: {ex})")
|
fatal(f"Failed to read OpenSK upgrade info (error: {ex})")
|
||||||
|
|
||||||
@@ -163,19 +173,14 @@ def sign_firmware(data: bytes, priv_key: Any) -> bytes:
|
|||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
colorama.init()
|
colorama.init()
|
||||||
|
if not args.priv_key:
|
||||||
|
fatal("Please pass in a private key file using --private-key.")
|
||||||
|
|
||||||
firmware_image = generate_firmware_image(args.board)
|
firmware_image = generate_firmware_image(args.board)
|
||||||
partition_address = PARTITION_ADDRESS[args.board]
|
partition_address = PARTITION_ADDRESS[args.board]
|
||||||
metadata = create_metadata(firmware_image, partition_address)
|
|
||||||
|
|
||||||
if not args.priv_key:
|
|
||||||
fatal("Please pass in a private key file using --private-key.")
|
|
||||||
priv_key = load_priv_key(args.priv_key)
|
priv_key = load_priv_key(args.priv_key)
|
||||||
signed_data = firmware_image + metadata[32:40]
|
metadata = create_metadata(firmware_image, partition_address, args.version,
|
||||||
signature = {
|
priv_key)
|
||||||
"alg": ES256_ALGORITHM,
|
|
||||||
"signature": sign_firmware(signed_data, priv_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.use_vendor_hid:
|
if args.use_vendor_hid:
|
||||||
patcher = patch.object(hid.base, "FIDO_USAGE_PAGE", 0xFF00)
|
patcher = patch.object(hid.base, "FIDO_USAGE_PAGE", 0xFF00)
|
||||||
@@ -206,11 +211,13 @@ def main(args):
|
|||||||
)
|
)
|
||||||
|
|
||||||
info("Writing metadata...")
|
info("Writing metadata...")
|
||||||
cbor_data = {2: metadata, 3: hash_message(metadata), 4: signature}
|
# TODO Write the correct address when the metadata is transparent.
|
||||||
|
cbor_data = {2: metadata, 3: hash_message(metadata)}
|
||||||
authenticator.send_cbor(
|
authenticator.send_cbor(
|
||||||
OPENSK_VENDOR_UPGRADE,
|
OPENSK_VENDOR_UPGRADE,
|
||||||
data=cbor_data,
|
data=cbor_data,
|
||||||
)
|
)
|
||||||
|
|
||||||
except ctap.CtapError as ex:
|
except ctap.CtapError as ex:
|
||||||
message = "Failed to upgrade OpenSK"
|
message = "Failed to upgrade OpenSK"
|
||||||
if ex.code.value == ctap.CtapError.ERR.INVALID_COMMAND:
|
if ex.code.value == ctap.CtapError.ERR.INVALID_COMMAND:
|
||||||
@@ -263,4 +270,10 @@ if __name__ == "__main__":
|
|||||||
dest="use_vendor_hid",
|
dest="use_vendor_hid",
|
||||||
help=("Whether to upgrade the device using the Vendor HID interface."),
|
help=("Whether to upgrade the device using the Vendor HID interface."),
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--version",
|
||||||
|
type=int,
|
||||||
|
dest="version",
|
||||||
|
help=("Firmware version that is built."),
|
||||||
|
)
|
||||||
main(parser.parse_args())
|
main(parser.parse_args())
|
||||||
|
|||||||
Reference in New Issue
Block a user