1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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>
|