impl make credential

This commit is contained in:
shimun 2020-04-26 14:24:39 +02:00
parent 9737a006e7
commit 33229a0b3c
Signed by: shimun
GPG Key ID: E81D8382DC2F971B

View File

@ -8,6 +8,8 @@ use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::fmt::Debug;
use std::io::{Read, Write};
use std::borrow::Cow;
use failure::_core::marker::PhantomData;
#[deny(missing_debug_implementations)]
@ -41,7 +43,7 @@ pub enum CborRequests {
// 0x05
____Unused,
// 0x06
ClientPin,
ClientPin(GetClientPinRequest),
// 0x07
Reset,
// 0x08
@ -58,7 +60,7 @@ pub enum CborRequests {
pub type GetInfoRequest = ();
#[derive(Debug, Clone, Serialize, Deserialize,Eq, PartialEq )]
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct GetInfoResponse {
/// Not used in protocol but required to bump the packed index to 0x01
// 0x00
@ -100,7 +102,7 @@ pub struct GetInfoResponse {
default_cred_protect: CredProtect,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct GetInfoOptions {
#[serde(default)]
plat: bool,
@ -118,7 +120,7 @@ pub struct GetInfoOptions {
config: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct PublicKeyCredentialParameters {
/// Not used in protocol but required to bump the packed index to 0x01
// 0x00
@ -131,10 +133,13 @@ pub struct PublicKeyCredentialParameters {
alg: PublicKeyCredentialAlgorithm,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub enum PublicKeyCredentialType {
#[serde(rename = "public-key")]
PublicKey = 0,
NoTwo = 2,
#[serde(other)]
Other,
}
impl Default for PublicKeyCredentialType {
@ -143,11 +148,13 @@ impl Default for PublicKeyCredentialType {
}
}
#[derive(Debug, Clone, FromPrimitive, ToPrimitive, Eq, PartialEq)]
#[derive(Debug, Clone, FromPrimitive, ToPrimitive, Eq, PartialEq)]
pub enum PublicKeyCredentialAlgorithm {
ES256 = -7,
// Unsupported
EdDSA = -8,
// Compatibility reasons
ECDH_ES_HKDF_256 = -25,
// Unsupported
ES384 = -35,
// Unsupported
@ -214,7 +221,7 @@ impl<'de> Deserialize<'de> for PublicKeyCredentialAlgorithm {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub enum CredProtect {
VerificationOptional,
VerificationOptionalWithCredentialIDList,
@ -227,7 +234,7 @@ impl Default for CredProtect {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct GetClientPinRequest {
/// Not used in protocol but required to bump the packed index to 0x01
// 0x00
@ -252,7 +259,7 @@ pub struct GetClientPinRequest {
#[serde(rename = "pinHashEnc", with = "serde_bytes", default)]
pin_hash_enc: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub enum GetClientPinSubommand {
/// Not used in protocol but required to bump the packed index to 0x01
// 0x00
@ -280,7 +287,7 @@ pub enum GetClientPinSubommand {
GetUVRetries,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct GetClientPinResponse {
/// Not used in protocol but required to bump the packed index to 0x01
// 0x00
@ -312,32 +319,13 @@ pub struct GetClientPinResponse {
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CoseKey {
/// Not used in protocol but required to bump the packed index to 0x01
// 0x00
// #[serde(default)]
//__bump: (),
// 0x01
//#[serde(rename = "type", default)]
type_: PublicKeyCredentialType,
// 0x02
//#[serde(with = "serde_bytes", default)]
id: Vec<u8>,
// 0x03
alg: PublicKeyCredentialAlgorithm,
parameters: BTreeMap<i32, serde_cbor::value::Value>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct CoseKeyDescriptor {
/// Not used in protocol but required to bump the packed index to 0x01
// 0x00
#[serde(default)]
__bump: (),
#[serde(rename = "type")]
type_: PublicKeyCredentialType,
#[serde(with = "serde_bytes", default)]
id: Vec<u8>,
alg: PublicKeyCredentialAlgorithm,
// remainder
parameters: BTreeMap<i32, serde_cbor::value::Value>,
}
@ -346,7 +334,9 @@ impl Serialize for CoseKey {
where
S: Serializer,
{
let mut s = serializer.serialize_map(Some(self.parameters.len() + 2 + (!self.id.is_empty()) as usize))?;
let mut s = serializer.serialize_map(Some(
self.parameters.len() + 2 + (!self.id.is_empty()) as usize,
))?;
s.serialize_entry(&0x01, &self.type_);
if !self.id.is_empty() {
s.serialize_entry(&0x02, &self.id);
@ -364,15 +354,17 @@ impl<'de> Deserialize<'de> for CoseKey {
where
D: Deserializer<'de>,
{
enum Field {
Type, Alg, Id, Other(i32)
Type,
Alg,
Id,
Other(i32),
}
impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
where
D: Deserializer<'de>,
where
D: Deserializer<'de>,
{
struct FieldVisitor;
@ -384,8 +376,8 @@ impl<'de> Deserialize<'de> for CoseKey {
}
fn visit_str<E>(self, value: &str) -> Result<Field, E>
where
E: de::Error,
where
E: de::Error,
{
match value {
"alg" => Ok(Field::Alg),
@ -394,24 +386,38 @@ impl<'de> Deserialize<'de> for CoseKey {
_ => Err(de::Error::unknown_field(value, &["type", "alg", "id"])),
}
}
fn visit_bytes<E>(self, value: &[u8]) -> Result<Field, E>
where
E: de::Error,
{
match value {
b"alg" => Ok(Field::Alg),
b"type" => Ok(Field::Type),
b"id" => Ok(Field::Id),
_ => Err(de::Error::unknown_field(
&format!("{:?}", value),
&["type", "alg", "id"],
)),
}
}
fn visit_i64<E>(self, value: i64) -> Result<Field, E>
where
E: de::Error,
where
E: de::Error,
{
match value {
0x01 => Ok(Field::Type),
0x02 => Ok(Field::Id),
0x03=> Ok(Field::Alg),
0x03 => Ok(Field::Alg),
other => Ok(Field::Other(other as i32)),
}
}
fn visit_u64<E>(self, value: u64) -> Result<Field, E>
where
E: de::Error,
where
E: de::Error,
{
let signed: i64 = value as i64;
self.visit_i64(signed)
self.visit_i64(signed)
}
}
@ -438,16 +444,18 @@ impl<'de> Deserialize<'de> for CoseKey {
let mut parameters: BTreeMap<i32, serde_cbor::value::Value> = BTreeMap::new();
while let Some(k) = map.next_key()? {
match k {
Field::Type => type_ = Some(map.next_value()?),
Field::Type => type_ = Some(map.next_value()?),
Field::Id => id = Some(map.next_value()?),
Field::Alg => alg = Some(map.next_value()?),
Field::Other(i) => {parameters.insert(i, map.next_value()?);}
Field::Other(i) => {
parameters.insert(i, map.next_value()?);
}
};
}
let type_ = type_.ok_or_else(|| serde::de::Error::missing_field("type"))?;
let id = id.map(|id| id.into_vec()).unwrap_or_else(|| vec![]);
let alg = alg.ok_or_else(|| serde::de::Error::missing_field("alg"))?;
Ok(CoseKey{
Ok(CoseKey {
type_,
id,
alg,
@ -460,13 +468,246 @@ impl<'de> Deserialize<'de> for CoseKey {
}
}
#[derive(Debug, Default, Clone,Serialize, Deserialize, PartialEq, Eq)]
pub struct MakeCredentialRequest<'a> {
/// Not used in protocol but required to bump the packed index to 0x01
// 0x00
#[serde(default)]
__bump: (),
// 0x01
#[serde(rename = "clientDataHash")]
client_data_hash: Cow<'a, serde_bytes::Bytes>,
// 0x02
rp: PublicKeyCredentialRpEntity<'a>,
// 0x03
user: PublicKeyCredentialUserEntity<'a>,
// 0x04
#[serde(rename = "pubKeyCredParams")]
pubkey_cred_params: Cow<'a, [PublicKeyCredentialParameters]>,
// 0x05
#[serde(rename = "excludeList", default)]
exclude_list: Cow<'a, [PublicKeyCredentialDescriptor<'a>]>,
// 0x06
#[serde(default)]
extensions: BTreeMap<Cow<'a ,str>, Cow<'a ,serde_cbor::value::Value>>,
#[serde(default)]
options: AuthenticatorOptions<'a>,
#[serde(rename = "pinUvAuthParam", default)]
pin_auth_param: Option<Cow<'a,serde_bytes::Bytes>>,
#[serde(rename = "pinUvAuthProtocol", default)]
pin_auth_protocol: Option<u64>,
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]
pub struct PublicKeyCredentialRpEntity<'a> {
id: Cow<'a ,str>,
#[serde(default)]
name: Option<Cow<'a ,str>>,
#[serde(default)]
icon: Option<Cow<'a ,str>>,
}
// Ensure keys aren't packed but instead serialized as string
impl<'a> Serialize for PublicKeyCredentialRpEntity<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_map(Some(1 + self.name.is_some() as usize + self.icon.is_some() as usize))?;
for (k, v) in &[("id", Some(&self.id)), ("name", self.name.as_ref()), ("icon", self.icon.as_ref())] {
s.serialize_entry(k, v)?;
}
s.end()
}
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]
pub struct PublicKeyCredentialUserEntity<'a> {
id: Cow<'a ,[u8]>,
#[serde(default)]
name: Option<Cow<'a ,str>>,
#[serde(default)]
icon: Option<Cow<'a ,str>>,
}
// Ensure keys aren't packed but instead serialized as string
impl<'a> Serialize for PublicKeyCredentialUserEntity<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_map(Some(1 + self.name.is_some() as usize + self.icon.is_some() as usize))?;
s.serialize_entry("id", &self.id)?;
for (k, v) in &[("name", self.name.as_ref()), ("icon", self.icon.as_ref())] {
s.serialize_entry(k, v)?;
}
s.end()
}
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]
pub struct PublicKeyCredentialDescriptor<'a> {
#[serde(rename = "type")]
type_: PublicKeyCredentialType,
id: Cow<'a ,[u8]>,
}
// Ensure keys aren't packed but instead serialized as string
impl<'a> Serialize for PublicKeyCredentialDescriptor<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_map(Some(2))?;
s.serialize_entry("type", &self.type_)?;
s.serialize_entry("id", &self.id)?;
s.end()
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct AuthenticatorOptions<'a> {
rk: bool,
uv: bool,
other: BTreeMap<Cow<'a, str>, bool>,
}
impl<'a> Serialize for AuthenticatorOptions<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_map(Some(
self.other.len() + [self.rk, self.uv].iter().filter(|o| **o).count(),
))?;
for (k, v) in &[("rk", self.rk), ("uv", self.uv)] {
if *v {
s.serialize_entry(k, v)?;
}
}
for (k, v) in &self.other {
s.serialize_entry(k, v)?;
}
s.end()
}
}
impl<'a, 'de> Deserialize<'de> for AuthenticatorOptions<'a> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
enum Field {
RK,
UV,
Other(String),
}
impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
where
D: Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> Visitor<'de> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("type, alg, id or other")
}
fn visit_str<E>(self, value: &str) -> Result<Field, E>
where
E: de::Error,
{
match value {
"rk" => Ok(Field::RK),
"uv" => Ok(Field::UV),
other => Ok(Field::Other(other.into())),
}
}
fn visit_bytes<E>(self, value: &[u8]) -> Result<Field, E>
where
E: de::Error,
{
match value {
b"rk" => Ok(Field::RK),
b"uv" => Ok(Field::UV),
_ => Err(de::Error::unknown_field(
&format!("{:?}", value),
&["rk", "uv"],
)),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
struct AuthenticatorOptionsVisitor;
impl<'de> Visitor<'de> for AuthenticatorOptionsVisitor {
type Value = AuthenticatorOptions<'static>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct AuthenticatorOptions")
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: MapAccess<'de>,
{
let mut options = AuthenticatorOptions::default();
while let Some(k) = map.next_key()? {
match k {
Field::RK => options.rk = map.next_value()?,
Field::UV => options.uv = map.next_value()?,
Field::Other(i) => {
options.other.insert(Cow::Owned(i), map.next_value()?);
}
};
}
Ok(options)
}
}
deserializer.deserialize_map(AuthenticatorOptionsVisitor)
}
}
#[derive(Debug, Default, Clone,Serialize, Deserialize, PartialEq, Eq)]
pub struct MakeCredentialResponse {
/// Not used in protocol but required to bump the packed index to 0x01
// 0x00
#[serde(default)]
__bump: (),
// 0x01
fmt: String,
// 0x02
#[serde(rename = "authData", with = "serde_bytes")]
auth_data: Vec<u8>,
// 0x03
#[serde(rename = "attStmt")]
attestation_statement: BTreeMap<serde_cbor::value::Value, serde_cbor::value::Value>,
}
#[cfg(test)]
mod test {
use super::*;
fn assert_ok<O: Debug, E: Debug>(res: Result<O, E>) {
assert!(dbg!(res).is_ok())
assert!(dbg!(res).is_ok());
}
fn encode_round<'a, T: Serialize + Deserialize<'a> + Debug + Eq>(val: T) {
let mut buf = Vec::new();
val.serialize_cbor(&mut buf).unwrap();
let mut buf_slice = &buf[..];
let val2 = T::deserialize_cbor(&mut buf_slice).unwrap();
assert_eq!(val, val2);
}
mod info {
@ -477,7 +718,6 @@ mod test {
}
#[test]
#[ignore]
fn response_yubikey() {
test_response(&[
170, 1, 131, 102, 85, 50, 70, 95, 86, 50, 104, 70, 73, 68, 79, 95, 50, 95, 48, 108,
@ -495,7 +735,6 @@ mod test {
}
#[test]
#[ignore]
fn response_solokey() {
test_response(&[
168, 1, 131, 102, 85, 50, 70, 95, 86, 50, 104, 70, 73, 68, 79, 95, 50, 95, 48, 108,
@ -520,8 +759,8 @@ mod test {
fn test_response(mut resp: &[u8]) {
let res = GetClientPinResponse::deserialize_cbor(&mut resp).unwrap();
let cose = res.key_agreement.unwrap();
dbg!(cose.alg);
//let cose = res.key_agreement;
//dbg!(cose.alg);
//assert_ne!(cose.alg, PublicKeyCredentialAlgorithm::Unknown);
/*let mut buf = Vec::new();
cose.serialize_cbor(&mut buf).unwrap();
@ -534,23 +773,41 @@ mod test {
test_request(&[162, 1, 1, 2, 2])
}
#[test]
fn cose_key_round() {
let key = CoseKey{
fn cose_key() -> CoseKey {
CoseKey {
type_: PublicKeyCredentialType::PublicKey,
id: vec![1u8, 3 ,4, 5],
id: vec![8, 3, 4, 5],
alg: PublicKeyCredentialAlgorithm::PS512,
parameters: vec![(-3, serde_cbor::value::Value::Text("test".into()))].into_iter().collect()
};
let mut buf = Vec::new();
key.serialize_cbor(&mut buf).unwrap();
let mut buf_slice = &buf[..];
let key2 = CoseKey::deserialize_cbor(&mut buf_slice).unwrap();
assert_eq!(key, key2);
parameters: vec![
(-3, serde_cbor::value::Value::Text("test".into())),
(
-42,
serde_cbor::value::Value::Bytes("test".as_bytes().to_vec()),
),
]
.into_iter()
.collect(),
}
}
#[test]
fn cose_key_round() {
encode_round(cose_key());
}
#[test]
fn client_pin_resp_round() {
encode_round(GetClientPinResponse {
__bump: (),
key_agreement: Some(cose_key()),
pin_auth_token: vec![1, 2, 3, 4],
pin_retries: 3,
requires_power_cycle: true,
uv_retries: 1,
});
}
#[test]
#[ignore]
fn resp_key_agreement_yubikey() {
test_response(&[
161, 1, 165, 1, 2, 3, 56, 24, 32, 1, 33, 88, 32, 225, 230, 133, 231, 92, 119, 250,
@ -563,7 +820,6 @@ mod test {
// SoloKey v1 fw 4.0.0
#[test]
#[ignore]
fn resp_key_agreement_solokey() {
test_response(&[
161, 1, 165, 1, 2, 3, 56, 24, 32, 1, 33, 88, 32, 9, 161, 111, 235, 155, 109, 166,
@ -576,7 +832,6 @@ mod test {
// Trezor Model T fw 2.3.0
#[test]
#[ignore]
fn resp_key_agreement_trezor() {
test_response(&[
161, 1, 165, 1, 2, 3, 56, 24, 32, 1, 33, 88, 32, 164, 211, 77, 255, 139, 183, 45,
@ -588,6 +843,81 @@ mod test {
}
}
mod make_credential {
use super::*;
fn rp_entity<'a>() -> PublicKeyCredentialRpEntity<'a> {
PublicKeyCredentialRpEntity {
id: Cow::Borrowed("test"),
name: Some("Test".into()),
icon: None
}
}
fn user_entity<'a>() -> PublicKeyCredentialUserEntity<'a> {
PublicKeyCredentialUserEntity {
id: Cow::Borrowed(&[0u8]),
name: Some("Tester".into()),
icon: None
}
}
fn pubkey_cred_desc<'a>() -> PublicKeyCredentialDescriptor<'a> {
PublicKeyCredentialDescriptor{
type_: Default::default(),
id: Cow::Owned(vec![1, 2, 3, 4])
}
}
fn authenticator_options<'a>() -> AuthenticatorOptions<'a> {
AuthenticatorOptions{
rk: false,
uv: true,
other: vec![(Cow::Borrowed("whatever the future may bring"), true)].into_iter().collect()
}
}
fn make_credential_request<'a>() -> MakeCredentialRequest<'a> {
MakeCredentialRequest{
__bump: (),
client_data_hash: Cow::Owned(serde_bytes::ByteBuf::from(vec![0u8; 32].into_boxed_slice())),
rp: rp_entity(),
user: user_entity(),
pubkey_cred_params: Cow::Owned(vec![PublicKeyCredentialParameters{
__bump: (),
type_: Default::default(),
alg: PublicKeyCredentialAlgorithm::EdDSA
}]) ,
exclude_list: Cow::Borrowed(&[]),
extensions: Default::default(),
options: authenticator_options(),
pin_auth_param: None,
pin_auth_protocol: None
}
}
#[test]
fn rp_entity_round() {
encode_round(rp_entity());
}
#[test]
fn user_entity_round() {
encode_round(user_entity());
}
#[test]
fn pubkey_cred_desc_round() {
encode_round(pubkey_cred_desc());
}
#[test]
fn authenticator_options_round() {
encode_round(authenticator_options());
}
}
//#[test]
fn vec_layout() {
#[derive(Debug, Clone, Serialize, Deserialize)]