function supportsWebAuthn() { return !("undefined" == typeof PublicKeyCredential || typeof PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable !== "function"); } function autoCheckDeviceType() { if (supportsWebAuthn()) { PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().then(available => { document.getElementById("WebAuthnDeviceTypePlatform").checked = available; document.getElementById("WebAuthnDeviceTypeCrossPlatform").checked = !available; }); } } function createWebAuthnCred(theobj) { try { /* Expected format: { "challenge": "bUe3HhZi4QcJ350Fb99PD42bDzhduPg_8HeAW8g9MSc", "id": "NzZwIbe_yhDDYOY5OUO_N_ffbTM7cbdvxtaNPFOiXHM", "name": "stewie", "displayName": "stewie", "origin": "malt.acme.com" } */ var createCredOpts = { challenge: stringToArrayBuffer(theobj.challenge), rp: { name: theobj.origin }, user: { id: stringToArrayBuffer(theobj.id), name: theobj.name, displayName: theobj.displayName }, pubKeyCredParams: [ { type: "public-key", alg: -7 }, // External authenticators support ES256 { type: "public-key", alg: -257 } // Windows Hello supports RS256 ], authenticatorSelection: { requireResidentKey: false, userVerification: "discouraged", // "required" for ators with 2nd factor (e.g. PIN, Bio) works with Windows Hello and MS Edge, NOT with FIDO U2F though authenticatorAttachment: $("input[name='WebAuthnDeviceType']:checked").val() }, timeout: WEBAUTHN_REGISTER_TIMEOUT * 1000, excludeCredentials: [], // prevent re-registration by specifying existing credentials here attestation: "none" //specifies whether you need an attestation statement }; return navigator.credentials.create({ publicKey: createCredOpts }) .then( function (newCredInfo) { var attestation = { id: base64encode(newCredInfo.rawId), clientDataJSON: JSON.parse(arrayBufferToString(newCredInfo.response.clientDataJSON)), attestationObject: base64encode(newCredInfo.response.attestationObject) }; attestation.aoJSON = CBOR.decode(stringToArrayBuffer(atob(attestation.attestationObject))); attestation.aoAuthData = base64encode(attestation.aoJSON.authData); //console.log(attestation); var frm = document.getElementById("WebAuthnForm"); frm.elements[PG_FLDNAME_GENERICDATA].value = JSON.stringify(attestation); frm.elements[PG_FLDNAME_ACCTSTEP].value = "2"; submitWebAuthnEnroll(); }) .catch(function() { console.log("Failed registering new WebAuthn device!"); var thediv = document.getElementById("ErrMsgWebAuthnEnroll"); setElemContentDirect(getWebAuthnRegisterFailed(), thediv); }); } catch (e) { console.log(formatException("createWebAuthnCred()", e)); } } function verifyWebAuthnCred(theobj, frmDest) { var getAssertOpts = { allowCredentials: [{ type: "public-key", id: Uint8Array.from(atob(theobj.credID), c=>c.charCodeAt(0)).buffer }], challenge: stringToArrayBuffer(theobj.challenge), timeout: WEBAUTHN_SIGNIN_TIMEOUT * 1000 }; return navigator.credentials.get({ publicKey: getAssertOpts }) .then(rawAssert => { var assertion = { id: base64encode(rawAssert.rawId), clientDataJSONBlock: arrayBufferToString(rawAssert.response.clientDataJSON), clientDataJSON: JSON.parse(arrayBufferToString(rawAssert.response.clientDataJSON)), userHandle: base64encode(rawAssert.response.userHandle), signature: base64encode(rawAssert.response.signature), authenticatorData: base64encode(rawAssert.response.authenticatorData) }; //console.log(assertion); frmDest.elements[PG_FLDNAME_OTP].value = JSON.stringify(assertion); enterKeySubmit(); }) .catch(e => { // Things to do when operation times out or is cancelled // 2021-01-31: If passwordless, revert auth type so Enter key works... if (PG_AUTHTYPE_FIDO2_PASSWORDLESS == AUTHTYPE) { setAuthType(PG_AUTHTYPE_LOGIN); } //frmDest.elements[PG_FLDNAME_OTP].value = ""; }); } function base64encode(arrayBuffer) { if (!arrayBuffer || arrayBuffer.length == 0) return undefined; return btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))); } function arrayBufferToString(arrayBuffer) { return String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)); } function stringToArrayBuffer(str){ return Uint8Array.from(str, c => c.charCodeAt(0)).buffer; }