diff options
Diffstat (limited to 'dom/u2f/tests/frame_register_sign.html')
-rw-r--r-- | dom/u2f/tests/frame_register_sign.html | 201 |
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> |