404 lines
15 KiB
JavaScript

$.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;
}
}