added make_credential_full
improved error messages fixed cbor issues added hmac example
This commit is contained in:
parent
61d81bda87
commit
4b58dd12f3
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ctap_hmac"
|
||||
description = "A Rust implementation of the FIDO2 CTAP protocol, including the HMAC extension"
|
||||
version = "0.1.1"
|
||||
version = "0.2.1"
|
||||
license = "Apache-2.0/MIT"
|
||||
homepage = "https://github.com/ArdaXi/ctap/pull/2"
|
||||
repository = "https://github.com/shimunn/ctap"
|
||||
@ -19,3 +19,5 @@ cbor-codec = "0.7"
|
||||
ring = "0.13"
|
||||
untrusted = "0.6"
|
||||
rust-crypto = "0.2"
|
||||
hex = "0.4.0"
|
||||
csv-core = "0.1.6"
|
||||
|
62
examples/hmac.rs
Normal file
62
examples/hmac.rs
Normal file
@ -0,0 +1,62 @@
|
||||
extern crate ctap_hmac as ctap;
|
||||
|
||||
use crypto::digest::Digest;
|
||||
use crypto::sha2::Sha256;
|
||||
use ctap::extensions::hmac::{FidoHmacCredential, HmacExtension};
|
||||
use ctap_hmac::{AuthenticatorOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity};
|
||||
use hex;
|
||||
use std::env::args;
|
||||
use std::io::prelude::*;
|
||||
use std::io::stdin;
|
||||
use std::io::stdout;
|
||||
|
||||
fn main() -> ctap::FidoResult<()> {
|
||||
let mut devices = ctap::get_devices()?;
|
||||
let device_info = &mut devices.next().expect("No authenicator found");
|
||||
let mut device = ctap::FidoDevice::new(device_info)?;
|
||||
let options = || Some(AuthenticatorOptions { uv: true, rk: true });
|
||||
let mut credential = match args().skip(1).next().map(|h| FidoHmacCredential {
|
||||
id: hex::decode(&h).expect("Invalid credential"),
|
||||
rp_id: "ctap_demo".into(),
|
||||
}) {
|
||||
Some(cred) => cred,
|
||||
_ => {
|
||||
let rp = PublicKeyCredentialRpEntity {
|
||||
id: "ctap_demo",
|
||||
name: Some("ctap_hmac crate"),
|
||||
icon: None,
|
||||
};
|
||||
let user = PublicKeyCredentialUserEntity {
|
||||
id: &[0u8],
|
||||
name: "commandline",
|
||||
icon: None,
|
||||
display_name: None,
|
||||
};
|
||||
|
||||
println!("Authorize using your device");
|
||||
let credential: FidoHmacCredential = device
|
||||
.make_hmac_credential_full(rp, user, &[0u8; 32], &[], options())
|
||||
.map(|cred| cred.into())?;
|
||||
println!("Credential: {}\nNote: You can pass this credential as first argument in order to reproduce results", hex::encode(&credential.id));
|
||||
credential
|
||||
}
|
||||
};
|
||||
let credential = credential;
|
||||
print!("Type in your message: ");
|
||||
stdout().flush();
|
||||
let mut message = String::new();
|
||||
stdin()
|
||||
.read_line(&mut message)
|
||||
.expect("Couldn't get your message\nNote: this demo does not accept binary data");
|
||||
println!("Authorize using your device");
|
||||
|
||||
let mut salt = [0u8; 32];
|
||||
let mut digest = Sha256::new();
|
||||
digest.input(&message.as_bytes());
|
||||
digest.result(&mut salt);
|
||||
let hash = device
|
||||
.get_hmac_assertion(&credential, &salt, None, options())?
|
||||
.0;
|
||||
println!("Hash: {}", hex::encode(&hash));
|
||||
Ok(())
|
||||
}
|
20
src/cbor.rs
20
src/cbor.rs
@ -82,7 +82,11 @@ impl<'a> MakeCredentialRequest<'a> {
|
||||
let mut length = 4;
|
||||
length += !self.exclude_list.is_empty() as usize;
|
||||
length += !self.extensions.is_empty() as usize;
|
||||
length += self.options.is_some() as usize;
|
||||
length += self
|
||||
.options
|
||||
.as_ref()
|
||||
.map(|opt| opt.encoded())
|
||||
.unwrap_or(false) as usize;
|
||||
length += self.pin_auth.is_some() as usize;
|
||||
length += self.pin_protocol.is_some() as usize;
|
||||
encoder.object(length)?;
|
||||
@ -145,7 +149,7 @@ impl MakeCredentialResponse {
|
||||
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
|
||||
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
|
||||
if status != 0 {
|
||||
Err(FidoErrorKind::CborError(status))?
|
||||
Err(FidoErrorKind::CborError(CborErrorCode::from(status)))?
|
||||
}
|
||||
let mut decoder = Decoder::new(Config::default(), reader);
|
||||
let mut response = MakeCredentialResponse::default();
|
||||
@ -182,7 +186,11 @@ impl<'a> GetAssertionRequest<'a> {
|
||||
let mut length = 2;
|
||||
length += !self.allow_list.is_empty() as usize;
|
||||
length += !self.extensions.is_empty() as usize;
|
||||
length += self.options.is_some() as usize;
|
||||
length += self
|
||||
.options
|
||||
.as_ref()
|
||||
.map(|opt| opt.encoded())
|
||||
.unwrap_or(false) as usize;
|
||||
length += self.pin_auth.is_some() as usize;
|
||||
length += self.pin_protocol.is_some() as usize;
|
||||
encoder.object(length)?;
|
||||
@ -236,7 +244,7 @@ impl GetAssertionResponse {
|
||||
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
|
||||
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
|
||||
if status != 0 {
|
||||
Err(FidoErrorKind::CborError(status))?
|
||||
Err(FidoErrorKind::CborError(CborErrorCode::from(status)))?
|
||||
}
|
||||
let mut decoder = Decoder::new(Config::default(), reader);
|
||||
let mut response = GetAssertionResponse::default();
|
||||
@ -272,7 +280,7 @@ impl GetInfoResponse {
|
||||
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
|
||||
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
|
||||
if status != 0 {
|
||||
Err(FidoErrorKind::CborError(status))?
|
||||
Err(FidoErrorKind::CborError(CborErrorCode::from(status)))?
|
||||
}
|
||||
let mut decoder = Decoder::new(Config::default(), reader);
|
||||
let mut response = GetInfoResponse::default();
|
||||
@ -360,7 +368,7 @@ impl ClientPinResponse {
|
||||
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
|
||||
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
|
||||
if status != 0 {
|
||||
Err(FidoErrorKind::CborError(status))?
|
||||
Err(FidoErrorKind::CborError(CborErrorCode::from(status)))?
|
||||
}
|
||||
let mut decoder = Decoder::new(Config::default(), reader);
|
||||
let mut response = ClientPinResponse::default();
|
||||
|
49
src/ctap_error_codes.csv
Normal file
49
src/ctap_error_codes.csv
Normal file
@ -0,0 +1,49 @@
|
||||
Code,Name,Description
|
||||
0x00,"CTAP1_ERR_SUCCESS, CTAP2_OK",Indicates successful response.
|
||||
0x01,CTAP1_ERR_INVALID_COMMAND,The command is not a valid CTAP command.
|
||||
0x02,CTAP1_ERR_INVALID_PARAMETER,The command included an invalid parameter.
|
||||
0x03,CTAP1_ERR_INVALID_LENGTH,Invalid message or item length.
|
||||
0x04,CTAP1_ERR_INVALID_SEQ,Invalid message sequencing.
|
||||
0x05,CTAP1_ERR_TIMEOUT,Message timed out.
|
||||
0x06,CTAP1_ERR_CHANNEL_BUSY,Channel busy.
|
||||
0x0A,CTAP1_ERR_LOCK_REQUIRED,Command requires channel lock.
|
||||
0x0B,CTAP1_ERR_INVALID_CHANNEL,Command not allowed on this cid.
|
||||
0x11,CTAP2_ERR_CBOR_UNEXPECTED_TYPE,Invalid/unexpected CBOR error.
|
||||
0x12,CTAP2_ERR_INVALID_CBOR,Error when parsing CBOR.
|
||||
0x14,CTAP2_ERR_MISSING_PARAMETER,Missing non-optional parameter.
|
||||
0x15,CTAP2_ERR_LIMIT_EXCEEDED,Limit for number of items exceeded.
|
||||
0x16,CTAP2_ERR_UNSUPPORTED_EXTENSION,Unsupported extension.
|
||||
0x19,CTAP2_ERR_CREDENTIAL_EXCLUDED,Valid credential found in the exclude list.
|
||||
0x21,CTAP2_ERR_PROCESSING,Processing (Lengthy operation is in progress).
|
||||
0x22,CTAP2_ERR_INVALID_CREDENTIAL,Credential not valid for the authenticator.
|
||||
0x23,CTAP2_ERR_USER_ACTION_PENDING,Authentication is waiting for user interaction.
|
||||
0x24,CTAP2_ERR_OPERATION_PENDING,"Processing, lengthy operation is in progress."
|
||||
0x25,CTAP2_ERR_NO_OPERATIONS,No request is pending.
|
||||
0x26,CTAP2_ERR_UNSUPPORTED_ALGORITHM,Authenticator does not support requested algorithm.
|
||||
0x27,CTAP2_ERR_OPERATION_DENIED,Not authorized for requested operation.
|
||||
0x28,CTAP2_ERR_KEY_STORE_FULL,Internal key storage is full.
|
||||
0x29,CTAP2_ERR_NOT_BUSY,Authenticator cannot cancel as it is not busy.
|
||||
0x2A,CTAP2_ERR_NO_OPERATION_PENDING,No outstanding operations.
|
||||
0x2B,CTAP2_ERR_UNSUPPORTED_OPTION,Unsupported option.
|
||||
0x2C,CTAP2_ERR_INVALID_OPTION,Not a valid option for current operation.
|
||||
0x2D,CTAP2_ERR_KEEPALIVE_CANCEL,Pending keep alive was cancelled.
|
||||
0x2E,CTAP2_ERR_NO_CREDENTIALS,No valid credentials provided.
|
||||
0x2F,CTAP2_ERR_USER_ACTION_TIMEOUT,Timeout waiting for user interaction.
|
||||
0x30,CTAP2_ERR_NOT_ALLOWED,"Continuation command, such as, authenticatorGetNextAssertion not allowed."
|
||||
0x31,CTAP2_ERR_PIN_INVALID,PIN Invalid.
|
||||
0x32,CTAP2_ERR_PIN_BLOCKED,PIN Blocked.
|
||||
0x33,CTAP2_ERR_PIN_AUTH_INVALID,"PIN authentication, pinAuth, verification failed."
|
||||
0x34,CTAP2_ERR_PIN_AUTH_BLOCKED,"PIN authentication,pinAuth, blocked. Requires power recycle to reset."
|
||||
0x35,CTAP2_ERR_PIN_NOT_SET,No PIN has been set.
|
||||
0x36,CTAP2_ERR_PIN_REQUIRED,PIN is required for the selected operation.
|
||||
0x37,CTAP2_ERR_PIN_POLICY_VIOLATION,PIN policy violation. Currently only enforces minimum length.
|
||||
0x38,CTAP2_ERR_PIN_TOKEN_EXPIRED,pinToken expired on authenticator.
|
||||
0x39,CTAP2_ERR_REQUEST_TOO_LARGE,Authenticator cannot handle this request due to memory constraints.
|
||||
0x3A,CTAP2_ERR_ACTION_TIMEOUT,The current operation has timed out.
|
||||
0x3B,CTAP2_ERR_UP_REQUIRED,User presence is required for the requested operation.
|
||||
0x7F,CTAP1_ERR_OTHER,Other unspecified error.
|
||||
0xDF,CTAP2_ERR_SPEC_LAST,CTAP 2 spec last error.
|
||||
0xE0,CTAP2_ERR_EXTENSION_FIRST,Extension specific error.
|
||||
0xEF,CTAP2_ERR_EXTENSION_LAST,Extension specific error.
|
||||
0xF0,CTAP2_ERR_VENDOR_FIRST,Vendor specific error.
|
||||
0xFF,CTAP2_ERR_VENDOR_LAST,Vendor specific error.
|
|
75
src/error.rs
75
src/error.rs
@ -5,7 +5,8 @@
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
use cbor_codec::{DecodeError, EncodeError};
|
||||
|
||||
use csv_core::{ReadFieldResult, Reader};
|
||||
use failure::_core::fmt::{Error, Formatter};
|
||||
use failure::{Backtrace, Context, Fail};
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
@ -15,6 +16,9 @@ pub type FidoResult<T> = Result<T, FidoError>;
|
||||
#[derive(Debug)]
|
||||
pub struct FidoError(Context<FidoErrorKind>);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Fail, Eq, PartialEq)]
|
||||
pub struct CborErrorCode(u8);
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
|
||||
pub enum FidoErrorKind {
|
||||
#[fail(display = "Read/write error with device.")]
|
||||
@ -43,8 +47,8 @@ pub enum FidoErrorKind {
|
||||
DecryptPin,
|
||||
#[fail(display = "Supplied key has incorrect type.")]
|
||||
KeyType,
|
||||
#[fail(display = "Device returned error: 0x{:x}", _0)]
|
||||
CborError(u8),
|
||||
#[fail(display = "Device returned error: {}", _0)]
|
||||
CborError(CborErrorCode),
|
||||
#[fail(display = "Device does not support FIDO2")]
|
||||
DeviceUnsupported,
|
||||
#[fail(display = "This operating requires a PIN but none was provided.")]
|
||||
@ -99,3 +103,68 @@ impl From<DecodeError> for FidoError {
|
||||
FidoError(err.context(FidoErrorKind::CborDecode))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for CborErrorCode {
|
||||
fn from(code: u8) -> Self {
|
||||
Self(code)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CborErrorCode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
let messages = include_str!("ctap_error_codes.csv");
|
||||
let mut rdr = Reader::new();
|
||||
let mut bytes = messages.as_bytes();
|
||||
let mut col: usize = 0;
|
||||
let mut row: usize = 0;
|
||||
let mut correct_row: bool = false;
|
||||
let mut field = [0u8; 1024];
|
||||
let hex = format!("{:x?}", self.0);
|
||||
let mut name: Option<String> = None;
|
||||
let mut desc: Option<String> = None;
|
||||
loop {
|
||||
let (result, nin, read) = rdr.read_field(&bytes, &mut field);
|
||||
bytes = &bytes[nin..];
|
||||
match result {
|
||||
ReadFieldResult::InputEmpty => {}
|
||||
ReadFieldResult::OutputFull => panic!("field too large"),
|
||||
ReadFieldResult::Field { record_end } => {
|
||||
let text = String::from_utf8(field[..read].iter().cloned().collect()).unwrap();
|
||||
if row > 0 {
|
||||
match col {
|
||||
0 if i64::from_str_radix(&text[2..], 16)
|
||||
.expect("malformed ctap_error_codes.csv")
|
||||
== self.0 as i64 =>
|
||||
{
|
||||
correct_row = true
|
||||
}
|
||||
1 | 2 if correct_row => {
|
||||
if let Some(_) = name {
|
||||
desc = Some(text);
|
||||
break;
|
||||
} else {
|
||||
name = Some(text);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
col += 1;
|
||||
if record_end {
|
||||
col = 0;
|
||||
row += 1;
|
||||
}
|
||||
}
|
||||
ReadFieldResult::End => break,
|
||||
}
|
||||
}
|
||||
if let Some((code, name, desc)) =
|
||||
name.and_then(|name| desc.map(|desc| (self.0, name, desc)))
|
||||
{
|
||||
write!(f, "CborError: 0x{:x?}: {}", code, desc);
|
||||
} else {
|
||||
write!(f, "CborError: 0x{:x?}", self.0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
use crate::cbor;
|
||||
use crate::{
|
||||
cbor, AuthenticatorOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
||||
};
|
||||
use crate::{FidoCredential, FidoDevice, FidoErrorKind, FidoResult};
|
||||
use cbor_codec::value::{Bytes, Int, Key, Text, Value};
|
||||
use cbor_codec::Encoder;
|
||||
@ -31,23 +33,37 @@ pub trait HmacExtension {
|
||||
"hmac-secret"
|
||||
}
|
||||
|
||||
fn extension_input() -> &'static Value {
|
||||
&Value::Bool(true)
|
||||
}
|
||||
|
||||
/// Generates data for the extension field as part of the assertion request
|
||||
fn get_dict(&mut self, salt: &[u8; 32], salt2: Option<&[u8; 32]>) -> FidoResult<Value> {
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(
|
||||
Key::Text(Text::Text(Self::extension_name().to_owned())),
|
||||
Key::Text(Text::Text(
|
||||
<Self as HmacExtension>::extension_name().to_owned(),
|
||||
)),
|
||||
self.get_data(salt, salt2)?,
|
||||
);
|
||||
Ok(Value::Map(map))
|
||||
}
|
||||
|
||||
/// Wraps [`get_dict`]
|
||||
fn get_data(&mut self, salt: &[u8; 32], salt2: Option<&[u8; 32]>) -> FidoResult<Value>;
|
||||
|
||||
/// Convenience function to create an credential with default rp_id and user_name
|
||||
/// Use `FidoDevice::make_credential` if you need more control
|
||||
fn make_hmac_credential(&mut self) -> FidoResult<FidoHmacCredential>;
|
||||
|
||||
fn make_hmac_credential_full(
|
||||
&mut self,
|
||||
rp: cbor::PublicKeyCredentialRpEntity,
|
||||
user: cbor::PublicKeyCredentialUserEntity,
|
||||
client_data_hash: &[u8],
|
||||
exclude_list: &[cbor::PublicKeyCredentialDescriptor],
|
||||
options: Option<cbor::AuthenticatorOptions>,
|
||||
) -> FidoResult<FidoCredential>;
|
||||
|
||||
/// Request an assertion from the authenticator for a given credential and salt(s).
|
||||
/// at least one `salt` must be provided, consider using a hashing function like SHA256
|
||||
/// to ensure that your salt will fit 32 bytes.
|
||||
@ -63,6 +79,7 @@ pub trait HmacExtension {
|
||||
credential: &FidoHmacCredential,
|
||||
salt: &[u8; 32],
|
||||
salt2: Option<&[u8; 32]>,
|
||||
options: Option<AuthenticatorOptions>,
|
||||
) -> FidoResult<([u8; 32], Option<[u8; 32]>)>;
|
||||
|
||||
/// Convenience function for `get_hmac_assertion` that will accept arbitrary
|
||||
@ -76,8 +93,13 @@ pub trait HmacExtension {
|
||||
let mut digest = Sha256::new();
|
||||
digest.input(input);
|
||||
digest.result(&mut salt);
|
||||
self.get_hmac_assertion(credential, &salt, None)
|
||||
.map(|secret| secret.0)
|
||||
self.get_hmac_assertion(
|
||||
credential,
|
||||
&salt,
|
||||
None,
|
||||
Some(AuthenticatorOptions { uv: true, rk: true }),
|
||||
)
|
||||
.map(|secret| secret.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,15 +157,53 @@ impl HmacExtension for FidoDevice {
|
||||
}
|
||||
|
||||
fn make_hmac_credential(&mut self) -> FidoResult<FidoHmacCredential> {
|
||||
self.make_credential("hmac", &[0u8], "commandline", &[0u8; 32])
|
||||
let rp = PublicKeyCredentialRpEntity {
|
||||
id: "hmac",
|
||||
name: None,
|
||||
icon: None,
|
||||
};
|
||||
let user = PublicKeyCredentialUserEntity {
|
||||
id: &[0u8],
|
||||
name: "commandline",
|
||||
icon: None,
|
||||
display_name: None,
|
||||
};
|
||||
let options = Some(AuthenticatorOptions {
|
||||
uv: true,
|
||||
rk: false,
|
||||
});
|
||||
|
||||
self.make_hmac_credential_full(rp, user, &[0u8; 32], &[], options)
|
||||
.map(|cred| cred.into())
|
||||
}
|
||||
|
||||
fn make_hmac_credential_full(
|
||||
&mut self,
|
||||
rp: cbor::PublicKeyCredentialRpEntity,
|
||||
user: cbor::PublicKeyCredentialUserEntity,
|
||||
client_data_hash: &[u8],
|
||||
exclude_list: &[cbor::PublicKeyCredentialDescriptor],
|
||||
options: Option<cbor::AuthenticatorOptions>,
|
||||
) -> FidoResult<FidoCredential> {
|
||||
self.make_credential_full(
|
||||
rp,
|
||||
user,
|
||||
client_data_hash,
|
||||
exclude_list,
|
||||
&[(
|
||||
<Self as HmacExtension>::extension_name(),
|
||||
<Self as HmacExtension>::extension_input(),
|
||||
)],
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_hmac_assertion(
|
||||
&mut self,
|
||||
credential: &FidoHmacCredential,
|
||||
salt: &[u8; 32],
|
||||
salt2: Option<&[u8; 32]>,
|
||||
options: Option<AuthenticatorOptions>,
|
||||
) -> FidoResult<([u8; 32], Option<[u8; 32]>)> {
|
||||
let client_data_hash = [0u8; 32];
|
||||
while self.shared_secret.is_none() {
|
||||
@ -170,10 +230,7 @@ impl HmacExtension for FidoDevice {
|
||||
client_data_hash: &client_data_hash,
|
||||
allow_list: &allow_list,
|
||||
extensions: &[(<Self as HmacExtension>::extension_name(), &ext_data)],
|
||||
options: Some(cbor::AuthenticatorOptions {
|
||||
rk: false,
|
||||
uv: true,
|
||||
}),
|
||||
options: options,
|
||||
pin_auth,
|
||||
pin_protocol: pin_auth.and(Some(0x01)),
|
||||
};
|
||||
|
@ -1 +1,2 @@
|
||||
pub mod hmac;
|
||||
pub use hmac::*;
|
||||
|
68
src/lib.rs
68
src/lib.rs
@ -64,6 +64,10 @@ use std::io::{Cursor, Write};
|
||||
use std::u16;
|
||||
use std::u8;
|
||||
|
||||
pub use self::cbor::{
|
||||
AuthenticatorOptions, PublicKeyCredentialDescriptor, PublicKeyCredentialRpEntity,
|
||||
PublicKeyCredentialUserEntity,
|
||||
};
|
||||
pub use self::error::*;
|
||||
use self::hid_linux as hid;
|
||||
use self::packet::CtapCommand;
|
||||
@ -232,16 +236,7 @@ impl FidoDevice {
|
||||
user_name: &str,
|
||||
client_data_hash: &[u8],
|
||||
) -> FidoResult<FidoCredential> {
|
||||
if self.needs_pin && self.pin_token.is_none() {
|
||||
Err(FidoErrorKind::PinRequired)?
|
||||
}
|
||||
if client_data_hash.len() != 32 {
|
||||
Err(FidoErrorKind::CborEncode)?
|
||||
}
|
||||
let pin_auth = self
|
||||
.pin_token
|
||||
.as_ref()
|
||||
.map(|token| token.auth(&client_data_hash));
|
||||
//TODO: implement all options: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential
|
||||
let rp = cbor::PublicKeyCredentialRpEntity {
|
||||
id: rp_id,
|
||||
name: None,
|
||||
@ -253,21 +248,60 @@ impl FidoDevice {
|
||||
icon: None,
|
||||
display_name: None,
|
||||
};
|
||||
|
||||
let options = Some(AuthenticatorOptions {
|
||||
uv: true,
|
||||
rk: false,
|
||||
});
|
||||
self.make_credential_full(rp, user, client_data_hash, &[], &[], options)
|
||||
}
|
||||
|
||||
/// Request a new credential from the authenticator. The `rp_id` should be
|
||||
/// a stable string used to identify the party for whom the credential is
|
||||
/// created, for convenience it will be returned with the credential.
|
||||
/// `user_id` and `user_name` are not required when requesting attestations
|
||||
/// but they MAY be displayed to the user and MAY be stored on the device
|
||||
/// to be returned with an attestation if the device supports this.
|
||||
/// `client_data_hash` SHOULD be a SHA256 hash of provided `client_data`,
|
||||
/// this is only used to verify the attestation provided by the
|
||||
/// authenticator. When not implementing WebAuthN this can be any random
|
||||
/// 32-byte array.
|
||||
///
|
||||
/// This method will fail if a PIN is required but the device is not
|
||||
/// unlocked or if the device returns malformed data.
|
||||
pub fn make_credential_full(
|
||||
&mut self,
|
||||
rp: cbor::PublicKeyCredentialRpEntity,
|
||||
user: cbor::PublicKeyCredentialUserEntity,
|
||||
client_data_hash: &[u8],
|
||||
exclude_list: &[cbor::PublicKeyCredentialDescriptor],
|
||||
extensions: &[(&str, &cbor_codec::value::Value)],
|
||||
options: Option<cbor::AuthenticatorOptions>,
|
||||
) -> FidoResult<FidoCredential> {
|
||||
if self.needs_pin && self.pin_token.is_none() {
|
||||
Err(FidoErrorKind::PinRequired)?
|
||||
}
|
||||
if client_data_hash.len() != 32 {
|
||||
Err(FidoErrorKind::CborEncode)?
|
||||
}
|
||||
let pub_key_cred_params = [("public-key", -7)];
|
||||
let pin_auth = self
|
||||
.pin_token
|
||||
.as_ref()
|
||||
.map(|token| token.auth(&client_data_hash));
|
||||
let rp_id = rp.id.to_owned();
|
||||
let request = cbor::MakeCredentialRequest {
|
||||
client_data_hash,
|
||||
rp,
|
||||
user,
|
||||
pub_key_cred_params: &pub_key_cred_params,
|
||||
exclude_list: Default::default(),
|
||||
extensions: Default::default(),
|
||||
options: Some(cbor::AuthenticatorOptions {
|
||||
rk: false,
|
||||
uv: true,
|
||||
}),
|
||||
exclude_list: exclude_list,
|
||||
extensions: extensions,
|
||||
options: options,
|
||||
pin_auth,
|
||||
pin_protocol: pin_auth.and(Some(0x01)),
|
||||
};
|
||||
|
||||
let response = match self.cbor(cbor::Request::MakeCredential(request))? {
|
||||
cbor::Response::MakeCredential(resp) => resp,
|
||||
_ => Err(FidoErrorKind::CborDecode)?,
|
||||
@ -281,7 +315,7 @@ impl FidoDevice {
|
||||
.bytes();
|
||||
Ok(FidoCredential {
|
||||
id: response.auth_data.attested_credential_data.credential_id,
|
||||
rp_id: String::from(rp_id),
|
||||
rp_id: rp_id,
|
||||
public_key: Vec::from(&public_key[..]),
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user