// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file provides common functionality for different configurations of test
// extensions using the chrome.certificateProvider API.
'use strict';
const assertEq = chrome.test.assertEq;
const assertTrue = chrome.test.assertTrue;
const assertLastError = chrome.test.assertLastError;
const callbackPass = chrome.test.callbackPass;
// Make sure all unhandled errors result in the test failure.
chrome.test.setExceptionHandler(function(message, exc) {
throw exc;
});
window.addEventListener('error', function(errorEvent) {
chrome.test.fail(`Unhandled exception: ${errorEvent.error}`);
});
// X.509 certificate in DER encoding issued by 'root.pem' which is set to be
// trusted by the test setup.
// Read from 'l1_leaf.der', generated by create_test_certs.sh .
let l1LeafCert = null;
const INVALID_CERT = new Uint8Array([1, 2, 3, 4, 5]);
// These variables are manipulated by the C++ side in some test cases:
let supportedAlgorithms = ['RSASSA_PKCS1_v1_5_SHA1'];
let supportedLegacyHashes = ['SHA1'];
function getInvalidClientCertificateInfos() {
const badDer = {
certificateChain: [INVALID_CERT.buffer],
supportedAlgorithms: ['RSASSA_PKCS1_v1_5_SHA256']
};
const emptyChain = {
certificateChain: [],
supportedAlgorithms: ['RSASSA_PKCS1_v1_5_SHA256']
};
const noAlgorithms = {
certificateChain: [l1LeafCert.buffer],
supportedAlgorithms: []
};
return [
badDer,
emptyChain,
noAlgorithms,
];
}
function getInvalidLegacyCertificateInfos() {
const badDer = {
certificate: INVALID_CERT.buffer,
supportedHashes: ['SHA256']
};
const noHashes = {certificate: l1LeafCert.buffer, supportedHashes: []};
return [
badDer,
noHashes,
];
}
function registerAsCertificateProvider() {
function reportCertificates(request) {
assertTrue(Number.isInteger(request.certificatesRequestId));
const validCert = {
certificateChain: [l1LeafCert.buffer],
supportedAlgorithms: supportedAlgorithms
};
chrome.certificateProvider.setCertificates(
{
certificatesRequestId: request.certificatesRequestId,
clientCertificates: [validCert, ...getInvalidClientCertificateInfos()]
},
() => {
chrome.test.succeed();
});
}
chrome.certificateProvider.onCertificatesUpdateRequested.addListener(
reportCertificates);
}
function registerAsLegacyCertificateProvider() {
function checkResult(rejectedCerts) {
assertEq(2, rejectedCerts.length);
const rejectedCertsBytes = rejectedCerts.map(
arrayBuffer => JSON.stringify(new Uint8Array(arrayBuffer)));
assertTrue(rejectedCertsBytes.includes(JSON.stringify(INVALID_CERT)));
assertTrue(rejectedCertsBytes.includes(JSON.stringify(l1LeafCert)));
}
function reportCertificates(reportCallback) {
const validCertInfo = {
certificate: l1LeafCert.buffer,
supportedHashes: supportedLegacyHashes
};
reportCallback(
[validCertInfo, ...getInvalidLegacyCertificateInfos()],
callbackPass(checkResult));
}
chrome.certificateProvider.onCertificatesRequested.addListener(
callbackPass(reportCertificates));
}
// Use setCertificates to let the extension proactively provide certificates.
// This can be combined with registerAsCertificateProvider(), but can also be
// used on its own.
function setCertificates() {
const validCert = {
certificateChain: [l1LeafCert.buffer],
supportedAlgorithms: supportedAlgorithms
};
return new Promise(resolve => {
chrome.certificateProvider.setCertificates(
{clientCertificates: [
validCert, ...getInvalidClientCertificateInfos()]},
() => {
const success = !chrome.runtime.lastError;
resolve(success);
});
});
}
// Similar to `setCertificates()`, but only provides invalid certificates.
function setInvalidCertificates() {
return new Promise(resolve => {
chrome.certificateProvider.setCertificates(
{clientCertificates: getInvalidClientCertificateInfos()}, () => {
const success = !chrome.runtime.lastError;
resolve(success);
});
});
}
// Indicates that there are no certificates available.
function unsetCertificates() {
return new Promise(resolve => {
chrome.certificateProvider.setCertificates({clientCertificates: []}, () => {
const success = !chrome.runtime.lastError;
resolve(success);
});
});
}
let signatureRequestAlgorithm;
let signatureRequestData;
let signatureCallback;
function registerForSignatureRequests() {
chrome.certificateProvider.onSignatureRequested.addListener(function(
request) {
assertTrue(Number.isInteger(request.signRequestId));
assertEq(l1LeafCert.buffer, request.certificate);
// The sign request must refer to the algorithm that was declared to be
// supported.
assertTrue(supportedAlgorithms.includes(request.algorithm));
signatureCallback = (signature) => {
// First, simulate malformed call parameters, with neither the signature
// nor the error being set. This call should be rejected by the API.
chrome.certificateProvider.reportSignature(
{signRequestId: request.signRequestId}, () => {
assertLastError('Neither the result nor an error supplied.');
});
// Then report the signature correctly.
chrome.certificateProvider.reportSignature(
{signRequestId: request.signRequestId, signature: signature});
};
signatureRequestAlgorithm = request.algorithm;
signatureRequestData = request.input;
chrome.test.sendMessage('signature request received');
});
}
function registerForLegacySignatureRequests() {
chrome.certificateProvider.onSignDigestRequested.addListener(function(
request, callback) {
assertEq(l1LeafCert.buffer, request.certificate);
// The sign request must refer to the hash that was declared to be
// supported.
assertTrue(supportedLegacyHashes.includes(request.hash));
signatureCallback = callback;
signatureRequestAlgorithm = request.hash;
signatureRequestData = request.digest;
chrome.test.sendMessage('signature request received');
});
}
function replyWithSignature(signature) {
signatureCallback(signature.buffer);
}
function replyWithSignatureSecondTime() {
const signature = new Uint8Array([1, 2, 3]);
try {
signatureCallback(signature.buffer);
} catch (e) {
return false;
}
return true;
}
// initialize is called from the cpp test.
// |cert| is the certificate data in an Uint8Array.
function initialize(cert) {
l1LeafCert = cert;
}