blob: 6efde8a8a5fa6830418df1a2562a0d357b27e1a2 [file] [log] [blame]
const testCredentialIdBase64 = "SMSXHngF7hEOsElA73C3RY+8bR4=";
const testES256PrivateKeyBase64 =
"BDj/zxSkzKgaBuS3cdWDF558of8AaIpgFpsjF/Qm1749VBJPgqUIwfhWHJ91nb7U" +
"PH76c0+WFOzZKslPyyFse4goGIW2R7k9VHLPEZl5nfnBgEVFh5zev+/xpHQIvuq6" +
"RQ==";
const testRpId = "localhost";
const testUserhandleBase64 = "AAECAwQFBgcICQ==";
const testAttestationCertificateBase64 =
"MIIB6jCCAZCgAwIBAgIGAWHAxcjvMAoGCCqGSM49BAMCMFMxJzAlBgNVBAMMHkJh" +
"c2ljIEF0dGVzdGF0aW9uIFVzZXIgU3ViIENBMTETMBEGA1UECgwKQXBwbGUgSW5j" +
"LjETMBEGA1UECAwKQ2FsaWZvcm5pYTAeFw0xODAyMjMwMzM3MjJaFw0xODAyMjQw" +
"MzQ3MjJaMGoxIjAgBgNVBAMMGTAwMDA4MDEwLTAwMEE0OUEyMzBBMDIxM0ExGjAY" +
"BgNVBAsMEUJBQSBDZXJ0aWZpY2F0aW9uMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMw" +
"EQYDVQQIDApDYWxpZm9ybmlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvCje" +
"Pzr6Sg76XMoHuGabPaG6zjpLFL8Zd8/74Hh5PcL2Zq+o+f7ENXX+7nEXXYt0S8Ux" +
"5TIRw4hgbfxXQbWLEqM5MDcwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBPAw" +
"FwYJKoZIhvdjZAgCBAowCKEGBAR0ZXN0MAoGCCqGSM49BAMCA0gAMEUCIAlK8A8I" +
"k43TbvKuYGHZs1DTgpTwmKTBvIUw5bwgZuYnAiEAtuJjDLKbGNJAJFMi5deEBqno" +
"pBTCqbfbDJccfyQpjnY=";
const testAttestationIssuingCACertificateBase64 =
"MIICIzCCAaigAwIBAgIIeNjhG9tnDGgwCgYIKoZIzj0EAwIwUzEnMCUGA1UEAwwe" +
"QmFzaWMgQXR0ZXN0YXRpb24gVXNlciBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ" +
"bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTE3MDQyMDAwNDIwMFoXDTMyMDMy" +
"MjAwMDAwMFowUzEnMCUGA1UEAwweQmFzaWMgQXR0ZXN0YXRpb24gVXNlciBTdWIg" +
"Q0ExMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMFkw" +
"EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoSZ/1t9eBAEVp5a8PrXacmbGb8zNC1X3" +
"StLI9YO6Y0CL7blHmSGmjGWTwD4Q+i0J2BY3+bPHTGRyA9jGB3MSbaNmMGQwEgYD" +
"VR0TAQH/BAgwBgEB/wIBADAfBgNVHSMEGDAWgBSD5aMhnrB0w/lhkP2XTiMQdqSj" +
"8jAdBgNVHQ4EFgQU5mWf1DYLTXUdQ9xmOH/uqeNSD80wDgYDVR0PAQH/BAQDAgEG" +
"MAoGCCqGSM49BAMCA2kAMGYCMQC3M360LLtJS60Z9q3vVjJxMgMcFQ1roGTUcKqv" +
"W+4hJ4CeJjySXTgq6IEHn/yWab4CMQCm5NnK6SOSK+AqWum9lL87W3E6AA1f2TvJ" +
"/hgok/34jr93nhS87tOQNdxDS8zyiqw=";
const testDummyMessagePayloadBase64 =
"/////wYAEQABAgMEBQYHAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAEQADoAAQIDBAUGBwECAwQAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgMEAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
const testCreationMessageBase64 =
"AKMBZnBhY2tlZAJYxEbMf7lnnVWy25CS4cjZ5eHQK3WA8LSBLHcJYuHkj1rYQQAA" +
"AE74oBHzjApNFYAGFxEfntx9AEAoCK3O6P5OyXN6V/f+9nAga0NA2Cgp4V3mgSJ5" +
"jOHLMDrmxp/S0rbD+aihru1C0aAN3BkiM6GNy5nSlDVqOgTgpQECAyYgASFYIEFb" +
"he3RkNud6sgyraBGjlh1pzTlCZehQlL/b18HZ6WGIlggJgfUd/en9p5AIqMQbUni" +
"nEeXdFLkvW0/zV5BpEjjNxADo2NhbGcmY3NpZ1hHMEUCIQDKg+ZBmEBtf0lWq4Re" +
"dH4/i/LOYqOR4uR2NAj2zQmw9QIgbTXb4hvFbj4T27bv/rGrc+y+0puoYOBkBk9P" +
"mCewWlNjeDVjgVkCwjCCAr4wggGmoAMCAQICBHSG/cIwDQYJKoZIhvcNAQELBQAw" +
"LjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEw" +
"IBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG8xCzAJBgNVBAYTAlNF" +
"MRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0" +
"ZXN0YXRpb24xKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDE5NTUwMDM4" +
"NDIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASVXfOt9yR9MXXv/ZzE8xpOh466" +
"4YEJVmFQ+ziLLl9lJ79XQJqlgaUNCsUvGERcChNUihNTyKTlmnBOUjvATevto2ww" +
"ajAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgyLjEuMTATBgsrBgEEAYLl" +
"HAIBAQQEAwIFIDAhBgsrBgEEAYLlHAEBBAQSBBD4oBHzjApNFYAGFxEfntx9MAwG" +
"A1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBADFcSIDmmlJ+OGaJvWn9Cqhv" +
"SeueToVFQVVvqtALOgCKHdwB+Wx29mg2GpHiMsgQp5xjB0ybbnpG6x212FxESJ+G" +
"inZD0ipchi7APwPlhIvjgH16zVX44a4e4hOsc6tLIOP71SaMsHuHgCcdH0vg5d2s" +
"c006WJe9TXO6fzV+ogjJnYpNKQLmCXoAXE3JBNwKGBIOCvfQDPyWmiiG5bGxYfPt" +
"y8Z3pnjX+1MDnM2hhr40ulMxlSNDnX/ZSnDyMGIbk8TOQmjTF02UO8auP8k3wt5D" +
"1rROIRU9+FCSX5WQYi68RuDrGMZB8P5+byoJqbKQdxn2LmE1oZAyohPAmLcoPO4=";
const testHidCredentialIdBase64 =
"KAitzuj-Tslzelf3_vZwIGtDQNgoKeFd5oEieYzhyzA65saf0tK2w_mooa7tQtGg" +
"DdwZIjOhjcuZ0pQ1ajoE4A";
const testAssertionMessageBase64 =
"AKMBomJpZFhAKAitzuj+Tslzelf3/vZwIGtDQNgoKeFd5oEieYzhyzA65saf0tK2" +
"w/mooa7tQtGgDdwZIjOhjcuZ0pQ1ajoE4GR0eXBlanB1YmxpYy1rZXkCWCVGzH+5" +
"Z51VstuQkuHI2eXh0Ct1gPC0gSx3CWLh5I9a2AEAAABQA1hHMEUCIQCSFTuuBWgB" +
"4/F0VB7DlUVM09IHPmxe1MzHUwRoCRZbCAIgGKov6xoAx2MEf6/6qNs8OutzhP2C" +
"QoJ1L7Fe64G9uBc=";
const testU2fApduNoErrorOnlyResponseBase64 = "kAA=";
const testU2fApduInsNotSupportedOnlyResponseBase64 = "bQA=";
const testU2fApduWrongDataOnlyResponseBase64 = "aoA=";
const testU2fApduConditionsNotSatisfiedOnlyResponseBase64 = "aYU=";
const testU2fRegisterResponse =
"BQTodiWJbuTkbcAydm6Ah5YvNt+d/otWfzdjAVsZkKYOFCfeYS1mQYvaGVBYHrxc" +
"jB2tcQyxTCL4yXBF9GEvsgyRQD69ib937FCXVe6cJjXvqqx7K5xc7xc2w3F9pIU0" +
"yMa2VNf/lF9QtcxOeAVb3TlrZPeNosX5YgDM1BXNCP5CADgwggJKMIIBMqADAgEC" +
"AgQEbIgiMA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNVBAMTI1l1YmljbyBVMkYgUm9v" +
"dCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgwMTAwMDAwMFoYDzIwNTAwOTA0" +
"MDAwMDAwWjAsMSowKAYDVQQDDCFZdWJpY28gVTJGIEVFIFNlcmlhbCAyNDkxODIz" +
"MjQ3NzAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ8yrksy5cofujmOUN+IfzW" +
"tvFlstWj89sTHTHBa3QrtHbY0emQgOtUbJu99VbmIQ/UJ4WJnnjMWJ6+MQ9s25/0" +
"ozswOTAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgyLjEuMjATBgsrBgEE" +
"AYLlHAIBAQQEAwIEMDANBgkqhkiG9w0BAQsFAAOCAQEAn5sFIki8TPQsxZkfyqus" +
"m2Ubvlvc3I7wrSwcH/s20YcV1C54skkiT5LH5uegXEnw5+TIgb8ulPReSiGDPXRW" +
"hR0PbBRaKVQMh08wksk0tD0iK4liwPQQzvHbdYkq8Ra0Spb101reo4IvxxRvYAQ4" +
"W8tptlyZ5+tpGXhnA8DYzUHo91zKRKqKtyWtjnmf86hpam8bJlbmMbHkAYPAj9pT" +
"+kqPhaBWk5RK4XmhM50ALRXKvYEAkOxyLvXe+ZZaNx1BXWJLaKJwfK2XvN0Xha+X" +
"4ljzPfVqAxqgNW2OjV68rcdOBxY2xrEQrOXMm5Df6srmQP8bsPH+XbTv96lfBgcz" +
"9TBFAiAyR3nGjzOAKIoRl7YJX3puubGxwSf2auEqmf6FMuwjuQIhAOOVFqxNYe5k" +
"BE1QtBWmpNTYS6bYlctat6GqfQgd40H6kAA=";
const testU2fCredentialIdBase64 =
"Pr2Jv3fsUJdV7pwmNe-qrHsrnFzvFzbDcX2khTTIxrZU1_-UX1C1zE54BVvdOWtk" +
"942ixfliAMzUFc0I_kIAOA";
const testU2fSignResponse =
"AQAAADswRAIge94KUqwfTIsn4AOjcM1mpMcRjdItVEeDX0W5nGhCP/cCIDxRe0eH" +
"f4V4LeEAhqeD0effTjY553H19q+jWq1Tc4WOkAA=";
const testCtapErrCredentialExcludedOnlyResponseBase64 = "GQ==";
const testCtapErrInvalidCredentialResponseBase64 = "Ig==";
const testNfcU2fVersionBase64 = "VTJGX1YykAA=";
const testNfcCtapVersionBase64 = "RklET18yXzCQAA==";
const testGetInfoResponseApduBase64 =
"AKYBgmZVMkZfVjJoRklET18yXzACgWtobWFjLXNlY3JldANQbUS6m/bsLkm5MAyP" +
"6SDLcwSkYnJr9WJ1cPVkcGxhdPRpY2xpZW50UGlu9AUZBLAGgQGQAA==";
const testCreationMessageApduBase64 =
"AKMBZnBhY2tlZAJYxEbMf7lnnVWy25CS4cjZ5eHQK3WA8LSBLHcJYuHkj1rYQQAA" +
"AE74oBHzjApNFYAGFxEfntx9AEAoCK3O6P5OyXN6V/f+9nAga0NA2Cgp4V3mgSJ5" +
"jOHLMDrmxp/S0rbD+aihru1C0aAN3BkiM6GNy5nSlDVqOgTgpQECAyYgASFYIEFb" +
"he3RkNud6sgyraBGjlh1pzTlCZehQlL/b18HZ6WGIlggJgfUd/en9p5AIqMQbUni" +
"nEeXdFLkvW0/zV5BpEjjNxADo2NhbGcmY3NpZ1hHMEUCIQDKg+ZBmEBtf0lWq4Re" +
"dH4/i/LOYqOR4uR2NAj2zQmw9QIgbTXb4hvFbj4T27bv/rGrc+y+0puoYOBkBk9P" +
"mCewWlNjeDVjgVkCwjCCAr4wggGmoAMCAQICBHSG/cIwDQYJKoZIhvcNAQELBQAw" +
"LjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEw" +
"IBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG8xCzAJBgNVBAYTAlNF" +
"MRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0" +
"ZXN0YXRpb24xKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDE5NTUwMDM4" +
"NDIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASVXfOt9yR9MXXv/ZzE8xpOh466" +
"4YEJVmFQ+ziLLl9lJ79XQJqlgaUNCsUvGERcChNUihNTyKTlmnBOUjvATevto2ww" +
"ajAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgyLjEuMTATBgsrBgEEAYLl" +
"HAIBAQQEAwIFIDAhBgsrBgEEAYLlHAEBBAQSBBD4oBHzjApNFYAGFxEfntx9MAwG" +
"A1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBADFcSIDmmlJ+OGaJvWn9Cqhv" +
"SeueToVFQVVvqtALOgCKHdwB+Wx29mg2GpHiMsgQp5xjB0ybbnpG6x212FxESJ+G" +
"inZD0ipchi7APwPlhIvjgH16zVX44a4e4hOsc6tLIOP71SaMsHuHgCcdH0vg5d2s" +
"c006WJe9TXO6fzV+ogjJnYpNKQLmCXoAXE3JBNwKGBIOCvfQDPyWmiiG5bGxYfPt" +
"y8Z3pnjX+1MDnM2hhr40ulMxlSNDnX/ZSnDyMGIbk8TOQmjTF02UO8auP8k3wt5D" +
"1rROIRU9+FCSX5WQYi68RuDrGMZB8P5+byoJqbKQdxn2LmE1oZAyohPAmLcoPO6Q" +
"AA==";
const testAssertionMessageApduBase64 =
"AKMBomJpZFhAKAitzuj+Tslzelf3/vZwIGtDQNgoKeFd5oEieYzhyzA65saf0tK2" +
"w/mooa7tQtGgDdwZIjOhjcuZ0pQ1ajoE4GR0eXBlanB1YmxpYy1rZXkCWCVGzH+5" +
"Z51VstuQkuHI2eXh0Ct1gPC0gSx3CWLh5I9a2AEAAABQA1hHMEUCIQCSFTuuBWgB" +
"4/F0VB7DlUVM09IHPmxe1MzHUwRoCRZbCAIgGKov6xoAx2MEf6/6qNs8OutzhP2C" +
"QoJ1L7Fe64G9uBeQAA==";
const RESOURCES_DIR = "/WebKit/webauthn/resources/";
function asciiToUint8Array(str)
{
var chars = [];
for (var i = 0; i < str.length; ++i)
chars.push(str.charCodeAt(i));
return new Uint8Array(chars);
}
// Builds a hex string representation for an array-like input.
// "bytes" can be an Array of bytes, an ArrayBuffer, or any TypedArray.
// The output looks like this:
// ab034c99
function bytesToHexString(bytes)
{
if (!bytes)
return null;
bytes = new Uint8Array(bytes);
var hexBytes = [];
for (var i = 0; i < bytes.length; ++i) {
var byteString = bytes[i].toString(16);
if (byteString.length < 2)
byteString = "0" + byteString;
hexBytes.push(byteString);
}
return hexBytes.join("");
}
function bytesToASCIIString(bytes)
{
return String.fromCharCode.apply(null, new Uint8Array(bytes));
}
var Base64URL = {
stringify: function (a) {
var base64string = btoa(String.fromCharCode.apply(0, a));
return base64string.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
},
parse: function (s) {
s = s.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, '');
return new Uint8Array(Array.prototype.map.call(atob(s), function (c) { return c.charCodeAt(0) }));
}
};
function hexStringToUint8Array(hexString)
{
if (hexString.length % 2 != 0)
throw "Invalid hexString";
var arrayBuffer = new Uint8Array(hexString.length / 2);
for (var i = 0; i < hexString.length; i += 2) {
var byteValue = parseInt(hexString.substr(i, 2), 16);
if (byteValue == NaN)
throw "Invalid hexString";
arrayBuffer[i/2] = byteValue;
}
return arrayBuffer;
}
function decodeAuthData(authDataUint8Array)
{
let authDataObject = { };
let pos = 0;
let size = 0;
// RP ID Hash
size = 32;
if (pos + size > authDataUint8Array.byteLength)
return { };
authDataObject.rpIdHash = authDataUint8Array.slice(pos, pos + size);
pos = pos + size;
// FLAGS
size = 1;
if (pos + size > authDataUint8Array.byteLength)
return { };
authDataObject.flags = authDataUint8Array.slice(pos, pos + size)[0];
pos = pos + size;
// Counter
size = 4;
if (pos + size > authDataUint8Array.byteLength)
return { };
authDataObject.counter = (authDataUint8Array[pos] << 24) + (authDataUint8Array[pos + 1] << 16) + (authDataUint8Array[pos + 2] << 8) + authDataUint8Array[pos + 3];
pos = pos + size;
if (pos == authDataUint8Array.byteLength)
return authDataObject;
// AAGUID
size = 16;
if (pos + size > authDataUint8Array.byteLength)
return { };
authDataObject.aaguid = authDataUint8Array.slice(pos, pos + size);
pos = pos + size;
// L
size = 2;
if (pos + size > authDataUint8Array.byteLength)
return { };
authDataObject.l = (authDataUint8Array[pos] << 8) + authDataUint8Array[pos + 1];
pos = pos + size;
// Credential ID
size = authDataObject.l;
if (pos + size > authDataUint8Array.byteLength)
return { };
authDataObject.credentialID = authDataUint8Array.slice(pos, pos + size);
pos = pos + size;
// Public Key
authDataObject.publicKey = CBOR.decode(authDataUint8Array.slice(pos).buffer);
if (!authDataObject.publicKey)
return { };
// Assume no extensions.
return authDataObject;
}
function concatenateBuffers(buffer1, buffer2)
{
let tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
}
// Very dirty asn1 decoder. Just works.
function extractRawSignature(asn1signature)
{
const signature = new Uint8Array(asn1signature);
let tmp = new Uint8Array(64);
const rStart = signature[3] - 32;
if (rStart >= 0)
tmp.set(new Uint8Array(signature.slice(4 + rStart, 36 + rStart)), 0);
else
tmp.set(new Uint8Array(signature.slice(4, 36 + rStart)), -rStart);
const sStart = signature[37 + rStart] - 32;
if (sStart >= 0)
tmp.set(new Uint8Array(signature.slice(38 + rStart + sStart)), 32);
else
tmp.set(new Uint8Array(signature.slice(38 + rStart)), 32 - sStart);
return tmp.buffer;
}
function waitForLoad()
{
return new Promise((resolve) => {
window.addEventListener('message', (message) => {
resolve(message);
});
});
}
function withCrossOriginIframe(resourceFile)
{
return new Promise((resolve) => {
waitForLoad().then((message) => {
resolve(message);
});
const frame = document.createElement("iframe");
frame.src = get_host_info().HTTPS_REMOTE_ORIGIN + RESOURCES_DIR + resourceFile;
document.body.appendChild(frame);
});
}
function promiseRejects(test, expected, promise, description)
{
return promise.then(test.unreached_func("Should have rejected: " + description)).catch(function(e) {
assert_throws(expected, function() { throw e }, description);
assert_equals(e.message, description);
});
}
// COSE Key Format: https://www.w3.org/TR/webauthn/#sctn-encoded-credPubKey-examples.
function checkPublicKey(publicKey)
{
if (publicKey['1'] != 2)
return false;
if (publicKey['3'] != -7)
return false;
if (publicKey['-1'] != 1)
return false;
if (publicKey['-2'].byteLength != 32)
return false;
if (publicKey['-3'].byteLength != 32)
return false;
return true;
}
function checkCtapMakeCredentialResult(credential, isNoneAttestation = true)
{
// Check response
assert_array_equals(Base64URL.parse(credential.id), Base64URL.parse(testHidCredentialIdBase64));
assert_equals(credential.type, 'public-key');
assert_array_equals(new Uint8Array(credential.rawId), Base64URL.parse(testHidCredentialIdBase64));
assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.create","challenge":"MTIzNDU2","origin":"https://localhost:9443"}');
assert_not_own_property(credential.getClientExtensionResults(), "appid");
// Check attestation
const attestationObject = CBOR.decode(credential.response.attestationObject);
if (isNoneAttestation)
assert_equals(attestationObject.fmt, "none");
else
assert_equals(attestationObject.fmt, "packed");
// Check authData
const authData = decodeAuthData(attestationObject.authData);
assert_equals(bytesToHexString(authData.rpIdHash), "46cc7fb9679d55b2db9092e1c8d9e5e1d02b7580f0b4812c770962e1e48f5ad8");
assert_equals(authData.flags, 65);
assert_equals(authData.counter, 78);
if (isNoneAttestation)
assert_equals(bytesToHexString(authData.aaguid), "00000000000000000000000000000000");
else
assert_equals(bytesToHexString(authData.aaguid), "f8a011f38c0a4d15800617111f9edc7d");
assert_array_equals(authData.credentialID, Base64URL.parse(testHidCredentialIdBase64));
// Check packed attestation
assert_true(checkPublicKey(authData.publicKey));
if (isNoneAttestation)
assert_object_equals(attestationObject.attStmt, { });
else {
assert_equals(attestationObject.attStmt.alg, -7);
assert_equals(attestationObject.attStmt.x5c.length, 1);
}
}
function checkU2fMakeCredentialResult(credential, isNoneAttestation = true)
{
// Check response
assert_array_equals(Base64URL.parse(credential.id), Base64URL.parse(testU2fCredentialIdBase64));
assert_equals(credential.type, 'public-key');
assert_array_equals(new Uint8Array(credential.rawId), Base64URL.parse(testU2fCredentialIdBase64));
assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.create","challenge":"MTIzNDU2","origin":"https://localhost:9443"}');
assert_not_own_property(credential.getClientExtensionResults(), "appid");
// Check attestation
const attestationObject = CBOR.decode(credential.response.attestationObject);
if (isNoneAttestation)
assert_equals(attestationObject.fmt, "none");
else
assert_equals(attestationObject.fmt, "fido-u2f");
// Check authData
const authData = decodeAuthData(attestationObject.authData);
assert_equals(bytesToHexString(authData.rpIdHash), "49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763");
assert_equals(authData.flags, 65);
assert_equals(authData.counter, 0);
assert_equals(bytesToHexString(authData.aaguid), "00000000000000000000000000000000");
assert_array_equals(authData.credentialID, Base64URL.parse(testU2fCredentialIdBase64));
// Check fido-u2f attestation
assert_true(checkPublicKey(authData.publicKey));
if (isNoneAttestation)
assert_object_equals(attestationObject.attStmt, { });
else
assert_equals(attestationObject.attStmt.x5c.length, 1);
}
function checkCtapGetAssertionResult(credential)
{
// Check respond
assert_array_equals(Base64URL.parse(credential.id), Base64URL.parse(testHidCredentialIdBase64));
assert_equals(credential.type, 'public-key');
assert_array_equals(new Uint8Array(credential.rawId), Base64URL.parse(testHidCredentialIdBase64));
assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.get","challenge":"MTIzNDU2","origin":"https://localhost:9443"}');
assert_equals(credential.response.userHandle, null);
assert_not_own_property(credential.getClientExtensionResults(), "appid");
// Check authData
const authData = decodeAuthData(new Uint8Array(credential.response.authenticatorData));
assert_equals(bytesToHexString(authData.rpIdHash), "46cc7fb9679d55b2db9092e1c8d9e5e1d02b7580f0b4812c770962e1e48f5ad8");
assert_equals(authData.flags, 1);
assert_equals(authData.counter, 80);
}
function checkU2fGetAssertionResult(credential, isAppID = false, appIDHash = "c2671b6eb9233197d5f2b1288a55ba4f0860f96f7199bba32fe6da7c3f0f31e5")
{
// Check respond
assert_array_equals(Base64URL.parse(credential.id), Base64URL.parse(testU2fCredentialIdBase64));
assert_equals(credential.type, 'public-key');
assert_array_equals(new Uint8Array(credential.rawId), Base64URL.parse(testU2fCredentialIdBase64));
assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.get","challenge":"MTIzNDU2","origin":"https://localhost:9443"}');
assert_equals(credential.response.userHandle, null);
if (!isAppID)
assert_not_own_property(credential.getClientExtensionResults(), "appid");
else
assert_true(credential.getClientExtensionResults().appid);
// Check authData
const authData = decodeAuthData(new Uint8Array(credential.response.authenticatorData));
if (!isAppID)
assert_equals(bytesToHexString(authData.rpIdHash), "49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763");
else
assert_equals(bytesToHexString(authData.rpIdHash), appIDHash);
assert_equals(authData.flags, 1);
assert_equals(authData.counter, 59);
}
function generateUserhandleBase64()
{
let buffer = new Uint8Array(16);
crypto.getRandomValues(buffer);
return btoa(String.fromCharCode.apply(0, buffer));
}
async function generatePrivateKeyBase64()
{
const algorithmKeyGen = {
name: "ECDSA",
namedCurve: "P-256"
};
const extractable = true;
const keyPair = await crypto.subtle.generateKey(algorithmKeyGen, extractable, ["sign", "verify"]);
const jwkPrivateKey = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
const x = Base64URL.parse(jwkPrivateKey.x);
const y = Base64URL.parse(jwkPrivateKey.y);
const d = Base64URL.parse(jwkPrivateKey.d);
let buffer = new Uint8Array(x.length + y.length + d.length + 1);
buffer[0] = 0x04;
let pos = 1;
buffer.set(x, pos);
pos += x.length;
buffer.set(y, pos);
pos += y.length;
buffer.set(d, pos);
return btoa(String.fromCharCode.apply(0, buffer));
}
async function calculateCredentialID(privateKeyBase64) {
const privateKey = Base64URL.parse(privateKeyBase64);
const publicKey = privateKey.slice(0, 65);
return new Uint8Array(await crypto.subtle.digest("sha-1", publicKey));
}