From 9a7981a23b2ad30ff29a058e4730f78244e2c085 Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Fri, 30 Aug 2024 15:32:20 +1000 Subject: [PATCH] atecc508a: Support the SignatureVerify HIL Signed-off-by: Alistair Francis --- .../lora_things_plus/src/tests/verify_sig.rs | 169 +++++++++++++ boards/components/src/atecc508a.rs | 6 +- capsules/extra/src/atecc508a.rs | 224 +++++++++++++++++- 3 files changed, 395 insertions(+), 4 deletions(-) create mode 100644 boards/apollo3/lora_things_plus/src/tests/verify_sig.rs diff --git a/boards/apollo3/lora_things_plus/src/tests/verify_sig.rs b/boards/apollo3/lora_things_plus/src/tests/verify_sig.rs new file mode 100644 index 0000000000..cd4a04e138 --- /dev/null +++ b/boards/apollo3/lora_things_plus/src/tests/verify_sig.rs @@ -0,0 +1,169 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::tests::run_kernel_op; +use crate::ATECC508A; +use core::cell::Cell; +use kernel::hil::public_key_crypto::signature::ClientVerify; +use kernel::hil::public_key_crypto::signature::SignatureVerify; +use kernel::static_init; +use kernel::utilities::cells::TakeCell; +use kernel::{debug, ErrorCode}; + +struct HmacTestCallback { + verify_done: Cell, + message_buffer: TakeCell<'static, [u8; 32]>, + signature_buffer: TakeCell<'static, [u8; 64]>, + pub_key_buffer: TakeCell<'static, [u8; 64]>, +} + +impl<'a> HmacTestCallback { + fn new( + message_buffer: &'static mut [u8; 32], + signature_buffer: &'static mut [u8; 64], + pub_key_buffer: &'static mut [u8; 64], + ) -> Self { + HmacTestCallback { + verify_done: Cell::new(false), + message_buffer: TakeCell::new(message_buffer), + signature_buffer: TakeCell::new(signature_buffer), + pub_key_buffer: TakeCell::new(pub_key_buffer), + } + } + + fn reset(&self) { + self.verify_done.set(false); + } +} + +impl<'a> ClientVerify<32, 64> for HmacTestCallback { + fn verification_done( + &self, + result: Result, + hash: &'static mut [u8; 32], + signature: &'static mut [u8; 64], + ) { + debug!("Verification Complete"); + + self.message_buffer.replace(hash); + self.signature_buffer.replace(signature); + assert_eq!(result, Ok(true)); + self.verify_done.set(true); + } +} + +/// The below values are generated from the following Python code +/// +/// ```python +/// import ecdsa +/// from ecdsa import SigningKey, NIST256p +/// from hashlib import sha256 +/// +/// sk = ecdsa.SigningKey.generate(curve=NIST256p, hashfunc=sha256) +/// +/// # Write the keys in PEM for future reference +/// with open("priv_key.pem", "wb") as f: +/// f.write(sk.to_pem(format="pkcs8")) +/// with open("pub_key.pem", "wb") as f: +/// f.write(public_key.to_pem()) +/// +/// # Public Key with X and Y values in a single 64-byte hex array +/// sk.verifying_key.to_string().hex() +/// +/// # Dump the Private Key so we have it in hex +/// sk.to_string().hex() +/// +/// # Prints the R and the S value in a single 64-byte hex array +/// sk.sign_deterministic(b"This is a test message to sign!!").hex() +/// +/// # The ATECC508A operates on a hash of the message, so calculate that +/// sha = sha256() +/// sha.update(b"This is a test message to sign!!") +/// sha.digest().hex() +/// ``` +/// +// These are the generated test keys used below, please do not use them +// for anything important!!!! +// +// These keys are not leaked, they are only used for this test case. +// +// -----BEGIN PRIVATE KEY----- +// MIGHAgEBMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWClhguWHtAK85Kqc +// /BucDBQMGQw6R2PEQkyISHkn5xWhRANCAAQUFMTFoNL9oFpGmg6Cp351hQMq9hol +// KpEdQfjP1nYF1jxqz52YjPpFHvudkK/fFsik5Rd0AevNkQqjBdWEqmpW +// -----END PRIVATE KEY----- +// +// -----BEGIN PUBLIC KEY----- +// MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFBTExaDS/aBaRpoOgqd+dYUDKvYa +// JSqRHUH4z9Z2BdY8as+dmIz6RR77nZCv3xbIpOUXdAHrzZEKowXVhKpqVg== +// -----END PUBLIC KEY----- +macro_rules! static_init_test_cb { + () => {{ + let message_data = static_init!( + [u8; 32], + [ + // The SHA256 of the message: 5468697320697320612074657374206d65737361676520746f207369676e2121 + 0x61, 0xff, 0x79, 0x61, 0x27, 0xe5, 0xf8, 0xe4, 0x61, 0x8d, 0xde, 0x14, 0x4f, + 0x5b, 0x91, 0xcc, 0xa4, 0x47, 0x16, 0xda, 0xc8, 0x75, 0x8b, 0xe2, 0x85, 0x9e, + 0xbf, 0x1d, 0xb1, 0x2f, 0xe2, 0xc7, + ] + ); + let signature_data = static_init!( + [u8; 64], + [ + 0xd7, 0x09, 0xd8, 0x2a, 0xdc, 0x15, 0x3c, 0xc4, 0x2e, 0x37, 0xd3, 0x91, 0x92, + 0xe2, 0x0d, 0x6a, 0xa9, 0x68, 0xf7, 0x10, 0xbb, 0x38, 0xc2, 0x16, 0xf3, 0x4f, + 0x59, 0xdc, 0x69, 0x72, 0x59, 0xc2, 0xe3, 0x9c, 0x27, 0x7f, 0x32, 0x63, 0xc8, + 0xbf, 0x27, 0x26, 0x5b, 0x8a, 0x11, 0x68, 0x90, 0x02, 0xa6, 0x7b, 0x3e, 0x72, + 0x59, 0x9e, 0x6c, 0x85, 0xda, 0x00, 0xc8, 0xca, 0x87, 0x37, 0x7d, 0x1a + ] + ); + // Note that the private key is: 58296182e587b402bce4aa9cfc1b9c0c140c190c3a4763c4424c88487927e715 + let public_key = static_init!( + [u8; 64], + [ + 0x14, 0x14, 0xc4, 0xc5, 0xa0, 0xd2, 0xfd, 0xa0, 0x5a, 0x46, 0x9a, 0x0e, 0x82, + 0xa7, 0x7e, 0x75, 0x85, 0x03, 0x2a, 0xf6, 0x1a, 0x25, 0x2a, 0x91, 0x1d, 0x41, + 0xf8, 0xcf, 0xd6, 0x76, 0x05, 0xd6, 0x3c, 0x6a, 0xcf, 0x9d, 0x98, 0x8c, 0xfa, + 0x45, 0x1e, 0xfb, 0x9d, 0x90, 0xaf, 0xdf, 0x16, 0xc8, 0xa4, 0xe5, 0x17, 0x74, + 0x01, 0xeb, 0xcd, 0x91, 0x0a, 0xa3, 0x05, 0xd5, 0x84, 0xaa, 0x6a, 0x56 + + ] + ); + + static_init!( + HmacTestCallback, + HmacTestCallback::new(message_data, signature_data, public_key) + ) + }}; +} + +#[test_case] +fn hmac_check_load_binary() { + let atecc508a = unsafe { ATECC508A.unwrap() }; + + let callback = unsafe { static_init_test_cb!() }; + + debug!("check signature verify... "); + run_kernel_op(100); + + SignatureVerify::set_verify_client(atecc508a, callback); + callback.reset(); + + atecc508a.set_public_key(Some(callback.pub_key_buffer.take().unwrap())); + + assert_eq!( + atecc508a.verify( + callback.message_buffer.take().unwrap(), + callback.signature_buffer.take().unwrap() + ), + Ok(()) + ); + + run_kernel_op(20_000); + assert_eq!(callback.verify_done.get(), true); + + debug!(" [ok]"); + run_kernel_op(100); +} diff --git a/boards/components/src/atecc508a.rs b/boards/components/src/atecc508a.rs index afc7b17f86..488278069c 100644 --- a/boards/components/src/atecc508a.rs +++ b/boards/components/src/atecc508a.rs @@ -23,7 +23,7 @@ macro_rules! atecc508a_component_static { ($I:ty $(,)?) => {{ let i2c_device = kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>); - let i2c_buffer = kernel::static_buf!([u8; 72]); + let i2c_buffer = kernel::static_buf!([u8; 140]); let entropy_buffer = kernel::static_buf!([u8; 32]); let digest_buffer = kernel::static_buf!([u8; 64]); let atecc508a = kernel::static_buf!(capsules_extra::atecc508a::Atecc508a<'static>); @@ -57,7 +57,7 @@ impl> Atecc508aComponent { impl> Component for Atecc508aComponent { type StaticInput = ( &'static mut MaybeUninit>, - &'static mut MaybeUninit<[u8; 72]>, + &'static mut MaybeUninit<[u8; 140]>, &'static mut MaybeUninit<[u8; 32]>, &'static mut MaybeUninit<[u8; 64]>, &'static mut MaybeUninit>, @@ -67,7 +67,7 @@ impl> Component for Atecc508aComponent { fn finalize(self, s: Self::StaticInput) -> Self::Output { let atecc508a_i2c = s.0.write(I2CDevice::new(self.i2c_mux, self.i2c_address)); - let i2c_buffer = s.1.write([0; 72]); + let i2c_buffer = s.1.write([0; 140]); let entropy_buffer = s.2.write([0; 32]); let digest_buffer = s.3.write([0; 64]); diff --git a/capsules/extra/src/atecc508a.rs b/capsules/extra/src/atecc508a.rs index e3f2439b87..439a5e4adb 100644 --- a/capsules/extra/src/atecc508a.rs +++ b/capsules/extra/src/atecc508a.rs @@ -26,6 +26,7 @@ use core::cell::Cell; use kernel::debug; use kernel::hil::i2c::{self, I2CClient, I2CDevice}; +use kernel::hil::public_key_crypto::signature::{ClientVerify, SignatureVerify}; use kernel::hil::{digest, entropy, entropy::Entropy32}; use kernel::utilities::cells::{MapCell, OptionalCell, TakeCell}; use kernel::utilities::leasable_buffer::{SubSlice, SubSliceMut, SubSliceMutImmut}; @@ -33,7 +34,6 @@ use kernel::ErrorCode; /* Protocol + Cryptographic defines */ const RESPONSE_COUNT_SIZE: usize = 1; -#[allow(dead_code)] const RESPONSE_SIGNAL_SIZE: usize = 1; const RESPONSE_SHA_SIZE: usize = 32; #[allow(dead_code)] @@ -103,6 +103,14 @@ const COMMAND_OPCODE_SIGN: u8 = 0x41; // Create an ECC signature with contents o #[allow(dead_code)] const COMMAND_OPCODE_VERIFY: u8 = 0x45; // takes an ECDSA signature and verifies that it is correctly generated from a given message and public key +const VERIFY_MODE_EXTERNAL: u8 = 0x02; // Use an external public key for verification, pass to command as data post param2, ds pg 89 +#[allow(dead_code)] +const VERIFY_MODE_STORED: u8 = 0b00000000; // Use an internally stored public key for verification, param2 = keyID, ds pg 89 +const VERIFY_PARAM2_KEYTYPE_ECC: u8 = 0x0004; // When verify mode external, param2 should be KeyType, ds pg 89 +#[allow(dead_code)] +const VERIFY_PARAM2_KEYTYPE_NONECC: u8 = 0x0007; // When verify mode external, param2 should be KeyType, ds pg 89 +const NONCE_MODE_PASSTHROUGH: u8 = 0b00000011; // Operate in pass-through mode and Write TempKey with NumIn. datasheet pg 79 + const LOCK_MODE_ZONE_CONFIG: u8 = 0b10000000; const LOCK_MODE_ZONE_DATA_AND_OTP: u8 = 0b10000001; const LOCK_MODE_SLOT0: u8 = 0b10000010; @@ -123,6 +131,8 @@ const SIGNATURE_SIZE: usize = 64; const BUFFER_SIZE: usize = 128; const RESPONSE_SIGNAL_INDEX: usize = RESPONSE_COUNT_SIZE; +const ATRCC508A_SUCCESSFUL_TEMPKEY: u8 = 0x00; +const ATRCC508A_SUCCESSFUL_VERIFY: u8 = 0x00; const ATRCC508A_SUCCESSFUL_LOCK: u8 = 0x00; const WORD_ADDRESS_VALUE_RESET: u8 = 0x00; @@ -164,6 +174,10 @@ enum Operation { ReadySha, ShaRun(usize), ShaEnd(usize), + LoadTempKeyNonce(usize), + LoadTempKeyCheckNonce(usize), + VerifySubmitData(usize), + CompleteVerify(usize), } pub struct Atecc508a<'a> { @@ -183,6 +197,11 @@ pub struct Atecc508a<'a> { hash_data: MapCell>, digest_data: TakeCell<'static, [u8; 32]>, + secure_client: OptionalCell<&'a dyn ClientVerify<32, 64>>, + message_data: TakeCell<'static, [u8; 32]>, + signature_data: TakeCell<'static, [u8; 64]>, + ext_public_key: TakeCell<'static, [u8; 64]>, + wakeup_device: fn(), config_lock: Cell, @@ -212,6 +231,10 @@ impl<'a> Atecc508a<'a> { remain_len: Cell::new(0), hash_data: MapCell::empty(), digest_data: TakeCell::empty(), + secure_client: OptionalCell::empty(), + message_data: TakeCell::empty(), + signature_data: TakeCell::empty(), + ext_public_key: TakeCell::empty(), wakeup_device, config_lock: Cell::new(false), data_lock: Cell::new(false), @@ -422,6 +445,24 @@ impl<'a> Atecc508a<'a> { Ok(&self.public_key) } + /// Set the public key to use for the `verify` command, if using an + /// external key. + /// + /// This will return the previous key if one was stored. Pass in + /// `None` to retrieve the key without providing a new one. + pub fn set_public_key( + &'a self, + public_key: Option<&'static mut [u8; 64]>, + ) -> Option<&'static mut [u8; 64]> { + let ret = self.ext_public_key.take(); + + if let Some(key) = public_key { + self.ext_public_key.replace(key); + } + + ret + } + /// Lock the data and OTP pub fn lock_data_and_otp(&self) -> Result<(), ErrorCode> { self.op.set(Operation::LockDataOtp(0)); @@ -1000,6 +1041,150 @@ impl<'a> I2CClient for Atecc508a<'a> { }) }); } + Operation::LoadTempKeyNonce(run) => { + if status == Err(i2c::Error::DataNak) || status == Err(i2c::Error::AddressNak) { + self.buffer.replace(buffer); + + // The device isn't ready yet, try again + if run == 10 { + self.op.set(Operation::Ready); + return; + } + + self.op.set(Operation::LoadTempKeyNonce(run + 1)); + self.send_command(COMMAND_OPCODE_NONCE, NONCE_MODE_PASSTHROUGH, 0x0000, 32); + return; + } + + self.op.set(Operation::LoadTempKeyCheckNonce(0)); + + let _ = self.i2c.read( + buffer, + RESPONSE_COUNT_SIZE + RESPONSE_SIGNAL_SIZE + CRC_SIZE, + ); + } + Operation::LoadTempKeyCheckNonce(run) => { + if status == Err(i2c::Error::DataNak) || status == Err(i2c::Error::AddressNak) { + // The device isn't ready yet, try again + if run == 10 { + self.op.set(Operation::Ready); + return; + } + + self.op.set(Operation::LoadTempKeyCheckNonce(run + 1)); + let _ = self.i2c.read( + buffer, + RESPONSE_COUNT_SIZE + RESPONSE_SIGNAL_SIZE + CRC_SIZE, + ); + return; + } + + if buffer[RESPONSE_SIGNAL_INDEX] != ATRCC508A_SUCCESSFUL_TEMPKEY { + self.buffer.replace(buffer); + + self.secure_client.map(|client| { + client.verification_done( + Err(ErrorCode::FAIL), + self.message_data.take().unwrap(), + self.signature_data.take().unwrap(), + ); + }); + + return; + } + + // Append Signature + self.signature_data.map(|signature_data| { + buffer[ATRCC508A_PROTOCOL_FIELD_DATA..(ATRCC508A_PROTOCOL_FIELD_DATA + 64)] + .copy_from_slice(signature_data); + }); + + // Append Public Key + self.ext_public_key.map(|ext_public_key| { + buffer[(ATRCC508A_PROTOCOL_FIELD_DATA + 64) + ..(ATRCC508A_PROTOCOL_FIELD_DATA + 128)] + .copy_from_slice(ext_public_key); + }); + + self.buffer.replace(buffer); + self.op.set(Operation::VerifySubmitData(0)); + + self.send_command( + COMMAND_OPCODE_VERIFY, + VERIFY_MODE_EXTERNAL, + VERIFY_PARAM2_KEYTYPE_ECC as u16, + 128, + ); + } + Operation::VerifySubmitData(run) => { + if status == Err(i2c::Error::DataNak) || status == Err(i2c::Error::AddressNak) { + self.buffer.replace(buffer); + + // The device isn't ready yet, try again + if run == 10 { + self.op.set(Operation::Ready); + return; + } + + self.op.set(Operation::VerifySubmitData(run + 1)); + self.send_command( + COMMAND_OPCODE_VERIFY, + VERIFY_MODE_EXTERNAL, + VERIFY_PARAM2_KEYTYPE_ECC as u16, + 128, + ); + return; + } + + self.op.set(Operation::CompleteVerify(0)); + let _ = self.i2c.read( + buffer, + RESPONSE_COUNT_SIZE + RESPONSE_SIGNAL_SIZE + CRC_SIZE, + ); + } + Operation::CompleteVerify(run) => { + if status == Err(i2c::Error::DataNak) || status == Err(i2c::Error::AddressNak) { + // The device isn't ready yet, try again + if run == 100 { + self.op.set(Operation::Ready); + return; + } + + self.op.set(Operation::CompleteVerify(run + 1)); + let _ = self.i2c.read( + buffer, + RESPONSE_COUNT_SIZE + RESPONSE_SIGNAL_SIZE + CRC_SIZE, + ); + return; + } + + let ret = buffer[RESPONSE_SIGNAL_INDEX]; + + self.op.set(Operation::Ready); + self.buffer.replace(buffer); + + self.secure_client.map(|client| { + if ret == ATRCC508A_SUCCESSFUL_VERIFY { + client.verification_done( + Ok(true), + self.message_data.take().unwrap(), + self.signature_data.take().unwrap(), + ); + } else if ret == 1 { + client.verification_done( + Ok(false), + self.message_data.take().unwrap(), + self.signature_data.take().unwrap(), + ); + } else { + client.verification_done( + Err(ErrorCode::FAIL), + self.message_data.take().unwrap(), + self.signature_data.take().unwrap(), + ); + } + }); + } } } } @@ -1166,3 +1351,40 @@ impl<'a> digest::DigestDataHash<'a, 32> for Atecc508a<'a> { self.digest_client.set(client); } } + +impl<'a> SignatureVerify<'a, 32, 64> for Atecc508a<'a> { + fn set_verify_client(&self, client: &'a dyn ClientVerify<32, 64>) { + self.secure_client.set(client); + } + + /// Check the signature against the external public key loaded via + /// `set_public_key()`. + /// + /// Verifying that a message was signed by the device is not support + /// yet. + fn verify( + &self, + hash: &'static mut [u8; 32], + signature: &'static mut [u8; 64], + ) -> Result<(), (ErrorCode, &'static mut [u8; 32], &'static mut [u8; 64])> { + if self.ext_public_key.is_none() { + return Err((ErrorCode::OFF, hash, signature)); + } + + (self.wakeup_device)(); + + self.op.set(Operation::LoadTempKeyNonce(0)); + + self.buffer.map(|buffer| { + buffer[ATRCC508A_PROTOCOL_FIELD_DATA..(ATRCC508A_PROTOCOL_FIELD_DATA + 32)] + .copy_from_slice(hash); + }); + + self.message_data.replace(hash); + self.signature_data.replace(signature); + + self.send_command(COMMAND_OPCODE_NONCE, NONCE_MODE_PASSTHROUGH, 0x0000, 32); + + Ok(()) + } +}