<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>wind (wang)</title>
    <link>https://yubikey.cn/wind</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Passkey:具有可自定义接口的 AAL3 强安全性</title>
      <description>&lt;p&gt;苹果一年前在 WWDC2022 中引入了 Passkey。这具有突破性意义，因为它允许将 FIDO 凭证中的私钥存储在 iCloud 密钥链中，并方便它在设备之间传播。虽然它使 FIDO 凭证易于使用，并解决了引导问题 (用户从新设备登录网站/应用程序),但它带来了一个安全缺点，即私钥不再存储在硬件 TPM 模块中。由于这个缺点，根据 NIST,Passkey 只能被分类为提供 AAL2 级保证。&lt;/p&gt;

&lt;p&gt;下图显示了常见认证因素之间的安全强度权衡。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://cdn.yubikey.cn/photo/wind/f99af855-6a61-41b4-8726-44df1a96f78f.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;要达到最高安全级别，我们需要利用硬件设备。不幸的是，苹果和谷歌目前的 Passkey 实现并没有给开发者一个选择 TPM 模块的选项。&lt;/p&gt;

&lt;p&gt;如果你真的想要很高的安全性保证怎么办？你显然可以使用物理安全密钥如 Yubikey，但这两者都昂贵且难以使用。这篇文章展示了如何利用 iOS 平台提供的原语来从零开始构建 FIDO 解决方案，利用内置的 TPM(也称为安全令牌)。&lt;/p&gt;

&lt;p&gt;FIDO 背后的概念很简单。它利用公钥密码学，用户使用私钥对服务器的 nonce 进行签名，并证明其拥有私钥。在本文中，我们将演示如何使用原生 iOS API 来实现相同的认证流程。这不是 FIDO 协议的完整实现，但由于篇幅限制，它只关注私钥和公钥部分。但是如果你选择的话可以在这个例子的基础上扩展。&lt;/p&gt;

&lt;p&gt;我们将使用 iOS 中的 Local Authentication 框架，它将生成一个 LAPublicKey 和 LAPrivateKey 对，存储在 TPM 模块中。然后，我们将演示如何使用 LAPrivateKey 进行签名，以及如何使用 LAPublicKey 验证签名。&lt;/p&gt;
&lt;h2 id="注册时的凭证注册"&gt;注册时的凭证注册&lt;/h2&gt;
&lt;p&gt;首先，我们演示如何在注册过程中生成密钥对。我们利用 LARightStore，它会在安全区域中存储由独特密钥加密的 LAPersistedRight。我们创建一个 generateClientKeys() 函数来捕获全部逻辑。首先，我们初始化一个 LARight(),它是“一组控制对资源或操作的访问要求的集合”。当我们调用 LARightStore.shared.saveRight() 时，会生成一个密钥对，并持久化密钥和权限，并返回一个 LAPersistedRight。我们可以通过调用 persistedRight.key.publicKey 获取新生成的密钥的公钥引用。这个公钥会被返回，以便调用者可以在服务器端持久化这个公钥用于未来验证。&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 生成密钥对&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;generateClientKeys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LARight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// 如果之前生成过密钥,在生成新密钥对前清除&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;LARightStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeRight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"fido-key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

  &lt;span class="c1"&gt;// 生成新密钥对&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;persistedRight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;LARightStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;saveRight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;keyIdentifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;persistedRight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publicKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，在 saveRight() 之前我们也调用了 LARightStore.shared.removeRight()。这是为了在相同标识符下如果之前保存了旧密钥的话删除它。&lt;/p&gt;
&lt;h2 id="认证"&gt;认证&lt;/h2&gt;
&lt;p&gt;在注册之后，当用户再次回到你的应用并需要登录时，我们需要通过认证流程来验证用户。下面的代码是一个简化的 FIDO 流程。首先，按照 FIDO 协议，我们需要调用服务器获取一个 nonce。nonce 用于防止重放攻击。然后，我们调用下面的函数对 nonce 进行签名。&lt;/p&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;signServerChallenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;persistedRight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;LARightStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;right&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"fido-key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;persistedRight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;localizedReason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Authenticating..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// 验证我们可以签名&lt;/span&gt;
  &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;persistedRight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canSign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ecdsaSignatureMessageX962SHA256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kt"&gt;NSError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"SampleErrorDomain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[:])&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;persistedRight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ecdsaSignatureMessageX962SHA256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个函数先根据相同的标识符查找 LAPersistedRight。如果找到，它会请求用户授权使用该密钥，然后使用私钥对 nonce 进行签名。nonce 和它的签名应该发送到服务器进行验证。&lt;/p&gt;
&lt;h2 id="示例代码和演示"&gt;示例代码和演示&lt;/h2&gt;
&lt;p&gt;FIDO in TPM 项目是一个演示项目，结合了上述代码片段，演示了注册和认证流程的用户界面。你也可以观看这个视频演示以查看注册和认证的用户体验。&lt;/p&gt;
&lt;h2 id="使用该解决方案的时机"&gt;使用该解决方案的时机&lt;/h2&gt;
&lt;p&gt;为什么你不使用平台提供的原生 WebAuthn API(iOS 上的 Authentication Services API 或 Android 上的 Credential Manager API)?有几个原因使用本文概述的自制解决方案：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;强安全性 (高达 AAL3 级)。该解决方案将允许你使用内置的平台认证器 (TouchID 或 FaceID) 提供强大的安全性保证，而无需购买独立的安全密钥 (如 Yubikey)。它既安全又易于使用，因为你不需要携带额外的硬件。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;更可定制的 UX 和 UI。如演示视频所示，注册体验已经大大简化，甚至可以无需用户交互就默默完成。认证体验也更简单，有很大的定制空间。特别是，你可以选择对最终用户提及“生物识别”或其他更熟悉的术语，因为大多数用户都没有听说过 Passkey。这为你提供了灵活性，如果你不想将功能营销为 Passkey。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;不需要绑定到网站。WebAuthn 是一个遵循 FIDO 标准的 Web API。为避免钓鱼攻击，WebAuthn 要求凭证绑定到 web 域。当 iOS 和 Android 引入等效的原生 API 时，它们遵循了这种设计，要求你的应用通过通用链接绑定到 web 域。但你的应用可能没有 web 存在。此解决方案可节省设置网站和配置通用链接的麻烦。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="结论"&gt;结论&lt;/h2&gt;
&lt;p&gt;与浏览器内部仅限于 WebAuthn API 不同，在原生应用中你可以利用各种 API。如果你不想使用平台提供的 FIDO API，你实际上可以自己实现 FIDO 协议。希望这篇文章为你奠定了基础，如果你想深入进行更多定制化和更强的安全性保证。&lt;/p&gt;

&lt;p&gt;注：本文也发表在 Passkey: AAL3 Strong Security with a Customizable Interface
来自：&lt;a href="https://drhuanliu.medium.com/passkey-aal3-strong-security-with-a-customizable-interface-7dfdca6cb557" rel="nofollow" target="_blank"&gt;https://drhuanliu.medium.com/passkey-aal3-strong-security-with-a-customizable-interface-7dfdca6cb557&lt;/a&gt;&lt;/p&gt;</description>
      <author>wind</author>
      <pubDate>Fri, 14 Jul 2023 13:46:49 +0800</pubDate>
      <link>https://yubikey.cn/topics/27</link>
      <guid>https://yubikey.cn/topics/27</guid>
    </item>
  </channel>
</rss>
