Mypal/dom/u2f/tests/frame_register_sign.html

202 lines
8.0 KiB
HTML

<!DOCTYPE html>
<meta charset=utf-8>
<head>
<script type="text/javascript" src="u2futil.js"></script>
<script type="text/javascript" src="pkijs/common.js"></script>
<script type="text/javascript" src="pkijs/asn1.js"></script>
<script type="text/javascript" src="pkijs/x509_schema.js"></script>
<script type="text/javascript" src="pkijs/x509_simpl.js"></script>
</head>
<body>
<p>Register and Sign Test for FIDO Universal Second Factor</p>
<script class="testbody" type="text/javascript">
"use strict";
var state = {
// Raw messages
regRequest: null,
regResponse: null,
regKey: null,
signChallenge: null,
signResponse: null,
// Parsed values
publicKey: null,
keyHandle: null,
// Constants
version: "U2F_V2",
appId: window.location.origin,
};
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
["security.webauth.u2f_enable_softtoken", true]]},
function() {
local_isnot(window.u2f, undefined, "U2F API endpoint must exist");
local_isnot(window.u2f.register, undefined, "U2F Register API endpoint must exist");
local_isnot(window.u2f.sign, undefined, "U2F Sign API endpoint must exist");
testRegistering();
function testRegistering() {
var challenge = new Uint8Array(16);
window.crypto.getRandomValues(challenge);
state.regRequest = {
version: state.version,
challenge: bytesToBase64UrlSafe(challenge),
};
u2f.register(state.appId, [state.regRequest], [], function(regResponse) {
state.regResponse = regResponse;
local_is(regResponse.errorCode, 0, "The registration did not error");
local_isnot(regResponse.registrationData, undefined, "The registration did not provide registration data");
if (regResponse.errorCode > 0) {
local_finished();
return;
}
// Parse the response data from the U2F token
var registrationData = base64ToBytesUrlSafe(regResponse.registrationData);
local_is(registrationData[0], 0x05, "Reserved byte is correct")
state.publicKeyBytes = registrationData.slice(1, 66);
var keyHandleLength = registrationData[66];
state.keyHandleBytes = registrationData.slice(67, 67 + keyHandleLength);
state.keyHandle = bytesToBase64UrlSafe(state.keyHandleBytes);
state.attestation = registrationData.slice(67 + keyHandleLength);
local_is(state.attestation[0], 0x30, "Attestation Certificate has correct starting byte");
var asn1 = org.pkijs.fromBER(state.attestation.buffer);
console.log(asn1);
state.attestationCert = new org.pkijs.simpl.CERT({ schema: asn1.result });
console.log(state.attestationCert);
state.attestationSig = state.attestation.slice(asn1.offset);
local_is(state.attestationCert.subject.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Subject");
local_is(state.attestationCert.issuer.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Issuer");
local_is(state.attestationCert.notAfter.value - state.attestationCert.notBefore.value, 1000*60*60*48, "Valid 48 hours (in millis)");
// Verify that the clientData from the U2F token makes sense
var clientDataJSON = "";
base64ToBytesUrlSafe(regResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
var clientData = JSON.parse(clientDataJSON);
local_is(clientData.typ, "navigator.id.finishEnrollment", "Register - Data type matches");
local_is(clientData.challenge, state.regRequest.challenge, "Register - Challenge matches");
local_is(clientData.origin, window.location.origin, "Register - Origins are the same");
// Verify the signature from the attestation certificate
deriveAppAndChallengeParam(state.appId, string2buffer(clientDataJSON))
.then(function(params){
state.appParam = params.appParam;
state.challengeParam = params.challengeParam;
return state.attestationCert.getPublicKey();
}).then(function(attestationPublicKey) {
var signedData = assembleRegistrationSignedData(state.appParam, state.challengeParam, state.keyHandleBytes, state.publicKeyBytes);
return verifySignature(attestationPublicKey, signedData, state.attestationSig);
}).then(function(verified) {
console.log("No error verifying signature");
local_ok(verified, "Attestation Certificate signature verified")
// Import the public key of the U2F token into WebCrypto
return importPublicKey(state.publicKeyBytes)
}).then(function(key) {
state.publicKey = key;
local_ok(true, "Imported public key")
// Ensure the attestation certificate is properly self-signed
return state.attestationCert.verify()
}).then(function(){
local_ok(true, "Attestation Certificate verification successful");
// Continue test
testReRegister()
}).catch(function(err){
console.log(err);
local_ok(false, "Attestation Certificate verification failed");
local_finished();
});
});
}
function testReRegister() {
state.regKey = {
version: state.version,
keyHandle: state.keyHandle,
};
// Test that we don't re-register if we provide regKey as an
// "already known" key handle. The U2F module should recognize regKey
// as being usable and, thus, give back errorCode 4.
u2f.register(state.appId, [state.regRequest], [state.regKey], function(regResponse) {
// Since we attempted to register with state.regKey as a known key, expect
// ineligible (=4).
local_is(regResponse.errorCode, 4, "The re-registration should show device ineligible");
local_is(regResponse.registrationData, undefined, "The re-registration did not provide registration data");
// Continue test
testSigning();
});
}
function testSigning() {
var challenge = new Uint8Array(16);
window.crypto.getRandomValues(challenge);
state.signChallenge = bytesToBase64UrlSafe(challenge);
// Now try to sign the signature challenge
u2f.sign(state.appId, state.signChallenge, [state.regKey], function(signResponse) {
state.signResponse = signResponse;
// Make sure this signature op worked, bailing early if it failed.
local_is(signResponse.errorCode, 0, "The signing did not error");
local_isnot(signResponse.clientData, undefined, "The signing did not provide client data");
if (signResponse.errorCode > 0) {
local_finished();
return;
}
// Decode the clientData that was returned from the module
var clientDataJSON = "";
base64ToBytesUrlSafe(signResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
var clientData = JSON.parse(clientDataJSON);
local_is(clientData.typ, "navigator.id.getAssertion", "Sign - Data type matches");
local_is(clientData.challenge, state.signChallenge, "Sign - Challenge matches");
local_is(clientData.origin, window.location.origin, "Sign - Origins are the same");
// Parse the signature data
var signatureData = base64ToBytesUrlSafe(signResponse.signatureData);
if (signatureData[0] != 0x01) {
throw "User presence byte not set";
}
var presenceAndCounter = signatureData.slice(0,5);
var signatureValue = signatureData.slice(5);
// Assemble the signed data and verify the signature
deriveAppAndChallengeParam(state.appId, string2buffer(clientDataJSON))
.then(function(params){
return assembleSignedData(params.appParam, presenceAndCounter, params.challengeParam);
})
.then(function(signedData) {
return verifySignature(state.publicKey, signedData, signatureValue);
})
.then(function(verified) {
console.log("No error verifying signing signature");
local_ok(verified, "Signing signature verified")
local_finished();
})
.catch(function(err) {
console.log(err);
local_ok(false, "Signing signature invalid");
local_finished();
});
});
}
});
</script>
</body>
</html>