зеркало из https://github.com/mozilla/gecko-dev.git
202 строки
8.0 KiB
HTML
202 строки
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>
|