Skip to content

Commit

Permalink
caBLE progress: Working make credential and get assertion with iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
AlfioEmanueleFresta committed Oct 20, 2024
1 parent e27d33f commit dfa1875
Show file tree
Hide file tree
Showing 10 changed files with 464 additions and 145 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions libwebauthn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ tungstenite = { version = "0.20.1" }
tokio-tungstenite = { version = "0.20.1", features = [
"rustls-tls-native-roots",
] }
tokio-stream = "0.1.4"
snow = { path = "../snow", features = ["p256"] }

[dev-dependencies]
Expand Down
23 changes: 19 additions & 4 deletions libwebauthn/examples/webauthn_cable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,8 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
Box::new(EphemeralDeviceInfoStore::default());

// Create QR code
let mut device = CableQrCodeDevice::new_persistent(
QrCodeOperationHint::MakeCredential,
&mut device_info_store,
);
let mut device: CableQrCodeDevice<'_> =
CableQrCodeDevice::new_transient(QrCodeOperationHint::MakeCredential);

println!("Created QR code, awaiting for advertisement.");
let qr_code = QrCode::new(device.qr_code.to_string()).unwrap();
Expand Down Expand Up @@ -105,6 +103,23 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
timeout: TIMEOUT,
};

// Create QR code
let mut device: CableQrCodeDevice<'_> =
CableQrCodeDevice::new_transient(QrCodeOperationHint::GetAssertionRequest);

println!("Created QR code, awaiting for advertisement.");
let qr_code = QrCode::new(device.qr_code.to_string()).unwrap();
let image = qr_code
.render::<unicode::Dense1x2>()
.dark_color(unicode::Dense1x2::Light)
.light_color(unicode::Dense1x2::Dark)
.build();
println!("{}", image);

// Connect to a known device
let mut channel: CableChannel = device.channel().await.unwrap();
println!("Tunnel established {:?}", channel);

let response = loop {
match channel
.webauthn_get_assertion(&get_assertion, &pin_provider)
Expand Down
17 changes: 12 additions & 5 deletions libwebauthn/src/ops/u2f.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::time::Duration;

use byteorder::{BigEndian, WriteBytesExt};
use ctap_types::cose;
use serde_bytes::ByteBuf;
use serde_cbor::to_vec;
use sha2::{Digest, Sha256};
use tracing::{error, trace};
use x509_parser::nom::AsBytes;
use ctap_types::cose;

use super::webauthn::MakeCredentialRequest;
use crate::ops::webauthn::{GetAssertionResponse, MakeCredentialResponse};
Expand Down Expand Up @@ -70,14 +70,14 @@ impl UpgradableResponse<MakeCredentialResponse, MakeCredentialRequest> for Regis
.expect("Not the identity point")
.as_bytes(),
)
.unwrap();
.unwrap();
let y: heapless::Vec<u8, 32> = heapless::Vec::from_slice(
encoded_point
.y()
.expect("Not identity nor compressed")
.as_bytes(),
)
.unwrap();
.unwrap();
let cose_public_key = cose::PublicKey::P256Key(cose::P256PublicKey {
x: x.into(),
y: y.into(),
Expand Down Expand Up @@ -148,6 +148,9 @@ impl UpgradableResponse<MakeCredentialResponse, MakeCredentialRequest> for Regis
format: String::from("fido-u2f"),
authenticator_data: ByteBuf::from(auth_data),
attestation_statement: attestation_statement,
enterprise_attestation: None,
large_blob_key: None,
unsigned_extension_output: None,
})
}
}
Expand All @@ -161,7 +164,7 @@ impl UpgradableResponse<GetAssertionResponse, SignRequest> for SignResponse {
// See also Authenticator Data section of [WebAuthn].
let mut flags: u8 = 0;
flags |= 0b00000001; // up always set
// bit 1 is unused, ignoring
// bit 1 is unused, ignoring

// Let signCount be a 4-byte unsigned integer initialized with CTAP1/U2F response counter field.
let sign_count = self.counter;
Expand Down Expand Up @@ -189,8 +192,12 @@ impl UpgradableResponse<GetAssertionResponse, SignRequest> for SignResponse {
user: None,
credentials_count: None,
user_selected: None,
large_blob_key: None,
unsigned_extension_outputs: None,
enterprise_attestation: None,
attestation_statement: None,
}
.into();
.into();

trace!(?upgraded_response);
Ok(upgraded_response)
Expand Down
2 changes: 1 addition & 1 deletion libwebauthn/src/proto/ctap2/cbor/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::proto::ctap2::model::Ctap2CommandCode;
use crate::proto::ctap2::model::Ctap2GetAssertionRequest;
use crate::proto::ctap2::model::Ctap2MakeCredentialRequest;

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct CborRequest {
pub command: Ctap2CommandCode,
pub encoded_data: Vec<u8>,
Expand Down
11 changes: 10 additions & 1 deletion libwebauthn/src/proto/ctap2/cbor/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ use std::convert::{TryFrom, TryInto};
use std::io::{Error as IOError, ErrorKind as IOErrorKind};
use tracing::error;

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct CborResponse {
pub status_code: CtapError,
pub data: Option<Vec<u8>>,
}

impl CborResponse {
pub fn new_success_from_slice(slice: &[u8]) -> Self {
Self {
status_code: CtapError::Ok,
data: Some(slice.to_vec()),
}
}
}

impl TryFrom<&Vec<u8>> for CborResponse {
type Error = IOError;
fn try_from(packet: &Vec<u8>) -> Result<Self, Self::Error> {
Expand Down
33 changes: 33 additions & 0 deletions libwebauthn/src/proto/ctap2/model.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::io::Cursor as IOCursor;

use byteorder::{BigEndian, ReadBytesExt};
use ctap_types::ctap2::make_credential::AttestationStatement;
use futures::future::Map;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use serde_bytes::ByteBuf;
use serde_cbor::Value;
use serde_derive::{Deserialize, Serialize};
use serde_indexed::{DeserializeIndexed, SerializeIndexed};
use serde_repr::{Deserialize_repr, Serialize_repr};
Expand Down Expand Up @@ -279,12 +283,20 @@ pub struct TpmAttestationStmt {
pub public_area: ByteBuf,
}

#[derive(Debug, Clone, Deserialize)]
pub struct AppleAnonymousAttestationStmt {
#[serde(rename = "x5c")]
pub certificates: Vec<ByteBuf>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum Ctap2AttestationStatement {
PackedOrAndroid(PackedAttestationStmt),
Tpm(TpmAttestationStmt),
FidoU2F(FidoU2fAttestationStmt),
AppleAnonymous(AppleAnonymousAttestationStmt),
None(BTreeMap<Value, Value>),
}

// https://www.w3.org/TR/webauthn/#authenticatormakecredential
Expand Down Expand Up @@ -368,6 +380,15 @@ pub struct Ctap2MakeCredentialResponse {
pub format: String,
pub authenticator_data: ByteBuf,
pub attestation_statement: Ctap2AttestationStatement,

#[serde(skip_serializing_if = "Option::is_none")]
pub enterprise_attestation: Option<bool>,

#[serde(skip_serializing_if = "Option::is_none")]
pub large_blob_key: Option<ByteBuf>,

#[serde(skip_serializing_if = "Option::is_none")]
pub unsigned_extension_output: Option<BTreeMap<Value, Value>>,
}

// https://www.w3.org/TR/webauthn/#op-get-assertion
Expand Down Expand Up @@ -435,6 +456,18 @@ pub struct Ctap2GetAssertionResponse {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub user_selected: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub large_blob_key: Option<ByteBuf>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub unsigned_extension_outputs: Option<BTreeMap<Value, Value>>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub enterprise_attestation: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub attestation_statement: Option<Ctap2AttestationStatement>,
}

pub trait Ctap2UserVerifiableRequest {
Expand Down
Loading

0 comments on commit dfa1875

Please sign in to comment.