зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
3495742c56
Коммит
2b78c018fd
|
@ -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
Двоичные данные
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(¬Before);
|
||||
validity->GetNotAfter(¬After);
|
||||
|
||||
// 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
|
||||
);
|
||||
|
|
Загрузка…
Ссылка в новой задаче