├── LICENSE.md ├── README.md ├── csharp └── app.cs ├── java ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── webauthenticator │ │ ├── WebAuthenticationPublicKey.java │ │ └── WebAuthenticator.java │ └── test │ └── java │ └── webauthenticator │ └── WebAuthenticatorTest.java ├── nodejs ├── fido.js └── package.json ├── php ├── fido-authenticator.php └── test.php └── polyfill ├── polyfill.html └── webauthn.js /LICENSE.md: -------------------------------------------------------------------------------- 1 | ### FIDO Snippets 2 | https://github.com/adrianba/fido-snippets 3 | Copyright (c) Microsoft Corporation 4 | All rights reserved. 5 | 6 | #### MIT License 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED \*AS IS\*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### FIDO Snippets 2 | 3 | This repo contains code samples for using FIDO 2.0 (aka Web Authentication) APIs. 4 | 5 | **IMPORTANT:** this repository is out of date. Microsoft Edge now supports the latest W3C specification for Web Authentication. See https://blogs.windows.com/msedgedev/2018/07/30/introducing-web-authentication-microsoft-edge/. The [Web Authentication dev guide](https://docs.microsoft.com/en-us/microsoft-edge/dev-guide/windows-integration/web-authentication) explains how to use the new API. 6 | -------------------------------------------------------------------------------- /csharp/app.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Web.Script.Serialization; 4 | using System.Security.Cryptography; 5 | 6 | /* 7 | * The App class is a simple example using pre-defined constants to be able to test the 8 | * FidoAuthenticator class. These constants are examples of what would be generated in the 9 | * browser when using the Web Authentication APIs. 10 | */ 11 | class App { 12 | const string challenge = "Y2xpbWIgYSBtb3VudGFpbg"; 13 | const string pk = "{ \"kty\" : \"RSA\", \"alg\" : \"RS256\", \"ext\" : false, \"n\" : \"k-d_ZbVlAu-EBhRNlevnd0cBqJEPlhBefOMMLgHeGTS28Uev6_xHVCZ774I2XdtVF-M5En0aTdekAX82K2SVnO9TEZ4GdJFR1kW1jppLtrlUyjQqggq60OwkUxHM14XDQBhvgeW3fjpETraB-R_scyGeK7lNWMF8jW-NjvU_nljTyuHoDVnYJxPuCunlQ7uzg80iURp0jFKhw7FedPkzyQllG0HRRfzwQG1WOyECxkdPmMk7iPdF-B-Z78S9Fd8Dx2R8OZHEpFMdQ3Z3bMLG8prSbXcmBXlBtmwSLPzEEb3FuPdJQtXzVyg2i3jAR25zWA_XemXHBv7XE5Mf3JYldQ\", \"e\" : \"AQAB\" }"; 14 | const string d = "ew0KCSJjaGFsbGVuZ2UiIDogIlkyeHBiV0lnWVNCdGIzVnVkR0ZwYmciLA0KCSJ1c2VyUHJvbXB0IiA6ICJIZWxsbyEiDQp9AA"; 15 | const string s = "M-GT64y3FXoFQI8fRPq8ogckxuVYqv65R2eJEXGpbmVtm3Zn9Oa6ik4nClFMsN4h42e9bSBslMTEKW-J1oAoxF8n4JkDH82b9j4bFhhSRMHCbmE-uZm1RX8zVrGIgoWnXDy2nGQSu5xN-BhGubru1x0sXo9ZAdXKc-5hkp6SfIdXAY15o9flsag_H_CpIJ1_L1-vO5K8xhya_iOezflNlqa8-D1lI-xMJ7dOqyPwqg33ryW4l6iTtexuiYhZaGOOyJ5ZxzchjKrw9zMgQOsjbsrM7Q6bu7K7YvOoULxM5WJFdCLj0OBZznrskEHlLrSe0TSr_WrY1SkLhRaUCetKkg"; 16 | const string a = "AQAAAAA"; 17 | 18 | static void Main() { 19 | var v = FidoAuthenticator.validateSignature(pk,d,a,s,challenge) ? "verified" : "unverified"; 20 | Console.WriteLine(v); 21 | } 22 | } 23 | 24 | /* 25 | * The FidoAuthenticator class contains the logic for validating the signature returned from getAssertion in the 26 | * browser. This code is currently specific to the early implementation in Microsoft Edge and will need to change 27 | * when the final standard is adopted. This code would run on the server in order to validate that the user 28 | * really is able to validate using the credentials previously created with the makeCredential API. 29 | * 30 | * The public key in pk would have been stored on the server using the results of makeCredential. The challenge 31 | * would have been created on ther server and sent to the client for use in the getAssertion call. The other 32 | * parameters are returned by getAssertion and transmitted from the browser to the server for validation. 33 | */ 34 | class FidoAuthenticator { 35 | public static bool validateSignature(string pk, string clientData, string authnrData, string signature, string challenge) 36 | { 37 | try { 38 | var c = rfc4648_base64_url_decode(clientData); 39 | var a = rfc4648_base64_url_decode(authnrData); 40 | var s = rfc4648_base64_url_decode(signature); 41 | 42 | // Make sure the challenge in the client data matches the expected challenge 43 | var cc = Encoding.ASCII.GetString(c); 44 | cc = cc.Replace("\0","").Trim(); 45 | var json = new JavaScriptSerializer(); 46 | var j = (System.Collections.Generic.Dictionary)json.DeserializeObject(cc); 47 | if((string)j["challenge"] != challenge) return false; 48 | 49 | // Hash data with sha-256 50 | var hash = new SHA256Managed(); 51 | var h = hash.ComputeHash(c); 52 | 53 | // Create data buffer to verify signature over 54 | var b = new byte[a.Length + h.Length]; 55 | a.CopyTo(b,0); 56 | h.CopyTo(b,a.Length); 57 | 58 | // Load public key 59 | j = (System.Collections.Generic.Dictionary)json.DeserializeObject(pk); 60 | var keyinfo = new RSAParameters(); 61 | keyinfo.Modulus = rfc4648_base64_url_decode((string)j["n"]); 62 | keyinfo.Exponent = rfc4648_base64_url_decode((string)j["e"]); 63 | var rsa = new RSACng(); 64 | rsa.ImportParameters(keyinfo); 65 | 66 | // Verify signature is correct for authnrData + hash 67 | return rsa.VerifyData(b,s,HashAlgorithmName.SHA256,RSASignaturePadding.Pkcs1); 68 | } catch(Exception) { 69 | return false; 70 | } 71 | } 72 | 73 | private static byte[] rfc4648_base64_url_decode(string url) { 74 | url = url.Replace('-','+'); 75 | url = url.Replace('_','/'); 76 | 77 | switch(url.Length % 4) { // Pad with trailing '='s 78 | case 0: 79 | // No pad chars in this case 80 | break; 81 | case 2: 82 | // Two pad chars 83 | url += "=="; 84 | break; 85 | case 3: 86 | // One pad char 87 | url += "="; 88 | break; 89 | default: 90 | throw new Exception("Invalid string."); 91 | } 92 | return Convert.FromBase64String(url); 93 | } 94 | } -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .settings/ 3 | .project 4 | .classpath 5 | -------------------------------------------------------------------------------- /java/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.example 5 | webauthenticator 6 | 0.0.1-SNAPSHOT 7 | 8 | 9 | 10 | 11 | junit 12 | junit 13 | 4.12 14 | test 15 | 16 | 17 | com.fasterxml.jackson.core 18 | jackson-databind 19 | 2.8.4 20 | 21 | 22 | -------------------------------------------------------------------------------- /java/src/main/java/webauthenticator/WebAuthenticationPublicKey.java: -------------------------------------------------------------------------------- 1 | package webauthenticator; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class WebAuthenticationPublicKey { 6 | 7 | public WebAuthenticationPublicKey(String kty, String alg, boolean ext, String n, String e) { 8 | this.alg = alg; 9 | this.e = e; 10 | this.ext = ext; 11 | this.kty = kty; 12 | this.n = n; 13 | } 14 | 15 | @JsonProperty("alg") 16 | private String alg; 17 | 18 | @JsonProperty("e") 19 | private String e; 20 | 21 | @JsonProperty("ext") 22 | private boolean ext; 23 | 24 | @JsonProperty("kty") 25 | private String kty; 26 | 27 | @JsonProperty("n") 28 | private String n; 29 | 30 | public String getAlg() { 31 | return alg; 32 | } 33 | 34 | public String getE() { 35 | return e; 36 | } 37 | 38 | public boolean isExt() { 39 | return ext; 40 | } 41 | 42 | public String getKty() { 43 | return kty; 44 | } 45 | 46 | public String getN() { 47 | return n; 48 | } 49 | } -------------------------------------------------------------------------------- /java/src/main/java/webauthenticator/WebAuthenticator.java: -------------------------------------------------------------------------------- 1 | package webauthenticator; 2 | 3 | import java.math.BigInteger; 4 | import java.nio.charset.StandardCharsets; 5 | import java.security.KeyFactory; 6 | import java.security.MessageDigest; 7 | import java.security.PublicKey; 8 | import java.security.Signature; 9 | import java.security.spec.RSAPublicKeySpec; 10 | import java.util.Base64; 11 | 12 | import com.fasterxml.jackson.databind.JsonNode; 13 | import com.fasterxml.jackson.databind.ObjectMapper; 14 | 15 | public class WebAuthenticator { 16 | 17 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 18 | 19 | public static boolean validateSignature(WebAuthenticationPublicKey pk, String clientData, String authnrData, String signature, String challenge) { 20 | try { 21 | byte[] c = Base64.getUrlDecoder().decode(clientData); 22 | byte[] a = Base64.getUrlDecoder().decode(authnrData); 23 | byte[] s = Base64.getUrlDecoder().decode(signature); 24 | 25 | String cc = new String(c, StandardCharsets.US_ASCII); 26 | cc = cc.replace("\0","").trim(); 27 | 28 | JsonNode j = OBJECT_MAPPER.readValue(cc, JsonNode.class); 29 | 30 | if(!j.get("challenge").asText().equals(challenge)) { 31 | return false; 32 | } 33 | 34 | MessageDigest digest = MessageDigest.getInstance("SHA-256"); 35 | byte[] h = digest.digest(c); 36 | 37 | RSAPublicKeySpec spec = new RSAPublicKeySpec( 38 | new BigInteger(1, Base64.getUrlDecoder().decode(pk.getN())), 39 | new BigInteger(1, Base64.getUrlDecoder().decode(pk.getE())) 40 | ); 41 | KeyFactory factory = KeyFactory.getInstance("RSA"); 42 | PublicKey pub = factory.generatePublic(spec); 43 | Signature verifier = Signature.getInstance("SHA256withRSA"); 44 | verifier.initVerify(pub); 45 | verifier.update(a); 46 | verifier.update(h); 47 | return verifier.verify(s); 48 | } catch (Exception e) { 49 | return false; 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /java/src/test/java/webauthenticator/WebAuthenticatorTest.java: -------------------------------------------------------------------------------- 1 | package webauthenticator; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.io.IOException; 6 | 7 | import org.junit.Test; 8 | 9 | import com.fasterxml.jackson.core.JsonParseException; 10 | import com.fasterxml.jackson.databind.JsonMappingException; 11 | 12 | public class WebAuthenticatorTest { 13 | private static final String CHALLENGE = "Y2xpbWIgYSBtb3VudGFpbg"; 14 | private static final String D = "ew0KCSJjaGFsbGVuZ2UiIDogIlkyeHBiV0lnWVNCdGIzVnVkR0ZwYmciLA0KCSJ1c2VyUHJvbXB0IiA6ICJIZWxsbyEiDQp9AA"; 15 | private static final String S = "M-GT64y3FXoFQI8fRPq8ogckxuVYqv65R2eJEXGpbmVtm3Zn9Oa6ik4nClFMsN4h42e9bSBslMTEKW-J1oAoxF8n4JkDH82b9j4bFhhSRMHCbmE-uZm1RX8zVrGIgoWnXDy2nGQSu5xN-BhGubru1x0sXo9ZAdXKc-5hkp6SfIdXAY15o9flsag_H_CpIJ1_L1-vO5K8xhya_iOezflNlqa8-D1lI-xMJ7dOqyPwqg33ryW4l6iTtexuiYhZaGOOyJ5ZxzchjKrw9zMgQOsjbsrM7Q6bu7K7YvOoULxM5WJFdCLj0OBZznrskEHlLrSe0TSr_WrY1SkLhRaUCetKkg"; 16 | private static final String A = "AQAAAAA"; 17 | 18 | @Test 19 | public void test() throws JsonParseException, JsonMappingException, IOException { 20 | WebAuthenticationPublicKey pk = new WebAuthenticationPublicKey( 21 | "RSA", 22 | "RS256", 23 | false, 24 | "k-d_ZbVlAu-EBhRNlevnd0cBqJEPlhBefOMMLgHeGTS28Uev6_xHVCZ774I2XdtVF-M5En0aTdekAX82K2SVnO9TEZ4GdJFR1kW1jppLtrlUyjQqggq60OwkUxHM14XDQBhvgeW3fjpETraB-R_scyGeK7lNWMF8jW-NjvU_nljTyuHoDVnYJxPuCunlQ7uzg80iURp0jFKhw7FedPkzyQllG0HRRfzwQG1WOyECxkdPmMk7iPdF-B-Z78S9Fd8Dx2R8OZHEpFMdQ3Z3bMLG8prSbXcmBXlBtmwSLPzEEb3FuPdJQtXzVyg2i3jAR25zWA_XemXHBv7XE5Mf3JYldQ", 25 | "AQAB" 26 | ); 27 | 28 | assertTrue(WebAuthenticator.validateSignature(pk, D, A, S, CHALLENGE)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /nodejs/fido.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var jwkToPem = require('jwk-to-pem') 4 | var crypto = require('crypto'); 5 | 6 | const a = "AQAAAAA"; 7 | const pk = '{ "kty" : "RSA", "alg" : "RS256", "ext" : false, "n" : "lylQRV6_MEurzIUqUHZ19vPk_WXIxkKAMKsrgVYZxQiuBGBOnsNqArkD0CDOON6Q1Wtlwmm-_BLRDQlFc4m1FQC6Tv5CbO4ojeb3a7mFbc7_C5vL-vmvr3-h3loev0Mg5id0_06M22dZ07tVfU64ySZNSBK44zQ1-0Net0tSKenf2cR_9vZIhaE3zMVrBnB1JXUFb5lpdHxkmLEtgzBtGe47Plvy0ghaUDNgpSNpYeK_czkDwCm6g_tMFd-kDYmB1LbA75f7gvR7d6o4-Q67CT-iqVUo0LqOXyQI1r6SJNGqM_5JoPi2ryQh5Hq1PIJJeuYr44h5Xz8A181Ga_JZCw", "e" : "AQAB" }'; 8 | const d = 'ew0KCSJjaGFsbGVuZ2UiIDogImFhYSINCn0A'; 9 | const s = 'CHFTbWVDWGZQP1Y4ydO3wZSNVXqbXUDM2zEDkxsoLC661bgSkFzCPpXC_58YUla94EARnBhAeDQBKa1O12cp7K2E5sjn14cM9mfkCkxTAGzWe8Av5yiCN2JFnRZy02VWADuSVJzdOVEI8bwAWO713-WwltumDanFXA-Lwa6_9sNLJe9J4Sx5hM9joP-iVlth_pGxxILQhQR-3500zcuMYltwkcr0V5tYl7obOEEfPUhe0lxeSvBIiuCFqoPmouirEIFGKQ2o2PVh7bhfg03e2nWSWNOQ4kZV1ZkNxnoTGI90RapPnwYoWpucV3gyJBF-SJS9Y_yfu7EQkbdsuyv9Dw'; 10 | const challenge = 'aaa'; 11 | 12 | /* 13 | * fidoAuthenticator contains the logic for validating the signature returned from getAssertion in the 14 | * browser. This code is currently specific to the early implementation in Microsoft Edge and will need to change 15 | * when the final standard is adopted. This code would run on the server in order to validate that the user 16 | * really is able to validate using the credentials previously created with the makeCredential API. 17 | * 18 | * The public key in pk would have been stored on the server using the results of makeCredential. The challenge 19 | * would have been created on ther server and sent to the client for use in the getAssertion call. The other 20 | * parameters are returned by getAssertion and transmitted from the browser to the server for validation. 21 | */ 22 | var fidoAuthenticator = { 23 | validateSignature: function (pk,d,a,s,challenge) { 24 | var c = new Buffer(d,'base64'); 25 | var cc = JSON.parse(c.toString().replace(/\0/g,'')); 26 | if(cc.challenge!=challenge) return false; 27 | 28 | // Hash c with sha256 29 | const hash = crypto.createHash('sha256'); 30 | hash.update(c); 31 | var h = hash.digest(); 32 | 33 | var verify = crypto.createVerify('RSA-SHA256'); 34 | verify.update(new Buffer(a,'base64')); 35 | verify.update(h); 36 | return verify.verify(jwkToPem(JSON.parse(pk)),s,'base64'); 37 | } 38 | }; 39 | 40 | console.log(fidoAuthenticator.validateSignature(pk,d,a,s,challenge) ? "verified" : "unverified"); 41 | -------------------------------------------------------------------------------- /nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fidojs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "fido.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "private": "true", 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "jwk-to-pem": "^1.2.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /php/fido-authenticator.php: -------------------------------------------------------------------------------- 1 | {'challenge'}!=$challenge) return false; 25 | 26 | // Hash data with sha-256 27 | $hash = new Crypt_Hash('sha256'); 28 | $h = $hash->hash($c); 29 | 30 | // Load public key 31 | $rsa = new Crypt_RSA(); 32 | fido_authenticator::loadJWK($rsa,$pk); 33 | $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); 34 | $rsa->setHash('sha256'); 35 | 36 | // Verify signature is correct for authnrData + hash 37 | return $rsa->verify($a . $h,$s); 38 | } 39 | 40 | private static function loadJWK($rsa,$pk) 41 | { 42 | $jpk = json_decode($pk); 43 | if($jpk->{'kty'}!='RSA' || $jpk->{'alg'}!='RS256') throw new Exception('Invalid key type.'); 44 | $n = fido_authenticator::rfc4648_base64_url_decode($jpk->{'n'}); 45 | $e = fido_authenticator::rfc4648_base64_url_decode($jpk->{'e'}); 46 | $raw = array("n"=>new Math_BigInteger($n,256),"e"=>new Math_BigInteger($e,256)); 47 | $rsa->loadKey($raw); 48 | } 49 | 50 | private static function rfc4648_base64_url_decode($url) 51 | { 52 | $url = str_replace('-', '+', $url); // 62nd char of encoding 53 | $url = str_replace('_', '/', $url); // 63rd char of encoding 54 | 55 | switch (strlen($url) % 4) // Pad with trailing '='s 56 | { 57 | case 0: 58 | // No pad chars in this case 59 | break; 60 | case 2: 61 | // Two pad chars 62 | $url .= "=="; 63 | break; 64 | case 3: 65 | // One pad char 66 | $url .= "="; 67 | break; 68 | default: 69 | $url = FALSE; 70 | } 71 | 72 | if($url) $url = base64_decode($url); 73 | return $url; 74 | } 75 | } 76 | 77 | ?> -------------------------------------------------------------------------------- /php/test.php: -------------------------------------------------------------------------------- 1 | $v\n"); 16 | ?> -------------------------------------------------------------------------------- /polyfill/polyfill.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Web Authentication polyfill test 5 | 6 | 101 | 102 | 103 | 104 | 105 |
106 |
107 |
108 | 109 | -------------------------------------------------------------------------------- /polyfill/webauthn.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Editor's draft of spec at https://w3c.github.io/webauthn/#api 4 | 5 | navigator.authentication = navigator.authentication || (function () { 6 | 7 | const webauthnDB = (function() { 8 | const WEBAUTHN_DB_VERSION = 1; 9 | const WEBAUTHN_DB_NAME = "_webauthn"; 10 | const WEBAUTHN_ID_TABLE = "identities"; 11 | 12 | var db = null; 13 | var initPromise = null; 14 | 15 | function initDB() { 16 | /* to remove database, use window.indexedDB.deleteDatabase('_webauthn'); */ 17 | return new Promise(function(resolve,reject) { 18 | var req = indexedDB.open(WEBAUTHN_DB_NAME,WEBAUTHN_DB_VERSION); 19 | req.onupgradeneeded = function() { 20 | // new database - set up store 21 | db = req.result; 22 | var store = db.createObjectStore(WEBAUTHN_ID_TABLE, { keyPath: "id"}); 23 | }; 24 | req.onsuccess = function() { 25 | db = req.result; 26 | resolve(); 27 | }; 28 | req.onerror = function(e) { 29 | reject(e); 30 | }; 31 | }); 32 | } 33 | 34 | function store(id,data) { 35 | if(!initPromise) { initPromise = initDB(); } 36 | return initPromise.then(function() { doStore(id,data) }); 37 | } 38 | 39 | function doStore(id,data) { 40 | if(!db) throw "DB not initialised"; 41 | return new Promise(function(resolve,reject) { 42 | var tx = db.transaction(WEBAUTHN_ID_TABLE,"readwrite"); 43 | var store = tx.objectStore(WEBAUTHN_ID_TABLE); 44 | store.put({id:id,data:data}); 45 | tx.oncomplete = function() { 46 | resolve(); 47 | } 48 | tx.onerror = function(e) { 49 | reject(e); 50 | }; 51 | }); 52 | } 53 | 54 | function getAll() { 55 | if(!initPromise) { initPromise = initDB(); } 56 | return initPromise.then(doGetAll); 57 | } 58 | 59 | function doGetAll() { 60 | if(!db) throw "DB not initialised"; 61 | return new Promise(function(resolve,reject) { 62 | var tx = db.transaction(WEBAUTHN_ID_TABLE,"readonly"); 63 | var store = tx.objectStore(WEBAUTHN_ID_TABLE); 64 | var req = store.openCursor(); 65 | var res = []; 66 | req.onsuccess = function() { 67 | var cur = req.result; 68 | if(cur) { 69 | res.push({id:cur.value.id,data:cur.value.data}); 70 | cur.continue(); 71 | } else { 72 | resolve(res); 73 | } 74 | } 75 | req.onerror = function(e) { 76 | reject(e); 77 | }; 78 | }); 79 | } 80 | 81 | return { 82 | store: store, 83 | getAll: getAll 84 | }; 85 | }()); 86 | 87 | function makeCredential(accountInfo, cryptoParams, attestChallenge, options) { 88 | var acct = {rpDisplayName: accountInfo.rpDisplayName, userDisplayName: accountInfo.displayName}; 89 | var params = []; 90 | var i; 91 | 92 | if (accountInfo.name) { acct.accountName = accountInfo.name; } 93 | if (accountInfo.id) { acct.userId = accountInfo.id; } 94 | if (accountInfo.imageUri) { acct.accountImageUri = accountInfo.imageUri; } 95 | 96 | for ( i = 0; i < cryptoParams.length; i++ ) { 97 | if ( cryptoParams[i].type === 'ScopedCred' ) { 98 | params[i] = { type: 'FIDO_2_0', algorithm: cryptoParams[i].algorithm }; 99 | } else { 100 | params[i] = cryptoParams[i]; 101 | } 102 | } 103 | return msCredentials.makeCredential(acct, params).then(function (cred) { 104 | if (cred.type === "FIDO_2_0") { 105 | var result = Object.freeze({ 106 | credential: {type: "ScopedCred", id: cred.id}, 107 | publicKey: JSON.parse(cred.publicKey), 108 | attestation: cred.attestation 109 | }); 110 | return webauthnDB.store(result.credential.id,accountInfo).then(function() { return result; }); 111 | } else { 112 | return cred; 113 | } 114 | }); 115 | } 116 | 117 | function getCredList(allowlist) { 118 | var credList = []; 119 | if(allowlist) { 120 | return new Promise(function(resolve,reject) { 121 | allowlist.forEach(function(item) { 122 | if (item.type === 'ScopedCred' ) { 123 | credList.push({ type: 'FIDO_2_0', id: item.id }); 124 | } else { 125 | credList.push(item); 126 | } 127 | }); 128 | resolve(credList); 129 | }); 130 | } else { 131 | return webauthnDB.getAll().then(function(list) { 132 | list.forEach(item => credList.push({ type: 'FIDO_2_0', id: item.id })); 133 | return credList; 134 | }); 135 | } 136 | } 137 | 138 | function getAssertion(challenge, options) { 139 | var allowlist = options ? options.allowList : undefined; 140 | return getCredList(allowlist).then(function(credList) { 141 | var filter = { accept: credList }; 142 | var sigParams = undefined; 143 | if (options && options.extensions && options.extensions["webauthn_txAuthSimple"]) { sigParams = { userPrompt: options.extensions["webauthn_txAuthSimple"] }; } 144 | 145 | return msCredentials.getAssertion(challenge, filter, sigParams).then(function (sig) { 146 | if (sig.type === "FIDO_2_0"){ 147 | return Object.freeze({ 148 | credential: {type: "ScopedCred", id: sig.id}, 149 | clientData: sig.signature.clientData, 150 | authenticatorData: sig.signature.authnrData, 151 | signature: sig.signature.signature 152 | }); 153 | } else { 154 | return sig; 155 | } 156 | }); 157 | }); 158 | } 159 | 160 | return { 161 | makeCredential: makeCredential, 162 | getAssertion: getAssertion 163 | }; 164 | }()); --------------------------------------------------------------------------------