大家好!欢迎来到今天的“WebAuthn Attestation Format 和 Authenticator Registration”专场脱口秀。我是今天的段子手,不对,是技术专家,我们来一起扒一扒 WebAuthn 里面的那些弯弯绕。
首先,我们得明确一点:WebAuthn 就像一个非常谨慎的保安,它要确保注册的用户(Authenticator)真的是他们声称的那个人,并且这个设备(Authenticator)是可信的。而 Attestation Format 就是保安用来验证用户身份的“身份证明”。
一、WebAuthn 基础概念回顾:Authenticator 的自我介绍
在 WebAuthn 的世界里,Authenticator 就像一个来面试的候选人。它要跟网站(Relying Party,RP)证明自己是靠谱的。这个证明的过程分为两个阶段:
- Registration (注册): Authenticator 第一次来,要告诉 RP “我是谁,我有什么能力,我怎么证明我是我”。
- Authentication (认证): Authenticator 已经注册过了,每次来都要证明 “我还是我,我没被掉包”。
我们今天主要关注 Registration 阶段,尤其是 Authenticator 如何把自己介绍给 RP,以及 RP 如何判断这个介绍是不是真的。
二、Attestation:Authenticator 的出生证明
Attestation 其实就是 Authenticator 提供的“出生证明”,证明它是由哪个厂家生产的,型号是什么,以及它是否被篡改过。这个“出生证明”是用密码学方法签名的,所以可以验证真伪。
1. Attestation Statement Format(证明声明格式)
这就是“出生证明”的具体格式。不同的 Authenticator 厂商可能使用不同的格式,WebAuthn 定义了一些常见的格式:
- Packed: 最常见,也比较简单。Authenticator 使用自己的私钥对 Attestation Statement 进行签名。
- TPM: 可信平台模块 (Trusted Platform Module) 使用,安全性很高,但是实现比较复杂。
- Android Key Attestation: 安卓设备使用,通过 Google 的 Keymaster 服务进行验证。
- None: Authenticator 声称自己不提供任何 Attestation 信息,RP 需要自己承担风险。
2. Attestation Statement(证明声明)
Attestation Statement 包含了 Authenticator 的信息和签名。它通常是一个 CBOR (Concise Binary Object Representation) 编码的数据结构。
Attestation Statement 的结构通常包含以下几个关键字段:
fmt
: Attestation Format 的字符串标识符,例如"packed"
,"tpm"
,"android-key"
等。attStmt
: 包含实际的 Attestation 信息,例如签名、证书链等。authData
: 认证数据,包含了 Authenticator 的信息,例如 AAGUID (Authenticator Attestation GUID)。
三、Authenticator Registration 流程:保安如何核实身份
Authenticator Registration 的流程大致如下:
- RP (网站) 生成 Challenge: RP 生成一个随机数,作为挑战值 (challenge)。
- RP 将 Challenge 发送给用户设备: 通过 WebAuthn API,将 challenge 发送给用户设备。
- Authenticator 生成密钥对: Authenticator 生成一个用于 WebAuthn 的密钥对。
- Authenticator 创建 Attestation Statement: Authenticator 使用自己的私钥对 Attestation Statement 进行签名,并包含必要的 Attestation 信息。
- Authenticator 将 Credential、Attestation Statement 和 Challenge 返回给 RP: 通过 WebAuthn API,将这些数据返回给 RP。
- RP 验证 Attestation Statement: RP 根据
fmt
字段选择相应的验证方法,验证 Attestation Statement 的签名和内容。 - RP 保存 Credential 信息: 如果 Attestation 验证通过,RP 将 Credential 信息(例如公钥、AAGUID 等)保存到数据库中。
四、代码示例:Packed Attestation Format 的验证
我们以最常见的 Packed Attestation Format 为例,展示如何验证 Attestation Statement。
// 假设我们已经从 WebAuthn API 获取了 registrationResponse
async function verifyPackedAttestation(registrationResponse) {
const { attestationObject, clientDataJSON } = registrationResponse;
// 1. 解析 attestationObject
const attestation = CBOR.decode(attestationObject);
const fmt = attestation.fmt;
const attStmt = attestation.attStmt;
const authData = attestation.authData;
// 2. 验证 fmt 是否为 "packed"
if (fmt !== "packed") {
throw new Error("Attestation format is not 'packed'");
}
// 3. 验证 clientDataHash
const clientDataHash = await crypto.subtle.digest('SHA-256', clientDataJSON);
const parsedAuthData = parseAuthData(authData); // 假设我们有这个函数来解析 authData
if (!arraysEqual(parsedAuthData.rpIdHash, new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(window.location.origin))))) {
throw new Error("rpIdHash mismatch!");
}
if (!arraysEqual(parsedAuthData.clientDataHash, new Uint8Array(clientDataHash))) {
throw new Error("clientDataHash mismatch!");
}
// 4. 验证签名
const signature = attStmt.sig;
const alg = attStmt.alg;
const pubArea = attStmt.pubArea;
let certificate = attStmt.certInfo;
let verificationResult = false;
if (attStmt.x5c && attStmt.x5c.length > 0) {
// 4.1 If x5c is present, validate the certificate chain
certificate = attStmt.x5c[0];
const isValidCertChain = await validateCertificateChain(attStmt.x5c); // 假设有验证证书链的函数
if (!isValidCertChain) {
throw new Error("Invalid certificate chain");
}
// 4.2 Verify signature using the certificate
verificationResult = await verifySignature(certificate, signature, authData);
} else {
// 4.3 If x5c is absent, retrieve public key from pubArea and verify signature
const publicKey = await extractPublicKeyFromPubArea(pubArea); // 假设有函数从pubArea提取公钥
verificationResult = await verifySignature(publicKey, signature, authData);
}
if (!verificationResult) {
throw new Error("Signature verification failed");
}
// 5. 返回验证结果
return true;
}
// 辅助函数 (需要根据实际情况实现)
async function validateCertificateChain(certificateChain) {
// 实现证书链验证逻辑
// 例如:检查证书是否过期,是否被吊销等
return true; // 简化示例,假设验证通过
}
async function verifySignature(key, signature, data) {
// 实现签名验证逻辑
const algorithm = {
name: "ECDSA",
hash: "SHA-256",
};
try {
const isVerified = await crypto.subtle.verify(
algorithm,
key,
signature,
data
);
return isVerified;
} catch (error) {
console.error("Signature verification error:", error);
return false;
}
}
async function extractPublicKeyFromPubArea(pubArea) {
// TODO: Implement logic to extract public key from pubArea
// This depends on the structure of the pubArea.
// For example, if it's an ASN.1 encoded structure, you'd need to parse it.
// See the FIDO2 specification for details.
return null;
}
function parseAuthData(authData) {
const rpIdHash = authData.slice(0, 32);
const flags = authData[32];
const signCount = new DataView(authData.slice(33, 37).buffer).getUint32(0, false);
let aaguid = null;
let credentialIdLength = null;
let credentialId = null;
let credentialPublicKey = null;
let offset = 37;
const atFlag = flags & 0x40;
const edFlag = flags & 0x80;
if (atFlag) {
aaguid = authData.slice(offset, offset + 16);
offset += 16;
credentialIdLength = new DataView(authData.slice(offset, offset + 2).buffer).getUint16(0, false);
offset += 2;
credentialId = authData.slice(offset, offset + credentialIdLength);
offset += credentialIdLength;
credentialPublicKey = authData.slice(offset);
}
return {
rpIdHash: rpIdHash,
flags: flags,
signCount: signCount,
aaguid: aaguid,
credentialIdLength: credentialIdLength,
credentialId: credentialId,
credentialPublicKey: credentialPublicKey,
clientDataHash: authData.slice(37+16+2+credentialIdLength,37+16+2+credentialIdLength+32) // Assuming clientDataHash is after credentialPublicKey
};
}
function arraysEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
代码解释:
verifyPackedAttestation(registrationResponse)
: 主函数,接收 WebAuthn API 返回的 registrationResponse。CBOR.decode(attestationObject)
: 解析 CBOR 编码的 attestationObject。fmt !== "packed"
: 检查 Attestation Format 是否为 "packed"。validateCertificateChain(attStmt.x5c)
: 验证证书链的有效性。verifySignature(certificate, signature, authData)
: 使用证书或公钥验证签名。
五、Attestation 的分类:保安的信任等级
Attestation 可以分为不同的类型,反映了 RP 对 Authenticator 的信任程度:
-
Attestation Conveyance Preference (证明传递偏好): RP 可以指定自己希望接收的 Attestation 类型。
none
: RP 不要求 Attestation 信息。indirect
: RP 接收 Attestation 信息,但是不强制验证。direct
: RP 必须验证 Attestation 信息。
-
Attestation Trustworthiness (证明可信度): RP 可以根据 Authenticator 的 AAGUID (Authenticator Attestation GUID) 来判断其可信度。AAGUID 可以用来识别 Authenticator 的厂商和型号。
六、Authenticator Registration 的安全性考虑:保安的防盗技巧
在 Authenticator Registration 过程中,需要考虑以下安全性问题:
- Challenge 的唯一性: Challenge 必须是随机的,并且只能使用一次,防止重放攻击。
- RP ID 的验证: RP 必须验证收到的 Attestation Statement 中的 RP ID 是否与自己的域名一致,防止钓鱼攻击。
- 证书链的验证: 如果 Attestation Statement 中包含证书链,RP 必须验证证书链的有效性,防止伪造证书。
- AAGUID 的管理: RP 应该维护一个 AAGUID 列表,记录已知的可信 Authenticator 厂商和型号。
- 用户验证要求: RP应该根据自身安全需求,选择合适的User Verification (UV)策略,比如需要用户输入PIN码或者指纹验证等。
七、总结:保安的职业素养
WebAuthn Attestation Format 和 Authenticator Registration 是 WebAuthn 安全性的重要组成部分。通过验证 Attestation Statement,RP 可以确保注册的 Authenticator 是可信的,并且没有被篡改。
总而言之,理解 Attestation Format 和 Authenticator Registration 的流程,就像了解保安如何核实身份一样,可以帮助我们更好地理解 WebAuthn 的安全性,并构建更安全的 Web 应用。
最后,来个小彩蛋:
WebAuthn 的世界远不止这些,还有各种各样的细节和坑等着你去踩。但是,只要你掌握了基本原理,就能在这个领域里游刃有余。希望今天的“脱口秀”能给你带来一些启发。
感谢大家的收听!我们下次再见!
表格总结:
概念 | 描述 | 作用 |
---|---|---|
Authenticator | 用于 WebAuthn 认证的设备,例如指纹识别器、安全密钥等。 | 提供用户身份验证功能。 |
Relying Party (RP) | 网站或应用,依赖 Authenticator 进行用户身份验证。 | 验证用户身份,提供安全访问。 |
Attestation | Authenticator 提供的“出生证明”,证明其是由哪个厂家生产的,型号是什么,以及它是否被篡改过。 | 验证 Authenticator 的真实性和可信度。 |
Attestation Format | Attestation Statement 的具体格式,例如 Packed, TPM, Android Key 等。 | 定义 Attestation Statement 的结构和内容。 |
Attestation Statement | 包含了 Authenticator 的信息和签名。 | 提供 Authenticator 的详细信息,用于验证其真实性。 |
AAGUID | Authenticator Attestation GUID,用于识别 Authenticator 的厂商和型号。 | 帮助 RP 判断 Authenticator 的可信度。 |
Challenge | RP 生成的随机数,用于防止重放攻击。 | 确保 Registration 和 Authentication 过程的安全性。 |
Packed | 一种常见的 Attestation Format,Authenticator 使用自己的私钥对 Attestation Statement 进行签名。 | 简化 Attestation 流程,降低实现复杂度。 |
TPM | 可信平台模块,一种硬件安全模块,提供更高的安全性。 | 提供更安全的 Attestation 机制。 |
Android Key | 安卓设备使用的 Attestation 机制,通过 Google 的 Keymaster 服务进行验证。 | 方便安卓设备进行 Attestation。 |
None | 不提供任何 Attestation 信息,RP 需要自己承担风险。 | 适用于对安全性要求不高的场景。 |
希望这个表格能帮助你更好地理解这些概念。