1、WebAuthn简介
WebAuthn 是FIDO联盟FIDO2规范集的核心组件,是一种基于Web的API,允许网站更新其登录页面,以便在支持的浏览器和平台上添加基于FIDO的身份验证。FIDO2使用户能够利用常见设备轻松地在移动的和桌面环境中对在线服务进行身份验证。
Web服务和应用程序可以-并且应该-打开此功能,通过生物识别技术,移动的设备和/或FIDO安全密钥为用户提供更轻松的登录体验-并且比单独的密码具有更高的安全性。
FIDO联盟决定与万维网国际标准组织万维网联盟(W3C)合作,为整个Web平台标准化FIDO身份验证。这种标准化将通过支持该标准的Web浏览器和Web应用服务器的整个社区来发展FIDO生态系统。
它允许服务器与现在内置于设备中的强身份验证器集成,如Windows Hello或Apple的Touch ID。为网站创建私钥-公钥对(称为 凭据 )而不是密码。私钥安全地存储在用户的设备上;公钥和随机生成的凭据 ID 将发送到服务器进行存储。然后,服务器可以使用该公钥来证明用户的身份。
公钥不是秘密的,因为如果没有相应的私钥,它实际上是无用的。服务器未收到任何机密这一事实对用户和组织的安全性具有深远的影响。数据库对黑客不再那么有吸引力,因为公钥对他们没有用。
FIDO联盟成员公司于2015年将FIDO规范提交给W3C进行正式标准化。然后,他们在W3C内部工作,最终确定了API,这被称为Web身份验证或WebAuthn。WebAuthn于2019年3月被正式认可为W3C Web标准。如今WebAuthn是FIDO联盟FIDO2规范的一部分,FIDO联盟运行认证程序以确保合规性。
什么是公钥密码?
公钥密码学发明于20世纪70年代,是解决共享秘密问题的一种方法。它是现代互联网安全的支柱;例如,每次我们连接到HTTPS网站时,都会发生公钥交易。
公钥加密使用密钥对的概念;一个私钥由用户安全存储,一个公钥可以与服务器共享。这些“密钥”是彼此具有数学关系的长随机数。
2、FIDO2:客户端到验证器协议(CTAP)
FIDO2的另一个组件,客户端到认证者协议(CTAP)
,是WebAuthn的补充。它使外部身份验证器(如安全密钥或移动的电话)能够与支持WebAuthn的浏览器一起使用,并且还可以用作桌面应用程序和Web服务的身份验证器。
3、浏览器和平台
WebAuthn目前在Google Chrome
、Mozilla Firefox、Microsoft Edge和Apple Safari
Web浏览器以及Windows 10和Android
平台中得到支持。
浏览器兼容性如下:
4、Web身份验证API 基本流程
本节规范性地指定了用于创建和使用公钥凭据的API。基本的 这个想法是凭证属于用户并由WebAuthn Authenticator管理,WebAuthn依赖方通过客户端平台与WebAuthn Authenticator交互。 依赖方脚本可以(在用户同意的情况下)请求 浏览器以创建新凭证供信赖方将来使用。参见下图:
注册流程:
1、依赖方服务端开始准备用户信息,以及依赖方信息,这些属于公钥凭证创建相关内容选项。
2、把对应信息在依赖房应用程序通过WebAuthnAPI,生成依赖方id,用户信息,依赖方信息,客户数据哈希,把这些信息发送身份验证器进行验证。
3、身份验证器开始验证用户信息,会生成新的密钥对,和对应认证。
4、身份验证器,把新的公钥和认证id等相关认证信息返回。
5、经浏览器和依赖方js应用返回客户JOSN数据,认证对象信息,这一步是验证器认证响应。
6、服务端进行验证。
脚本还可以请求用户的权限,以便使用现有凭据执行身份验证操作。参见下图1、依赖方服务端发起询问,需要公钥认证请求需要的参数。
身份验证流程:
1、依赖方服务端发起询问,需要公钥认证请求需要的参数。
2、经依赖方JS应用,浏览器得到依赖方id,客户对应的哈希数据发送给身份验证器。
3、身份验证器开始验证用户信息,开始创建认证信息。
4、验证数据签名返回给浏览器。
5、经依赖方返回客户端的json数据,验证数据,签名等信息。
6、依赖方服务端
5、使用 WebAuthn API
5.1 注册WebAuthn凭据
在基于密码的用户注册流程中,服务器通常会向用户呈现一个表单,询问用户名和密码。密码将被发送到服务器进行存储。
在WebAuthn中,服务器必须提供将用户绑定到凭证(私钥-公钥对)的数据;该数据包括用户和组织(也称为“依赖方”)的标识符。然后,网站将使用Web身份验证API来提示用户创建新的密钥对。需要注意的是,我们需要从服务器随机生成的字符串作为挑战,以防止重放攻击。
navigator.credentials.create()
服务器将通过调用客户机上的navigator.credentials.create()开始创建新的凭据。
1. navigator.credentials.create({ publicKeyCredentialCreationOptions }) 2. .then(function (newCredentialInfo) { 3. // 发送新的凭证信息到服务器进行验证和注册. 4. }).catch(function (err) { 5. // 没有可接受的身份验证者或用户拒绝同意。再做对应处理。 6. });
publicKeyCredentialCreationOptions 对象包含许多必需和可选字段,服务器指定这些字段为用户创建新凭证。
1. const publicKeyCredentialCreationOptions = { 2. // 在服务器上生成的加密随机字节的缓冲区,并且需要防止“重放攻击”。 3. challenge: bufferToBase64URLString(utf8StringToBuffer('sd')), 4. // 服务器希望从认证器接收证明数据(direct) 5. attestation: 'direct', 6. // 这是一个对象数组,描述服务器可以接受哪些公钥类型 7. // -7表示服务器接受使用SHA-256签名算法的椭圆曲线公钥 8. pubKeyCredParams: [ 9. { 10. alg: -7, 11. type: 'public-key', 12. }, 13. ], 14. // 依赖方的id,是浏览器当前域名的子集 15. rp: { 16. id: 'duosecurity.dev', 17. name: 'duosecurity', 18. }, 19. // 用户信息 20. user: { 21. id: '5678', 22. displayName: 'username', 23. name: 'username', 24. }, 25. timeout: 1000, 26. // 希望限制在单个身份验证器上为同一帐户创建多个凭据的依赖方使用。 27. excludeCredentials: [ 28. { 29. id: 'C0VGlvYFratUdAV1iCw-ULpUW8E-exHPXQChBfyVeJZCMfjMFcwDmOFgoMUz39LoMtCJUBW8WPlLkGT6q8qTCg', 30. type: 'public-key', 31. transports: ['internal'], 32. }, 33. ], 34. };
返回的create()对象是一个包含公钥和用于验证注册事件的其他属性的对象。
1. PublicKeyCredential { 2. // 新生成的凭证的ID;它将用于在认证用户时标识凭证。ID是一个base64编码的字符串 3. id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...', 4. // 属性返回包含内部槽中的标识符 5. rawId: ArrayBuffer(59), 6. response: AuthenticatorAttestationResponse { 7. // 这表示从浏览器传递到身份验证器的数据,以便将新凭据与服务器和浏览器相关联。 8. // 验证器将其作为UTF-8字节数组提供。 9. clientDataJSON: ArrayBuffer(121), 10. // 此对象包含凭证公钥、可选的证明证书和其他用于验证注册事件的元数据。 11. // 它是以CBOR编码的二进制数据。 12. attestationObject: ArrayBuffer(306), 13. }, 14. type: 'public-key' 15. }
解析clientDataJSON数据:
1. // decode the clientDataJSON into a utf-8 string 2. const utf8Decoder = new TextDecoder('utf-8'); 3. const decodedClientData = utf8Decoder.decode( 4. credential.response.clientDataJSON) 5. 6. // parse the string as an object 7. const clientDataObj = JSON.parse(decodedClientData); 8. 9. console.log(clientDataObj) 10. 11. { 12. // 这是传递到create()调用中的challenge是否相同 13. challenge: "p5aV2uHXr0AOqUk7HQitvi-Ny1....", 14. // 服务器必须验证这个“origin”字符串与应用程序的源代码匹配。 15. origin: "https://webauthn.guide", 16. // 服务器验证该字符串是不是"webauthn.create",否则可能执行不正确的操作。 17. type: "webauthn.create", 18. // 客户端和callerOrigin之间的令牌绑定状态 19. tokenBinding: { // 如果缺失,表示客户端不支持令牌的绑定 20. // status存在,则该成员必须存在, 21. // 并且必须是与依赖方通信时使用的令牌绑定ID的base64url编码 22. id:'ASAFDQWE12312aasDASD......'; 23. // supported 支持令牌绑定,但与依赖方通信时未协商 24. // present 与依赖方通信时使用了令牌绑定,id必须存在 25. status: 'present' | 'supported''; 26. } 27. 28. }