Backed out 3 changesets (bug 1770869) for causing xpcshell failures at test_tls_server.js. CLOSED TREE

Backed out changeset 5c95392b800d (bug 1770869)
Backed out changeset 149d04a209b6 (bug 1770869)
Backed out changeset 5a138d047be1 (bug 1770869)
This commit is contained in:
Butkovits Atila 2022-06-02 00:18:26 +03:00
Родитель 3495742c56
Коммит 2b78c018fd
43 изменённых файлов: 2098 добавлений и 140 удалений

Просмотреть файл

@ -121,12 +121,24 @@ add_task(async function() {
// This test fails on some platforms if we leave IPv6 enabled.
set: [["network.dns.disableIPv6", true]],
});
let certService = Cc["@mozilla.org/security/local-cert-service;1"].getService(
Ci.nsILocalCertService
);
let certOverrideService = Cc[
"@mozilla.org/security/certoverride;1"
].getService(Ci.nsICertOverrideService);
let cert = getTestServerCertificate();
let cert = await new Promise((resolve, reject) => {
certService.getOrCreateCert("broken-tls-server", {
handleCert(c, rv) {
if (!Components.isSuccessCode(rv)) {
reject(rv);
return;
}
resolve(c);
},
});
});
// Start a server and trust its certificate.
let server = startServer(cert);
let overrideBits =

Просмотреть файл

@ -127,11 +127,24 @@ add_task(async function() {
set: [["network.dns.disableIPv6", true]],
});
let certService = Cc["@mozilla.org/security/local-cert-service;1"].getService(
Ci.nsILocalCertService
);
let certOverrideService = Cc[
"@mozilla.org/security/certoverride;1"
].getService(Ci.nsICertOverrideService);
let cert = getTestServerCertificate();
let cert = await new Promise((resolve, reject) => {
certService.getOrCreateCert("http-over-https-proxy", {
handleCert(c, rv) {
if (!Components.isSuccessCode(rv)) {
reject(rv);
return;
}
resolve(c);
},
});
});
// Start the proxy and configure Firefox to trust its certificate.
let server = startServer(cert);
let overrideBits =

Просмотреть файл

@ -422,18 +422,3 @@ async function loadBadCertPage(url) {
});
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
}
// nsITLSServerSocket needs a certificate with a corresponding private key
// available. In mochitests, the certificate with the common name "Mochitest
// client" has such a key.
function getTestServerCertificate() {
const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
for (const cert of certDB.getCerts()) {
if (cert.commonName == "Mochitest client") {
return cert;
}
}
return null;
}

Просмотреть файл

@ -11,6 +11,9 @@ const { MockRegistrar } = ChromeUtils.import(
"resource://testing-common/MockRegistrar.jsm"
);
const certService = Cc["@mozilla.org/security/local-cert-service;1"].getService(
Ci.nsILocalCertService
);
const certOverrideService = Cc[
"@mozilla.org/security/certoverride;1"
].getService(Ci.nsICertOverrideService);
@ -115,18 +118,6 @@ function startServer(cert) {
let server;
function getTestServerCertificate() {
const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
for (const cert of certDB.getCerts()) {
if (cert.commonName == "Mochitest client") {
return cert;
}
}
return null;
}
add_setup(async function() {
await SpecialPowers.pushPrefEnv({
set: [
@ -148,7 +139,17 @@ add_setup(async function() {
clientAuthDialogs
);
let cert = getTestServerCertificate();
let cert = await new Promise((resolve, reject) => {
certService.getOrCreateCert("speculative-connect", {
handleCert(c, rv) {
if (!Components.isSuccessCode(rv)) {
reject(rv);
return;
}
resolve(c);
},
});
});
server = startServer(cert);
uri = `https://${host}:${server.port}/`;
info(`running tls server at ${uri}`);

Просмотреть файл

@ -4,8 +4,13 @@
"use strict";
var { Ci, Cc } = require("chrome");
var Services = require("Services");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { dumpn, dumpv } = DevToolsUtils;
loader.lazyRequireGetter(this, "prompt", "devtools/shared/security/prompt");
loader.lazyRequireGetter(this, "cert", "devtools/shared/security/cert");
loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
/**
* A simple enum-like object with keys mirrored to values.
@ -49,7 +54,7 @@ var AuthenticationResult = (exports.AuthenticationResult = createEnum({
/**
* Allow the current connection, and persist this choice for future
* connections from the same client. This requires a trustable mechanism to
* identify the client in the future.
* identify the client in the future, such as the cert used during OOB_CERT.
*/
ALLOW_PERSIST: null,
}));
@ -98,6 +103,8 @@ Prompt.Client.prototype = {
* The port number of the devtools server.
* @param encryption boolean (optional)
* Whether the server requires encryption. Defaults to false.
* @param cert object (optional)
* The server's cert details.
* @param s nsISocketTransport
* Underlying socket transport, in case more details are needed.
* @return boolean
@ -130,6 +137,25 @@ Prompt.Server = function() {};
Prompt.Server.prototype = {
mode: Prompt.mode,
/**
* Verify that listener settings are appropriate for this authentication mode.
*
* @param listener SocketListener
* The socket listener about to be opened.
* @throws if validation requirements are not met
*/
validateOptions() {},
/**
* Augment options on the listening socket about to be opened.
*
* @param listener SocketListener
* The socket listener about to be opened.
* @param socket nsIServerSocket
* The socket that is about to start listening.
*/
augmentSocketOptions() {},
/**
* Augment the service discovery advertisement with any additional data needed
* to support this authentication mode.
@ -201,6 +227,405 @@ Prompt.Server.prototype = {
allowConnection: prompt.Server.defaultAllowConnection,
};
/**
* The out-of-band (OOB) cert authenticator is based on self-signed X.509 certs
* at both the client and server end.
*
* The user is first prompted to verify the connection, similar to the prompt
* method above. This prompt may display cert fingerprints if desired.
*
* Assuming the user approves the connection, further UI is used to assist the
* user in tranferring out-of-band (OOB) verification of the client's
* certificate. For example, this could take the form of a QR code that the
* client displays which is then scanned by a camera on the server.
*
* Since it is assumed that an attacker can't forge the client's X.509 cert, the
* user may also choose to always allow a client, which would permit immediate
* connections in the future with no user interaction needed.
*
* See docs/wifi.md for details of the authentication design.
*/
var OOBCert = (Authenticators.OOBCert = {});
OOBCert.mode = "OOB_CERT";
OOBCert.Client = function() {};
OOBCert.Client.prototype = {
mode: OOBCert.mode,
/**
* When client is about to make a new connection, verify that the connection settings
* are compatible with this authenticator.
* @throws if validation requirements are not met
*/
validateSettings({ encryption }) {
if (!encryption) {
throw new Error(`${OOBCert.mode} authentication requires encryption.`);
}
},
/**
* When client has just made a new socket connection, validate the connection
* to ensure it meets the authenticator's policies.
*
* @param host string
* The host name or IP address of the devtools server.
* @param port number
* The port number of the devtools server.
* @param encryption boolean (optional)
* Whether the server requires encryption. Defaults to false.
* @param cert object (optional)
* The server's cert details.
* @param socket nsISocketTransport
* Underlying socket transport, in case more details are needed.
* @return boolean
* Whether the connection is valid.
*/
// eslint-disable-next-line no-shadow
validateConnection({ cert, socket }) {
// Step B.7
// Client verifies that Server's cert matches hash(ServerCert) from the
// advertisement
dumpv("Validate server cert hash");
const serverCert = socket.securityInfo.QueryInterface(
Ci.nsITransportSecurityInfo
).serverCert;
const advertisedCert = cert;
if (serverCert.sha256Fingerprint != advertisedCert.sha256) {
dumpn("Server cert hash doesn't match advertisement");
return false;
}
return true;
},
/**
* Work with the server to complete any additional steps required by this
* authenticator's policies.
*
* Debugging commences after this hook completes successfully.
*
* @param host string
* The host name or IP address of the devtools server.
* @param port number
* The port number of the devtools server.
* @param encryption boolean (optional)
* Whether the server requires encryption. Defaults to false.
* @param cert object (optional)
* The server's cert details.
* @param transport DebuggerTransport
* A transport that can be used to communicate with the server.
* @return A promise can be used if there is async behavior.
*/
// eslint-disable-next-line no-shadow
authenticate({ host, port, cert, transport }) {
return new Promise((resolve, reject) => {
let oobData;
let activeSendDialog;
const closeDialog = () => {
// Close any prompts the client may have been showing from previous
// authentication steps
if (activeSendDialog?.close) {
activeSendDialog.close();
activeSendDialog = null;
}
};
transport.hooks = {
onPacket: async packet => {
closeDialog();
const { authResult } = packet;
switch (authResult) {
case AuthenticationResult.PENDING:
// Step B.8
// Client creates hash(ClientCert) + K(random 128-bit number)
oobData = await this._createOOB();
activeSendDialog = this.sendOOB({
host,
port,
cert,
authResult,
oob: oobData,
});
break;
case AuthenticationResult.ALLOW:
// Step B.12
// Client verifies received value matches K
if (packet.k != oobData.k) {
transport.close(new Error("Auth secret mismatch"));
return;
}
// Step B.13
// Debugging begins
transport.hooks = null;
resolve(transport);
break;
case AuthenticationResult.ALLOW_PERSIST:
// Server previously persisted Client as allowed
// Step C.5
// Debugging begins
transport.hooks = null;
resolve(transport);
break;
default:
transport.close(new Error("Invalid auth result: " + authResult));
break;
}
},
onTransportClosed(reason) {
closeDialog();
// Transport died before auth completed
transport.hooks = null;
reject(reason);
},
};
transport.ready();
});
},
/**
* Create the package of data that needs to be transferred across the OOB
* channel.
*/
async _createOOB() {
const clientCert = await cert.local.getOrCreate();
return {
sha256: clientCert.sha256Fingerprint,
k: this._createRandom(),
};
},
_createRandom() {
// 16 bytes / 128 bits
const length = 16;
const rng = Cc["@mozilla.org/security/random-generator;1"].createInstance(
Ci.nsIRandomGenerator
);
const bytes = rng.generateRandomBytes(length);
return bytes.map(byte => byte.toString(16)).join("");
},
/**
* Send data across the OOB channel to the server to authenticate the devices.
*
* @param host string
* The host name or IP address of the devtools server.
* @param port number
* The port number of the devtools server.
* @param cert object (optional)
* The server's cert details.
* @param authResult AuthenticationResult
* Authentication result sent from the server.
* @param oob object (optional)
* The token data to be transferred during OOB_CERT step 8:
* * sha256: hash(ClientCert)
* * k : K(random 128-bit number)
* @return object containing:
* * close: Function to hide the notification
*/
sendOOB: prompt.Client.defaultSendOOB,
};
OOBCert.Server = function() {};
OOBCert.Server.prototype = {
mode: OOBCert.mode,
/**
* Verify that listener settings are appropriate for this authentication mode.
*
* @param listener SocketListener
* The socket listener about to be opened.
* @throws if validation requirements are not met
*/
validateOptions(listener) {
if (!listener.encryption) {
throw new Error(OOBCert.mode + " authentication requires encryption.");
}
},
/**
* Augment options on the listening socket about to be opened.
*
* @param listener SocketListener
* The socket listener about to be opened.
* @param socket nsIServerSocket
* The socket that is about to start listening.
*/
augmentSocketOptions(listener, socket) {
const requestCert = Ci.nsITLSServerSocket.REQUIRE_ALWAYS;
socket.setRequestClientCertificate(requestCert);
},
/**
* Augment the service discovery advertisement with any additional data needed
* to support this authentication mode.
*
* @param listener SocketListener
* The socket listener that was just opened.
* @param advertisement object
* The advertisement being built.
*/
augmentAdvertisement(listener, advertisement) {
advertisement.authentication = OOBCert.mode;
// Step A.4
// Server announces itself via service discovery
// Announcement contains hash(ServerCert) as additional data
advertisement.cert = listener.cert;
},
/**
* Determine whether a connection the server should be allowed or not based on
* this authenticator's policies.
*
* @param session object
* In OOB_CERT mode, the |session| includes:
* {
* client: {
* host,
* port,
* cert: {
* sha256
* },
* },
* server: {
* host,
* port,
* cert: {
* sha256
* }
* },
* transport
* }
* @return An AuthenticationResult value.
* A promise that will be resolved to the above is also allowed.
*/
async authenticate({ client, server, transport }) {
// Step B.3 / C.3
// TLS connection established, authentication begins
const storageKey = `devtools.auth.${this.mode}.approved-clients`;
const approvedClients = (await asyncStorage.getItem(storageKey)) || {};
// Step C.4
// Server sees that ClientCert is from a known client via hash(ClientCert)
if (approvedClients[client.cert.sha256]) {
const authResult = AuthenticationResult.ALLOW_PERSIST;
transport.send({ authResult });
// Step C.5
// Debugging begins
return authResult;
}
// Step B.4
// Server sees that ClientCert is from a unknown client
// Tell client they are unknown and should display OOB client UX
transport.send({
authResult: AuthenticationResult.PENDING,
});
// Step B.5
// User is shown a Allow / Deny / Always Allow prompt on the Server
// with Client name and hash(ClientCert)
const authResult = await this.allowConnection({
authentication: this.mode,
client,
server,
});
switch (authResult) {
case AuthenticationResult.ALLOW_PERSIST:
case AuthenticationResult.ALLOW:
// Further processing
break;
default:
// Abort for any negative results
return authResult;
}
// Examine additional data for authentication
const oob = await this.receiveOOB();
if (!oob) {
dumpn("Invalid OOB data received");
return AuthenticationResult.DENY;
}
const { sha256, k } = oob;
// The OOB auth prompt should have transferred:
// hash(ClientCert) + K(random 128-bit number)
// from the client.
if (!sha256 || !k) {
dumpn("Invalid OOB data received");
return AuthenticationResult.DENY;
}
// Step B.10
// Server verifies that Client's cert matches hash(ClientCert) from
// out-of-band channel
if (client.cert.sha256 != sha256) {
dumpn("Client cert hash doesn't match OOB data");
return AuthenticationResult.DENY;
}
// Step B.11
// Server sends K to Client over TLS connection
transport.send({ authResult, k });
// Persist Client if we want to always allow in the future
if (authResult === AuthenticationResult.ALLOW_PERSIST) {
approvedClients[client.cert.sha256] = true;
await asyncStorage.setItem(storageKey, approvedClients);
}
// Client may decide to abort if K does not match.
// Server's portion of authentication is now complete.
// Step B.13
// Debugging begins
return authResult;
},
/**
* Prompt the user to accept or decline the incoming connection. The default
* implementation is used unless this is overridden on a particular
* authenticator instance.
*
* It is expected that the implementation of |allowConnection| will show a
* prompt to the user so that they can allow or deny the connection.
*
* @param session object
* In OOB_CERT mode, the |session| includes:
* {
* authentication: "OOB_CERT",
* client: {
* host,
* port,
* cert: {
* sha256
* },
* },
* server: {
* host,
* port,
* cert: {
* sha256
* }
* }
* }
* @return An AuthenticationResult value.
* A promise that will be resolved to the above is also allowed.
*/
allowConnection: prompt.Server.defaultAllowConnection,
/**
* The user must transfer some data through some out of band mechanism from
* the client to the server to authenticate the devices.
*
* @return An object containing:
* * sha256: hash(ClientCert)
* * k : K(random 128-bit number)
* A promise that will be resolved to the above is also allowed.
*/
receiveOOB: prompt.Server.defaultReceiveOOB,
};
exports.Authenticators = {
get(mode) {
if (!mode) {

Просмотреть файл

@ -0,0 +1,62 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var { Ci, Cc } = require("chrome");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
DevToolsUtils.defineLazyGetter(this, "localCertService", () => {
// Ensure PSM is initialized to support TLS sockets
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
return Cc["@mozilla.org/security/local-cert-service;1"].getService(
Ci.nsILocalCertService
);
});
const localCertName = "devtools";
exports.local = {
/**
* Get or create a new self-signed X.509 cert to represent this device for
* DevTools purposes over a secure transport, like TLS.
*
* The cert is stored permanently in the profile's key store after first use,
* and is valid for 1 year. If an expired or otherwise invalid cert is found,
* it is removed and a new one is made.
*
* @return promise
*/
getOrCreate() {
return new Promise((resolve, reject) => {
localCertService.getOrCreateCert(localCertName, {
handleCert: function(cert, rv) {
if (rv) {
reject(rv);
return;
}
resolve(cert);
},
});
});
},
/**
* Remove the DevTools self-signed X.509 cert for this device.
*
* @return promise
*/
remove() {
return new Promise((resolve, reject) => {
localCertService.removeCert(localCertName, {
handleCert: function(rv) {
if (rv) {
reject(rv);
return;
}
resolve();
},
});
});
},
};

Просмотреть файл

@ -9,6 +9,7 @@ XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.ini"]
DevToolsModules(
"auth.js",
"cert.js",
"DevToolsSocketStatus.jsm",
"prompt.js",
"socket.js",

Просмотреть файл

@ -34,6 +34,8 @@ var Server = (exports.Server = {});
* The host name or IP address of the devtools server.
* @param port number
* The port number of the devtools server.
* @param cert object (optional)
* The server's cert details.
* @param authResult AuthenticationResult
* Authentication result sent from the server.
* @param oob object (optional)

Просмотреть файл

@ -11,7 +11,7 @@ Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
var Services = require("Services");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { dumpn } = DevToolsUtils;
var { dumpn, dumpv } = DevToolsUtils;
loader.lazyRequireGetter(
this,
"WebSocketServer",
@ -33,6 +33,7 @@ loader.lazyRequireGetter(
"discovery",
"devtools/shared/discovery/discovery"
);
loader.lazyRequireGetter(this, "cert", "devtools/shared/security/cert");
loader.lazyRequireGetter(
this,
"Authenticators",
@ -64,6 +65,18 @@ DevToolsUtils.defineLazyGetter(this, "socketTransportService", () => {
);
});
DevToolsUtils.defineLazyGetter(this, "certOverrideService", () => {
return Cc["@mozilla.org/security/certoverride;1"].getService(
Ci.nsICertOverrideService
);
});
DevToolsUtils.defineLazyGetter(this, "nssErrorsService", () => {
return Cc["@mozilla.org/nss_errors_service;1"].getService(
Ci.nsINSSErrorsService
);
});
var DebuggerSocket = {};
/**
@ -73,11 +86,15 @@ var DebuggerSocket = {};
* The host name or IP address of the devtools server.
* @param port number
* The port number of the devtools server.
* @param encryption boolean (optional)
* Whether the server requires encryption. Defaults to false.
* @param webSocket boolean (optional)
* Whether to use WebSocket protocol to connect. Defaults to false.
* @param authenticator Authenticator (optional)
* |Authenticator| instance matching the mode in use by the server.
* Defaults to a PROMPT instance if not supplied.
* @param cert object (optional)
* The server's cert details. Used with OOB_CERT authentication.
* @return promise
* Resolved to a DebuggerTransport instance.
*/
@ -88,11 +105,13 @@ DebuggerSocket.connect = async function(settings) {
}
_validateSettings(settings);
// eslint-disable-next-line no-shadow
const { host, port, authenticator } = settings;
const { host, port, encryption, authenticator, cert } = settings;
const transport = await _getTransport(settings);
await authenticator.authenticate({
host,
port,
encryption,
cert,
transport,
});
transport.connectionSettings = settings;
@ -103,8 +122,11 @@ DebuggerSocket.connect = async function(settings) {
* Validate that the connection settings have been set to a supported configuration.
*/
function _validateSettings(settings) {
const { authenticator } = settings;
const { encryption, webSocket, authenticator } = settings;
if (webSocket && encryption) {
throw new Error("Encryption not supported on WebSocket transport");
}
authenticator.validateSettings(settings);
}
@ -116,17 +138,21 @@ function _validateSettings(settings) {
* The host name or IP address of the devtools server.
* @param port number
* The port number of the devtools server.
* @param encryption boolean (optional)
* Whether the server requires encryption. Defaults to false.
* @param webSocket boolean (optional)
* Whether to use WebSocket protocol to connect to the server. Defaults to false.
* @param authenticator Authenticator
* |Authenticator| instance matching the mode in use by the server.
* Defaults to a PROMPT instance if not supplied.
* @param cert object (optional)
* The server's cert details. Used with OOB_CERT authentication.
* @return transport DebuggerTransport
* A possible DevTools transport (if connection succeeded and streams
* are actually alive and working)
*/
var _getTransport = async function(settings) {
const { host, port, webSocket } = settings;
const { host, port, encryption, webSocket } = settings;
if (webSocket) {
// Establish a connection and wait until the WebSocket is ready to send and receive
@ -139,28 +165,51 @@ var _getTransport = async function(settings) {
return new WebSocketDebuggerTransport(socket);
}
const attempt = await _attemptTransport(settings);
let attempt = await _attemptTransport(settings);
if (attempt.transport) {
// Success
return attempt.transport;
}
throw new Error("Connection failed");
// If the server cert failed validation, store a temporary override and make
// a second attempt.
if (encryption && attempt.certError) {
_storeCertOverride(attempt.s, host, port);
} else {
throw new Error("Connection failed");
}
attempt = await _attemptTransport(settings);
if (attempt.transport) {
// Success
return attempt.transport;
}
throw new Error("Connection failed even after cert override");
};
/**
* Make a single attempt to connect and create a DevTools transport.
* Make a single attempt to connect and create a DevTools transport. This could
* fail if the remote host is unreachable, for example. If there is security
* error due to the use of self-signed certs, you should make another attempt
* after storing a cert override.
*
* @param host string
* The host name or IP address of the devtools server.
* @param port number
* The port number of the devtools server.
* @param encryption boolean (optional)
* Whether the server requires encryption. Defaults to false.
* @param authenticator Authenticator
* |Authenticator| instance matching the mode in use by the server.
* Defaults to a PROMPT instance if not supplied.
* @param cert object (optional)
* The server's cert details. Used with OOB_CERT authentication.
* @return transport DebuggerTransport
* A possible DevTools transport (if connection succeeded and streams
* are actually alive and working)
* @return certError boolean
* Flag noting if cert trouble caused the streams to fail
* @return s nsISocketTransport
* Underlying socket transport, in case more details are needed.
*/
@ -170,11 +219,13 @@ var _attemptTransport = async function(settings) {
// aborts the connection process immedidately.
const { s, input, output } = await _attemptConnect(settings);
// Check if the input stream is alive.
let alive;
// Check if the input stream is alive. If encryption is enabled, we need to
// watch out for cert errors by testing the input stream.
let alive, certError;
try {
const results = await _isInputAlive(input);
alive = results.alive;
certError = results.certError;
} catch (e) {
// For other unexpected errors, like NS_ERROR_CONNECTION_REFUSED, we reach
// this block.
@ -182,6 +233,7 @@ var _attemptTransport = async function(settings) {
output.close();
throw e;
}
dumpv("Server cert accepted? " + !certError);
// The |Authenticator| examines the connection as well and may determine it
// should be dropped.
@ -190,6 +242,8 @@ var _attemptTransport = async function(settings) {
authenticator.validateConnection({
host: settings.host,
port: settings.port,
encryption: settings.encryption,
cert: settings.cert,
socket: s,
});
@ -202,7 +256,7 @@ var _attemptTransport = async function(settings) {
output.close();
}
return { transport, s };
return { transport, certError, s };
};
/**
@ -219,8 +273,13 @@ var _attemptTransport = async function(settings) {
* @return output nsIAsyncOutputStream
* The socket's output stream.
*/
var _attemptConnect = async function({ host, port }) {
const s = socketTransportService.createTransport([], host, port, null, null);
var _attemptConnect = async function({ host, port, encryption }) {
let s;
if (encryption) {
s = socketTransportService.createTransport(["ssl"], host, port, null, null);
} else {
s = socketTransportService.createTransport([], host, port, null, null);
}
// Force disabling IPV6 if we aren't explicitely connecting to an IPv6 address
// It fails intermitently on MacOS when opening the Browser Toolbox (bug 1615412)
@ -233,15 +292,35 @@ var _attemptConnect = async function({ host, port }) {
// initializing, the connection is stuck in connecting state for 18.20 hours!
s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
// If encrypting, load the client cert now, so we can deliver it at just the
// right time.
let clientCert;
if (encryption) {
clientCert = await cert.local.getOrCreate();
}
let input;
let output;
return new Promise((resolve, reject) => {
// Delay opening the input stream until the transport has fully connected.
// The goal is to avoid showing the user a client cert UI prompt when
// encryption is used. This prompt is shown when the client opens the input
// stream and does not know which client cert to present to the server. To
// specify a client cert programmatically, we need to access the transport's
// nsISSLSocketControl interface, which is not accessible until the transport
// has connected.
s.setEventSink(
{
onTransportStatus(transport, status) {
if (status != Ci.nsISocketTransport.STATUS_CONNECTING_TO) {
return;
}
if (encryption) {
const sslSocketControl = transport.securityInfo.QueryInterface(
Ci.nsISSLSocketControl
);
sslSocketControl.clientCert = clientCert;
}
try {
input = s.openInputStream(0, 0, 0);
} catch (e) {
@ -273,7 +352,9 @@ var _attemptConnect = async function({ host, port }) {
};
/**
* Check if the input stream is alive.
* Check if the input stream is alive. For an encrypted connection, it may not
* be if the client refuses the server's cert. A cert error is expected on
* first connection to a new host because the cert is self-signed.
*/
function _isInputAlive(input) {
return new Promise((resolve, reject) => {
@ -284,7 +365,17 @@ function _isInputAlive(input) {
stream.available();
resolve({ alive: true });
} catch (e) {
reject(e);
try {
// getErrorClass may throw if you pass a non-NSS error
const errorClass = nssErrorsService.getErrorClass(e.result);
if (errorClass === Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
resolve({ certError: true });
} else {
reject(e);
}
} catch (nssErr) {
reject(e);
}
}
},
},
@ -295,6 +386,28 @@ function _isInputAlive(input) {
});
}
/**
* To allow the connection to proceed with self-signed cert, we store a cert
* override. This implies that we take on the burden of authentication for
* these connections.
*/
function _storeCertOverride(s, host, port) {
// eslint-disable-next-line no-shadow
const cert = s.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo)
.serverCert;
const overrideBits =
Ci.nsICertOverrideService.ERROR_UNTRUSTED |
Ci.nsICertOverrideService.ERROR_MISMATCH;
certOverrideService.rememberValidityOverride(
host,
port,
{},
cert,
overrideBits,
true /* temporary */
);
}
/**
* Creates a new socket listener for remote connections to the DevToolsServer.
* This helps contain and organize the parts of the server that may differ or
@ -314,6 +427,9 @@ function _isInputAlive(input) {
* discoverable:
* Controls whether this listener is announced via the service discovery
* mechanism. Defaults is false.
* encryption:
* Controls whether this listener's transport uses encryption.
* Defaults is false.
* fromBrowserToolbox:
* Should only be passed when opening a socket for a Browser Toolbox
* session. This will skip notifying DevToolsSocketStatus
@ -336,6 +452,7 @@ function SocketListener(devToolsServer, socketOptions) {
authenticator:
socketOptions.authenticator || new (Authenticators.get().Server)(),
discoverable: !!socketOptions.discoverable,
encryption: !!socketOptions.encryption,
fromBrowserToolbox: !!socketOptions.fromBrowserToolbox,
portOrPath: socketOptions.portOrPath || null,
webSocket: !!socketOptions.webSocket,
@ -353,6 +470,10 @@ SocketListener.prototype = {
return this._socketOptions.discoverable;
},
get encryption() {
return this._socketOptions.encryption;
},
get fromBrowserToolbox() {
return this._socketOptions.fromBrowserToolbox;
},
@ -375,6 +496,10 @@ SocketListener.prototype = {
if (this.discoverable && !Number(this.portOrPath)) {
throw new Error("Discovery only supported for TCP sockets.");
}
if (this.encryption && this.webSocket) {
throw new Error("Encryption not supported on WebSocket transport");
}
this.authenticator.validateOptions(this);
},
/**
@ -407,6 +532,7 @@ SocketListener.prototype = {
// Path isn't absolute path, so we use abstract socket address
self._socket.initWithAbstractAddress(self.portOrPath, backlog);
}
await self._setAdditionalSocketOptions();
self._socket.asyncListen(self);
dumpn("Socket listening on: " + (self.port || self.portOrPath));
})()
@ -434,6 +560,7 @@ SocketListener.prototype = {
const advertisement = {
port: this.port,
encryption: this.encryption,
};
this.authenticator.augmentAdvertisement(this, advertisement);
@ -442,11 +569,26 @@ SocketListener.prototype = {
},
_createSocketInstance: function() {
if (this.encryption) {
return Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
Ci.nsITLSServerSocket
);
}
return Cc["@mozilla.org/network/server-socket;1"].createInstance(
Ci.nsIServerSocket
);
},
async _setAdditionalSocketOptions() {
if (this.encryption) {
this._socket.serverCert = await cert.local.getOrCreate();
this._socket.setSessionTickets(false);
const requestCert = Ci.nsITLSServerSocket.REQUEST_NEVER;
this._socket.setRequestClientCertificate(requestCert);
}
this.authenticator.augmentSocketOptions(this, this._socket);
},
/**
* Closes the SocketListener. Notifies the server to remove the listener from
* the set of active SocketListeners.
@ -494,6 +636,15 @@ SocketListener.prototype = {
return this._socket.port;
},
get cert() {
if (!this._socket || !this._socket.serverCert) {
return null;
}
return {
sha256: this._socket.serverCert.sha256Fingerprint,
};
},
onAllowedConnection(transport) {
dumpn("onAllowedConnection, transport: " + transport);
this.emit("accepted", transport, this);
@ -515,9 +666,16 @@ SocketListener.prototype = {
},
};
// Client must complete TLS handshake within this window (ms)
loader.lazyGetter(this, "HANDSHAKE_TIMEOUT", () => {
return Services.prefs.getIntPref("devtools.remote.tls-handshake-timeout");
});
/**
* A |ServerSocketConnection| is created by a |SocketListener| for each accepted
* incoming socket.
* incoming socket. This is a short-lived object used to implement
* authentication and verify encryption prior to handing off the connection to
* the |DevToolsServer|.
*/
function ServerSocketConnection(listener, socketTransport) {
this._listener = listener;
@ -539,6 +697,15 @@ ServerSocketConnection.prototype = {
return this._socketTransport.port;
},
get cert() {
if (!this._clientCert) {
return null;
}
return {
sha256: this._clientCert.sha256Fingerprint,
};
},
get address() {
return this.host + ":" + this.port;
},
@ -548,6 +715,9 @@ ServerSocketConnection.prototype = {
host: this.host,
port: this.port,
};
if (this.cert) {
client.cert = this.cert;
}
return client;
},
@ -556,6 +726,9 @@ ServerSocketConnection.prototype = {
host: this._listener.host,
port: this._listener.port,
};
if (this._listener.cert) {
server.cert = this._listener.cert;
}
return server;
},
@ -567,7 +740,9 @@ ServerSocketConnection.prototype = {
async _handle() {
dumpn("Debugging connection starting authentication on " + this.address);
try {
this._listenForTLSHandshake();
await this._createTransport();
await this._awaitTLSHandshake();
await this._authenticate();
this.allow();
} catch (e) {
@ -604,6 +779,72 @@ ServerSocketConnection.prototype = {
this._transport.ready();
},
/**
* Set the socket's security observer, which receives an event via the
* |onHandshakeDone| callback when the TLS handshake completes.
*/
_setSecurityObserver(observer) {
if (!this._socketTransport || !this._socketTransport.securityInfo) {
return;
}
const connectionInfo = this._socketTransport.securityInfo.QueryInterface(
Ci.nsITLSServerConnectionInfo
);
connectionInfo.setSecurityObserver(observer);
},
/**
* When encryption is used, we wait for the client to complete the TLS
* handshake before proceeding. The handshake details are validated in
* |onHandshakeDone|.
*/
_listenForTLSHandshake() {
if (!this._listener.encryption) {
this._handshakePromise = Promise.resolve();
return;
}
this._handshakePromise = new Promise((resolve, reject) => {
this._observer = {
// nsITLSServerSecurityObserver implementation
onHandshakeDone: (socket, clientStatus) => {
clearTimeout(this._handshakeTimeout);
this._setSecurityObserver(null);
dumpv("TLS version: " + clientStatus.tlsVersionUsed.toString(16));
dumpv("TLS cipher: " + clientStatus.cipherName);
dumpv("TLS key length: " + clientStatus.keyLength);
dumpv("TLS MAC length: " + clientStatus.macLength);
this._clientCert = clientStatus.peerCert;
/*
* TODO: These rules should be really be set on the TLS socket directly, but
* this would need more platform work to expose it via XPCOM.
*
* Enforcing cipher suites here would be a bad idea, as we want TLS
* cipher negotiation to work correctly. The server already allows only
* Gecko's normal set of cipher suites.
*/
if (
clientStatus.tlsVersionUsed < Ci.nsITLSClientStatus.TLS_VERSION_1_2
) {
reject(Cr.NS_ERROR_CONNECTION_REFUSED);
return;
}
resolve();
},
};
this._setSecurityObserver(this._observer);
this._handshakeTimeout = setTimeout(() => {
dumpv("Client failed to complete TLS handshake");
reject(Cr.NS_ERROR_NET_TIMEOUT);
}, HANDSHAKE_TIMEOUT);
});
},
_awaitTLSHandshake() {
return this._handshakePromise;
},
async _authenticate() {
const result = await this._listener.authenticator.authenticate({
client: this.client,
@ -662,9 +903,12 @@ ServerSocketConnection.prototype = {
destroy() {
this._destroyed = true;
clearTimeout(this._handshakeTimeout);
this._setSecurityObserver(null);
this._listener = null;
this._socketTransport = null;
this._transport = null;
this._clientCert = null;
},
};

Просмотреть файл

@ -29,6 +29,8 @@ loader.lazyRequireGetter(
// Services.prefs.setBoolPref("devtools.debugger.log.verbose", true);
// Enable remote debugging for the relevant tests.
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
// Fast timeout for TLS tests
Services.prefs.setIntPref("devtools.remote.tls-handshake-timeout", 1000);
// Convert an nsIScriptError 'logLevel' value into an appropriate string.
function scriptErrorLogLevel(message) {

Просмотреть файл

@ -0,0 +1,106 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test basic functionality of DevTools client and server TLS encryption mode
function run_test() {
// Need profile dir to store the key / cert
do_get_profile();
// Ensure PSM is initialized
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
run_next_test();
}
function connectClient(client) {
return client.connect();
}
add_task(async function() {
initTestDevToolsServer();
});
// Client w/ encryption connects successfully to server w/ encryption
add_task(async function() {
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
const AuthenticatorType = DevToolsServer.Authenticators.get("PROMPT");
const authenticator = new AuthenticatorType.Server();
authenticator.allowConnection = () => {
return DevToolsServer.AuthenticationResult.ALLOW;
};
const socketOptions = {
authenticator,
encryption: true,
portOrPath: -1,
};
const listener = new SocketListener(DevToolsServer, socketOptions);
ok(listener, "Socket listener created");
await listener.open();
equal(DevToolsServer.listeningSockets, 1, "1 listening socket");
const transport = await DevToolsClient.socketConnect({
host: "127.0.0.1",
port: listener.port,
encryption: true,
});
ok(transport, "Client transport created");
const client = new DevToolsClient(transport);
const onUnexpectedClose = () => {
do_throw("Closed unexpectedly");
};
client.on("closed", onUnexpectedClose);
await connectClient(client);
// Send a message the server will echo back
const message = "secrets";
const reply = await client.mainRoot.echo({ message });
equal(reply.message, message, "Encrypted echo matches");
client.off("closed", onUnexpectedClose);
transport.close();
listener.close();
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
});
// Client w/o encryption fails to connect to server w/ encryption
add_task(async function() {
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
const AuthenticatorType = DevToolsServer.Authenticators.get("PROMPT");
const authenticator = new AuthenticatorType.Server();
authenticator.allowConnection = () => {
return DevToolsServer.AuthenticationResult.ALLOW;
};
const socketOptions = {
authenticator,
encryption: true,
portOrPath: -1,
};
const listener = new SocketListener(DevToolsServer, socketOptions);
ok(listener, "Socket listener created");
await listener.open();
equal(DevToolsServer.listeningSockets, 1, "1 listening socket");
try {
await DevToolsClient.socketConnect({
host: "127.0.0.1",
port: listener.port,
// encryption: false is the default
});
} catch (e) {
ok(true, "Client failed to connect as expected");
listener.close();
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
return;
}
do_throw("Connection unexpectedly succeeded");
});
add_task(async function() {
DevToolsServer.destroy();
});

Просмотреть файл

@ -0,0 +1,275 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const cert = require("devtools/shared/security/cert");
// Test basic functionality of DevTools client and server OOB_CERT auth (used
// with WiFi debugging)
function run_test() {
// Need profile dir to store the key / cert
do_get_profile();
// Ensure PSM is initialized
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
run_next_test();
}
function connectClient(client) {
return client.connect();
}
add_task(async function() {
initTestDevToolsServer();
});
// Client w/ OOB_CERT auth connects successfully to server w/ OOB_CERT auth
add_task(async function() {
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
// Grab our cert, instead of relying on a discovery advertisement
const serverCert = await cert.local.getOrCreate();
const AuthenticatorType = DevToolsServer.Authenticators.get("OOB_CERT");
const serverAuth = new AuthenticatorType.Server();
serverAuth.allowConnection = () => {
return DevToolsServer.AuthenticationResult.ALLOW;
};
// Skip prompt for tests
const clientAuth = new AuthenticatorType.Client();
serverAuth.receiveOOB = () =>
new Promise(resolve => {
clientAuth.sendOOB = ({ oob }) => {
info(oob);
// Pass to server, skipping prompt for tests
resolve(oob);
};
});
const socketOptions = {
authenticator: serverAuth,
encryption: true,
portOrPath: -1,
};
const listener = new SocketListener(DevToolsServer, socketOptions);
ok(listener, "Socket listener created");
await listener.open();
equal(DevToolsServer.listeningSockets, 1, "1 listening socket");
const transport = await DevToolsClient.socketConnect({
host: "127.0.0.1",
port: listener.port,
encryption: true,
authenticator: clientAuth,
cert: {
sha256: serverCert.sha256Fingerprint,
},
});
ok(transport, "Client transport created");
const client = new DevToolsClient(transport);
const onUnexpectedClose = () => {
do_throw("Closed unexpectedly");
};
client.on("closed", onUnexpectedClose);
await connectClient(client);
// Send a message the server will echo back
const message = "secrets";
const reply = await client.mainRoot.echo({ message });
equal(reply.message, message, "Encrypted echo matches");
client.off("closed", onUnexpectedClose);
transport.close();
listener.close();
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
});
// Client w/o OOB_CERT auth fails to connect to server w/ OOB_CERT auth
add_task(async function() {
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
const AuthenticatorType = DevToolsServer.Authenticators.get("OOB_CERT");
const serverAuth = new AuthenticatorType.Server();
serverAuth.allowConnection = () => {
return DevToolsServer.AuthenticationResult.ALLOW;
};
// Skip prompt for tests
const clientAuth = new AuthenticatorType.Client();
serverAuth.receiveOOB = () =>
new Promise(resolve => {
clientAuth.sendOOB = ({ oob }) => {
info(oob);
// Pass to server, skipping prompt for tests
resolve(oob);
};
});
const socketOptions = {
authenticator: serverAuth,
encryption: true,
portOrPath: -1,
};
const listener = new SocketListener(DevToolsServer, socketOptions);
ok(listener, "Socket listener created");
await listener.open();
equal(DevToolsServer.listeningSockets, 1, "1 listening socket");
// This will succeed, but leaves the client in confused state, and no data is
// actually accessible
const transport = await DevToolsClient.socketConnect({
host: "127.0.0.1",
port: listener.port,
encryption: true,
// authenticator: PROMPT is the default
});
// Attempt to use the transport
const client = new DevToolsClient(transport);
const promise = new Promise(resolve => {
client.onPacket = packet => {
// Client did not authenticate, so it ends up seeing the server's auth data
// which is effectively malformed data from the client's perspective
ok(!packet.from && packet.authResult, "Got auth packet instead of data");
resolve();
};
});
client.connect();
await promise;
// Try to send a message the server will echo back
const message = "secrets";
try {
await client.request({
to: "root",
type: "echo",
message,
});
} catch (e) {
ok(true, "Sending a message failed");
transport.close();
listener.close();
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
return;
}
do_throw("Connection unexpectedly succeeded");
});
// Client w/ invalid K value fails to connect
add_task(async function() {
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
// Grab our cert, instead of relying on a discovery advertisement
const serverCert = await cert.local.getOrCreate();
const AuthenticatorType = DevToolsServer.Authenticators.get("OOB_CERT");
const serverAuth = new AuthenticatorType.Server();
serverAuth.allowConnection = () => {
return DevToolsServer.AuthenticationResult.ALLOW;
};
// Skip prompt for tests
const clientAuth = new AuthenticatorType.Client();
serverAuth.receiveOOB = () =>
new Promise(resolve => {
clientAuth.sendOOB = ({ oob }) => {
info(oob);
info("Modifying K value, should fail");
// Pass to server, skipping prompt for tests
resolve({
k: oob.k + 1,
sha256: oob.sha256,
});
};
});
const socketOptions = {
authenticator: serverAuth,
encryption: true,
portOrPath: -1,
};
const listener = new SocketListener(DevToolsServer, socketOptions);
ok(listener, "Socket listener created");
await listener.open();
equal(DevToolsServer.listeningSockets, 1, "1 listening socket");
try {
await DevToolsClient.socketConnect({
host: "127.0.0.1",
port: listener.port,
encryption: true,
authenticator: clientAuth,
cert: {
sha256: serverCert.sha256Fingerprint,
},
});
} catch (e) {
ok(true, "Client failed to connect as expected");
listener.close();
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
return;
}
do_throw("Connection unexpectedly succeeded");
});
// Client w/ invalid cert hash fails to connect
add_task(async function() {
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
// Grab our cert, instead of relying on a discovery advertisement
const serverCert = await cert.local.getOrCreate();
const AuthenticatorType = DevToolsServer.Authenticators.get("OOB_CERT");
const serverAuth = new AuthenticatorType.Server();
serverAuth.allowConnection = () => {
return DevToolsServer.AuthenticationResult.ALLOW;
};
// Skip prompt for tests
const clientAuth = new AuthenticatorType.Client();
serverAuth.receiveOOB = () =>
new Promise(resolve => {
clientAuth.sendOOB = ({ oob }) => {
info(oob);
info("Modifying cert hash, should fail");
// Pass to server, skipping prompt for tests
resolve({
k: oob.k,
sha256: oob.sha256 + 1,
});
};
});
const socketOptions = {
authenticator: serverAuth,
encryption: true,
portOrPath: -1,
};
const listener = new SocketListener(DevToolsServer, socketOptions);
ok(listener, "Socket listener created");
await listener.open();
equal(DevToolsServer.listeningSockets, 1, "1 listening socket");
try {
await DevToolsClient.socketConnect({
host: "127.0.0.1",
port: listener.port,
encryption: true,
authenticator: clientAuth,
cert: {
sha256: serverCert.sha256Fingerprint,
},
});
} catch (e) {
ok(true, "Client failed to connect as expected");
listener.close();
equal(DevToolsServer.listeningSockets, 0, "0 listening sockets");
return;
}
do_throw("Connection unexpectedly succeeded");
});
add_task(async function() {
DevToolsServer.destroy();
});

Просмотреть файл

@ -8,3 +8,5 @@ support-files=
testactors.js
[test_devtools_socket_status.js]
[test_encryption.js]
[test_oob_cert_auth.js]

Просмотреть файл

@ -4374,6 +4374,8 @@ pref("devtools.dump.emit", false);
pref("devtools.discovery.log", false);
// Whether to scan for DevTools devices via WiFi.
pref("devtools.remote.wifi.scan", true);
// Client must complete TLS handshake within this window (ms).
pref("devtools.remote.tls-handshake-timeout", 10000);
// The extension ID for devtools-adb-extension.
pref("devtools.remote.adb.extensionID", "adb@mozilla.org");

Просмотреть файл

@ -225,6 +225,19 @@ FuzzySecurityInfo::GetMACAlgorithmUsed(int16_t* aMac) {
return NS_OK;
}
NS_IMETHODIMP
FuzzySecurityInfo::GetClientCert(nsIX509Cert** aClientCert) {
NS_ENSURE_ARG_POINTER(aClientCert);
*aClientCert = nullptr;
return NS_OK;
}
NS_IMETHODIMP
FuzzySecurityInfo::SetClientCert(nsIX509Cert* aClientCert) {
MOZ_CRASH("Unused");
return NS_OK;
}
bool FuzzySecurityInfo::GetDenyClientCert() { return false; }
void FuzzySecurityInfo::SetDenyClientCert(bool aDenyClientCert) {

Просмотреть файл

@ -128,6 +128,7 @@ parent:
uint32_t aProviderFlags,
uint32_t aProviderTlsFlags,
ByteArray aServerCert,
ByteArray? aClientCert,
ByteArray[] aCollectedCANames)
returns (bool aSucceeded, ByteArray aOutCert, ByteArray[] aBuiltChain);
async PProxyConfigLookup(nsIURI aUri, uint32_t aFlags);

Просмотреть файл

@ -265,8 +265,8 @@ mozilla::ipc::IPCResult SocketProcessParent::RecvGetTLSClientCert(
const nsCString& aHostName, const OriginAttributes& aOriginAttributes,
const int32_t& aPort, const uint32_t& aProviderFlags,
const uint32_t& aProviderTlsFlags, const ByteArray& aServerCert,
nsTArray<ByteArray>&& aCollectedCANames, bool* aSucceeded,
ByteArray* aOutCert, nsTArray<ByteArray>* aBuiltChain) {
Maybe<ByteArray>&& aClientCert, nsTArray<ByteArray>&& aCollectedCANames,
bool* aSucceeded, ByteArray* aOutCert, nsTArray<ByteArray>* aBuiltChain) {
*aSucceeded = false;
SECItem serverCertItem = {
@ -278,8 +278,13 @@ mozilla::ipc::IPCResult SocketProcessParent::RecvGetTLSClientCert(
return IPC_OK();
}
RefPtr<nsIX509Cert> clientCert;
if (aClientCert) {
clientCert = new nsNSSCertificate(std::move(aClientCert->data()));
}
ClientAuthInfo info(aHostName, aOriginAttributes, aPort, aProviderFlags,
aProviderTlsFlags);
aProviderTlsFlags, clientCert);
nsTArray<nsTArray<uint8_t>> collectedCANames;
for (auto& name : aCollectedCANames) {
collectedCANames.AppendElement(std::move(name.data()));

Просмотреть файл

@ -83,8 +83,8 @@ class SocketProcessParent final
const nsCString& aHostName, const OriginAttributes& aOriginAttributes,
const int32_t& aPort, const uint32_t& aProviderFlags,
const uint32_t& aProviderTlsFlags, const ByteArray& aServerCert,
nsTArray<ByteArray>&& aCollectedCANames, bool* aSucceeded,
ByteArray* aOutCert, nsTArray<ByteArray>* aBuiltChain);
Maybe<ByteArray>&& aClientCert, nsTArray<ByteArray>&& aCollectedCANames,
bool* aSucceeded, ByteArray* aOutCert, nsTArray<ByteArray>* aBuiltChain);
mozilla::ipc::IPCResult RecvFindIPCClientCertObjects(
nsTArray<IPCClientCertObject>* aObjects);

Просмотреть файл

@ -126,6 +126,13 @@ interface nsISSLSocketControl : nsISupports {
*/
[notxpcom, nostdcall] attribute boolean denyClientCert;
/**
* If set before the server requests a client cert (assuming it does so at
* all), then this cert will be presented to the server, instead of asking
* the user or searching the set of rememebered user cert decisions.
*/
attribute nsIX509Cert clientCert;
/**
* True iff a client cert has been sent to the server - i.e. this
* socket has been client-cert authenticated.

Просмотреть файл

@ -0,0 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIoXjtIGzP1OkCAggA
MBQGCCqGSIb3DQMHBAg7NkhJaDJEVwSCBMhYUI4JRAIdvJtCmP7lKl30QR+HG4JC
9gUJflQrCsa1WhxKCHa7VXzqlItQTu0FTMwn9GRFUOtqpY8ZE6XJFuvzKPox3RMa
1TMod6v3NS3KIs6/l2Pb2HcGtcMzOJVei1nwtjsT5fvq36x3eoKzgqd9l0fLcvlD
wf+byzgY0Nsau+ER8DWy65jXF8bPsQQcKTc+U+p4moO3UuXG4+Pnd8ooSaM4X2on
1jIYDU1aFoSDUvze8+MvQCD32QLuO63iK7ox4sFharG7KucYqeWCihDx5rlGaVGB
5647v4oHRysEdLVTkU12mIC/Hx/yPXcLhHYmawmnYwEoh1S+wd7rOo9Wn/l16NTK
8BcDuvfM8km4T5oO/UFaNDIBLBQsNM5sNHDYFDlhmR4x6d5nXeERJ6DQbvhQtgnV
bTtT9h24rsC8Irflz/abcvTvqqp8I1+gYEzmhgDRUgp9zAPZUoH3E4DKk5rVgApR
ARX9Y88S7k/OBnU8r+cT+0CjsusbbIv5W2nAFqEX9jMend0cHzYvq3m6v1Jqxjfn
kQRP1n+SagmAPBIAzy1wSHGV43+COk6FB+blfAGbO55lLglEM9PLH7Nnl0XrPtaE
dXx5RTtdBnb349Ow8H3WnleTfKspUbIVNyM48aPaXJu6Y784pUXDOC13ISFVbOew
dPr/s/GoHgBUIm9gxkhNQYUlcSNrJCyJ6bqvrYbOmVQRusO/SaM6ozY8wFL8LDnS
GeXmg3dAslHhuaHlFN7atF7rBtTWPsH+oQdHNKcLDK7nYq45v8VfjPUrWPfYc2nB
l+zT4LozY3VPfPW7BG2zVBTyxXkiynz0w7tJaN/HokZGAUDqWXqjSceJqc9Q4XAG
slIxbxkfxEJUEmJ2wHEnure6T0dJOIfbJzkCqWAeJjkrbI5mdKLuXFj94VgSlfK2
iq3J20/5HVdHqoVGRZ5rxBUIaVEgSXB3/+9C/M0U0uxx23zxRmVkMGdhhCqXQRh/
jFUkBzq4x3yibxJW3fRe7jXEJdo1DAAfgBnDvCUWH7lRX8hDkx6OIX4ZS4D7Va0j
ogSC04IdZWxOP3YJ4gGwx8vvgHWnBLyFfmdFnfHXUr9A8HDDJQTupYg25PDUGHla
SxukgOYdQ2O6jUCW0TYeUzX7y/P/Za93kWJp7XqA4v76fQ+C9d3CZT/TY0CqNgxB
C5+PWRGvxtcy+Bne8QYCJhvNPEhfgFa9fU3Rd4w43lvTb9rsy7eBR3jJKdPLKExJ
zEPIgVUGaMf0lawL0fIgoUI5Q3kRCmrswkTK9kr4+rSA//p0NralnZtHCWRvgs9W
Lg4hkf1vXxsa9f16Nk6PxqU/OnJmhTnTnv9MzFoX3Sce2neD86H5c7tdguySbrsj
5fww64rH1UwHhn/i49i3hkseax48gOAZPA8rl+L70FS8dXLpHOm4ihmv6ubVjr82
yOxi4WmaoXfmOPBgOgGhz1nTFAaetwfhZIsgEtysuWAOsApOUyjlD/wM25988bAa
m5FwslUGLWQfBIV1N9PC+Q0ui1ywRuLoKHNiKDSE+T5iOuv2Yf7du4nncoM/ANmU
FnWJL3Aj1VE/O+OeUyuNEPWLHvVX5TChe5mFXZO4bXfTR4tgdJJ15HWf4LKMQdcl
BEA=
-----END ENCRYPTED PRIVATE KEY-----

Двоичные данные
netwerk/test/unit/client-cert.p12

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,27 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
"use strict";
const { ComponentUtils } = ChromeUtils.import(
"resource://gre/modules/ComponentUtils.jsm"
);
var Prompter = {
QueryInterface: ChromeUtils.generateQI(["nsIPrompt"]),
alert() {}, // Do nothing when asked to show an alert
};
function WindowWatcherService() {}
WindowWatcherService.prototype = {
classID: Components.ID("{01ae923c-81bb-45db-b860-d423b0fc4fe1}"),
QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"]),
getNewPrompter() {
return Prompter;
},
};
this.NSGetFactory = ComponentUtils.generateNSGetFactory([WindowWatcherService]);

Просмотреть файл

@ -0,0 +1,2 @@
component {01ae923c-81bb-45db-b860-d423b0fc4fe1} cert_dialog.js
contract @mozilla.org/embedcomp/window-watcher;1 {01ae923c-81bb-45db-b860-d423b0fc4fe1}

Просмотреть файл

@ -450,21 +450,3 @@ async function with_node_servers(arrayOfClasses, asyncClosure) {
await server.stop();
}
}
// nsITLSServerSocket needs a certificate with a corresponding private key
// available. xpcshell tests can import the test file "client-cert.p12" using
// the password "password", resulting in a certificate with the common name
// "Test End-entity" being available with a corresponding private key.
function getTestServerCertificate() {
const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
const certFile = do_get_file("client-cert.p12");
certDB.importPKCS12File(certFile, "password");
for (const cert of certDB.getCerts()) {
if (cert.commonName == "Test End-entity") {
return cert;
}
}
return null;
}

Просмотреть файл

@ -13,6 +13,23 @@
do_get_profile();
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
function getCert() {
return new Promise((resolve, reject) => {
let certService = Cc[
"@mozilla.org/security/local-cert-service;1"
].getService(Ci.nsILocalCertService);
certService.getOrCreateCert("beConservative-test", {
handleCert(c, rv) {
if (rv) {
reject(rv);
return;
}
resolve(c);
},
});
});
}
class InputStreamCallback {
constructor(output) {
this.output = output;
@ -145,7 +162,6 @@ function storeCertOverride(port, cert) {
].getService(Ci.nsICertOverrideService);
let overrideBits =
Ci.nsICertOverrideService.ERROR_UNTRUSTED |
Ci.nsICertOverrideService.ERROR_TIME |
Ci.nsICertOverrideService.ERROR_MISMATCH;
certOverrideService.rememberValidityOverride(
hostname,
@ -186,7 +202,7 @@ function startClient(port, beConservative, expectSuccess) {
add_task(async function() {
Services.prefs.setIntPref("security.tls.version.max", 4);
Services.prefs.setCharPref("network.dns.localDomains", hostname);
let cert = getTestServerCertificate();
let cert = await getCert();
// First run a server that accepts TLS 1.2 and 1.3. A conservative client
// should succeed in connecting.

Просмотреть файл

@ -13,6 +13,23 @@
do_get_profile();
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
function getCert() {
return new Promise((resolve, reject) => {
let certService = Cc[
"@mozilla.org/security/local-cert-service;1"
].getService(Ci.nsILocalCertService);
certService.getOrCreateCert("beConservative-test", {
handleCert(c, rv) {
if (rv) {
reject(rv);
return;
}
resolve(c);
},
});
});
}
class InputStreamCallback {
constructor(output) {
this.output = output;
@ -145,7 +162,6 @@ function storeCertOverride(port, cert) {
].getService(Ci.nsICertOverrideService);
let overrideBits =
Ci.nsICertOverrideService.ERROR_UNTRUSTED |
Ci.nsICertOverrideService.ERROR_TIME |
Ci.nsICertOverrideService.ERROR_MISMATCH;
certOverrideService.rememberValidityOverride(
hostname,
@ -188,7 +204,7 @@ add_task(async function() {
Services.prefs.setIntPref("security.tls.version.min", 4);
Services.prefs.setIntPref("security.tls.version.max", 4);
Services.prefs.setCharPref("network.dns.localDomains", hostname);
let cert = getTestServerCertificate();
let cert = await getCert();
// Run a server that accepts TLS 1.2 and 1.3. The connection should succeed.
let server = startServer(

Просмотреть файл

@ -15,6 +15,23 @@
do_get_profile();
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
function getCert() {
return new Promise((resolve, reject) => {
let certService = Cc[
"@mozilla.org/security/local-cert-service;1"
].getService(Ci.nsILocalCertService);
certService.getOrCreateCert("tlsflags-test", {
handleCert(c, rv) {
if (rv) {
reject(rv);
return;
}
resolve(c);
},
});
});
}
class InputStreamCallback {
constructor(output) {
this.output = output;
@ -160,7 +177,6 @@ function storeCertOverride(port, cert) {
].getService(Ci.nsICertOverrideService);
let overrideBits =
Ci.nsICertOverrideService.ERROR_UNTRUSTED |
Ci.nsICertOverrideService.ERROR_TIME |
Ci.nsICertOverrideService.ERROR_MISMATCH;
certOverrideService.rememberValidityOverride(
hostname,
@ -201,7 +217,7 @@ function startClient(port, tlsFlags, expectSuccess) {
add_task(async function() {
Services.prefs.setIntPref("security.tls.version.max", 4);
Services.prefs.setCharPref("network.dns.localDomains", hostname);
let cert = getTestServerCertificate();
let cert = await getCert();
// server that accepts 1.1->1.3 and a client max 1.3. expect 1.3
info("TEST 1");

Просмотреть файл

@ -8,12 +8,12 @@ do_get_profile();
// Ensure PSM is initialized
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
const { MockRegistrar } = ChromeUtils.import(
"resource://testing-common/MockRegistrar.jsm"
);
const { PromiseUtils } = ChromeUtils.import(
"resource://gre/modules/PromiseUtils.jsm"
);
const certService = Cc["@mozilla.org/security/local-cert-service;1"].getService(
Ci.nsILocalCertService
);
const certOverrideService = Cc[
"@mozilla.org/security/certoverride;1"
].getService(Ci.nsICertOverrideService);
@ -23,6 +23,20 @@ const socketTransportService = Cc[
const prefs = Services.prefs;
function getCert() {
return new Promise((resolve, reject) => {
certService.getOrCreateCert("tls-test", {
handleCert(c, rv) {
if (rv) {
reject(rv);
return;
}
resolve(c);
},
});
});
}
function areCertsEqual(certA, certB) {
let derA = certA.getRawDER();
let derB = certB.getRawDER();
@ -83,7 +97,7 @@ function startServer(
if (expectedVersion >= 772) {
expectedCipher = "TLS_AES_128_GCM_SHA256";
} else {
expectedCipher = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
expectedCipher = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
}
equal(status.cipherName, expectedCipher, "Using expected cipher");
equal(status.keyLength, 128, "Using 128-bit key");
@ -118,7 +132,6 @@ function startServer(
function storeCertOverride(port, cert) {
let overrideBits =
Ci.nsICertOverrideService.ERROR_UNTRUSTED |
Ci.nsICertOverrideService.ERROR_TIME |
Ci.nsICertOverrideService.ERROR_MISMATCH;
certOverrideService.rememberValidityOverride(
"127.0.0.1",
@ -130,8 +143,7 @@ function storeCertOverride(port, cert) {
);
}
function startClient(port, sendClientCert, expectingAlert, tlsVersion) {
gClientAuthDialogs.selectCertificate = sendClientCert;
function startClient(port, cert, expectingAlert, tlsVersion) {
let SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
let SSL_ERROR_BAD_CERT_ALERT = SSL_ERROR_BASE + 17;
let SSL_ERROR_RX_CERTIFICATE_REQUIRED_ALERT = SSL_ERROR_BASE + 181;
@ -193,6 +205,13 @@ function startClient(port, sendClientCert, expectingAlert, tlsVersion) {
onOutputStreamReady(output) {
try {
// Set the client certificate as appropriate.
if (cert) {
let clientSecInfo = transport.securityInfo;
let tlsControl = clientSecInfo.QueryInterface(Ci.nsISSLSocketControl);
tlsControl.clientCert = cert;
}
output.write("HELLO", 5);
info("Output to server written");
outputDeferred.resolve();
@ -215,37 +234,7 @@ function startClient(port, sendClientCert, expectingAlert, tlsVersion) {
}
// Replace the UI dialog that prompts the user to pick a client certificate.
const gClientAuthDialogs = {
_selectCertificate: false,
set selectCertificate(value) {
this._selectCertificate = value;
},
chooseCertificate(
hostname,
port,
organization,
issuerOrg,
certList,
selectedIndex,
rememberClientAuthCertificate
) {
rememberClientAuthCertificate.value = false;
if (this._selectCertificate) {
selectedIndex.value = 0;
return true;
}
return false;
},
QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthDialogs]),
};
MockRegistrar.register(
"@mozilla.org/nsClientAuthDialogs;1",
gClientAuthDialogs
);
do_load_manifest("client_cert_chooser.manifest");
const tests = [
{
@ -300,7 +289,7 @@ const versions = [
];
add_task(async function() {
let cert = getTestServerCertificate();
let cert = await getCert();
ok(!!cert, "Got self-signed cert");
for (let v of versions) {
prefs.setIntPref("security.tls.version.max", v.prefValue);
@ -315,7 +304,7 @@ add_task(async function() {
storeCertOverride(server.port, cert);
await startClient(
server.port,
t.sendClientCert,
t.sendClientCert ? cert : null,
t.expectingAlert,
v.version
);

Просмотреть файл

@ -11,6 +11,9 @@ Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
const { PromiseUtils } = ChromeUtils.import(
"resource://gre/modules/PromiseUtils.jsm"
);
const certService = Cc["@mozilla.org/security/local-cert-service;1"].getService(
Ci.nsILocalCertService
);
const certOverrideService = Cc[
"@mozilla.org/security/certoverride;1"
].getService(Ci.nsICertOverrideService);
@ -18,6 +21,20 @@ const socketTransportService = Cc[
"@mozilla.org/network/socket-transport-service;1"
].getService(Ci.nsISocketTransportService);
function getCert() {
return new Promise((resolve, reject) => {
certService.getOrCreateCert("tls-test", {
handleCert(c, rv) {
if (rv) {
reject(rv);
return;
}
resolve(c);
},
});
});
}
function startServer(cert) {
let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
Ci.nsITLSServerSocket
@ -64,7 +81,6 @@ function startServer(cert) {
function storeCertOverride(port, cert) {
let overrideBits =
Ci.nsICertOverrideService.ERROR_UNTRUSTED |
Ci.nsICertOverrideService.ERROR_TIME |
Ci.nsICertOverrideService.ERROR_MISMATCH;
certOverrideService.rememberValidityOverride(
"127.0.0.1",
@ -129,7 +145,7 @@ function startClient(port) {
}
add_task(async function() {
let cert = getTestServerCertificate();
let cert = await getCert();
ok(!!cert, "Got self-signed cert");
let port = startServer(cert);
storeCertOverride(port, cert);

Просмотреть файл

@ -3,7 +3,8 @@
head = head_channels.js head_cache.js head_cache2.js head_cookies.js head_trr.js head_http3.js head_servers.js
support-files =
http2-ca.pem
client-cert.p12
client_cert_chooser.js
client_cert_chooser.manifest
data/cookies_v10.sqlite
data/image.png
data/system_root.lnk

Просмотреть файл

@ -289,6 +289,16 @@ bool CommonSocketControl::GetDenyClientCert() { return true; }
void CommonSocketControl::SetDenyClientCert(bool aDenyClientCert) {}
NS_IMETHODIMP
CommonSocketControl::GetClientCert(nsIX509Cert** aClientCert) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
CommonSocketControl::SetClientCert(nsIX509Cert* aClientCert) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
CommonSocketControl::GetClientCertSent(bool* arg) {
*arg = mSentClientCert;

Просмотреть файл

@ -0,0 +1,444 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LocalCertService.h"
#include "CryptoTask.h"
#include "ScopedNSSTypes.h"
#include "cert.h"
#include "mozilla/Casting.h"
#include "mozilla/ModuleUtils.h"
#include "mozilla/RefPtr.h"
#include "nsIPK11Token.h"
#include "nsIPK11TokenDB.h"
#include "nsIX509Cert.h"
#include "nsIX509CertValidity.h"
#include "nsLiteralString.h"
#include "nsNSSCertificate.h"
#include "nsProxyRelease.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "pk11pub.h"
namespace mozilla {
// Given a name, searches the internal certificate/key database for a
// self-signed certificate with subject and issuer distinguished name equal to
// "CN={name}". This assumes that the user has already authenticated to the
// internal DB if necessary.
static nsresult FindLocalCertByName(const nsACString& aName,
/*out*/ UniqueCERTCertificate& aResult) {
aResult.reset(nullptr);
constexpr auto commonNamePrefix = "CN="_ns;
nsAutoCString expectedDistinguishedName(commonNamePrefix + aName);
UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
if (!slot) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
UniqueCERTCertList certList(PK11_ListCertsInSlot(slot.get()));
if (!certList) {
return NS_ERROR_UNEXPECTED;
}
for (const CERTCertListNode* node = CERT_LIST_HEAD(certList);
!CERT_LIST_END(node, certList); node = CERT_LIST_NEXT(node)) {
// If this isn't a self-signed cert, it's not what we're interested in.
if (!node->cert->isRoot) {
continue;
}
if (!expectedDistinguishedName.Equals(node->cert->subjectName)) {
continue; // Subject should match nickname
}
if (!expectedDistinguishedName.Equals(node->cert->issuerName)) {
continue; // Issuer should match nickname
}
// We found a match.
aResult.reset(CERT_DupCertificate(node->cert));
return NS_OK;
}
return NS_OK;
}
class LocalCertTask : public CryptoTask {
protected:
explicit LocalCertTask(const nsACString& aNickname) : mNickname(aNickname) {}
nsresult RemoveExisting() {
// Search for any existing self-signed certs with this name and remove them
for (;;) {
UniqueCERTCertificate cert;
nsresult rv = FindLocalCertByName(mNickname, cert);
if (NS_FAILED(rv)) {
return rv;
}
// If we didn't find a match, we're done.
if (!cert) {
return NS_OK;
}
rv = MapSECStatus(PK11_DeleteTokenCertAndKey(cert.get(), nullptr));
if (NS_FAILED(rv)) {
return rv;
}
}
}
nsCString mNickname;
};
class LocalCertGetTask final : public LocalCertTask {
public:
LocalCertGetTask(const nsACString& aNickname,
nsILocalCertGetCallback* aCallback)
: LocalCertTask(aNickname),
mCallback(new nsMainThreadPtrHolder<nsILocalCertGetCallback>(
"LocalCertGetTask::mCallback", aCallback)),
mCert(nullptr) {}
private:
virtual nsresult CalculateResult() override {
// Try to lookup an existing cert in the DB
nsresult rv = GetFromDB();
// Make a new one if getting fails
if (NS_FAILED(rv)) {
rv = Generate();
}
// If generation fails, we're out of luck
if (NS_FAILED(rv)) {
return rv;
}
// Validate cert, make a new one if it fails
rv = Validate();
if (NS_FAILED(rv)) {
rv = Generate();
}
// If generation fails, we're out of luck
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
nsresult Generate() {
nsresult rv;
// Get the key slot for generation later
UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
if (!slot) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Remove existing certs with this name (if any)
rv = RemoveExisting();
if (NS_FAILED(rv)) {
return rv;
}
// Generate a new cert
constexpr auto commonNamePrefix = "CN="_ns;
nsAutoCString subjectNameStr(commonNamePrefix + mNickname);
UniqueCERTName subjectName(CERT_AsciiToName(subjectNameStr.get()));
if (!subjectName) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Use the well-known NIST P-256 curve
SECOidData* curveOidData = SECOID_FindOIDByTag(SEC_OID_SECG_EC_SECP256R1);
if (!curveOidData) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Get key params from the curve
ScopedAutoSECItem keyParams(2 + curveOidData->oid.len);
keyParams.data[0] = SEC_ASN1_OBJECT_ID;
keyParams.data[1] = curveOidData->oid.len;
memcpy(keyParams.data + 2, curveOidData->oid.data, curveOidData->oid.len);
// Generate cert key pair
SECKEYPublicKey* tempPublicKey;
UniqueSECKEYPrivateKey privateKey(PK11_GenerateKeyPair(
slot.get(), CKM_EC_KEY_PAIR_GEN, &keyParams, &tempPublicKey,
true /* token */, true /* sensitive */, nullptr));
UniqueSECKEYPublicKey publicKey(tempPublicKey);
tempPublicKey = nullptr;
if (!privateKey || !publicKey) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Create subject public key info and cert request
UniqueCERTSubjectPublicKeyInfo spki(
SECKEY_CreateSubjectPublicKeyInfo(publicKey.get()));
if (!spki) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
UniqueCERTCertificateRequest certRequest(
CERT_CreateCertificateRequest(subjectName.get(), spki.get(), nullptr));
if (!certRequest) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Valid from one day before to 1 year after
static const PRTime oneDay = PRTime(PR_USEC_PER_SEC) * PRTime(60) // sec
* PRTime(60) // min
* PRTime(24); // hours
PRTime now = PR_Now();
PRTime notBefore = now - oneDay;
PRTime notAfter = now + (PRTime(365) * oneDay);
UniqueCERTValidity validity(CERT_CreateValidity(notBefore, notAfter));
if (!validity) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Generate random serial
unsigned long serial;
// This serial in principle could collide, but it's unlikely
rv = MapSECStatus(PK11_GenerateRandomOnSlot(
slot.get(), BitwiseCast<unsigned char*, unsigned long*>(&serial),
sizeof(serial)));
if (NS_FAILED(rv)) {
return rv;
}
// Create the cert from these pieces
UniqueCERTCertificate cert(CERT_CreateCertificate(
serial, subjectName.get(), validity.get(), certRequest.get()));
if (!cert) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Update the cert version to X509v3
if (!cert->version.data) {
return NS_ERROR_INVALID_POINTER;
}
*(cert->version.data) = SEC_CERTIFICATE_VERSION_3;
cert->version.len = 1;
// Set cert signature algorithm
PLArenaPool* arena = cert->arena;
if (!arena) {
return NS_ERROR_INVALID_POINTER;
}
rv = MapSECStatus(SECOID_SetAlgorithmID(
arena, &cert->signature, SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, 0));
if (NS_FAILED(rv)) {
return rv;
}
// Encode and self-sign the cert
UniqueSECItem certDER(SEC_ASN1EncodeItem(
nullptr, nullptr, cert.get(), SEC_ASN1_GET(CERT_CertificateTemplate)));
if (!certDER) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
rv = MapSECStatus(SEC_DerSignData(arena, &cert->derCert, certDER->data,
certDER->len, privateKey.get(),
SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE));
if (NS_FAILED(rv)) {
return rv;
}
// Create a CERTCertificate from the signed data
UniqueCERTCertificate certFromDER(
CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &cert->derCert,
nullptr, true /* perm */, true /* copyDER */));
if (!certFromDER) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Save the cert in the DB
rv = MapSECStatus(PK11_ImportCert(slot.get(), certFromDER.get(),
CK_INVALID_HANDLE, mNickname.get(),
false /* unused */));
if (NS_FAILED(rv)) {
return rv;
}
// We should now have cert in the DB, read it back in nsIX509Cert form
return GetFromDB();
}
nsresult GetFromDB() {
UniqueCERTCertificate cert;
nsresult rv = FindLocalCertByName(mNickname, cert);
if (NS_FAILED(rv)) {
return rv;
}
if (!cert) {
return NS_ERROR_FAILURE;
}
mCert = new nsNSSCertificate(cert.get());
return NS_OK;
}
nsresult Validate() {
// Check that subject and issuer match nickname
nsAutoString subjectName;
nsAutoString issuerName;
mCert->GetSubjectName(subjectName);
mCert->GetIssuerName(issuerName);
if (!subjectName.Equals(issuerName)) {
return NS_ERROR_FAILURE;
}
constexpr auto commonNamePrefix = u"CN="_ns;
nsAutoString subjectNameFromNickname(commonNamePrefix +
NS_ConvertASCIItoUTF16(mNickname));
if (!subjectName.Equals(subjectNameFromNickname)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIX509CertValidity> validity;
mCert->GetValidity(getter_AddRefs(validity));
PRTime notBefore, notAfter;
validity->GetNotBefore(&notBefore);
validity->GetNotAfter(&notAfter);
// Ensure cert will last at least one more day
static const PRTime oneDay = PRTime(PR_USEC_PER_SEC) * PRTime(60) // sec
* PRTime(60) // min
* PRTime(24); // hours
PRTime now = PR_Now();
if (notBefore > now || notAfter < (now - oneDay)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
virtual void CallCallback(nsresult rv) override {
(void)mCallback->HandleCert(mCert, rv);
}
nsMainThreadPtrHandle<nsILocalCertGetCallback> mCallback;
nsCOMPtr<nsIX509Cert> mCert; // out
};
class LocalCertRemoveTask final : public LocalCertTask {
public:
LocalCertRemoveTask(const nsACString& aNickname,
nsILocalCertCallback* aCallback)
: LocalCertTask(aNickname),
mCallback(new nsMainThreadPtrHolder<nsILocalCertCallback>(
"LocalCertRemoveTask::mCallback", aCallback)) {}
private:
virtual nsresult CalculateResult() override { return RemoveExisting(); }
virtual void CallCallback(nsresult rv) override {
(void)mCallback->HandleResult(rv);
}
nsMainThreadPtrHandle<nsILocalCertCallback> mCallback;
};
NS_IMPL_ISUPPORTS(LocalCertService, nsILocalCertService)
LocalCertService::LocalCertService() = default;
LocalCertService::~LocalCertService() = default;
nsresult LocalCertService::LoginToKeySlot() {
nsresult rv;
// Get access to key slot
UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
if (!slot) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// If no user password yet, set it an empty one
if (PK11_NeedUserInit(slot.get())) {
rv = MapSECStatus(PK11_InitPin(slot.get(), "", ""));
if (NS_FAILED(rv)) {
return rv;
}
}
// If user has a password set, prompt to login
if (PK11_NeedLogin(slot.get()) && !PK11_IsLoggedIn(slot.get(), nullptr)) {
// Switching to XPCOM to get the UI prompt that PSM owns
nsCOMPtr<nsIPK11TokenDB> tokenDB = do_GetService(NS_PK11TOKENDB_CONTRACTID);
if (!tokenDB) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIPK11Token> keyToken;
tokenDB->GetInternalKeyToken(getter_AddRefs(keyToken));
if (!keyToken) {
return NS_ERROR_FAILURE;
}
// Prompt the user to login
return keyToken->Login(false /* force */);
}
return NS_OK;
}
NS_IMETHODIMP
LocalCertService::GetOrCreateCert(const nsACString& aNickname,
nsILocalCertGetCallback* aCallback) {
if (NS_WARN_IF(aNickname.IsEmpty())) {
return NS_ERROR_INVALID_ARG;
}
if (NS_WARN_IF(!aCallback)) {
return NS_ERROR_INVALID_POINTER;
}
// Before sending off the task, login to key slot if needed
nsresult rv = LoginToKeySlot();
if (NS_FAILED(rv)) {
aCallback->HandleCert(nullptr, rv);
return NS_OK;
}
RefPtr<LocalCertGetTask> task(new LocalCertGetTask(aNickname, aCallback));
return task->Dispatch();
}
NS_IMETHODIMP
LocalCertService::RemoveCert(const nsACString& aNickname,
nsILocalCertCallback* aCallback) {
if (NS_WARN_IF(aNickname.IsEmpty())) {
return NS_ERROR_INVALID_ARG;
}
if (NS_WARN_IF(!aCallback)) {
return NS_ERROR_INVALID_POINTER;
}
// Before sending off the task, login to key slot if needed
nsresult rv = LoginToKeySlot();
if (NS_FAILED(rv)) {
aCallback->HandleResult(rv);
return NS_OK;
}
RefPtr<LocalCertRemoveTask> task(
new LocalCertRemoveTask(aNickname, aCallback));
return task->Dispatch();
}
NS_IMETHODIMP
LocalCertService::GetLoginPromptRequired(bool* aRequired) {
nsresult rv;
// Get access to key slot
UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
if (!slot) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// If no user password yet, set it an empty one
if (PK11_NeedUserInit(slot.get())) {
rv = MapSECStatus(PK11_InitPin(slot.get(), "", ""));
if (NS_FAILED(rv)) {
return rv;
}
}
*aRequired =
PK11_NeedLogin(slot.get()) && !PK11_IsLoggedIn(slot.get(), nullptr);
return NS_OK;
}
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef LocalCertService_h
#define LocalCertService_h
#include "nsILocalCertService.h"
namespace mozilla {
class LocalCertService final : public nsILocalCertService {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSILOCALCERTSERVICE
LocalCertService();
private:
nsresult LoginToKeySlot();
~LocalCertService();
};
} // namespace mozilla
#endif // LocalCertService_h

Просмотреть файл

@ -131,7 +131,6 @@
#include "mozpkix/pkix.h"
#include "mozpkix/pkixcheck.h"
#include "mozpkix/pkixnss.h"
#include "mozpkix/pkixutil.h"
#include "secerr.h"
#include "secport.h"
#include "ssl.h"

Просмотреть файл

@ -29,6 +29,12 @@ Classes = [
'type': 'nsNSSVersion',
'headers': ['/security/manager/ssl/nsNSSVersion.h'],
},
{
'cid': '{47402be2-e653-45d0-8daa-9f0dce0ac148}',
'contract_ids': ['@mozilla.org/security/local-cert-service;1'],
'type': 'mozilla::LocalCertService',
'headers': ['/security/manager/ssl/LocalCertService.h'],
},
{
'cid': '{0c4f1ddc-1dd2-11b2-9d95-f2fdf113044b}',
'contract_ids': ['@mozilla.org/security/sdr;1'],

Просмотреть файл

@ -25,6 +25,7 @@ XPIDL_SOURCES += [
"nsIClientAuthRememberService.idl",
"nsIContentSignatureVerifier.idl",
"nsICryptoHash.idl",
"nsILocalCertService.idl",
"nsINSSComponent.idl",
"nsINSSErrorsService.idl",
"nsINSSVersion.idl",
@ -109,6 +110,7 @@ UNIFIED_SOURCES += [
"EnterpriseRoots.cpp",
"IPCClientCertsChild.cpp",
"IPCClientCertsParent.cpp",
"LocalCertService.cpp",
"nsCertOverrideService.cpp",
"nsClientAuthRemember.cpp",
"nsCryptoHash.cpp",

Просмотреть файл

@ -0,0 +1,72 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIX509Cert;
interface nsILocalCertGetCallback;
interface nsILocalCertCallback;
[scriptable, uuid(9702fdd4-4c2c-439c-ba2e-19cda018eb99)]
interface nsILocalCertService : nsISupports
{
/**
* Get or create a new self-signed X.509 cert to represent this device over a
* secure transport, like TLS.
*
* The cert is stored permanently in the profile's key store after first use,
* and is valid for 1 year. If an expired or otherwise invalid cert is found
* with the nickname supplied here, it is removed and a new one is made.
*
* @param nickname Nickname that identifies the cert
* @param cb Callback to be notified with the result
*/
[must_use]
void getOrCreateCert(in ACString nickname, in nsILocalCertGetCallback cb);
/**
* Remove a X.509 cert with the given nickname.
*
* @param nickname Nickname that identifies the cert
* @param cb Callback to be notified with the result
*/
[must_use]
void removeCert(in ACString nickname, in nsILocalCertCallback cb);
/**
* Whether calling |getOrCreateCert| or |removeCert| will trigger a login
* prompt to be displayed. Generally this happens if the user has set a
* master password, but has not yet logged in.
*/
[must_use]
readonly attribute boolean loginPromptRequired;
};
[scriptable, uuid(cc09633e-7c70-4093-a9cf-79ab676ca8a9)]
interface nsILocalCertGetCallback : nsISupports
{
/**
* Called with the result of the getOrCreateCert operation above.
*
* @param cert Requested cert, or null if some error
* @param result Result code from the get operation
*/
void handleCert(in nsIX509Cert cert, in nsresult result);
};
[scriptable, uuid(518124e9-55e6-4e23-97c0-4995b3a1bec6)]
interface nsILocalCertCallback : nsISupports
{
/**
* Called with the result of the removeCert operation above.
*
* @param result Result code from the operation
*/
void handleResult(in nsresult result);
};
%{ C++
#define LOCALCERTSERVICE_CONTRACTID \
"@mozilla.org/security/local-cert-service;1"
%}

Просмотреть файл

@ -139,7 +139,8 @@ nsNSSSocketInfo::nsNSSSocketInfo(SharedSSLState& aState, uint32_t providerFlags,
mMACAlgorithmUsed(nsISSLSocketControl::SSL_MAC_UNKNOWN),
mProviderTlsFlags(providerTlsFlags),
mSocketCreationTimestamp(TimeStamp::Now()),
mPlaintextBytesRead(0) {
mPlaintextBytesRead(0),
mClientCert(nullptr) {
mTLSVersionRange.min = 0;
mTLSVersionRange.max = 0;
}
@ -179,6 +180,20 @@ nsNSSSocketInfo::GetMACAlgorithmUsed(int16_t* aMac) {
return NS_OK;
}
NS_IMETHODIMP
nsNSSSocketInfo::GetClientCert(nsIX509Cert** aClientCert) {
NS_ENSURE_ARG_POINTER(aClientCert);
*aClientCert = mClientCert;
NS_IF_ADDREF(*aClientCert);
return NS_OK;
}
NS_IMETHODIMP
nsNSSSocketInfo::SetClientCert(nsIX509Cert* aClientCert) {
mClientCert = aClientCert;
return NS_OK;
}
void nsNSSSocketInfo::NoteTimeUntilReady() {
MutexAutoLock lock(mMutex);
if (mNotedTimeUntilReady) return;
@ -1774,19 +1789,22 @@ static bool hasExplicitKeyUsageNonRepudiation(CERTCertificate* cert) {
ClientAuthInfo::ClientAuthInfo(const nsACString& hostName,
const OriginAttributes& originAttributes,
int32_t port, uint32_t providerFlags,
uint32_t providerTlsFlags)
uint32_t providerTlsFlags,
nsIX509Cert* clientCert)
: mHostName(hostName),
mOriginAttributes(originAttributes),
mPort(port),
mProviderFlags(providerFlags),
mProviderTlsFlags(providerTlsFlags) {}
mProviderTlsFlags(providerTlsFlags),
mClientCert(clientCert) {}
ClientAuthInfo::ClientAuthInfo(ClientAuthInfo&& aOther) noexcept
: mHostName(std::move(aOther.mHostName)),
mOriginAttributes(std::move(aOther.mOriginAttributes)),
mPort(aOther.mPort),
mProviderFlags(aOther.mProviderFlags),
mProviderTlsFlags(aOther.mProviderTlsFlags) {}
mProviderTlsFlags(aOther.mProviderTlsFlags),
mClientCert(std::move(aOther.mClientCert)) {}
const nsACString& ClientAuthInfo::HostName() const { return mHostName; }
@ -1800,6 +1818,11 @@ uint32_t ClientAuthInfo::ProviderFlags() const { return mProviderFlags; }
uint32_t ClientAuthInfo::ProviderTlsFlags() const { return mProviderTlsFlags; }
already_AddRefed<nsIX509Cert> ClientAuthInfo::GetClientCert() const {
nsCOMPtr<nsIX509Cert> cert = mClientCert;
return cert.forget();
}
class ClientAuthDataRunnable : public SyncRunnableBase {
public:
ClientAuthDataRunnable(ClientAuthInfo&& info,
@ -1914,9 +1937,11 @@ SECStatus nsNSS_SSLGetClientAuthData(void* arg, PRFileDesc* socket,
return SECSuccess;
}
nsCOMPtr<nsIX509Cert> socketClientCert;
info->GetClientCert(getter_AddRefs(socketClientCert));
ClientAuthInfo authInfo(info->GetHostName(), info->GetOriginAttributes(),
info->GetPort(), info->GetProviderFlags(),
info->GetProviderTlsFlags());
info->GetProviderTlsFlags(), socketClientCert);
nsTArray<nsTArray<uint8_t>> collectedCANames(CollectCANames(caNames));
UniqueCERTCertificate selectedCertificate;
@ -2311,6 +2336,17 @@ void ClientAuthDataRunnable::RunOnTargetThread() {
return;
}
// If a client cert preference was set on the socket info, use that and skip
// the client cert UI and/or search of the user's past cert decisions.
nsCOMPtr<nsIX509Cert> socketClientCert = mInfo.GetClientCert();
if (socketClientCert) {
mSelectedCertificate.reset(socketClientCert->GetCert());
if (NS_WARN_IF(!mSelectedCertificate)) {
return;
}
return;
}
UniqueCERTCertList certList(FindClientCertificatesWithPrivateKeys());
if (!certList) {
return;
@ -2519,6 +2555,17 @@ void RemoteClientAuthDataRunnable::RunOnTargetThread() {
const ByteArray serverCertSerialized = CopyableTArray<uint8_t>{
mServerCert->derCert.data, mServerCert->derCert.len};
// Note that client cert is NULL in socket process until bug 1632809 is done.
Maybe<ByteArray> clientCertSerialized;
nsCOMPtr<nsIX509Cert> socketClientCert = mInfo.GetClientCert();
if (socketClientCert) {
nsTArray<uint8_t> certBytes;
if (NS_FAILED(socketClientCert->GetRawDER(certBytes))) {
return;
}
clientCertSerialized.emplace(std::move(certBytes));
}
nsTArray<ByteArray> collectedCANames;
for (auto& name : mCollectedCANames) {
collectedCANames.AppendElement(std::move(name));
@ -2529,7 +2576,7 @@ void RemoteClientAuthDataRunnable::RunOnTargetThread() {
mozilla::net::SocketProcessChild::GetSingleton()->SendGetTLSClientCert(
nsCString(mInfo.HostName()), mInfo.OriginAttributesRef(), mInfo.Port(),
mInfo.ProviderFlags(), mInfo.ProviderTlsFlags(), serverCertSerialized,
collectedCANames, &succeeded, &cert, &mBuiltChain);
clientCertSerialized, collectedCANames, &succeeded, &cert, &mBuiltChain);
if (!succeeded) {
return;

Просмотреть файл

@ -68,6 +68,8 @@ class nsNSSSocketInfo final : public CommonSocketControl {
NS_IMETHOD GetMACAlgorithmUsed(int16_t* aMACAlgorithmUsed) override;
bool GetDenyClientCert() override;
void SetDenyClientCert(bool aDenyClientCert) override;
NS_IMETHOD GetClientCert(nsIX509Cert** aClientCert) override;
NS_IMETHOD SetClientCert(nsIX509Cert* aClientCert) override;
NS_IMETHOD GetEsniTxt(nsACString& aEsniTxt) override;
NS_IMETHOD SetEsniTxt(const nsACString& aEsniTxt) override;
NS_IMETHOD GetEchConfig(nsACString& aEchConfig) override;
@ -225,6 +227,7 @@ class nsNSSSocketInfo final : public CommonSocketControl {
mozilla::TimeStamp mSocketCreationTimestamp;
uint64_t mPlaintextBytesRead;
nsCOMPtr<nsIX509Cert> mClientCert;
// Regarding the client certificate message in the TLS handshake, RFC 5246
// (TLS 1.2) says:
// If the certificate_authorities list in the certificate request
@ -254,13 +257,14 @@ class ClientAuthInfo final {
explicit ClientAuthInfo(const nsACString& hostName,
const OriginAttributes& originAttributes,
int32_t port, uint32_t providerFlags,
uint32_t providerTlsFlags);
uint32_t providerTlsFlags, nsIX509Cert* clientCert);
~ClientAuthInfo() = default;
ClientAuthInfo(ClientAuthInfo&& aOther) noexcept;
const nsACString& HostName() const;
const OriginAttributes& OriginAttributesRef() const;
int32_t Port() const;
already_AddRefed<nsIX509Cert> GetClientCert() const;
uint32_t ProviderFlags() const;
uint32_t ProviderTlsFlags() const;
@ -273,6 +277,7 @@ class ClientAuthInfo final {
int32_t mPort;
uint32_t mProviderFlags;
uint32_t mProviderTlsFlags;
nsCOMPtr<nsIX509Cert> mClientCert;
};
class nsSSLIOLayerHelpers {

Просмотреть файл

@ -0,0 +1,87 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const certService = Cc["@mozilla.org/security/local-cert-service;1"].getService(
Ci.nsILocalCertService
);
const gNickname = "local-cert-test";
function run_test() {
// Need profile dir to store the key / cert
do_get_profile();
// Ensure PSM is initialized
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
run_next_test();
}
function getOrCreateCert(nickname) {
return new Promise((resolve, reject) => {
certService.getOrCreateCert(nickname, {
handleCert(c, rv) {
if (rv) {
reject(rv);
return;
}
resolve(c);
},
});
});
}
function removeCert(nickname) {
return new Promise((resolve, reject) => {
certService.removeCert(nickname, {
handleResult(rv) {
if (rv) {
reject(rv);
return;
}
resolve();
},
});
});
}
add_task(async function() {
// No master password, so no prompt required here
ok(!certService.loginPromptRequired);
let certA = await getOrCreateCert(gNickname);
// The local cert service implementation takes the given nickname and uses it
// as the common name for the certificate it creates. nsIX509Cert.displayName
// uses the common name if it is present, so these should match. Should either
// implementation change to do something else, this won't necessarily work.
equal(certA.displayName, gNickname);
// Getting again should give the same cert
let certB = await getOrCreateCert(gNickname);
equal(certB.displayName, gNickname);
// Should be matching instances
ok(areCertsEqual(certA, certB));
// Check an expected attribute
equal(certA.certType, Ci.nsIX509Cert.USER_CERT);
// New nickname should give a different cert
let diffNameCert = await getOrCreateCert("cool-stuff");
ok(!areCertsEqual(diffNameCert, certA));
// Remove the cert, and get a new one again
await removeCert(gNickname);
let newCert = await getOrCreateCert(gNickname);
ok(!areCertsEqual(newCert, certA));
// Drop all cert references and GC
let serial = newCert.serialNumber;
certA = certB = diffNameCert = newCert = null;
Cu.forceGC();
Cu.forceCC();
// Should still get the same cert back
let certAfterGC = await getOrCreateCert(gNickname);
equal(certAfterGC.serialNumber, serial);
});

Просмотреть файл

@ -11,18 +11,21 @@
do_get_profile();
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
function getTestServerCertificate() {
const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
const certFile = do_get_file("test_certDB_import/encrypted_with_aes.p12");
certDB.importPKCS12File(certFile, "password");
for (const cert of certDB.getCerts()) {
if (cert.commonName == "John Doe") {
return cert;
}
}
return null;
function getCert() {
return new Promise((resolve, reject) => {
let certService = Cc[
"@mozilla.org/security/local-cert-service;1"
].getService(Ci.nsILocalCertService);
certService.getOrCreateCert("beConservative-test", {
handleCert: (c, rv) => {
if (rv) {
reject(rv);
return;
}
resolve(c);
},
});
});
}
class InputStreamCallback {
@ -192,7 +195,7 @@ function startClient(port) {
add_task(async function() {
Services.prefs.setCharPref("network.dns.localDomains", hostname);
let cert = getTestServerCertificate();
let cert = await getCert();
let server = getStartedServer(cert);
storeCertOverride(server.port, cert);

Просмотреть файл

@ -151,6 +151,7 @@ run-sequentially = hardcoded ports
[test_keysize.js]
[test_keysize_ev.js]
run-sequentially = hardcoded ports
[test_local_cert.js]
[test_logoutAndTeardown.js]
skip-if = socketprocess_networking && os == "linux" && debug
run-sequentially = hardcoded ports

Просмотреть файл

@ -3,6 +3,9 @@
"use strict";
const certService = Cc["@mozilla.org/security/local-cert-service;1"].getService(
Ci.nsILocalCertService
);
const overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
Ci.nsICertOverrideService
);