summaryrefslogtreecommitdiffstats
path: root/dom/u2f/tests/frame_register_sign.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/u2f/tests/frame_register_sign.html')
-rw-r--r--dom/u2f/tests/frame_register_sign.html201
1 files changed, 201 insertions, 0 deletions
diff --git a/dom/u2f/tests/frame_register_sign.html b/dom/u2f/tests/frame_register_sign.html
new file mode 100644
index 000000000..e0cf4cb1c
--- /dev/null
+++ b/dom/u2f/tests/frame_register_sign.html
@@ -0,0 +1,201 @@
+<!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>