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 testSelectWithInputClientCerts() {
232 var expectedCerts
= [];
233 if (systemTokenEnabled
)
234 expectedCerts
.push(data
.client_2
);
239 clientCerts
: [data
.client_2
.buffer
]
244 function testSelectCA1Certs() {
245 assertCertsSelected({interactive
: false, request
: requestCA1()},
249 function testSelectAllReturnsNoCerts() {
250 assertCertsSelected({interactive
: false, request
: requestAll
},
251 [] /* no certs selected */);
254 function testSelectAllReturnsClient1() {
255 assertCertsSelected({interactive
: false, request
: requestAll
},
259 function testInteractiveSelectNoCerts() {
260 assertCertsSelected({interactive
: true, request
: requestAll
},
261 [] /* no certs selected */);
264 function testInteractiveSelectClient1() {
265 assertCertsSelected({interactive
: true, request
: requestAll
},
269 function testInteractiveSelectClient2() {
270 var expectedCerts
= [];
271 if (systemTokenEnabled
)
272 expectedCerts
.push(data
.client_2
);
273 assertCertsSelected({interactive
: true, request
: requestAll
}, expectedCerts
);
276 function testMatchResultCA1() {
277 chrome
.platformKeys
.selectClientCertificates(
278 {interactive
: false, request
: requestCA1()},
279 callbackPass(function(matches
) {
280 var expectedAlgorithm
= {
282 name
: "RSASSA-PKCS1-v1_5",
283 publicExponent
: new Uint8Array([0x01, 0x00, 0x01])
285 var actualAlgorithm
= matches
[0].keyAlgorithm
;
287 expectedAlgorithm
, actualAlgorithm
,
288 'Member algorithm of Match does not equal the expected algorithm');
292 function testMatchResultECDSA() {
294 certificateTypes
: ['ecdsaSign'],
295 certificateAuthorities
: []
297 chrome
.platformKeys
.selectClientCertificates(
298 {interactive
: false, request
: requestECDSA
},
299 callbackPass(function(matches
) {
300 assertEq(0, matches
.length
, 'No matches expected.');
304 function testMatchResultRSA() {
306 certificateTypes
: ['rsaSign'],
307 certificateAuthorities
: []
309 chrome
.platformKeys
.selectClientCertificates(
310 {interactive
: false, request
: requestRSA
},
311 callbackPass(function(matches
) {
312 var expectedAlgorithm
= {
314 name
: "RSASSA-PKCS1-v1_5",
315 publicExponent
: new Uint8Array([0x01, 0x00, 0x01])
317 var actualAlgorithm
= matches
[0].keyAlgorithm
;
319 expectedAlgorithm
, actualAlgorithm
,
320 'Member algorithm of Match does not equal the expected algorithm');
324 function testGetKeyPairMissingAlgorithName() {
326 // This is missing the algorithm name.
327 hash
: {name
: 'SHA-1'}
330 chrome
.platformKeys
.getKeyPair(
331 data
.client_1
.buffer
, keyParams
, function(error
) {
332 fail('getKeyPair call was expected to fail.');
334 fail('getKeyPair did not throw error');
336 assertEq('Algorithm: name: Missing or not a String', e
.message
);
341 function testGetKeyPairRejectsRSAPSS() {
344 hash
: {name
: 'SHA-1'}
346 chrome
.platformKeys
.getKeyPair(
347 data
.client_1
.buffer
, keyParams
,
349 'The requested Algorithm is not permitted by the certificate.'));
352 function testGetKeyPair() {
354 // Algorithm names are case-insensitive.
355 name
: 'RSASSA-Pkcs1-V1_5',
356 hash
: {name
: 'sha-1'}
358 chrome
.platformKeys
.getKeyPair(
359 data
.client_1
.buffer
, keyParams
,
360 callbackPass(function(publicKey
, privateKey
) {
361 var expectedAlgorithm
= {
363 name
: "RSASSA-PKCS1-v1_5",
364 publicExponent
: new Uint8Array([0x01, 0x00, 0x01]),
365 hash
: {name
: 'SHA-1'}
367 assertEq(expectedAlgorithm
, publicKey
.algorithm
);
368 assertEq(expectedAlgorithm
, privateKey
.algorithm
);
370 checkPublicKeyFormat(publicKey
);
371 checkPrivateKeyFormat(privateKey
);
373 chrome
.platformKeys
.subtleCrypto()
374 .exportKey('spki', publicKey
)
375 .then(callbackPass(function(actualPublicKeySpki
) {
377 compareArrays(data
.client_1_spki
, actualPublicKeySpki
) == 0,
378 'Match did not contain correct public key');
380 function(error
) { fail("Export failed: " + error
); });
384 function testSignNoHash() {
386 // Algorithm names are case-insensitive.
387 name
: 'RSASSA-PKCS1-V1_5',
391 name
: 'RSASSA-PKCS1-v1_5'
393 chrome
.platformKeys
.getKeyPair(
394 data
.client_1
.buffer
, keyParams
,
395 callbackPass(function(publicKey
, privateKey
) {
396 chrome
.platformKeys
.subtleCrypto()
397 .sign(signParams
, privateKey
, data
.raw_data
)
398 .then(callbackPass(function(signature
) {
399 var actualSignature
= new Uint8Array(signature
);
400 assertTrue(compareArrays(data
.signature_nohash_pkcs
,
401 actualSignature
) == 0,
402 'Incorrect signature');
407 function testSignSha1Client1() {
409 name
: 'RSASSA-PKCS1-v1_5',
410 // Algorithm names are case-insensitive.
411 hash
: {name
: 'Sha-1'}
414 // Algorithm names are case-insensitive.
415 name
: 'RSASSA-Pkcs1-v1_5'
417 chrome
.platformKeys
.getKeyPair(
418 data
.client_1
.buffer
, keyParams
,
419 callbackPass(function(publicKey
, privateKey
) {
420 chrome
.platformKeys
.subtleCrypto()
421 .sign(signParams
, privateKey
, data
.raw_data
)
422 .then(callbackPass(function(signature
) {
423 var actualSignature
= new Uint8Array(signature
);
425 compareArrays(data
.signature_sha1_pkcs
, actualSignature
) == 0,
426 'Incorrect signature');
431 // TODO(pneubeck): Test this by verifying that no private key is returned, once
432 // that's implemented.
433 function testSignFails(cert
) {
435 name
: 'RSASSA-PKCS1-v1_5',
436 hash
: {name
: 'SHA-1'}
439 name
: 'RSASSA-PKCS1-v1_5'
441 chrome
.platformKeys
.getKeyPair(
442 cert
.buffer
, keyParams
, callbackPass(function(publicKey
, privateKey
) {
443 chrome
.platformKeys
.subtleCrypto()
444 .sign(signParams
, privateKey
, data
.raw_data
)
445 .then(function(signature
) { fail('sign was expected to fail.'); },
446 callbackPass(function(error
) {
447 assertTrue(error
instanceof Error
);
449 'The operation failed for an operation-specific reason',
455 function testSignClient1Fails() {
456 testSignFails(data
.client_1
);
459 function testSignClient2Fails() {
460 testSignFails(data
.client_2
);
463 function testBackgroundNoninteractiveSelect() {
464 var details
= {interactive
: false, request
: requestAll
};
466 chrome
.runtime
.getBackgroundPage(callbackPass(function(bp
) {
467 bp
.chrome
.platformKeys
.selectClientCertificates(
468 details
, callbackPass(function(actualMatches
) {
469 assertTrue(!bp
.chrome
.runtime
.lastError
);
470 var expectedCount
= systemTokenEnabled
? 2 : 1;
471 assertEq(expectedCount
, actualMatches
.length
);
476 function testBackgroundInteractiveSelect() {
477 var details
= {interactive
: true, request
: requestAll
};
479 chrome
.runtime
.getBackgroundPage(callbackPass(function(bp
) {
480 bp
.chrome
.platformKeys
.selectClientCertificates(
481 // callbackPass checks chrome.runtime.lastError and not the error of
482 // the background page.
483 details
, callbackPass(function(actualMatches
) {
484 assertEq(bp
.chrome
.runtime
.lastError
.message
,
485 'Interactive calls must happen in the context of a ' +
486 'browser tab or a window.');
487 assertEq([], actualMatches
);
492 function testVerifyTrusted() {
494 serverCertificateChain
: [data
.trusted_l1_leaf_cert
.buffer
],
497 chrome
.platformKeys
.verifyTLSServerCertificate(
498 details
, callbackPass(function(result
) {
499 assertTrue(result
.trusted
);
500 assertEq([], result
.debug_errors
);
504 function testVerifyTrustedChain() {
506 serverCertificateChain
:
507 [data
.trusted_l2_leaf_cert
.buffer
, data
.trusted_l1_interm_cert
.buffer
],
510 chrome
.platformKeys
.verifyTLSServerCertificate(
511 details
, callbackPass(function(result
) {
512 assertTrue(result
.trusted
);
513 assertEq([], result
.debug_errors
);
517 function testVerifyCommonNameInvalid() {
519 serverCertificateChain
:
520 [data
.trusted_l2_leaf_cert
.buffer
, data
.trusted_l1_interm_cert
.buffer
],
521 // Use any hostname not matching the common name 'l2_leaf' of the cert.
522 hostname
: "abc.example"
524 chrome
.platformKeys
.verifyTLSServerCertificate(
525 details
, callbackPass(function(result
) {
526 assertFalse(result
.trusted
);
527 assertEq(["COMMON_NAME_INVALID"], result
.debug_errors
);
531 function testVerifyUntrusted() {
533 serverCertificateChain
: [data
.client_1
.buffer
],
534 hostname
: "127.0.0.1"
536 chrome
.platformKeys
.verifyTLSServerCertificate(
537 details
, callbackPass(function(result
) {
538 assertFalse(result
.trusted
);
539 assertEq(["COMMON_NAME_INVALID", "AUTHORITY_INVALID"],
540 result
.debug_errors
);
545 // On interactive selectClientCertificates calls, the simulated user does not
547 basicTests: function() {
551 // Interactively select client_1 and client_2 to grant permissions for
552 // these certificates.
553 testInteractiveSelectClient1
,
554 testInteractiveSelectClient2
,
556 // In non-interactive calls both certs must be returned now.
559 testBackgroundNoninteractiveSelect
,
560 testBackgroundInteractiveSelect
,
561 testSelectWithInputClientCerts
,
563 testInteractiveSelectNoCerts
,
565 testMatchResultECDSA
,
567 testGetKeyPairMissingAlgorithName
,
568 testGetKeyPairRejectsRSAPSS
,
573 testVerifyTrustedChain
,
574 testVerifyCommonNameInvalid
,
578 chrome
.test
.runTests(tests
);
581 // On interactive selectClientCertificates calls, the simulated user selects
582 // client_1, if matching.
583 permissionTests: function() {
585 // Without permissions both sign attempts fail.
586 testSignClient1Fails
,
587 testSignClient2Fails
,
589 // Without permissions, non-interactive select calls return no certs.
590 testSelectAllReturnsNoCerts
,
592 testInteractiveSelectClient1
,
593 // Now the permission for client_1 is granted.
595 // Verify that signing with client_1 is possible and with client_2 still
598 testSignClient2Fails
,
600 // Verify that client_1 can still be selected interactively.
601 testInteractiveSelectClient1
,
603 // Verify that client_1 but not client_2 is selected in non-interactive
605 testSelectAllReturnsClient1
,
608 chrome
.test
.runTests(tests
);
611 managedProfile: function() {
613 // If the profile is managed, the user cannot grant permissions for any
615 testInteractiveSelectNoCerts
617 chrome
.test
.runTests(tests
);
620 corporateKeyWithoutPermissionTests: function() {
622 // Directly trying to sign must fail
623 testSignClient1Fails
,
625 // Interactively selecting must not show any cert to the user.
626 testInteractiveSelectNoCerts
,
628 chrome
.test
.runTests(tests
);
631 corporateKeyWithPermissionTests: function() {
633 // The extension has non-interactive access to all corporate keys, even
634 // without previous additional consent of the user.
637 // Interactively selecting for client_1 will work as well.
638 testInteractiveSelectClient1
,
640 chrome
.test
.runTests(tests
);
643 policyDoesGrantAccessToNonCorporateKey: function() {
644 // The permission from policy must not affect usage of non-corproate keys.
646 // Attempts to sign must fail.
647 testSignClient1Fails
,
649 // Interactive selection must not prompt the user and not return any
651 testInteractiveSelectNoCerts
,
653 chrome
.test
.runTests(tests
);
658 setUp(testSuites
[selectedTestSuite
]);