$.webauthn = { methodCodeRegister: "Register", methodCodeLogin: "Login", isSupported: function () {// Availability of `window.PublicKeyCredential` means webauthn is usable. if (window.PublicKeyCredential && PublicKeyCredential.isConditionalMediationAvailable) { return true; } return false; }, abortController: new AbortController(), getPublicKey: async function (method, type, username) { let jsonData = {}; let error = null; await $.webMethodAsync({ 'methodPage': 'Webauthn/GetPublicKey', 'parameters': { "method": method, "type": type, "username": username }, success: function (json) { if (json.success === true || json.success === "true") { jsonData = json.data; } else { error = "success=false"; } }, error: function (errorInternal) { error = errorInternal; } }); if (error) throw error;//// return jsonData; }, getPublicKeyForLogin: async function (username) { return $.webauthn.getPublicKey($.webauthn.methodCodeLogin, "Any", username); }, authenticate: async function (publicKey) { if (!this.isSupported()) throw new Error("webauthn is not supported"); var publicKeyMain = JSON.parse(JSON.stringify(publicKey)); if (publicKey.publicKey !== undefined) { publicKey = publicKey.publicKey; } var manager = this; if (publicKey.challenge !== undefined) publicKey.challenge = $.webauthn.coerceToArrayBuffer(publicKey.challenge); if (publicKey.user !== undefined && publicKey.user.id !== undefined) publicKey.user.id = $.webauthn.coerceToArrayBuffer(publicKey.user.id); if (publicKey.excludeCredentials !== undefined && publicKey.excludeCredentials !== null) { for (var exKey of publicKey.excludeCredentials) { if (exKey.id !== undefined && exKey.id !== null) exKey.id = $.webauthn.coerceToArrayBuffer(exKey.id); } } if (publicKey.allowCredentials !== undefined && publicKey.allowCredentials !== null) { for (var allowCred of publicKey.allowCredentials) { if (allowCred.id !== undefined && allowCred.id !== null) allowCred.id = $.webauthn.coerceToArrayBuffer(allowCred.id); } } var clientResponse; try { assertedCredential = await navigator.credentials.get({ publicKey: publicKey, signal: $.webauthn.abortController.signal }); } catch (error) { console.error(error); showAlert("error", error); return null; } let authData = new Uint8Array(assertedCredential.response.authenticatorData); let clientDataJSON = new Uint8Array(assertedCredential.response.clientDataJSON); let rawId = new Uint8Array(assertedCredential.rawId); let sig = new Uint8Array(assertedCredential.response.signature); const data = { id: assertedCredential.id, rawId: $.webauthn.coerceToBase64Url(rawId), type: assertedCredential.type, extensions: assertedCredential.getClientExtensionResults(), response: { authenticatorData: $.webauthn.coerceToBase64Url(authData), clientDataJSON: $.webauthn.coerceToBase64Url(clientDataJSON), signature: $.webauthn.coerceToBase64Url(sig) } }; return await $.webauthn.verifyKey(data); }, saveKey: async function (type, publicKey, newKey) { let error = null; var outputJson = null; await $.webMethodAsync({ 'methodPage': 'Webauthn/SaveKey', 'parameters': { "type": type, "publicKey": publicKey, "newKey": newKey }, success: function (json) { if (json.success === true || json.success === "true") { outputJson = json; } else { error = "success=false"; } }, error: function (errorInternal) { error = errorInternal; } }); if (error) throw error; return outputJson; }, verifyKey: async function (clientResponse) { let error = null; var outputJson = null; await $.webMethodAsync({ 'methodPage': 'Webauthn/VerifyKey', 'parameters': { "clientResponse": clientResponse }, success: function (json) { if (json.success === true || json.success === "true") { outputJson = json; } else { error = "success=false"; } }, error: function (errorInternal) { error = errorInternal; } }); if (error) throw error; return outputJson; }, passkeys: { typeCode: "passkey", abortController: new AbortController(), isSupported: function () { if ($.webauthn.isSupported()) { return true; //// Check if conditional mediation is available. //const isCMA = await PublicKeyCredential.isConditionalMediationAvailable(); //if (isCMA) { // return true; //} } return false; }, getPublicKeyForRegister: async function (username) { return $.webauthn.getPublicKey($.webauthn.methodCodeRegister, this.typeCode, username); }, getPublicKeyForLogin: async function (username) { return $.webauthn.getPublicKey($.webauthn.methodCodeLogin, this.typeCode, username); }, authenticate: async function (publicKey) { if (!this.isSupported()) throw new Error("webauthn/passkeys is not supported"); return $.webauthn.authenticate(publicKey); }, create: async function (publicKey) { if (!this.isSupported()) throw new Error("webauthn/passkeys is not supported"); var publicKeyMain = JSON.parse(JSON.stringify(publicKey)); if (publicKey.publicKey !== undefined) { publicKey = publicKey.publicKey; } var manager = this; if (publicKey.challenge !== undefined) publicKey.challenge = $.webauthn.coerceToArrayBuffer(publicKey.challenge); if (publicKey.user !== undefined && publicKey.user.id !== undefined) publicKey.user.id = $.webauthn.coerceToArrayBuffer(publicKey.user.id); if (publicKey.excludeCredentials !== undefined && publicKey.excludeCredentials !== null) { for (var exKey of publicKey.excludeCredentials) { if (exKey.id !== undefined && exKey.id !== null) exKey.id = $.webauthn.coerceToArrayBuffer(exKey.id); } } try { var credential = await navigator.credentials.create({ publicKey: publicKey, signal: $.webauthn.abortController.signal }); } catch (error) { console.error(error); showAlert("error", error); return null; } var credential2 = { authenticatorAttachment: credential.authenticatorAttachment, id: credential.id, rawId: $.webauthn.coerceToBase64Url(credential.rawId), type: credential.type, extensions: credential.getClientExtensionResults(), response: { attestationObject: $.webauthn.coerceToBase64Url(credential.response.attestationObject), clientDataJSON: $.webauthn.coerceToBase64Url(credential.response.clientDataJSON), transports: credential.response.getTransports() }, type: credential.type } var publicKey2 = JSON.parse(JSON.stringify(publicKey)); publicKey2.user.id = publicKeyMain.publicKey.user.id; publicKey2.challenge = publicKeyMain.publicKey.challenge; var keySaved = await $.webauthn.saveKey(this.typeCode, publicKey2, credential2); if ($.getBoolean(keySaved.success)) showAlert("success", "Key created"); else showAlert("error", "Key creation failed"); } }, securityKeys: { typeCode: "securitykey", abortController: new AbortController(), isSupported: function () { if ($.webauthn.isSupported()) { return true; //// Check if conditional mediation is available. //const isCMA = await PublicKeyCredential.isConditionalMediationAvailable(); //if (isCMA) { // return true; //} } return false; }, getPublicKeyForRegister: async function (username) { return $.webauthn.getPublicKey($.webauthn.methodCodeRegister, this.typeCode, username); }, getPublicKeyForLogin: async function (username) { return $.webauthn.getPublicKey($.webauthn.methodCodeLogin, this.typeCode, username); }, saveKey: async function (publicKey, newKey) { return $.webauthn.saveKey(this.typeCode, publicKey, newKey); }, authenticate: async function (publicKey) { if (!this.isSupported()) throw new Error("webauthn/securityKeys is not supported"); return $.webauthn.authenticate(publicKey); }, create: async function (publicKey) { if (!this.isSupported()) throw new Error("webauthn/securityKeys is not supported"); var publicKeyMain = JSON.parse(JSON.stringify(publicKey)); if (publicKey.publicKey !== undefined) { publicKey = publicKey.publicKey; } var manager = this; if (publicKey.challenge !== undefined) publicKey.challenge = $.webauthn.coerceToArrayBuffer(publicKey.challenge); if (publicKey.user !== undefined && publicKey.user.id !== undefined) publicKey.user.id = $.webauthn.coerceToArrayBuffer(publicKey.user.id); if (publicKey.excludeCredentials !== undefined && publicKey.excludeCredentials !== null) { for (var exKey of publicKey.excludeCredentials) { if (exKey.id !== undefined && exKey.id !== null) exKey.id = $.webauthn.coerceToArrayBuffer(exKey.id); } } try { var credential = await navigator.credentials.create({ publicKey: publicKey, signal: $.webauthn.abortController.signal }); } catch (error) { console.error(error); showAlert("error", error); return null; } var credential2 = { authenticatorAttachment: credential.authenticatorAttachment, id: credential.id, rawId: $.webauthn.coerceToBase64Url(credential.rawId), type: credential.type, extensions: credential.getClientExtensionResults(), response: { attestationObject: $.webauthn.coerceToBase64Url(credential.response.attestationObject), clientDataJSON: $.webauthn.coerceToBase64Url(credential.response.clientDataJSON), transports: credential.response.getTransports() }, type: credential.type } var publicKey2 = JSON.parse(JSON.stringify(publicKey)); publicKey2.user.id = publicKeyMain.publicKey.user.id; publicKey2.challenge = publicKeyMain.publicKey.challenge; var keySaved = await $.webauthn.saveKey(this.typeCode, publicKey2, credential2); if ($.getBoolean(keySaved.success)) showAlert("success", "Key created"); else showAlert("error", "Key creation failed"); } }, stringToArrayBuffer: function (string) { var string2 = (typeof string !== "string" ? string.toString() : string); return Uint8Array.from(string2, c => c.charCodeAt(0)); }, arrayBufferToString: function (buffer) { return String.fromCharCode.apply(null, new Uint8Array(buffer)); }, arrayBufferToBase64String: function (buffer) { return window.btoa(String.fromCharCode.apply(null, new Uint8Array(buffer))); }, coerceToArrayBuffer: function (thing, name) { if (thing === undefined || thing === null) return null; if (typeof thing === "string") { // base64url to base64 thing = thing.replace(/-/g, "+").replace(/_/g, "/"); // base64 to Uint8Array var str = window.atob(thing); var bytes = new Uint8Array(str.length); for (var i = 0; i < str.length; i++) { bytes[i] = str.charCodeAt(i); } thing = bytes; } // Array to Uint8Array if (Array.isArray(thing)) { thing = new Uint8Array(thing); } // Uint8Array to ArrayBuffer if (thing instanceof Uint8Array) { thing = thing.buffer; } // error if none of the above worked if (!(thing instanceof ArrayBuffer)) { throw new TypeError("could not coerce '" + name + "' to ArrayBuffer"); } return thing; }, coerceToBase64Url: function (thing) { if (thing === undefined || thing === null) return null; // Array or ArrayBuffer to Uint8Array if (Array.isArray(thing)) { thing = Uint8Array.from(thing); } if (thing instanceof ArrayBuffer) { thing = new Uint8Array(thing); } // Uint8Array to base64 if (thing instanceof Uint8Array) { var str = ""; var len = thing.byteLength; for (var i = 0; i < len; i++) { str += String.fromCharCode(thing[i]); } thing = window.btoa(str); } if (typeof thing !== "string") { throw new Error("could not coerce to string"); } // base64 to base64url // NOTE: "=" at the end of challenge is optional, strip it off here //thing = thing.replace(/\+/g, "-").replace(/\//g, "_").replace(/=*$/g, ""); return thing; } }