1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 var systemTokenEnabled
= (location
.search
.indexOf("systemTokenEnabled") != -1);
8 var selectedTestSuite
= location
.hash
.slice(1);
9 console
.log('[SELECTED TEST SUITE] ' + selectedTestSuite
+
10 ', systemTokenEnable ' + systemTokenEnabled
);
12 var assertEq
= chrome
.test
.assertEq
;
13 var assertTrue
= chrome
.test
.assertTrue
;
14 var assertFalse
= chrome
.test
.assertFalse
;
15 var fail
= chrome
.test
.fail
;
16 var succeed
= chrome
.test
.succeed
;
17 var callbackPass
= chrome
.test
.callbackPass
;
18 var callbackFail
= chrome
.test
.callbackFail
;
20 // Each value is the path to a file in this extension's folder that will be
21 // loaded and replaced by a Uint8Array in the setUp() function below.
23 // X.509 certificate in DER encoding issued by 'root.pem' which is set to be
24 // trusted by the test setup.
25 // Generated by create_test_certs.sh .
26 trusted_l1_leaf_cert
: 'l1_leaf.der',
28 // X.509 intermediate CA certificate in DER encoding issued by 'root.pem'
29 // which is set to be trusted by the test setup.
30 // Generated by create_test_certs.sh .
31 trusted_l1_interm_cert
: 'l1_interm.der',
33 // X.509 certificate in DER encoding issued by 'l1_interm'.
34 // Generated by create_test_certs.sh .
35 trusted_l2_leaf_cert
: 'l2_leaf.der',
37 // X.509 client certificate in DER encoding.
38 // Algorithm in SPKI: rsaEncryption.
39 // openssl x509 -in net/data/ssl/certificates/client_1.pem -outform DER -out
41 client_1
: 'client_1.der',
43 // X.509 client certificate in DER encoding.
44 // Algorithm in SPKI: rsaEncryption.
45 // openssl x509 -in net/data/ssl/certificates/client_2.pem -outform DER -out
47 client_2
: 'client_2.der',
49 // The public key of client_1 as Subject Public Key Info in DER encoding.
50 // openssl rsa -in net/data/ssl/certificates/client_1.key -inform PEM -out
51 // pubkey.der -pubout -outform DER
52 client_1_spki
: 'client_1_spki.der',
54 // The distinguished name of the CA that issued client_1 in DER encoding.
55 // openssl asn1parse -in client_1.der -inform DER -strparse 32 -out
56 // client_1_issuer_dn.der
57 client_1_issuer_dn
: 'client_1_issuer_dn.der',
59 // echo -n "hello world" > data
62 // openssl rsautl -inkey net/data/ssl/certificates/client_1.key -sign -in
63 // data -pkcs -out signature_nohash_pkcs
64 signature_nohash_pkcs
: 'signature_nohash_pkcs',
66 // openssl dgst -sha1 -sign net/data/ssl/certificates/client_1.key
67 // -out signature_sha1_pkcs data
68 signature_sha1_pkcs
: 'signature_sha1_pkcs',
71 // Reads the binary file at |path| and passes it as a Uin8Array to |callback|.
72 function readFile(path
, callback
) {
73 var oReq
= new XMLHttpRequest();
74 oReq
.responseType
= "arraybuffer";
75 oReq
.open("GET", path
, true /* asynchronous */);
76 oReq
.onload = function() {
77 var arrayBuffer
= oReq
.response
;
79 callback(new Uint8Array(arrayBuffer
));
87 // For each key in dictionary, replaces the path dictionary[key] by the content
88 // of the resource located at that path stored in a Uint8Array.
89 function readData(dictionary
, callback
) {
90 var keys
= Object
.keys(dictionary
);
91 function recurse(index
) {
92 if (index
>= keys
.length
) {
96 var key
= keys
[index
];
97 var path
= dictionary
[key
];
98 readFile(path
, function(array
) {
100 dictionary
[key
] = array
;
108 function setUp(callback
) {
109 readData(data
, callback
);
112 // Some array comparison. Note: not lexicographical!
113 function compareArrays(array1
, array2
) {
114 if (array1
.length
< array2
.length
)
116 if (array1
.length
> array2
.length
)
118 for (var i
= 0; i
< array1
.length
; i
++) {
119 if (array1
[i
] < array2
[i
])
121 if (array1
[i
] > array2
[i
])
128 * @param {ArrayBufferView[]} certs
129 * @return {ArrayBufferView[]} |certs| sorted in some order.
131 function sortCerts(certs
) {
132 return certs
.sort(compareArrays
);
135 function assertCertsSelected(details
, expectedCerts
, callback
) {
136 chrome
.platformKeys
.selectClientCertificates(
137 details
, callbackPass(function(actualMatches
) {
138 assertEq(expectedCerts
.length
, actualMatches
.length
,
139 'Number of stored certs not as expected');
140 if (expectedCerts
.length
== actualMatches
.length
) {
141 var actualCerts
= actualMatches
.map(function(match
) {
142 return new Uint8Array(match
.certificate
);
144 actualCerts
= sortCerts(actualCerts
);
145 expectedCerts
= sortCerts(expectedCerts
);
146 for (var i
= 0; i
< expectedCerts
.length
; i
++) {
147 assertEq(expectedCerts
[i
], actualCerts
[i
],
148 'Certs at index ' + i
+ ' differ');
156 function checkAlgorithmIsCopiedOnRead(key
) {
157 var algorithm
= key
.algorithm
;
158 var originalAlgorithm
= {
159 name
: algorithm
.name
,
160 modulusLength
: algorithm
.modulusLength
,
161 publicExponent
: algorithm
.publicExponent
,
162 hash
: {name
: algorithm
.hash
.name
}
164 var originalModulusLength
= algorithm
.modulusLength
;
165 algorithm
.hash
.name
= null;
166 algorithm
.hash
= null;
167 algorithm
.name
= null;
168 algorithm
.modulusLength
= null;
169 algorithm
.publicExponent
= null;
170 assertEq(originalAlgorithm
, key
.algorithm
);
173 function checkPropertyIsReadOnly(object
, key
) {
174 var original
= object
[key
];
177 fail('Expected the property ' + key
+
178 ' to be read-only and an exception to be thrown');
180 assertEq(original
, object
[key
]);
184 function checkPrivateKeyFormat(privateKey
) {
185 assertEq('private', privateKey
.type
);
186 assertEq(false, privateKey
.extractable
);
187 checkPropertyIsReadOnly(privateKey
, 'algorithm');
188 checkAlgorithmIsCopiedOnRead(privateKey
);
191 function checkPublicKeyFormat(publicKey
) {
192 assertEq('public', publicKey
.type
);
193 assertEq(true, publicKey
.extractable
);
194 checkPropertyIsReadOnly(publicKey
, 'algorithm');
195 checkAlgorithmIsCopiedOnRead(publicKey
);
198 function testStaticMethods() {
199 assertTrue(!!chrome
.platformKeys
, "No platformKeys namespace.");
200 assertTrue(!!chrome
.platformKeys
.selectClientCertificates
,
201 "No selectClientCertificates function.");
202 assertTrue(!!chrome
.platformKeys
.getKeyPair
, "No getKeyPair method.");
203 assertTrue(!!chrome
.platformKeys
.subtleCrypto
, "No subtleCrypto getter.");
204 assertTrue(!!chrome
.platformKeys
.subtleCrypto(), "No subtleCrypto object.");
205 assertTrue(!!chrome
.platformKeys
.subtleCrypto().sign
, "No sign method.");
206 assertTrue(!!chrome
.platformKeys
.subtleCrypto().exportKey
,
207 "No exportKey method.");
212 certificateTypes
: [],
213 certificateAuthorities
: []
216 // Depends on |data|, thus it cannot be created immediately.
217 function requestCA1() {
219 certificateTypes
: [],
220 certificateAuthorities
: [data
.client_1_issuer_dn
.buffer
]
224 function testSelectAllCerts() {
225 var expectedCerts
= [data
.client_1
];
226 if (systemTokenEnabled
)
227 expectedCerts
.push(data
.client_2
);
228 assertCertsSelected({interactive
: false, request
: requestAll
}, expectedCerts
);
231 function testSelectCA1Certs() {
232 assertCertsSelected({interactive
: false, request
: requestCA1()},
236 function testSelectAllReturnsNoCerts() {
237 assertCertsSelected({interactive
: false, request
: requestAll
},
238 [] /* no certs selected */);
241 function testSelectAllReturnsClient1() {
242 assertCertsSelected({interactive
: false, request
: requestAll
},
246 function testInteractiveSelectNoCerts() {
247 assertCertsSelected({interactive
: true, request
: requestAll
},
248 [] /* no certs selected */);
251 function testInteractiveSelectClient1() {
252 assertCertsSelected({interactive
: true, request
: requestAll
},
256 function testInteractiveSelectClient2() {
257 var expectedCerts
= [];
258 if (systemTokenEnabled
)
259 expectedCerts
.push(data
.client_2
);
260 assertCertsSelected({interactive
: true, request
: requestAll
}, expectedCerts
);
263 function testMatchResultCA1() {
264 chrome
.platformKeys
.selectClientCertificates(
265 {interactive
: false, request
: requestCA1()},
266 callbackPass(function(matches
) {
267 var expectedAlgorithm
= {
269 name
: "RSASSA-PKCS1-v1_5",
270 publicExponent
: new Uint8Array([0x01, 0x00, 0x01])
272 var actualAlgorithm
= matches
[0].keyAlgorithm
;
274 expectedAlgorithm
, actualAlgorithm
,
275 'Member algorithm of Match does not equal the expected algorithm');
279 function testMatchResultECDSA() {
281 certificateTypes
: ['ecdsaSign'],
282 certificateAuthorities
: []
284 chrome
.platformKeys
.selectClientCertificates(
285 {interactive
: false, request
: requestECDSA
},
286 callbackPass(function(matches
) {
287 assertEq(0, matches
.length
, 'No matches expected.');
291 function testMatchResultRSA() {
293 certificateTypes
: ['rsaSign'],
294 certificateAuthorities
: []
296 chrome
.platformKeys
.selectClientCertificates(
297 {interactive
: false, request
: requestRSA
},
298 callbackPass(function(matches
) {
299 var expectedAlgorithm
= {
301 name
: "RSASSA-PKCS1-v1_5",
302 publicExponent
: new Uint8Array([0x01, 0x00, 0x01])
304 var actualAlgorithm
= matches
[0].keyAlgorithm
;
306 expectedAlgorithm
, actualAlgorithm
,
307 'Member algorithm of Match does not equal the expected algorithm');
311 function testGetKeyPairMissingAlgorithName() {
313 // This is missing the algorithm name.
314 hash
: {name
: 'SHA-1'}
317 chrome
.platformKeys
.getKeyPair(
318 data
.client_1
.buffer
, keyParams
, function(error
) {
319 fail('getKeyPair call was expected to fail.');
321 fail('getKeyPair did not throw error');
323 assertEq('Algorithm: name: Missing or not a String', e
.message
);
328 function testGetKeyPairRejectsRSAPSS() {
331 hash
: {name
: 'SHA-1'}
333 chrome
.platformKeys
.getKeyPair(
334 data
.client_1
.buffer
, keyParams
,
336 'The requested Algorithm is not permitted by the certificate.'));
339 function testGetKeyPair() {
341 // Algorithm names are case-insensitive.
342 name
: 'RSASSA-Pkcs1-V1_5',
343 hash
: {name
: 'sha-1'}
345 chrome
.platformKeys
.getKeyPair(
346 data
.client_1
.buffer
, keyParams
,
347 callbackPass(function(publicKey
, privateKey
) {
348 var expectedAlgorithm
= {
350 name
: "RSASSA-PKCS1-v1_5",
351 publicExponent
: new Uint8Array([0x01, 0x00, 0x01]),
352 hash
: {name
: 'SHA-1'}
354 assertEq(expectedAlgorithm
, publicKey
.algorithm
);
355 assertEq(expectedAlgorithm
, privateKey
.algorithm
);
357 checkPublicKeyFormat(publicKey
);
358 checkPrivateKeyFormat(privateKey
);
360 chrome
.platformKeys
.subtleCrypto()
361 .exportKey('spki', publicKey
)
362 .then(callbackPass(function(actualPublicKeySpki
) {
364 compareArrays(data
.client_1_spki
, actualPublicKeySpki
) == 0,
365 'Match did not contain correct public key');
367 function(error
) { fail("Export failed: " + error
); });
371 function testSignNoHash() {
373 // Algorithm names are case-insensitive.
374 name
: 'RSASSA-PKCS1-V1_5',
378 name
: 'RSASSA-PKCS1-v1_5'
380 chrome
.platformKeys
.getKeyPair(
381 data
.client_1
.buffer
, keyParams
,
382 callbackPass(function(publicKey
, privateKey
) {
383 chrome
.platformKeys
.subtleCrypto()
384 .sign(signParams
, privateKey
, data
.raw_data
)
385 .then(callbackPass(function(signature
) {
386 var actualSignature
= new Uint8Array(signature
);
387 assertTrue(compareArrays(data
.signature_nohash_pkcs
,
388 actualSignature
) == 0,
389 'Incorrect signature');
394 function testSignSha1Client1() {
396 name
: 'RSASSA-PKCS1-v1_5',
397 // Algorithm names are case-insensitive.
398 hash
: {name
: 'Sha-1'}
401 // Algorithm names are case-insensitive.
402 name
: 'RSASSA-Pkcs1-v1_5'
404 chrome
.platformKeys
.getKeyPair(
405 data
.client_1
.buffer
, keyParams
,
406 callbackPass(function(publicKey
, privateKey
) {
407 chrome
.platformKeys
.subtleCrypto()
408 .sign(signParams
, privateKey
, data
.raw_data
)
409 .then(callbackPass(function(signature
) {
410 var actualSignature
= new Uint8Array(signature
);
412 compareArrays(data
.signature_sha1_pkcs
, actualSignature
) == 0,
413 'Incorrect signature');
418 // TODO(pneubeck): Test this by verifying that no private key is returned, once
419 // that's implemented.
420 function testSignFails(cert
) {
422 name
: 'RSASSA-PKCS1-v1_5',
423 hash
: {name
: 'SHA-1'}
426 name
: 'RSASSA-PKCS1-v1_5'
428 chrome
.platformKeys
.getKeyPair(
429 cert
.buffer
, keyParams
, callbackPass(function(publicKey
, privateKey
) {
430 chrome
.platformKeys
.subtleCrypto()
431 .sign(signParams
, privateKey
, data
.raw_data
)
432 .then(function(signature
) { fail('sign was expected to fail.'); },
433 callbackPass(function(error
) {
434 assertTrue(error
instanceof Error
);
436 'The operation failed for an operation-specific reason',
442 function testSignClient1Fails() {
443 testSignFails(data
.client_1
);
446 function testSignClient2Fails() {
447 testSignFails(data
.client_2
);
450 function testBackgroundNoninteractiveSelect() {
451 var details
= {interactive
: false, request
: requestAll
};
453 chrome
.runtime
.getBackgroundPage(callbackPass(function(bp
) {
454 bp
.chrome
.platformKeys
.selectClientCertificates(
455 details
, callbackPass(function(actualMatches
) {
456 assertTrue(!bp
.chrome
.runtime
.lastError
);
457 var expectedCount
= systemTokenEnabled
? 2 : 1;
458 assertEq(expectedCount
, actualMatches
.length
);
463 function testBackgroundInteractiveSelect() {
464 var details
= {interactive
: true, request
: requestAll
};
466 chrome
.runtime
.getBackgroundPage(callbackPass(function(bp
) {
467 bp
.chrome
.platformKeys
.selectClientCertificates(
468 // callbackPass checks chrome.runtime.lastError and not the error of
469 // the background page.
470 details
, callbackPass(function(actualMatches
) {
471 assertEq(bp
.chrome
.runtime
.lastError
.message
,
472 'Interactive calls must happen in the context of a ' +
473 'browser tab or a window.');
474 assertEq([], actualMatches
);
479 function testVerifyTrusted() {
481 serverCertificateChain
: [data
.trusted_l1_leaf_cert
.buffer
],
484 chrome
.platformKeys
.verifyTLSServerCertificate(
485 details
, callbackPass(function(result
) {
486 assertTrue(result
.trusted
);
487 assertEq([], result
.debug_errors
);
491 function testVerifyTrustedChain() {
493 serverCertificateChain
:
494 [data
.trusted_l2_leaf_cert
.buffer
, data
.trusted_l1_interm_cert
.buffer
],
497 chrome
.platformKeys
.verifyTLSServerCertificate(
498 details
, callbackPass(function(result
) {
499 assertTrue(result
.trusted
);
500 assertEq([], result
.debug_errors
);
504 function testVerifyCommonNameInvalid() {
506 serverCertificateChain
:
507 [data
.trusted_l2_leaf_cert
.buffer
, data
.trusted_l1_interm_cert
.buffer
],
508 // Use any hostname not matching the common name 'l2_leaf' of the cert.
509 hostname
: "abc.example"
511 chrome
.platformKeys
.verifyTLSServerCertificate(
512 details
, callbackPass(function(result
) {
513 assertFalse(result
.trusted
);
514 assertEq(["COMMON_NAME_INVALID"], result
.debug_errors
);
518 function testVerifyUntrusted() {
520 serverCertificateChain
: [data
.client_1
.buffer
],
521 hostname
: "127.0.0.1"
523 chrome
.platformKeys
.verifyTLSServerCertificate(
524 details
, callbackPass(function(result
) {
525 assertFalse(result
.trusted
);
526 assertEq(["COMMON_NAME_INVALID", "AUTHORITY_INVALID"],
527 result
.debug_errors
);
532 // On interactive selectClientCertificates calls, the simulated user does not
534 basicTests: function() {
538 // Interactively select client_1 and client_2 to grant permissions for
539 // these certificates.
540 testInteractiveSelectClient1
,
541 testInteractiveSelectClient2
,
543 // In non-interactive calls both certs must be returned now.
546 testBackgroundNoninteractiveSelect
,
547 testBackgroundInteractiveSelect
,
549 testInteractiveSelectNoCerts
,
551 testMatchResultECDSA
,
553 testGetKeyPairMissingAlgorithName
,
554 testGetKeyPairRejectsRSAPSS
,
559 testVerifyTrustedChain
,
560 testVerifyCommonNameInvalid
,
564 chrome
.test
.runTests(tests
);
567 // On interactive selectClientCertificates calls, the simulated user selects
568 // client_1, if matching.
569 permissionTests: function() {
571 // Without permissions both sign attempts fail.
572 testSignClient1Fails
,
573 testSignClient2Fails
,
575 // Without permissions, non-interactive select calls return no certs.
576 testSelectAllReturnsNoCerts
,
578 testInteractiveSelectClient1
,
579 // Now the permission for client_1 is granted.
581 // Verify that signing with client_1 is possible and with client_2 still
584 testSignClient2Fails
,
586 // Verify that client_1 can still be selected interactively.
587 testInteractiveSelectClient1
,
589 // Verify that client_1 but not client_2 is selected in non-interactive
591 testSelectAllReturnsClient1
,
594 chrome
.test
.runTests(tests
);
597 managedProfile: function() {
599 // If the profile is managed, the user cannot grant permissions for any
601 testInteractiveSelectNoCerts
603 chrome
.test
.runTests(tests
);
606 corporateKeyWithoutPermissionTests: function() {
608 // Directly trying to sign must fail
609 testSignClient1Fails
,
611 // Interactively selecting must not show any cert to the user.
612 testInteractiveSelectNoCerts
,
614 chrome
.test
.runTests(tests
);
617 corporateKeyWithPermissionTests: function() {
619 // The extension has non-interactive access to all corporate keys, even
620 // without previous additional consent of the user.
623 // Interactively selecting for client_1 will work as well.
624 testInteractiveSelectClient1
,
626 chrome
.test
.runTests(tests
);
629 policyDoesGrantAccessToNonCorporateKey: function() {
630 // The permission from policy must not affect usage of non-corproate keys.
632 // Attempts to sign must fail.
633 testSignClient1Fails
,
635 // Interactive selection must not prompt the user and not return any
637 testInteractiveSelectNoCerts
,
639 chrome
.test
.runTests(tests
);
644 setUp(testSuites
[selectedTestSuite
]);