404 lines
15 KiB
JavaScript
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;
|
|
}
|
|
} |