Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No getTransports when attesting a security key #403

Open
zacknewman opened this issue Dec 28, 2023 · 3 comments
Open

No getTransports when attesting a security key #403

zacknewman opened this issue Dec 28, 2023 · 3 comments

Comments

@zacknewman
Copy link

zacknewman commented Dec 28, 2023

I was told that getTransports was recently added to the master branch; however I don't believe that is true. I cloned 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(&registration)?,
    )
    .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, &registration)?;
    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(&regs)?,
            );
            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(&regs)?;
            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

@zacknewman
Copy link
Author

Perhaps I am looking at the wrong thing, but getTransports appears to not be extracted from web_sys::PublicKeyCredential.

@stefan0xC

This comment was marked as outdated.

@Firstyear
Copy link
Member

I just submitted the updated idl to web-sys for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants