blob: 6ca0611d3f9170262decffac4b69ca028e12d843 [file] [log] [blame]
//! This is a featured CTAP example
//! WARNING! This currently uses unsound crypto operations
//! This is only a demo and should not be used in real enviroments
#![no_std]
#![feature(alloc_error_handler)]
extern crate alloc;
use core::alloc::Layout;
use core::cell::Cell;
use core::convert::TryInto;
use core::fmt::{self, Debug, Error, Formatter};
use core::time::Duration;
use ctap2_authenticator::credentials::{RpCredential, UserCredential};
use ctap2_authenticator::usbhid::{
CtapHidCapabilities, CtapHidPlatform, KeepaliveResponse, TransactionProcessor,
};
use ctap2_authenticator::Authenticator;
use ctap2_authenticator::{
AuthenticatorPlatform, CredentialDescriptorList, CtapOptions, PublicKey, Signature,
};
use generic_array::GenericArray;
use libtock::ctap::{CtapRecvBuffer, CtapSendBuffer};
use libtock::hmac::{HmacDataBuffer, HmacDestBuffer, HmacDriverFactory, HmacKeyBuffer};
use libtock::println;
use libtock::result::TockResult;
use libtock::syscalls;
use p256::ecdsa::{signature::Signer, SigningKey};
use p256::elliptic_curve::ff::PrimeField;
use p256::{Scalar, SecretKey};
use subtle::{Choice, ConditionallySelectable};
libtock_core::stack_size! {0x4000}
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub struct PrivateKey(Scalar);
impl ConditionallySelectable for PrivateKey {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
Self(ConditionallySelectable::conditional_select(
&a.0, &b.0, choice,
))
}
}
impl Default for PrivateKey {
fn default() -> Self {
Self(Scalar::default())
}
}
impl PrivateKey {
pub fn from_bytes(bytes: &[u8; 32]) -> Option<Self> {
Scalar::from_repr(GenericArray::clone_from_slice(bytes)).map(|s| Self(s))
}
}
/// This is the provided implementation of `CtapHidPlatform` to be used in the `UsbContext`
pub(crate) struct UsbKeyHidPlatform {}
impl UsbKeyHidPlatform {
pub fn new() -> Self {
Self {}
}
}
impl CtapHidPlatform for UsbKeyHidPlatform {
// Should be kept in sync with the Crates version if possible
const MAJOR_VERSION: u8 = 0;
const MINOR_VERSION: u8 = 0;
const BUILD_VERSION: u8 = 0;
const CAPABILITIES: CtapHidCapabilities = CtapHidCapabilities {
wink: true,
cbor: true,
msg: false,
};
fn wink(&mut self) {}
fn cancel(&mut self) {
println!("cancel");
unimplemented!()
}
fn keepalive_needed(&mut self) -> KeepaliveResponse {
println!("keepalive_needed");
unimplemented!()
}
fn start_timer(&mut self) {
println!("start_timer");
}
fn has_timed_out(&mut self) -> bool {
println!("has_timed_out");
false
}
}
#[derive(Clone, Copy)]
pub struct HmacKeyCredential(pub(crate) [u8; 40]);
impl HmacKeyCredential {
fn new(mac: &[u8; 32], nonce: [u8; 8]) -> Self {
let mut new = [0; 40];
new[..32].copy_from_slice(&mac[..]);
new[32..].copy_from_slice(&nonce[..]);
Self(new)
}
fn get_mac(&self) -> [u8; 32] {
let mut mac = [0; 32];
mac.copy_from_slice(&self.0[..32]);
mac
}
}
impl AsRef<[u8]> for HmacKeyCredential {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Debug for HmacKeyCredential {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(
f,
"HmacKeyCredential {{ mac: {:?}, nonce: {:?} }}",
&self.0[..32],
&self.0[32..]
)
}
}
impl PartialEq for HmacKeyCredential {
fn eq(&self, other: &Self) -> bool {
self.0[..] == other.0[..]
}
}
impl Eq for HmacKeyCredential {}
const ITERATOR_MAX_LENGTH: usize = 8;
pub struct HmacCredentialQueue {
index: usize,
length: usize,
list: [Option<(HmacKeyCredential, u32)>; ITERATOR_MAX_LENGTH],
}
impl HmacCredentialQueue {
pub(crate) fn new() -> Self {
Self {
index: 0,
length: 0,
list: [None; ITERATOR_MAX_LENGTH],
}
}
pub(crate) fn push(&mut self, credential: HmacKeyCredential, counter: u32) -> Result<(), ()> {
if self.length < ITERATOR_MAX_LENGTH {
self.list[self.length] = Some((credential, counter));
self.length += 1;
Ok(())
} else {
Err(())
}
}
pub(crate) fn len(&self) -> usize {
self.length
}
}
impl Iterator for HmacCredentialQueue {
type Item = (HmacKeyCredential, u32);
fn next(&mut self) -> Option<Self::Item> {
if self.list[self.index].is_some() {
self.index += 1;
self.list[self.index - 1]
} else {
None
}
}
}
pub(crate) struct CtapPlatform {
hmac: HmacDriverFactory,
}
impl CtapPlatform {
fn new(hmac: HmacDriverFactory) -> Self {
Self { hmac }
}
}
impl fmt::Debug for CtapPlatform {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}
impl CtapPlatform {
fn generate_key_seed(
&mut self,
rp_id: &str,
nonce: &[u8; 8],
) -> [u8; libtock::hmac::DEST_BUFFER_SIZE] {
let hmac_driver;
match self.hmac.init_driver() {
Err(_) => {
panic!("Hmac init error");
}
Ok(driver) => {
hmac_driver = driver;
}
}
let mut key_buffer = HmacKeyBuffer::default();
for (i, d) in rp_id.as_bytes().iter().enumerate() {
key_buffer[i] = *d;
}
// We need to make sure this isn't dropped straight away
let key_buffer_ret = hmac_driver.init_key_buffer(&mut key_buffer);
if key_buffer_ret.is_err() {
panic!("Hmac key buffer init error");
}
let mut data_buffer = HmacDataBuffer::default();
for (i, d) in nonce.iter().enumerate() {
data_buffer[i] = *d;
}
// We need to make sure this isn't dropped straight away
let data_buffer_ret = hmac_driver.init_data_buffer(&mut data_buffer);
if data_buffer_ret.is_err() {
panic!("Hmac data buffer init error");
}
let mut dest_buffer = HmacDestBuffer::default();
let seed_buffer;
match hmac_driver.init_dest_buffer(&mut dest_buffer) {
Err(_) => {
panic!("Hmac subscribe error");
}
Ok(buffer) => {
seed_buffer = buffer;
}
}
let mut callback = |_result, _digest| {};
// We need to make sure this isn't dropped straight away
let subscribe = hmac_driver.subscribe(&mut callback);
if subscribe.is_err() {
panic!("Hmac subscribe error");
}
if hmac_driver.run().is_err() {
panic!("Hmac run error");
}
// Yield waiting for the HMAC callback
unsafe {
syscalls::raw::yieldk();
}
let mut temp_buffer = [0; libtock::hmac::DEST_BUFFER_SIZE];
seed_buffer.read_bytes(&mut temp_buffer[..]);
temp_buffer
}
/// Generates a Credential from a CredentialId and checks that the Id is valid
fn checked_generate_hmac_cred(
&mut self,
rp_id: &str,
credential_id: &[u8],
) -> Option<HmacKeyCredential> {
if credential_id.len() != 40 {
return None;
}
let received_mac = &credential_id[..32];
let seed_buffer = self.generate_key_seed(rp_id, &credential_id[32..40].try_into().unwrap());
let credential =
HmacKeyCredential::new(&seed_buffer, credential_id[32..].try_into().unwrap());
let generated_mac = &credential.0[..32];
if generated_mac == received_mac {
Some(credential)
} else {
None
}
}
}
impl AuthenticatorPlatform for CtapPlatform {
const AAGUID: [u8; 16] = [
0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc,
0x7d,
];
const CERTIFICATE: Option<&'static [u8]> = None;
const MAX_MSG_LENGTH: u16 = 7609;
const SUPPORTED_OPTIONS: CtapOptions = CtapOptions {
plat: None,
rk: Some(true),
client_pin: None,
up: Some(true),
uv: Some(false),
};
type CredentialId = HmacKeyCredential;
type PublicKeyBuffer = [u8; 32];
type SignatureBuffer = [u8; 32];
type CredentialIterator = HmacCredentialQueue;
fn reset(&mut self) -> Result<(), ()> {
println!("reset");
unimplemented!()
}
fn check_exclude_list(&mut self, _rp_id: &str, _list: CredentialDescriptorList) -> bool {
println!("check_exclude_list");
unimplemented!()
}
fn locate_credentials(
&mut self,
rp_id: &str,
list: Option<CredentialDescriptorList>,
) -> (u16, Self::CredentialIterator) {
match list {
None => (0, HmacCredentialQueue::new()),
Some(list) => {
let mut result = HmacCredentialQueue::new();
for descriptor in list {
match self.checked_generate_hmac_cred(rp_id, descriptor.get_id()) {
None => (),
Some(credential) => {
match result.push(credential, 0) {
Ok(()) => (),
// NOTE: We just truncate if we don't have enough space to store all
// fitting credentials
Err(()) => break,
}
}
}
}
(result.len() as u16, result)
}
}
}
fn create_credential(
&mut self,
rp: RpCredential<&[u8], &str>,
_user: UserCredential<&[u8], &str>,
) -> (Self::CredentialId, PublicKey<Self::PublicKeyBuffer>, u32) {
println!("create_credential");
let rp_id = core::str::from_utf8(rp.rp_id()).unwrap();
// This nonce is static!!!!
// This is really bad cryptowise, let's print a warning
// TODO: Convert this to generate a nonce from Tock's TRNG
println!("WARNING!!! The nonce is static, this key is insecure");
println!("WARNING!!! Do not use this anywhere important");
let nonce = [0; 8];
let seed_buffer = self.generate_key_seed(rp_id, &nonce);
let credential = HmacKeyCredential::new(&seed_buffer, nonce.try_into().unwrap());
let secret_key = SecretKey::from_bytes(&seed_buffer[0..32]).unwrap();
let pub_key = p256::EncodedPoint::from_secret_key(&secret_key, false);
let x: [u8; 32] = pub_key.as_bytes()[1..33].try_into().unwrap();
let y: [u8; 32] = pub_key.as_bytes()[33..].try_into().unwrap();
let ret_key = PublicKey::nistp256(x, y);
(credential, ret_key, 0)
}
fn attest(
&mut self,
id: &Self::CredentialId,
data: &[u8],
) -> Option<Signature<Self::SignatureBuffer>> {
// TODO: Should attest be different then sign?
let attest = {
let secret_key = SecretKey::from_bytes(id.get_mac()).unwrap();
let signer = SigningKey::from(&secret_key);
let sig = signer.sign(data);
let mut r: [u8; 32] = [0; 32];
r.clone_from_slice(&sig.r().as_ref().to_bytes());
let mut s: [u8; 32] = [0; 32];
s.clone_from_slice(&sig.s().as_ref().to_bytes());
Signature::nistp256(r, s)
};
Some(attest)
}
fn sign(
&mut self,
id: &Self::CredentialId,
data: &[u8],
) -> Option<Signature<Self::SignatureBuffer>> {
let attest = {
let secret_key = SecretKey::from_bytes(id.get_mac()).unwrap();
let signer = SigningKey::from(&secret_key);
let sig = signer.sign(data);
let mut r: [u8; 32] = [0; 32];
r.clone_from_slice(&sig.r().as_ref().to_bytes());
let mut s: [u8; 32] = [0; 32];
s.clone_from_slice(&sig.s().as_ref().to_bytes());
Signature::nistp256(r, s)
};
Some(attest)
}
fn start_timeout(&mut self) {
println!("start_timeout");
unimplemented!()
}
fn has_timed_out(&mut self, _time: Duration) -> bool {
println!("has_timed_out");
unimplemented!()
}
}
#[alloc_error_handler]
unsafe fn alloc_error_handler(_: Layout) -> ! {
println!("alloc_error_handler called");
loop {
syscalls::raw::yieldk();
}
}
#[libtock::main]
async fn main() -> TockResult<()> {
let mut tp: Cell<TransactionProcessor<&'static mut [u8], UsbKeyHidPlatform>>;
let mut temp_buffer = [0; libtock::ctap::RECV_BUFFER_SIZE];
let mut drivers = libtock::retrieve_drivers()?;
drivers.console.create_console();
println!("Starting CTAP feature example");
let ctap_driver = drivers.ctap.init_driver()?;
let mut recv_buffer = CtapRecvBuffer::default();
let recv_buffer = ctap_driver.init_recv_buffer(&mut recv_buffer)?;
let mut send_buffer = CtapSendBuffer::default();
let mut send_buffer = ctap_driver.init_send_buffer(&mut send_buffer)?;
static mut BUFFER: &'static mut [u8] = &mut [0; 2048];
unsafe {
tp = Cell::new(TransactionProcessor::new(BUFFER, UsbKeyHidPlatform::new()));
}
let mut authenticator = Authenticator::create(CtapPlatform::new(drivers.hmac));
let mut callback = |sent, _| {
let mut looping = true;
recv_buffer.read_bytes(&mut temp_buffer[..]);
let temp_tp = tp.get_mut();
let mut poll_msg = if sent == 0 { Some(&temp_buffer) } else { None };
while looping {
looping = false;
let (maybe_request, maybe_data) = temp_tp.poll(poll_msg);
if let Some(request) = maybe_request {
let response = authenticator.process(request);
temp_tp.response(response);
looping = true;
}
if let Some(mut data) = maybe_data {
send_buffer.write_bytes(&mut data[0..]);
let _ = ctap_driver.send_data();
}
poll_msg = None;
}
let _ = ctap_driver.allow_receive();
};
let _subscription = ctap_driver.subscribe(&mut callback)?;
ctap_driver.allow_receive()?;
loop {
unsafe { syscalls::raw::yieldk() };
}
}