FIDO2 .NET 库 (WebAuthn)
使用 .NET 的 FIDO2 和 WebAuthn 的工作实现库 + 演示 发布 & 更新日志
💡 无密码 API 现已可用!
开始使用 FIDO2 和 WebAuthn 的最快方法是使用 无密码 API。在决定使用此库实现自己的服务器之前,可以免费试用和开始。
目的
我们的目的是为所有 .NET 应用(asp、core、原生)启用无密码登录。为开发人员提供友好且经过充分测试的 .NET FIDO2 服务器 / WebAuthn 依赖方 库,以便轻松验证 FIDO2 / WebAuthn 凭证的 注册(证明)和 认证(断言),以增加该技术的采用率,最终击败钓鱼攻击。
该项目是 .NET foundation 的一部分
Install-Package Fido2
要使用 asp.net 助手,请安装 asp.net 包。
Install-Package Fido2.AspNet
演示
什么是 FIDO2?
无密码网络即将到来。 FIDO2 / WebAuthn 是一种新的开放认证标准,得到 浏览器 和 许多大型科技公司 如微软、谷歌等的支持。其主要目的是允许用户无需密码登录,为网站上的用户注册/登录创建无密码流程或强大的 MFA。该标准不仅限于网络应用,还将支持 Active Directory 和原生应用。该技术基于公钥/私钥,允许在不在用户和平台之间共享秘密的情况下进行身份验证。这带来了许多好处,如更简单和安全的登录,并使钓鱼尝试变得极其困难。
阅读更多:
- 为什么令人兴奋
- Medium
- FIDO 联盟
- Yubico
- WebAuthn.Guide 来自 Duo Security
- WebAuthn.io
- WebAuthn Awesome
支持的功能
- ✅ 证明 API 和验证(注册和验证凭证/认证器)
- ✅ 断言 API 和验证(认证用户)
- ✅ 一致性测试 100% 通过率(结果)
- ✅ FIDO2 安全密钥又称漫游认证器(规范),如 SoloKeys Solo、Yubico YubiKey 和 Feitian BioPass FIDO2)
- ✅ 设备嵌入式认证器又称平台认证器(规范),如 Android Key 和 TPM)
- ✅ 向后兼容 FIDO U2F 认证器(规范)
- ✅ Windows Hello
- ✅ Face ID 和 Touch ID for the Web(又称"Apple Hello")
- ✅ FIDO2 服务器的所有当前引用的加密算法(规范)
- ✅ 所有当前证明格式:"packed"、"tpm"、"android-key"、"android-safetynet"、"fido-u2f"、"apple"、"apple-appattest" 和 "none"(规范)
- ✅ 通过 FIDO 元数据服务 V3 进行 FIDO2 服务器证明验证(规范)
- ✅ WebAuthn 扩展(规范)
- ✅ 示例和演示
- ✅ Intellisense 文档
- 💤 正式文档
- 💤 推荐的 使用模式
配置
这里只列出了一些选项,所有选项请参见 Configuration 类
fido2:MDSCacheDirPath
- 设置 MDS 缓存路径的应用秘密/环境变量。默认为"当前用户的临时文件夹"/fido2mdscache。使用默认 MetadataService 提供程序 时可选。
示例
有关如何将此库与本地 Active Directory 集成的想法,请参见 Active Directory 存储信息 和 示例凭证存储。
创建证明选项
要将 FIDO2 凭证添加到现有用户帐户,我们执行证明过程。它从向客户端返回选项开始。
// 文件:Controller.cs
// 1. 根据用户名从数据库获取用户(在我们的示例中,自动创建缺失的用户)
var user = DemoStorage.GetOrAddUser(username, () => new User
{
DisplayName = "Display " + username,
Name = username,
Id = Encoding.UTF8.GetBytes(username) // 需要用户ID的字节表示
});
// 2. 根据用户名获取用户现有的密钥
List<PublicKeyCredentialDescriptor> existingKeys = DemoStorage.GetCredentialsByUser(user).Select(c => c.Descriptor).ToList();
// 3. 创建选项
var options = _lib.RequestNewCredential(user, existingKeys, AuthenticatorSelection.Default, AttestationConveyancePreference.Parse(attType));
// 4. 临时存储选项,会话/内存缓存/redis/数据库
HttpContext.Session.SetString("fido2.attestationOptions", options.ToJson());
// 5. 将选项返回给客户端
return Json(options);
注册凭证
当客户端返回响应时,我们验证并注册凭证。
// 文件:Controller.cs
// 1. 获取我们发送给客户端的选项并从存储中删除
var jsonOptions = HttpContext.Session.GetString("fido2.attestationOptions");
HttpContext.Session.Remove("fido2.attestationOptions");
var options = CredentialCreateOptions.FromJson(jsonOptions);
// 2. 创建回调,以便库可以验证凭证ID对该用户是唯一的
IsCredentialIdUniqueToUserAsyncDelegate callback = async (IsCredentialIdUniqueToUserParams args) =>
{
List<User> users = await DemoStorage.GetUsersByCredentialIdAsync(args.CredentialId);
if (users.Count > 0) return false;
return true;
};
// 2. 验证并创建凭证
var success = await _lib.MakeNewCredentialAsync(attestationResponse, options, callback);
// 3. 将凭证存储在数据库中
DemoStorage.AddCredentialToUser(options.User, new StoredCredential
{
Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId),
PublicKey = success.Result.PublicKey,
UserHandle = success.Result.User.Id
});
// 4. 向客户端返回"ok"
return Json(success);
创建断言选项
当用户想要登录时,我们基于注册的凭证进行断言。
首先我们创建断言选项并返回给客户端。
// 文件:Controller.cs
// 1. 从数据库获取用户
var user = DemoStorage.GetUser(username);
if (user == null) return NotFound("用户名未注册");
// 2. 从数据库获取已注册的凭证
List<PublicKeyCredentialDescriptor> existingCredentials = DemoStorage.GetCredentialsByUser(user).Select(c => c.Descriptor).ToList();
// 3. 创建选项
var options = _lib.GetAssertionOptions(
existingCredentials,
UserVerificationRequirement.Discouraged
);
// 4. 临时存储选项,会话/内存缓存/redis/数据库
HttpContext.Session.SetString("fido2.assertionOptions", options.ToJson());
// 5. 将选项返回给客户端
return Json(options);
验证断言响应
当客户端返回响应时,我们验证它并接受登录。
// 1. 获取我们发送给客户端的断言选项并从存储中删除
var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
HttpContext.Session.Remove("fido2.assertionOptions");
var options = AssertionOptions.FromJson(jsonOptions);
// 2. 从数据库获取已注册的凭证
StoredCredential creds = DemoStorage.GetCredentialById(clientResponse.Id);
// 3. 从数据库获取凭证计数器
var storedCounter = creds.SignatureCounter;
// 4. 创建回调以检查用户句柄是否拥有凭证ID
IsUserHandleOwnerOfCredentialIdAsync callback = async (args) =>
{
List<StoredCredential> storedCreds = await DemoStorage.GetCredentialsByUserHandleAsync(args.UserHandle);
return storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId));
};
// 5. 进行断言
var res = await _lib.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback);
// 6. 存储更新后的计数器
DemoStorage.UpdateCounter(res.CredentialId, res.Counter);
// 7. 向客户端返回OK
return Json(res);
Nuget包
https://www.nuget.org/packages/Fido2/ 和 https://www.nuget.org/packages/Fido2.Models/
贡献
有关项目贡献的信息,请参阅贡献。
本项目采用了《贡献者公约》中定义的行为准则,以明确我们社区中的预期行为。 欲了解更多信息,请参阅.NET Foundation行为准则。
关于安全和渗透测试,请查看我们的漏洞披露计划
贡献者
代码贡献者
本项目的存在要感谢所有做出贡献的人。[贡献]。
财务贡献者
成为财务贡献者并帮助我们维持我们的社区。[贡献]
个人
组织
支持此项目的组织。您的徽标将显示在此处,并链接到您的网站。[贡献]
.NET Foundation
本项目得到.NET Foundation的支持。