#webauthn #fido2 #ctap #passwordless

passkey

实现Passkey客户端和验证器的多功能库

3个版本 (重大更新)

0.2.0 2023年12月14日
0.1.0 2023年7月28日
0.0.1 2023年2月8日

身份验证中排名第219

Download history 58/week @ 2024-04-22 62/week @ 2024-04-29 56/week @ 2024-05-06 8/week @ 2024-05-13 8/week @ 2024-05-20 47/week @ 2024-06-03 35/week @ 2024-06-10 88/week @ 2024-06-17 64/week @ 2024-06-24 32/week @ 2024-07-01 41/week @ 2024-07-29 185/week @ 2024-08-05

每月下载226

MIT/Apache

335KB
4.5K SLoC

1Password的Passkey-rs

github version documentation

passkey-rs库是一组Rust库,允许开发者通过全面实现Webauthn Level 3CTAP2标准,在Rust代码中使用Passkeys。它由五个子库组成

  • passkey-client - 一个库,可以作为client使用,实现了Webauthn Level 3标准,用于网站的身份验证。
  • passkey-authenticator - 一个库,可以作为authenticator使用,实现了CTAP2标准。
  • passkey-transports - 一个库,可以作为transports使用,实现了CTAP HID协议
  • passkey-types - 类型定义,可以作为types用于-client-authenticator库。
  • public-suffix - 一个库,根据Mozilla公共后缀列表,高效地确定给定URL的有效顶级域名。

为了了解如何使用此库,开发者应阅读Webauthn Level 3CTAP2标准。这些库中的许多类型命名直接引用了这些标准中的术语,熟悉这些术语将大大有助于您理解如何使用这些库。

本文档中的示例显示了某些值被假定为来自网站(依赖方)。这些库的范围不包括管理与依赖方的交互的详细信息。这些值和身份验证结果如何与依赖方通信是这些crate用户实现的一个细节。

基本概念

从概念上讲,使用Passkeys涉及接收注册新凭据的请求、存储这些凭据以及使用现有凭据进行身份验证。这里涉及到两个标准:Webauthn 是网站(一个“依赖方”)与您的应用程序通信的协议。 客户端到认证器协议(CTAP2) 是您的应用程序与认证器(可以是软件或FIDO2 USB密钥等硬件设备)通信的协议。

此库提供了Rust类型以实现这两个协议,以及一个可以用于替代许多用户熟悉的USB密钥的软件 Authenticator

您可以将这些库视为一个与依赖方交互的链条,如下所示

RelyingParty <-> Client <-> Authenticator <-> CredentialStore

Client 类型将选项和数据从依赖方进行序列化和反序列化。它提供了以下注册和身份验证的API

  • register() - 注册webauthn凭据。
  • authenticate() - 验证webauthn请求。

Client 本身不执行加密操作。相反,它依赖于一个内部的 Authenticator 来执行这些操作。

Authenticator 类型提供了一个虚拟认证器,可以使用以下函数创建新凭据并使用户进行身份验证

  • make_credential() - 创建一个凭据。
  • get_assertion() - 生成用户身份验证的加密证明

Authenticator 本身不存储凭据,而是依赖于一个实现 CredentialStore 特征的通用类型。此特征提供了存储和检索Passkey结构的API

  • save_credential() - 存储 make_credential() 中创建的凭据
  • find_credentials() - 在 CredentialStore 的内部存储中搜索可能用于身份验证的凭据

authenticator 库提供了 authenticator::CredentialStore 的各种实现,但库的用户可以提供自己的。

passkey/examples/usage.rs 中提供了一个可运行的演示二进制文件。

示例:使用Client类型进行Webauthn操作

这些库中最高级的类型是 passkey-client::Client。这是您将主要用于在应用程序中实现Webauthn身份验证的类型。

以下示例演示了如何创建一个 Client 并使用它来创建凭据。从头开始这样做涉及到创建一个 Authenticator 和一个 CredentialStore,然后将这些提供给 Client

在本例中,我们将手动创建一个名为 *_from_rpCredentialCreationOptions 结构体,并使用假设值来表示这些值通常是依赖方提供的。为了简化,这里大部分的 CredentialCreationOptions 都被设置为 None

use passkey::{
    authenticator::{Authenticator, UserValidationMethod},
    client::{Client, WebauthnError},
    types::{ctap2::*, webauthn::*, Bytes, Passkey},
};
use url::Url;

// First create an Authenticator for the Client to use.
let my_aaguid = Aaguid::new_empty();
let user_validation_method = MyUserValidationMethod {};
// Create the CredentialStore for the Authenticator.
// Option<Passkey> is the simplest possible implementation of CredentialStore
let store: Option<Passkey> = None;
let my_authenticator = Authenticator::new(my_aaguid, store, user_validation_method);

// Create the Client
// If you are creating credentials, you need to declare the Client as mut
let mut my_client = Client::new(my_authenticator);

// The following values, provided as parameters to this function would usually be
// retrieved from a Relying Party according to the context of the application.
let request = CredentialCreationOptions {
    public_key: PublicKeyCredentialCreationOptions {
        rp: PublicKeyCredentialRpEntity {
            id: None, // Leaving the ID as None means use the effective domain
            name: name_from_rp.clone(),
        },
        user: PublicKeyCredentialUserEntity {
            id: user_handle_from_rp,
            display_name: display_name_from_rp,
            name: name_from_rp,
        },
        challenge: challenge_bytes_from_rp,
        pub_key_cred_params: vec![parameters_from_rp],
        timeout: None,
        exclude_credentials: None,
        authenticator_selection: None,
        attestation: AttestationConveyancePreference::None,
        extensions: None,
    },
};

// Now create the credential.
let my_webauthn_credential: CreatedPublicKeyCredential = my_client.register(origin, request).await?;

上述示例展示了如何创建 Webauthn 证书。现在,我们可以尝试对用户进行身份验证。

let challenge_bytes_from_rp: Bytes = random_vec(32).into();
// Now try and authenticate
let credential_request = CredentialRequestOptions {
    public_key: PublicKeyCredentialRequestOptions {
        challenge: challenge_bytes_from_rp,
        timeout: None,
        rp_id: Some(String::from(origin.domain().unwrap())),
        allow_credentials: None,
        user_verification: UserVerificationRequirement::default(),
        extensions: None,
    },
};

let authenticated_cred: AuthenticatedPublicKeyCredential = my_client
    .authenticate(origin, credential_request, None)
    .await?;

示例:使用 CTAP2 操作的认证器

以下代码提供了一个基本示例,说明如何仅使用 Authenticator 来创建和存储证书。

// Option<Passkey> is the simplest possible implementation of CredentialStore
let store: Option<Passkey> = None;
let user_validation_method = MyUserValidationMethod {};
let my_aaguid = Aaguid::new_empty();

let mut my_authenticator = Authenticator::new(my_aaguid, store, user_validation_method);

let reg_request = make_credential::Request {
    client_data_hash: client_data_hash_from_rp.clone(),
    rp: make_credential::PublicKeyCredentialRpEntity {
        id: tld_from_rp.clone(),
        name: None,
    },
    user: PublicKeyCredentialUserEntity {
        id: user_handle_from_rp,
        display_name: display_name_from_rp,
        name: name_from_rp,
    },
    pub_key_cred_params: vec![algorithms_from_rp],
    exclude_list: None,
    extensions: None,
    options: make_credential::Options::default(),
    pin_auth: None,
    pin_protocol: None,
};

let credential: make_credential::Response =
    my_authenticator.make_credential(reg_request).await?;

证书在创建过程中作为 AuthenticatorCredentialStore 部分存储。现在,我们可以直接使用此证书进行身份验证。

let auth_request = get_assertion::Request {
    rp_id: tld_from_rp,
    client_data_hash: client_data_hash_from_rp,
    allow_list: None,
    extensions: None,
    options: make_credential::Options::default(),
    pin_auth: None,
    pin_protocol: None,
};

let response = my_authenticator.get_assertion(auth_request).await?;

详细说明

Passkey-Types

Passkey-types 为其他包提供了类型定义。可能会令人困惑的是,有时似乎相同的类型有两个变体。例如,我们有 webauthn::PublicKeyCredentialRpEntityctap2::PublicKeyCredentialRpEntity。这是因为不同的协议对哪些字段必须存在以及哪些是可选的有不同的要求。在 CTAP2 结构与 Webauthn 相同的情况下,CTAP2 库使用 Webauthn 类型。

Public-Suffix

Public-Suffix 包提供了一个高效的库,用于确定给定 URL 的有效顶级域名或“公共后缀”。大多数包用户不需要直接与这个库交互。它被 passkey-client::Client 类型内部使用。

但是,根据上下文,某些应用程序可能需要以不同的方式解释某些顶级域名。为此,用户可以重新实现这个包中的公共后缀列表,或提供替代实现。然后,用户应该实现 public-suffix::EffectiveTLDProvider 特性,并将该实现提供给 Client::new_with_custom_tld_provider()。如何做到这一点的示例可以在 passkey_client::tests 中的 validate_domain_with_private_list_provider() 测试中找到。有关如何根据应用程序的需求生成自定义 TLD 列表的更多信息,请参阅 public-suffix 中的 README。

当前限制

  • Client::authenticate()Client::register() 不尊重在 CredentialRequestOptions 中设置的超时值。
  • Client 总是报告其 认证器附件模式 为“平台”
  • Client 只支持 认证器传输 的“内部”类型
  • 当前,Authenticator 仅支持 ECDSA w/SHA-256 算法

贡献和反馈

passkey-rs 是一个 开源项目

🐛 如果您发现了一个想要报告的问题,或者有其他反馈,请提交一个新的问题

🧑‍💻 如果您想为代码做出贡献,请先通过问题提交或评论,以便我们跟踪工作。

致谢

由1Password团队用❤️和☕制作。

为您的开源项目获取免费的1Password账户

您的团队需要一种安全的方式来管理开源项目的密码和其他凭证吗?请访问我们的其他存储库,免费获取1Password Teams账户

1Password for Open Source Projects

许可证

根据您的选择,在Apache License, Version 2.0MIT许可证下授权。
除非您明确表示,否则您提交的任何旨在包含在此包中的贡献,根据Apache-2.0许可证定义,应如上双许可,不附加任何额外条款或条件。

依赖项

~7–14MB
~221K SLoC