JS `WebAuthn` `Attestation` `FIDO Metadata Service` (MDS) `Trust Anchors`

嘿,大家好!欢迎来到今天的“WebAuthn、Attestation、FIDO Metadata Service (MDS) 和 Trust Anchors:一场身份认证的狂欢”讲座!准备好迎接一波代码和概念轰炸了吗?

一、WebAuthn:无密码认证的派对入场券

WebAuthn,全称 Web Authentication API,是 W3C 推出的一个标准,旨在让用户摆脱对密码的依赖,拥抱更安全、更便捷的身份验证方式。你可以把它想象成一个超级VIP通行证,让你轻松进入各种网站和应用,还不用担心密码泄露的烦恼。

1.1 WebAuthn 的工作流程:一段浪漫的握手

WebAuthn 的核心在于公钥加密。简单来说,就是你的设备(比如手机、指纹识别器、安全密钥)会生成一对密钥:一个公钥和一个私钥。公钥会交给网站,私钥则安全地保存在你的设备里。

  • 注册 (Registration):

    • 网站发起注册请求,告诉你的浏览器:“嘿,我想让你用 WebAuthn 注册一下。”
    • 浏览器会提示你选择一个身份验证器(比如指纹识别器)。
    • 身份验证器会生成密钥对,并将公钥返回给浏览器。
    • 浏览器将公钥发送给网站,网站保存这个公钥,并与你的用户账号关联起来。
    // 客户端代码 (注册)
    async function register() {
      const options = {
        publicKey: {
          challenge: new Uint8Array([/* 随机 challenge,服务器生成 */]),
          rp: {
            name: "Your Website" // Relying Party (网站) 名称
          },
          user: {
            id: new Uint8Array([/* 用户ID */]),
            name: "[email protected]",
            displayName: "Awesome User"
          },
          pubKeyCredParams: [
            { type: "public-key", alg: -7 } // 算法 -7 代表 ES256
          ],
          attestation: "direct" // 或者 "indirect" 或 "none"
        }
      };
    
      try {
        const credential = await navigator.credentials.create({
          publicKey: options.publicKey
        });
    
        // 将 credential.response.attestationObject, credential.response.clientDataJSON, credential.rawId 发送给服务器
        console.log(credential);
        return credential;
    
      } catch (error) {
        console.error("注册失败:", error);
      }
    }
    
    // 服务器端代码 (注册,简化版)
    async function handleRegistration(credential) {
        // 验证 challenge
        // 验证 origin
        // 验证 credential.response.clientDataJSON
        // 验证 attestationObject (非常重要,后面会详细讲)
        // 保存 credential.rawId 和 credential.response.attestationObject 里的公钥
        // 将用户ID与公钥关联
    }
  • 认证 (Authentication):

    • 当你再次访问网站时,网站会发起认证请求。
    • 浏览器会提示你使用之前注册的身份验证器。
    • 身份验证器会使用私钥对一个由网站生成的挑战 (challenge) 进行签名。
    • 浏览器将签名后的挑战发送给网站。
    • 网站使用之前保存的公钥验证签名,如果签名有效,则认证成功。
    // 客户端代码 (认证)
    async function authenticate() {
      const options = {
        publicKey: {
          challenge: new Uint8Array([/* 随机 challenge,服务器生成 */]),
          allowCredentials: [
            {
              id: new Uint8Array([/* credential ID,从服务器获取 */]),
              type: "public-key",
              transports: ["usb", "nfc", "ble"] // 可选,指定允许的传输方式
            }
          ],
          userVerification: "preferred" // "required", "preferred", "discouraged"
        }
      };
    
      try {
        const assertion = await navigator.credentials.get({
          publicKey: options.publicKey
        });
    
        // 将 assertion.response.authenticatorData, assertion.response.clientDataJSON, assertion.response.signature, assertion.rawId 发送给服务器
        console.log(assertion);
        return assertion;
    
      } catch (error) {
        console.error("认证失败:", error);
      }
    }
    
    // 服务器端代码 (认证,简化版)
    async function handleAuthentication(assertion, credentialId) {
        // 获取与 credentialId 关联的公钥
        // 验证 challenge
        // 验证 origin
        // 验证 assertion.response.clientDataJSON
        // 验证 assertion.response.authenticatorData
        // 使用公钥验证 assertion.response.signature
        // 如果验证成功,则认证通过
    }

1.2 什么是 Challenge?

Challenge 是一个由服务器生成的随机数,用于防止重放攻击。每次注册或认证时,服务器都会生成一个新的 challenge,并确保浏览器返回的签名是基于这个 challenge 生成的。如果没有 challenge,攻击者可以截获之前的签名,并重复使用它来冒充用户。

二、Attestation:验证身份验证器的真伪

Attestation 是 WebAuthn 中一个至关重要的概念。它允许网站验证你使用的身份验证器是否是可信的,以及它是否由合法的制造商生产。这就像检查你的 VIP 通行证是否是伪造的,以确保只有真正的 VIP 才能进入派对。

2.1 Attestation 的工作原理:证据链

Attestation 的过程涉及到一系列的证书和签名。

  1. 身份验证器生成 Attestation 证书: 在生产过程中,身份验证器会生成一个 Attestation 证书,这个证书由制造商的私钥签名。
  2. Attestation 对象: 当你注册 WebAuthn 时,身份验证器会创建一个 Attestation 对象,其中包含了 Attestation 证书和一些关于身份验证器的信息(比如制造商 ID、型号)。
  3. 网站验证 Attestation 对象: 网站会验证 Attestation 证书的有效性,并检查它是否由可信的制造商签名。

2.2 Attestation 语句格式:

Attestation 对象中包含一个 Attestation 语句,它描述了如何验证身份验证器的真实性。常见的 Attestation 语句格式有:

  • FIDO U2F: 一种早期的认证协议,Attestation 语句通常包含一个 U2F 证书。
  • Packed: 一种通用的 Attestation 格式,Attestation 语句包含一个 X.509 证书链。
  • TPM: 用于 TPM (Trusted Platform Module) 芯片的 Attestation 格式。
  • Android SafetyNet: 用于 Android 设备的 Attestation 格式,依赖于 Google 的 SafetyNet API。
  • None: 没有 Attestation,网站无法验证身份验证器的真实性。(不推荐)

2.3 代码示例 (解析 Attestation 对象):

// 一个简化的解析 attestationObject 的示例
function parseAttestationObject(attestationObject) {
  const decoded = CBOR.decode(attestationObject); // 假设使用了 CBOR 解码库

  const fmt = decoded.fmt; // Attestation 格式 (例如 "packed", "fido-u2f")
  const attStmt = decoded.attStmt; // Attestation 语句

  if (fmt === "packed") {
    const sig = attStmt.sig; // 签名
    const alg = attStmt.alg; // 签名算法
    const x5c = attStmt.x5c; // X.509 证书链

    // TODO: 验证证书链,并验证签名

    console.log("Packed Attestation:", { sig, alg, x5c });
  } else if (fmt === "fido-u2f") {
    const sig = attStmt.sig;
    const x5c = attStmt.x5c;

    // TODO: 验证 U2F 证书,并验证签名

    console.log("FIDO U2F Attestation:", { sig, x5c });
  } else {
    console.warn("不支持的 Attestation 格式:", fmt);
  }

  return decoded;
}

// 假设 credential.response.attestationObject 是 ArrayBuffer
const attestationObject = credential.response.attestationObject;
const parsedAttestation = parseAttestationObject(attestationObject);

三、FIDO Metadata Service (MDS):身份验证器的百科全书

FIDO Metadata Service (MDS) 是 FIDO 联盟维护的一个数据库,其中包含了关于各种 FIDO 认证身份验证器的元数据信息。你可以把它想象成一本身份验证器的百科全书,里面记录了每个身份验证器的制造商、型号、功能、安全级别等信息。

3.1 MDS 的作用:信任的基石

MDS 的主要作用是帮助网站评估身份验证器的可信度。通过查询 MDS,网站可以:

  • 验证制造商的身份: 确认身份验证器是否由 FIDO 认证的制造商生产。
  • 了解身份验证器的功能: 确定身份验证器支持哪些功能(比如指纹识别、NFC、蓝牙)。
  • 评估身份验证器的安全级别: 了解身份验证器是否通过了安全认证,以及它是否容易受到攻击。
  • 检查身份验证器是否被撤销: 确认身份验证器是否因为安全问题而被制造商或 FIDO 联盟撤销。

3.2 MDS 的数据结构:

MDS 的数据以 JSON 格式存储,包含以下主要字段:

  • legalHeader: 法律声明
  • no: 序列号,每次更新都会递增
  • nextUpdate: 下次更新的时间
  • entries: 一个数组,包含多个身份验证器的元数据条目

每个元数据条目包含以下字段:

  • aaid: 身份验证器 Attestation ID,用于唯一标识一个身份验证器型号。
  • aaguid: 身份验证器 Attestation GUID,用于唯一标识一个身份验证器实例。(注意AAID和AAGUID的区别)
  • metadataStatement: 包含身份验证器的详细信息。
  • statusReports: 包含身份验证器的状态报告(比如是否被撤销)。
  • timeOfLastStatusChange: 上次状态改变的时间。

3.3 代码示例 (从 MDS 获取元数据):

// 假设你已经从 FIDO MDS 获取了 metadataBlob
// 并已经验证了 metadataBlob 的签名 (后面会讲 Trust Anchors)

async function getMetadata(aaid) {
  // 解码 metadataBlob (通常是 base64 编码的 JWT)
  const decodedMetadata = JSON.parse(atob(metadataBlob.split(".")[1])); // 简化版,不处理错误

  const entries = decodedMetadata.entries;
  const metadataEntry = entries.find(entry => entry.aaid === aaid);

  if (metadataEntry) {
    console.log("找到元数据:", metadataEntry.metadataStatement);
    return metadataEntry.metadataStatement;
  } else {
    console.warn("找不到 AAID 为", aaid, "的元数据");
    return null;
  }
}

// 假设 parsedAttestation.attStmt.attCertInfo.aaguid 包含了 aaguid
const aaid = parsedAttestation.attStmt.attCertInfo.aaguid;
const metadata = await getMetadata(aaid);

if (metadata) {
  // 根据元数据进行进一步的验证和处理
  console.log("身份验证器制造商:", metadata.manufacturerName);
  console.log("身份验证器型号:", metadata.modelName);
  // 检查是否被撤销
  // 检查安全级别
}

四、Trust Anchors:信任的根基

Trust Anchors 是指受信任的根证书颁发机构 (CA) 的证书。在 WebAuthn 的上下文中,Trust Anchors 用于验证 FIDO Metadata Service (MDS) 发布的元数据签名,确保你从 MDS 获取的数据是真实可靠的。你可以把它想象成政府颁发的公章,确保 MDS 发布的信息是官方的、权威的。

4.1 Trust Anchors 的作用:防止中间人攻击

如果没有 Trust Anchors,攻击者可以伪造 MDS 服务器,并向网站提供虚假的元数据。通过使用 Trust Anchors 验证 MDS 的签名,网站可以确保它们与真正的 MDS 服务器通信,并获取真实的元数据。

4.2 Trust Anchors 的来源:

FIDO 联盟会定期发布 Trust Anchors,你可以从 FIDO 联盟的官方网站下载。Trust Anchors 通常以 PEM 格式存储,包含根证书的公钥信息。

4.3 代码示例 (验证 MDS 签名):

const fs = require('fs');
const jwt = require('jsonwebtoken');
const jwkToPem = require('jwk-to-pem');

// 假设你已经下载了 FIDO 联盟的 Trust Anchors (root.pem)
const trustAnchor = fs.readFileSync('root.pem', 'utf8');

// 假设你已经从 FIDO MDS 获取了 metadataBlob (JWT 格式)
function verifyMdsSignature(metadataBlob) {
  try {
    const decodedHeader = jwt.decode(metadataBlob, { complete: true }).header;
    const key = {
        kty: 'RSA',
        n: decodedHeader.x5c[0], // 假设 x5c[0] 是 JWT Header 中的 RSA 模数 (n)
        e: decodedHeader.x5c[1]  // 假设 x5c[1] 是 JWT Header 中的 RSA 指数 (e)
    };
    const publicKey = jwkToPem(key);
    const verified = jwt.verify(metadataBlob, publicKey, { algorithms: ['RS256'] }); // 或者其他算法
    console.log("MDS 签名验证成功");
    return verified;
  } catch (error) {
    console.error("MDS 签名验证失败:", error);
    return null;
  }
}

const verifiedMetadata = verifyMdsSignature(metadataBlob);

if (verifiedMetadata) {
  // 现在你可以安全地使用 verifiedMetadata 中的数据了
  console.log("MDS 版本:", verifiedMetadata.no);
}

五、WebAuthn 安全考量:别让你的派对被捣乱

WebAuthn 提供了比传统密码认证更高的安全性,但仍然需要注意一些安全问题:

  • Challenge 的随机性: 确保 challenge 是真正的随机数,并且足够长,以防止被破解。
  • Origin 验证: 验证请求的来源 (origin) 是否与你的网站域名一致,防止跨站请求伪造 (CSRF) 攻击。
  • Attestation 验证: 始终验证 Attestation 对象,确保你使用的身份验证器是可信的。
  • MDS 查询: 定期查询 MDS,检查身份验证器是否被撤销,并更新元数据信息。
  • 用户验证策略: 根据应用的安全需求,选择合适的用户验证策略 (userVerification),例如 "required"、"preferred" 或 "discouraged"。
  • 传输方式选择: 考虑支持的传输方式 (transports),例如 USB、NFC、蓝牙,并根据用户的设备和安全需求进行选择。
  • 密钥泄露: 即使 WebAuthn 使用公钥加密,私钥仍然需要安全地存储在用户的设备上。如果私钥泄露,攻击者可以冒充用户。

六、总结:WebAuthn 的未来

WebAuthn 正在迅速普及,成为无密码认证的未来。通过理解 WebAuthn、Attestation、FIDO Metadata Service (MDS) 和 Trust Anchors 的工作原理,你可以构建更安全、更便捷的身份验证系统,为用户提供更好的体验。

表格:WebAuthn 相关概念总结

概念 描述 作用
WebAuthn W3C 推出的无密码认证标准 提供更安全、更便捷的身份验证方式,取代密码
Attestation 验证身份验证器的真实性 确保身份验证器是由可信的制造商生产,防止使用伪造的身份验证器
FIDO Metadata Service (MDS) 包含各种 FIDO 认证身份验证器的元数据信息 帮助网站评估身份验证器的可信度,了解身份验证器的功能和安全级别,检查是否被撤销
Trust Anchors 受信任的根证书颁发机构 (CA) 的证书 验证 FIDO Metadata Service (MDS) 发布的元数据签名,确保从 MDS 获取的数据是真实可靠的,防止中间人攻击
Challenge 服务器生成的随机数 防止重放攻击,确保每次注册或认证都是唯一的
Relying Party 依赖 WebAuthn 进行身份验证的网站或应用 负责发起注册和认证请求,验证用户的身份
Authenticator 身份验证器,例如指纹识别器、安全密钥 负责生成密钥对,对 challenge 进行签名
AAID 身份验证器 Attestation ID,用于唯一标识一个身份验证器型号 用于在 MDS 中查找特定型号的身份验证器的元数据
AAGUID 身份验证器 Attestation GUID,用于唯一标识一个身份验证器实例 一些身份验证器使用 AAGUID 代替 AAID。两者目的相同,都是为了在 MDS 中查找元数据。

希望这次讲座能帮助你更好地理解 WebAuthn 的世界。记住,安全是一个持续的过程,需要不断学习和改进。下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注