webauthn-rs
webauthn-rs copied to clipboard
No getTransports when attesting a security key
I was told that getTransports
was recently added to the master
branch; however I don't believe that is true. I clone
d master
just yesterday (i.e., after the comment was made), and I am still seeing null
for transports
in the JSON:
{"cred":{"cred_id":"bDtrPzJpvN4FLrD3Cc5TI8r_B4do93OpnjWwsQpd1zrFttEnyaAt7BPG9etzE3pU_QdM9F3gsPJ6diRLJ_BjXw","cred":{"type_":"ES256","key":{"EC_EC2":{"curve":"SECP256R1","x":"CNXexDRYjAW6xDTMnMweFfn2wvuclOx-qF6fvxEG0rY","y":"_pZlbgMvZUOpDDzsjd5-JujbjCVbexQJPMNd9K_1SGY"}}},"counter":2,"transports":null,"user_verified":true,"backup_eligible":false,"backup_state":false,"registration_policy":"preferred","extensions":{"cred_protect":"Ignored","hmac_create_secret":"NotRequested","appid":"NotRequested","cred_props":"Ignored"},"attestation":{"data":{"Basic":["MIIC2DCCAcCgAwIBAgIJAPkeEJBFzRXcMA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNVBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgwMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjBuMQswCQYDVQQGEwJTRTESMBAGA1UECgwJWXViaWNvIEFCMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMScwJQYDVQQDDB5ZdWJpY28gVTJGIEVFIFNlcmlhbCAyNjk0OTE5NjEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATxBlFac6ZCOTaesMSVQq-qRj_pCX5LiTu2t9QhAnsXkanB0XQM4d_lfJW_YIlWvegwqiDVb9IFIBOHWC5patxso4GBMH8wEwYKKwYBBAGCxAoNAQQFBAMFBAMwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjcwEwYLKwYBBAGC5RwCAQEEBAMCBSAwIQYLKwYBBAGC5RwBAQQEEgQQ7ogoeXIcSROXdT38zpcHKjAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCocCl0EQ8Ke0ySq5k4TyiCmDNWPz8KRz8lg4Gpliwg4KuWfK3S7klIfaqDpEkYUtlYsZPfckEL-_0u0ldjNzG63NwmEmFmKmAA1fVYRRSTfhiY6eIWeikeEajFkatlckQ7apazTTxCx2oO53B7PoGVzznPb1BcUKyCClGzrJg703nbNuwxcnGx6A6ctOomTC3InptvVPkVsC7ekYiTgAPanPJw0jCDSjPEaLVVh3Y7U79siV9fGFoazCoCwDpJ0AusKEZ-9HgZ9nx-zBUaHyPgiLetfCIh6y-yX68Jhc1QaWXOfFro81TLwc3kzKQe83B16eILycdxFRZWOROW2YpK"]},"metadata":{"Packed":{"aaguid":"ee882879-721c-4913-9775-3dfcce97072a"}}},"attestation_format":"Packed"}}}
Here is a snippet of the application that registers the key:
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct EnableWebauthnData {
id: u32,
name: String,
device_response: RegisterPublicKeyCredential,
master_password_hash: String,
}
fn build_webauthn() -> Result<Webauthn, WebauthnError> {
WebauthnBuilder::new(
config::get_config()
.domain
.domain()
.expect("a valid domain"),
&Url::parse(&config::get_config().domain_origin()).expect("a valid URL"),
)?
.build()
}
#[post("/two-factor/get-webauthn-challenge", data = "<data>")]
async fn generate_webauthn_challenge(
data: JsonUpcase<PasswordOrOtpData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
let data: PasswordOrOtpData = data.into_inner().data;
let user = headers.user;
data.validate(&user, false, &conn).await?;
let mut ca_builder = webauthn_rs::prelude::AttestationCaListBuilder::new();
// We only allow YubiKeys with firmware 5.2 or 5.4.
ca_builder
.insert_device_pem(
b"-----BEGIN CERTIFICATE-----
MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ
dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw
MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290
IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk
5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep
8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw
nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT
9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw
LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ
hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN
BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4
MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt
hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k
LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U
sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc
U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==
-----END CERTIFICATE-----"
.as_slice(),
Uuid::try_parse("ee882879-721c-4913-9775-3dfcce97072a").expect("invaild UUID"),
String::from("YubiKey 5"),
alloc::collections::BTreeMap::new(),
)
.expect("unable to insert YubiKey 5C firwmare 5.2 and 5.4 attestation");
let (challenge, registration) = build_webauthn()?.start_securitykey_registration(
Uuid::try_parse(user.uuid.as_str()).expect("unable to create UUID"),
user.email.as_str(),
user.name.as_str(),
Some(WebAuthn::get_all_credentials_by_user(&user.uuid, &conn).await?),
Some(ca_builder.build()),
Some(AuthenticatorAttachment::CrossPlatform),
)?;
// We replace any existing registration challenges.
TwoFactor::new(
user.uuid,
TwoFactorType::WebauthnRegisterChallenge,
serde_json::to_string(®istration)?,
)
.replace_challenge(&conn)
.await?;
let mut challenge_value = serde_json::to_value(challenge.public_key)?;
challenge_value["status"] = "ok".into();
challenge_value["errorMessage"] = "".into();
Ok(Json(challenge_value))
}
#[post("/two-factor/webauthn", data = "<data>")]
async fn activate_webauthn(
data: Json<EnableWebauthnData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
let data = data.into_inner();
let user = headers.user;
PasswordOrOtpData {
MasterPasswordHash: Some(data.master_password_hash),
Otp: None,
}
.validate(&user, true, &conn)
.await?;
// Retrieve and delete the saved challenge state
let tf_challenge = get_tf_entry(
&user.uuid,
i32::from(TwoFactorType::WebauthnRegisterChallenge),
&conn,
)
.await
.ok_or_else(|| Error::from(String::from("no webauthn challenge")))?;
let registration = serde_json::from_str::<SecurityKeyRegistration>(&tf_challenge.data)?;
tf_challenge.delete_challenge(&conn).await?;
// Verify the credentials with the saved state
let security_key =
build_webauthn()?.finish_securitykey_registration(&data.device_response, ®istration)?;
let cred_id = security_key.cred_id().to_string();
let regs = match get_tf_entry(&user.uuid, i32::from(TwoFactorType::Webauthn), &conn).await {
None => {
let regs = vec![WebauthnRegistration {
id: data.id,
name: data.name,
security_key,
}];
let tf = TwoFactor::new(
user.uuid,
TwoFactorType::Webauthn,
serde_json::to_string(®s)?,
);
tf.insert_insert_webauthn(tf.create_webauthn(cred_id), &conn)
.await?;
regs
}
Some(mut tf) => {
let mut regs = tf.get_webauthn_registrations()?;
regs.push(WebauthnRegistration {
id: data.id,
name: data.name,
security_key,
});
tf.data = serde_json::to_string(®s)?;
tf.update_insert_webauthn(tf.create_webauthn(cred_id), &conn)
.await?;
regs
}
};
Ok(Json(json!({
"Enabled": true,
"Keys": regs.iter().map(WebauthnRegistration::to_json).collect::<Value>(),
"Object": "twoFactorU2f"
})))
}
I am saving the raw JSON payload to the database. Registration succeeds, and the payload is mostly correct. Can someone point to me where this was allegedly added, so I can help debug the issue? Perhaps this is a browser problem?
System information
[zack@laptop ~]$ uname -a
Linux laptop 6.6.8-arch1-1 #1 SMP PREEMPT_DYNAMIC Thu, 21 Dec 2023 19:01:01 +0000 x86_64 GNU/Linux
[zack@laptop ~]$ openssl version
OpenSSL 3.2.0 23 Nov 2023 (Library: OpenSSL 3.2.0 23 Nov 2023)
OS: Arch Linux Browser: Firefox 121.0 Client: Vaultwarden's patch to Bitwarden's web-vault v2023.12.0