Bug 1712837 - introduce ipcclientcerts to allow client certificates to work with the socket process r=rmf,kershaw,necko-reviewers,ipc-reviewers,nika,jschanck

This patch introduces ipcclientcerts, a PKCS#11 module that the socket process
can load to get access to client certificates and keys managed by the parent
process. This enables client certificate authentication to work with the socket
process (particularly for keys stored outside of NSS, as with osclientcerts or
third-party PKCS#11 modules).

Differential Revision: https://phabricator.services.mozilla.com/D122392
This commit is contained in:
Dana Keeler 2021-12-06 23:43:32 +00:00
parent d2380075fb
commit 8d68ea1d86
41 changed files with 2456 additions and 300 deletions

13
Cargo.lock generated
View File

@ -2437,6 +2437,19 @@ dependencies = [
"libc",
]
[[package]]
name = "ipcclientcerts-static"
version = "0.1.0"
dependencies = [
"byteorder",
"env_logger",
"lazy_static",
"log",
"pkcs11",
"rsclientcerts",
"sha2",
]
[[package]]
name = "itertools"
version = "0.8.2"

View File

@ -9,6 +9,7 @@ members = [
"js/src/rust",
"js/src/wasm/cranelift",
"netwerk/test/http3server",
"security/manager/ssl/ipcclientcerts",
"security/manager/ssl/osclientcerts",
"testing/geckodriver",
"toolkit/crashreporter/rust_minidump_writer_linux",

View File

@ -49,7 +49,8 @@ this.pkcs11 = class extends ExtensionAPI {
}
if (
manifestLib !== ctypes.libraryName("nssckbi") &&
manifestLib !== ctypes.libraryName("osclientcerts")
manifestLib !== ctypes.libraryName("osclientcerts") &&
manifestLib !== ctypes.libraryName("ipcclientcerts")
) {
return hostInfo.manifest;
}

View File

@ -204,6 +204,26 @@ add_task(async function test_pkcs11() {
/No such PKCS#11 module osclientcerts/,
"getModuleSlots should not work on the built-in osclientcerts module"
);
await browser.test.assertRejects(
browser.pkcs11.installModule("ipcclientcerts", 0),
/No such PKCS#11 module ipcclientcerts/,
"installModule should not work on the built-in ipcclientcerts module"
);
await browser.test.assertRejects(
browser.pkcs11.uninstallModule("ipcclientcerts"),
/No such PKCS#11 module ipcclientcerts/,
"uninstallModule should not work on the built-in ipcclientcerts module"
);
await browser.test.assertRejects(
browser.pkcs11.isModuleInstalled("ipcclientcerts"),
/No such PKCS#11 module ipcclientcerts/,
"isModuleLoaded should not work on the built-in ipcclientcerts module"
);
await browser.test.assertRejects(
browser.pkcs11.getModuleSlots("ipcclientcerts"),
/No such PKCS#11 module ipcclientcerts/,
"getModuleSlots should not work on the built-in ipcclientcerts module"
);
browser.test.notifyPass("pkcs11");
} catch (e) {
browser.test.fail(`Error: ${String(e)} :: ${e.stack}`);

View File

@ -372,6 +372,8 @@ bin/libfreebl_64int_3.so
#endif
#endif
@BINPATH@/@DLL_PREFIX@ipcclientcerts@DLL_SUFFIX@
; For process sandboxing
#if defined(MOZ_SANDBOX)
#if defined(XP_LINUX)

View File

@ -15,6 +15,7 @@
#include "ssl.h"
#include "mozpkix/nss_scoped_ptrs.h"
#include "secerr.h"
#include "sslerr.h"
#include "mozilla/Sprintf.h"
#include "mozilla/dom/CryptoBuffer.h"
@ -24,10 +25,103 @@
namespace mozilla {
SECItem* WrapPrivateKeyInfoWithEmptyPassword(
SECKEYPrivateKey* pk) /* encrypt this private key */
{
if (!pk) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
if (!slot) {
return nullptr;
}
// For private keys, NSS cannot export anything other than RSA, but we need EC
// also. So, we use the private key encryption function to serialize instead,
// using a hard-coded dummy password; this is not intended to provide any
// additional security, it just works around a limitation in NSS.
SECItem dummyPassword = {siBuffer, nullptr, 0};
UniqueSECKEYEncryptedPrivateKeyInfo epki(PK11_ExportEncryptedPrivKeyInfo(
slot.get(), SEC_OID_AES_128_CBC, &dummyPassword, pk, 1, nullptr));
if (!epki) {
return nullptr;
}
return SEC_ASN1EncodeItem(
nullptr, nullptr, epki.get(),
NSS_Get_SECKEY_EncryptedPrivateKeyInfoTemplate(nullptr, false));
}
SECStatus UnwrapPrivateKeyInfoWithEmptyPassword(
SECItem* derPKI, const UniqueCERTCertificate& aCert,
SECKEYPrivateKey** privk) {
if (!derPKI || !aCert || !privk) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return SECFailure;
}
UniqueSECKEYPublicKey publicKey(CERT_ExtractPublicKey(aCert.get()));
// This is a pointer to data inside publicKey
SECItem* publicValue = nullptr;
switch (publicKey->keyType) {
case dsaKey:
publicValue = &publicKey->u.dsa.publicValue;
break;
case dhKey:
publicValue = &publicKey->u.dh.publicValue;
break;
case rsaKey:
publicValue = &publicKey->u.rsa.modulus;
break;
case ecKey:
publicValue = &publicKey->u.ec.publicValue;
break;
default:
MOZ_ASSERT(false);
PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0);
return SECFailure;
}
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
if (!slot) {
return SECFailure;
}
UniquePLArenaPool temparena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
if (!temparena) {
return SECFailure;
}
SECKEYEncryptedPrivateKeyInfo* epki =
PORT_ArenaZNew(temparena.get(), SECKEYEncryptedPrivateKeyInfo);
if (!epki) {
return SECFailure;
}
SECStatus rv = SEC_ASN1DecodeItem(
temparena.get(), epki,
NSS_Get_SECKEY_EncryptedPrivateKeyInfoTemplate(nullptr, false), derPKI);
if (rv != SECSuccess) {
// If SEC_ASN1DecodeItem fails, we cannot assume anything about the
// validity of the data in epki. The best we can do is free the arena
// and return.
return rv;
}
// See comment in WrapPrivateKeyInfoWithEmptyPassword about this
// dummy password stuff.
SECItem dummyPassword = {siBuffer, nullptr, 0};
return PK11_ImportEncryptedPrivateKeyInfoAndReturnKey(
slot.get(), epki, &dummyPassword, nullptr, publicValue, false, false,
publicKey->keyType, KU_ALL, privk, nullptr);
}
nsresult DtlsIdentity::Serialize(nsTArray<uint8_t>* aKeyDer,
nsTArray<uint8_t>* aCertDer) {
ScopedSECItem derPki(
psm::WrapPrivateKeyInfoWithEmptyPassword(private_key_.get()));
ScopedSECItem derPki(WrapPrivateKeyInfoWithEmptyPassword(private_key_.get()));
if (!derPki) {
return NS_ERROR_FAILURE;
}
@ -50,7 +144,7 @@ RefPtr<DtlsIdentity> DtlsIdentity::Deserialize(
static_cast<unsigned int>(aKeyDer.Length())};
SECKEYPrivateKey* privateKey;
if (psm::UnwrapPrivateKeyInfoWithEmptyPassword(&derPKI, cert, &privateKey) !=
if (UnwrapPrivateKeyInfoWithEmptyPassword(&derPKI, cert, &privateKey) !=
SECSuccess) {
MOZ_ASSERT(false);
return nullptr;

View File

@ -57,6 +57,7 @@
#include "mozilla/dom/WebAuthnTransactionChild.h"
#include "mozilla/dom/MIDIPortChild.h"
#include "mozilla/dom/MIDIManagerChild.h"
#include "mozilla/psm/IPCClientCertsChild.h"
#include "mozilla/RemoteLazyInputStreamChild.h"
#include "nsID.h"
#include "nsTraceRefcnt.h"

View File

@ -71,6 +71,8 @@
#include "mozilla/net/HttpBackgroundChannelParent.h"
#include "mozilla/net/HttpConnectionMgrParent.h"
#include "mozilla/net/WebSocketConnectionParent.h"
#include "mozilla/psm/IPCClientCertsChild.h"
#include "mozilla/psm/IPCClientCertsParent.h"
#include "mozilla/psm/VerifySSLServerCertParent.h"
#include "nsIHttpChannelInternal.h"
#include "nsIPrincipal.h"
@ -1047,6 +1049,21 @@ mozilla::ipc::IPCResult BackgroundParentImpl::RecvPMessagePortConstructor(
return IPC_OK();
}
already_AddRefed<psm::PIPCClientCertsParent>
BackgroundParentImpl::AllocPIPCClientCertsParent() {
// This should only be called in the parent process with the socket process
// as the child process, not any content processes, hence the check that the
// child ID be 0.
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(mozilla::ipc::BackgroundParent::GetChildID(this) == 0);
if (!XRE_IsParentProcess() ||
mozilla::ipc::BackgroundParent::GetChildID(this) != 0) {
return nullptr;
}
RefPtr<psm::IPCClientCertsParent> result = new psm::IPCClientCertsParent();
return result.forget();
}
bool BackgroundParentImpl::DeallocPMessagePortParent(
PMessagePortParent* aActor) {
AssertIsInMainOrSocketProcess();

View File

@ -295,6 +295,8 @@ class BackgroundParentImpl : public PBackgroundParent,
PMessagePortParent* aActor, const nsID& aUUID,
const nsID& aDestinationUUID, const uint32_t& aSequenceID) override;
already_AddRefed<PIPCClientCertsParent> AllocPIPCClientCertsParent() override;
bool DeallocPMessagePortParent(PMessagePortParent* aActor) override;
mozilla::ipc::IPCResult RecvMessagePortForceClose(

View File

@ -25,6 +25,7 @@ include protocol PFileSystemRequest;
include protocol PGamepadEventChannel;
include protocol PGamepadTestChannel;
include protocol PHttpBackgroundChannel;
include protocol PIPCClientCerts;
include protocol PIdleScheduler;
include protocol PRemoteLazyInputStream;
include protocol PMediaTransport;
@ -105,6 +106,7 @@ sync protocol PBackground
manages PGamepadEventChannel;
manages PGamepadTestChannel;
manages PHttpBackgroundChannel;
manages PIPCClientCerts;
manages PIdleScheduler;
manages PRemoteLazyInputStream;
manages PLockManager;
@ -287,6 +289,8 @@ parent:
async PLockManager(ContentPrincipalInfo aPrincipalInfo, nsID aClientId);
async PIPCClientCerts();
child:
async PCache();
async PCacheStreamControl();

View File

@ -1070,6 +1070,10 @@ description = Reflection is cold code, but synchronous by spec.
# -
[PSocketProcess::GetTLSClientCert]
description = Synchronously get client certificate and key from parent process. Once bug 696976 has been fixed, this can be removed.
[PIPCClientCerts::FindObjects]
description = Finds certificates and private keys in the parent process. As this is called from PKCS#11, there is no way to make this asynchronous.
[PIPCClientCerts::Sign]
description = Performs a signature on given data with a key corresponding to the given identifier. This is called from PKCS#11, so there is no way to make this asynchronous.
#############################################################
# AVOID ADDING NEW MESSAGES TO THIS FILE #

View File

@ -66,6 +66,8 @@
@BINPATH@/@DLL_PREFIX@softokn3.chk
#endif
@BINPATH@/@DLL_PREFIX@ipcclientcerts@DLL_SUFFIX@
#ifndef MOZ_FOLD_LIBS
@BINPATH@/@DLL_PREFIX@mozsqlite3@DLL_SUFFIX@
#endif

View File

@ -128,7 +128,7 @@ parent:
ByteArray aServerCert,
ByteArray? aClientCert,
ByteArray[] aCollectedCANames)
returns (bool aSucceeded, ByteArray aOutCert, ByteArray aOutKey, ByteArray[] aBuiltChain);
returns (bool aSucceeded, ByteArray aOutCert, ByteArray[] aBuiltChain);
async PProxyConfigLookup(nsIURI aUri, uint32_t aFlags);
async CachePushCheck(nsIURI aPushedURL,
OriginAttributes aOriginAttributes,

View File

@ -165,7 +165,15 @@ bool SocketProcessChild::Init(base::ProcessId aParentPid,
// Initialize DNS Service here, since it needs to be done in main thread.
nsCOMPtr<nsIDNSService> dns =
do_GetService("@mozilla.org/network/dns-service;1", &rv);
return NS_SUCCEEDED(rv);
if (NS_FAILED(rv)) {
return false;
}
if (!EnsureNSSInitializedChromeOrContent()) {
return false;
}
return true;
}
void SocketProcessChild::ActorDestroy(ActorDestroyReason aWhy) {
@ -214,6 +222,7 @@ mozilla::ipc::IPCResult SocketProcessChild::RecvInit(
if (aAttributes.mInitSandbox()) {
Unused << RecvInitLinuxSandbox(aAttributes.mSandboxBroker());
}
return IPC_OK();
}
@ -472,9 +481,7 @@ SocketProcessChild::GetAndRemoveDataBridge(uint64_t aChannelId) {
}
mozilla::ipc::IPCResult SocketProcessChild::RecvClearSessionCache() {
if (EnsureNSSInitializedChromeOrContent()) {
nsNSSComponent::DoClearSSLExternalAndInternalSessionCache();
}
nsNSSComponent::DoClearSSLExternalAndInternalSessionCache();
return IPC_OK();
}

View File

@ -49,11 +49,13 @@ bool SocketProcessImpl::Init(int aArgc, char* aArgv[]) {
LoadLibraryW(L"nss3.dll");
LoadLibraryW(L"softokn3.dll");
LoadLibraryW(L"freebl3.dll");
LoadLibraryW(L"ipcclientcerts.dll");
mozilla::SandboxTarget::Instance()->StartSandbox();
#elif defined(__OpenBSD__) && defined(MOZ_SANDBOX)
PR_LoadLibrary("libnss3.so");
PR_LoadLibrary("libsoftokn3.so");
PR_LoadLibrary("libfreebl3.so");
PR_LoadLibrary("libipcclientcerts.so");
StartOpenBSDSandbox(GeckoProcessType_Socket);
#endif

View File

@ -25,6 +25,7 @@
#include "nsIConsoleService.h"
#include "nsIHttpActivityObserver.h"
#include "nsIObserverService.h"
#include "nsNSSComponent.h"
#include "nsNSSIOLayer.h"
#include "nsIOService.h"
#include "nsHttpHandler.h"
@ -311,8 +312,7 @@ mozilla::ipc::IPCResult SocketProcessParent::RecvGetTLSClientCert(
const int32_t& aPort, const uint32_t& aProviderFlags,
const uint32_t& aProviderTlsFlags, const ByteArray& aServerCert,
Maybe<ByteArray>&& aClientCert, nsTArray<ByteArray>&& aCollectedCANames,
bool* aSucceeded, ByteArray* aOutCert, ByteArray* aOutKey,
nsTArray<ByteArray>* aBuiltChain) {
bool* aSucceeded, ByteArray* aOutCert, nsTArray<ByteArray>* aBuiltChain) {
*aSucceeded = false;
SECItem serverCertItem = {
@ -342,16 +342,15 @@ mozilla::ipc::IPCResult SocketProcessParent::RecvGetTLSClientCert(
}
UniqueCERTCertificate cert;
UniqueSECKEYPrivateKey key;
UniqueCERTCertList builtChain;
SECStatus status =
DoGetClientAuthData(std::move(info), serverCert,
std::move(collectedCANames), cert, key, builtChain);
std::move(collectedCANames), cert, builtChain);
if (status != SECSuccess) {
return IPC_OK();
}
SerializeClientCertAndKey(cert, key, *aOutCert, *aOutKey);
aOutCert->data().AppendElements(cert->derCert.data, cert->derCert.len);
if (builtChain) {
for (CERTCertListNode* n = CERT_LIST_HEAD(builtChain);

View File

@ -99,8 +99,14 @@ class SocketProcessParent final
const int32_t& aPort, const uint32_t& aProviderFlags,
const uint32_t& aProviderTlsFlags, const ByteArray& aServerCert,
Maybe<ByteArray>&& aClientCert, nsTArray<ByteArray>&& aCollectedCANames,
bool* aSucceeded, ByteArray* aOutCert, ByteArray* aOutKey,
nsTArray<ByteArray>* aBuiltChain);
bool* aSucceeded, ByteArray* aOutCert, nsTArray<ByteArray>* aBuiltChain);
mozilla::ipc::IPCResult RecvFindIPCClientCertObjects(
nsTArray<IPCClientCertObject>* aObjects);
mozilla::ipc::IPCResult RecvIPCClientCertSign(ByteArray aCert,
ByteArray aData,
ByteArray aParams,
ByteArray* aSignature);
already_AddRefed<PProxyConfigLookupParent> AllocPProxyConfigLookupParent(
nsIURI* aURI, const uint32_t& aProxyResolveFlags);

View File

@ -35,6 +35,7 @@
#include "nsNSSCertHelper.h"
#include "nsNSSCertificate.h"
#include "nsNSSCertificateDB.h"
#include "nsNSSIOLayer.h"
#include "nsPrintfCString.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
@ -1604,8 +1605,12 @@ void DisableMD5() {
NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
}
// Load a given PKCS#11 module located in the given directory. It will be named
// the given module name. Optionally pass some string parameters to it via
// 'params'. This argument will be provided to C_Initialize when called on the
// module.
bool LoadUserModuleAt(const char* moduleName, const char* libraryName,
const nsCString& dir) {
const nsCString& dir, /* optional */ const char* params) {
// If a module exists with the same name, make a best effort attempt to delete
// it. Note that it isn't possible to delete the internal module, so checking
// the return value would be detrimental in that case.
@ -1629,6 +1634,11 @@ bool LoadUserModuleAt(const char* moduleName, const char* libraryName,
pkcs11ModuleSpec.AppendLiteral("\" library=\"");
pkcs11ModuleSpec.Append(fullLibraryPath);
pkcs11ModuleSpec.AppendLiteral("\"");
if (params) {
pkcs11ModuleSpec.AppendLiteral("\" parameters=\"");
pkcs11ModuleSpec.Append(params);
pkcs11ModuleSpec.AppendLiteral("\"");
}
UniqueSECMODModule userModule(SECMOD_LoadUserModule(
const_cast<char*>(pkcs11ModuleSpec.get()), nullptr, false));
@ -1643,6 +1653,19 @@ bool LoadUserModuleAt(const char* moduleName, const char* libraryName,
return true;
}
const char* kIPCClientCertsModuleName = "IPC Client Cert Module";
bool LoadIPCClientCertsModule(const nsCString& dir) {
// The IPC client certs module needs to be able to call back into gecko to be
// able to communicate with the parent process over IPC. This is achieved by
// serializing the addresses of the relevant functions and passing them as an
// extra string parameter that will be available when C_Initialize is called
// on IPC client certs.
nsPrintfCString addrs("%p,%p", DoFindObjects, DoSign);
return LoadUserModuleAt(kIPCClientCertsModuleName, "ipcclientcerts", dir,
addrs.get());
}
const char* kOSClientCertsModuleName = "OS Client Cert Module";
bool LoadOSClientCertsModule(const nsCString& dir) {
@ -1652,7 +1675,8 @@ bool LoadOSClientCertsModule(const nsCString& dir) {
return false;
}
#endif
return LoadUserModuleAt(kOSClientCertsModuleName, "osclientcerts", dir);
return LoadUserModuleAt(kOSClientCertsModuleName, "osclientcerts", dir,
nullptr);
}
bool LoadLoadableRoots(const nsCString& dir) {
@ -1663,7 +1687,7 @@ bool LoadLoadableRoots(const nsCString& dir) {
// "Root Certs" module allows us to load the correct one. See bug 1406396.
int unusedModType;
Unused << SECMOD_DeleteModule("Root Certs", &unusedModType);
return LoadUserModuleAt(kRootModuleName, "nssckbi", dir);
return LoadUserModuleAt(kRootModuleName, "nssckbi", dir, nullptr);
}
nsresult DefaultServerNicknameForCert(const CERTCertificate* cert,

View File

@ -76,6 +76,23 @@ bool LoadOSClientCertsModule(const nsCString& dir);
extern const char* kOSClientCertsModuleName;
/**
* Loads the IPC client certs module.
*
* @param dir
* The path to the directory containing the module. This should be the
* same as where all of the other gecko libraries live.
* @return true if the module was successfully loaded, false otherwise.
*/
bool LoadIPCClientCertsModule(const nsCString& dir);
extern const char* kIPCClientCertsModuleName;
/**
* Unloads the loadable roots module and os client certs module, if loaded.
*/
void UnloadUserModules();
nsresult DefaultServerNicknameForCert(const CERTCertificate* cert,
/*out*/ nsCString& nickname);

View File

@ -0,0 +1,17 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* 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 "IPCClientCertsChild.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
namespace mozilla::psm {
IPCClientCertsChild::IPCClientCertsChild() = default;
} // namespace mozilla::psm

View File

@ -0,0 +1,36 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* 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 mozilla_psm_IPCClientCertsChild_h__
#define mozilla_psm_IPCClientCertsChild_h__
#include "mozilla/psm/PIPCClientCertsChild.h"
namespace mozilla {
namespace ipc {
class BackgroundChildImpl;
} // namespace ipc
namespace psm {
class IPCClientCertsChild final : public PIPCClientCertsChild {
friend class mozilla::ipc::BackgroundChildImpl;
public:
IPCClientCertsChild();
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IPCClientCertsChild);
private:
~IPCClientCertsChild() = default;
};
} // namespace psm
} // namespace mozilla
#endif

View File

@ -0,0 +1,114 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* 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 "IPCClientCertsParent.h"
#include "mozilla/ipc/BackgroundParent.h"
namespace mozilla::psm {
IPCClientCertsParent::IPCClientCertsParent() = default;
// When the IPC client certs module needs to find certificate and key objects
// in the socket process, it will cause this function to be called in the
// parent process. The parent process needs to find all certificates with
// private keys (because these are potential client certificates).
mozilla::ipc::IPCResult IPCClientCertsParent::RecvFindObjects(
nsTArray<IPCClientCertObject>* aObjects) {
UniqueCERTCertList certList(psm::FindClientCertificatesWithPrivateKeys());
if (!certList) {
return IPC_OK();
}
CERTCertListNode* n = CERT_LIST_HEAD(certList);
while (!CERT_LIST_END(n, certList)) {
nsTArray<uint8_t> certDER(n->cert->derCert.data, n->cert->derCert.len);
uint32_t slotType;
UniqueSECKEYPublicKey pubkey(CERT_ExtractPublicKey(n->cert));
if (!pubkey) {
return IPC_OK();
}
switch (SECKEY_GetPublicKeyType(pubkey.get())) {
case rsaKey:
case rsaPssKey: {
slotType = PK11_DoesMechanism(n->cert->slot, CKM_RSA_PKCS_PSS)
? kIPCClientCertsSlotTypeModern
: kIPCClientCertsSlotTypeLegacy;
nsTArray<uint8_t> modulus(pubkey->u.rsa.modulus.data,
pubkey->u.rsa.modulus.len);
RSAKey rsakey(modulus, certDER, slotType);
aObjects->AppendElement(std::move(rsakey));
break;
}
case ecKey: {
slotType = kIPCClientCertsSlotTypeModern;
nsTArray<uint8_t> params(pubkey->u.ec.DEREncodedParams.data,
pubkey->u.ec.DEREncodedParams.len);
ECKey eckey(params, certDER, slotType);
aObjects->AppendElement(std::move(eckey));
break;
}
default:
n = CERT_LIST_NEXT(n);
continue;
}
Certificate cert(certDER, slotType);
aObjects->AppendElement(std::move(cert));
n = CERT_LIST_NEXT(n);
}
return IPC_OK();
}
// When the IPC client certs module needs to sign data using a key managed by
// the parent process, it will cause this function to be called in the parent
// process. The parent process needs to find the key corresponding to the given
// certificate and sign the given data with the given parameters.
mozilla::ipc::IPCResult IPCClientCertsParent::RecvSign(ByteArray aCert,
ByteArray aData,
ByteArray aParams,
ByteArray* aSignature) {
SECItem certItem = {siBuffer, const_cast<uint8_t*>(aCert.data().Elements()),
static_cast<unsigned int>(aCert.data().Length())};
aSignature->data().Clear();
UniqueCERTCertificate cert(CERT_NewTempCertificate(
CERT_GetDefaultCertDB(), &certItem, nullptr, false, true));
if (!cert) {
return IPC_OK();
}
UniqueSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert.get(), nullptr));
if (!key) {
return IPC_OK();
}
SECItem params = {siBuffer, aParams.data().Elements(),
static_cast<unsigned int>(aParams.data().Length())};
SECItem* paramsPtr = aParams.data().Length() > 0 ? &params : nullptr;
CK_MECHANISM_TYPE mechanism;
switch (key->keyType) {
case ecKey:
mechanism = CKM_ECDSA;
break;
case rsaKey:
mechanism = aParams.data().Length() > 0 ? CKM_RSA_PKCS_PSS : CKM_RSA_PKCS;
break;
default:
return IPC_OK();
}
uint32_t len = PK11_SignatureLen(key.get());
UniqueSECItem sig(::SECITEM_AllocItem(nullptr, nullptr, len));
SECItem hash = {siBuffer, aData.data().Elements(),
static_cast<unsigned int>(aData.data().Length())};
SECStatus srv =
PK11_SignWithMechanism(key.get(), mechanism, paramsPtr, sig.get(), &hash);
if (srv != SECSuccess) {
return IPC_OK();
}
aSignature->data().AppendElements(sig->data, sig->len);
return IPC_OK();
}
} // namespace mozilla::psm

View File

@ -0,0 +1,40 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* 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 mozilla_psm_IPCClientCertsParent_h__
#define mozilla_psm_IPCClientCertsParent_h__
#include "mozilla/psm/PIPCClientCertsParent.h"
namespace mozilla {
namespace ipc {
class BackgroundParentImpl;
} // namespace ipc
namespace psm {
class IPCClientCertsParent final : public PIPCClientCertsParent {
friend class mozilla::ipc::BackgroundParentImpl;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IPCClientCertsParent)
mozilla::ipc::IPCResult RecvFindObjects(
nsTArray<IPCClientCertObject>* aObjects);
mozilla::ipc::IPCResult RecvSign(ByteArray aCert, ByteArray aData,
ByteArray aParams, ByteArray* aSignature);
private:
IPCClientCertsParent();
~IPCClientCertsParent() = default;
};
} // namespace psm
} // namespace mozilla
#endif

View File

@ -0,0 +1,35 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
include protocol PBackground;
include PSMIPCTypes;
using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
namespace mozilla {
namespace psm {
[RefCounted] sync protocol PIPCClientCerts
{
manager PBackground;
parent:
// Called from the socket process to the parent process to find client
// certificates and associated keys.
sync FindObjects() returns (IPCClientCertObject[] aObjects);
// Called from the socket process to the parent process to sign the given
// data with the given parameters using the key associated with the given
// certificate. Used when a TLS server requests a client authentication
// certificate.
sync Sign(ByteArray aCert, ByteArray aData, ByteArray aParams)
returns (ByteArray aSignature);
async __delete__();
};
} // namespace psm
} // namespace mozilla

View File

@ -1,161 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* 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 "CTVerifyResult.h"
#include "PSMIPCCommon.h"
namespace mozilla {
namespace psm {
SECItem* WrapPrivateKeyInfoWithEmptyPassword(
SECKEYPrivateKey* pk) /* encrypt this private key */
{
if (!pk) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
if (!slot) {
return nullptr;
}
// For private keys, NSS cannot export anything other than RSA, but we need EC
// also. So, we use the private key encryption function to serialize instead,
// using a hard-coded dummy password; this is not intended to provide any
// additional security, it just works around a limitation in NSS.
SECItem dummyPassword = {siBuffer, nullptr, 0};
UniqueSECKEYEncryptedPrivateKeyInfo epki(PK11_ExportEncryptedPrivKeyInfo(
slot.get(), SEC_OID_AES_128_CBC, &dummyPassword, pk, 1, nullptr));
if (!epki) {
return nullptr;
}
return SEC_ASN1EncodeItem(
nullptr, nullptr, epki.get(),
NSS_Get_SECKEY_EncryptedPrivateKeyInfoTemplate(nullptr, false));
}
SECStatus UnwrapPrivateKeyInfoWithEmptyPassword(
SECItem* derPKI, const UniqueCERTCertificate& aCert,
SECKEYPrivateKey** privk) {
if (!derPKI || !aCert || !privk) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return SECFailure;
}
UniqueSECKEYPublicKey publicKey(CERT_ExtractPublicKey(aCert.get()));
// This is a pointer to data inside publicKey
SECItem* publicValue = nullptr;
switch (publicKey->keyType) {
case dsaKey:
publicValue = &publicKey->u.dsa.publicValue;
break;
case dhKey:
publicValue = &publicKey->u.dh.publicValue;
break;
case rsaKey:
publicValue = &publicKey->u.rsa.modulus;
break;
case ecKey:
publicValue = &publicKey->u.ec.publicValue;
break;
default:
MOZ_ASSERT(false);
PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0);
return SECFailure;
}
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
if (!slot) {
return SECFailure;
}
UniquePLArenaPool temparena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
if (!temparena) {
return SECFailure;
}
SECKEYEncryptedPrivateKeyInfo* epki =
PORT_ArenaZNew(temparena.get(), SECKEYEncryptedPrivateKeyInfo);
if (!epki) {
return SECFailure;
}
SECStatus rv = SEC_ASN1DecodeItem(
temparena.get(), epki,
NSS_Get_SECKEY_EncryptedPrivateKeyInfoTemplate(nullptr, false), derPKI);
if (rv != SECSuccess) {
// If SEC_ASN1DecodeItem fails, we cannot assume anything about the
// validity of the data in epki. The best we can do is free the arena
// and return.
return rv;
}
// See comment in WrapPrivateKeyInfoWithEmptyPassword about this
// dummy password stuff.
SECItem dummyPassword = {siBuffer, nullptr, 0};
return PK11_ImportEncryptedPrivateKeyInfoAndReturnKey(
slot.get(), epki, &dummyPassword, nullptr, publicValue, false, false,
publicKey->keyType, KU_ALL, privk, nullptr);
}
void SerializeClientCertAndKey(const UniqueCERTCertificate& aCert,
const UniqueSECKEYPrivateKey& aKey,
ByteArray& aOutSerializedCert,
ByteArray& aOutSerializedKey) {
if (!aCert || !aKey) {
return;
}
UniqueSECItem derPki(WrapPrivateKeyInfoWithEmptyPassword(aKey.get()));
if (!derPki) {
return;
}
aOutSerializedCert.data().AppendElements(aCert->derCert.data,
aCert->derCert.len);
aOutSerializedKey.data().AppendElements(derPki->data, derPki->len);
}
void DeserializeClientCertAndKey(const ByteArray& aSerializedCert,
const ByteArray& aSerializedKey,
UniqueCERTCertificate& aOutCert,
UniqueSECKEYPrivateKey& aOutKey) {
if (aSerializedCert.data().IsEmpty() || aSerializedKey.data().IsEmpty()) {
return;
}
SECItem item = {siBuffer,
const_cast<uint8_t*>(aSerializedCert.data().Elements()),
static_cast<unsigned int>(aSerializedCert.data().Length())};
UniqueCERTCertificate cert(CERT_NewTempCertificate(
CERT_GetDefaultCertDB(), &item, nullptr, false, true));
if (!cert) {
return;
}
SECItem derPKI = {siBuffer,
const_cast<uint8_t*>(aSerializedKey.data().Elements()),
static_cast<unsigned int>(aSerializedKey.data().Length())};
SECKEYPrivateKey* privateKey;
if (UnwrapPrivateKeyInfoWithEmptyPassword(&derPKI, cert, &privateKey) !=
SECSuccess) {
MOZ_ASSERT(false);
return;
}
aOutCert = std::move(cert);
aOutKey.reset(privateKey);
}
} // namespace psm
} // namespace mozilla

View File

@ -1,34 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* 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 PSMIPCCommon_h__
#define PSMIPCCommon_h__
#include "mozilla/psm/PSMIPCTypes.h"
#include "seccomon.h"
#include "ScopedNSSTypes.h"
namespace mozilla {
namespace psm {
SECItem* WrapPrivateKeyInfoWithEmptyPassword(SECKEYPrivateKey* pk);
SECStatus UnwrapPrivateKeyInfoWithEmptyPassword(
SECItem* derPKI, const UniqueCERTCertificate& aCert,
SECKEYPrivateKey** privk);
void SerializeClientCertAndKey(const UniqueCERTCertificate& aCert,
const UniqueSECKEYPrivateKey& aKey,
ByteArray& aOutSerializedCert,
ByteArray& aOutSerializedKey);
void DeserializeClientCertAndKey(const ByteArray& aSerializedCert,
const ByteArray& aSerializedKey,
UniqueCERTCertificate& aOutCert,
UniqueSECKEYPrivateKey& aOutKey);
} // namespace psm
} // namespace mozilla
#endif // PSMIPCCommon_h__

View File

@ -12,6 +12,36 @@ struct ByteArray{
uint8_t[] data;
};
// For ECKey, RSAKey, and Certificate, slotType indicates which slot this object
// should exist on:
// 1: modern (supports EC, RSA-PSS)
// 2: legacy (only supports RSA PKCS#1v1.5)
struct ECKey{
uint8_t[] params; // the EC point representing this key
uint8_t[] cert; // the encoded certificate containing this key
uint32_t slotType;
};
struct RSAKey{
uint8_t[] modulus; // the modulus of this RSA key
uint8_t[] cert; // the encoded certificate containing this key
uint32_t slotType;
};
struct Certificate{
uint8_t[] der; // the encoding of this certificate
uint32_t slotType;
};
// Helper type for sending keys and certificates over IPC for use by IPC client
// certs.
union IPCClientCertObject{
ECKey;
RSAKey;
Certificate;
};
struct DelegatedCredentialInfoArg {
uint32_t scheme;
uint32_t authKeyBits;

View File

@ -0,0 +1,17 @@
[package]
name = "ipcclientcerts-static"
version = "0.1.0"
authors = ["Dana Keeler <dkeeler@mozilla.com>"]
edition = "2018"
[dependencies]
byteorder = "1.3"
env_logger = {version = "0.8", default-features = false } # disable `regex` to reduce code size
lazy_static = "1"
log = "0.4"
pkcs11 = "0.4"
rsclientcerts = { path = "../rsclientcerts" }
sha2 = "0.8"
[lib]
crate-type = ["staticlib"]

View File

@ -0,0 +1 @@
C_GetFunctionList

View File

@ -0,0 +1,22 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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_LIBS += ["ipcclientcerts-static"]
SOURCES += [
"stub.cpp",
]
if CONFIG["OS_ARCH"] == "WINNT":
OS_LIBS += [
"userenv",
"ws2_32",
]
SharedLibrary("ipcclientcerts")
NoVisibilityFlags()
SYMBOLS_FILE = "ipcclientcerts.symbols"

View File

@ -0,0 +1,29 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#include "pkcs11.h"
// The build system builds the rust library ipcclientcerts as a static library
// called ipcclientcerts_static. On macOS and Windows, that static library can
// be linked with an empty file and turned into a shared library with the
// function C_GetFunctionList exposed. This allows that shared library to be
// used as a PKCS#11 module (see osclientcerts).
// Unfortunately, on Linux, exposing the C_GetFunctionList in the static
// library doesn't work for some unknown reason. As a workaround, this file
// declares its own C_GetFunctionList that can be exposed in the shared
// library. It then calls the function IPCCC_GetFunctionList exposed
// (internally to the linkage in question) by ipcclientcerts. This enables
// the build system to ultimately turn ipcclientcerts into a shared library
// that exposes a C_GetFunctionList function, meaning it can be used as a
// PKCS#11 module.
extern "C" {
CK_RV IPCCC_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList);
CK_RV C_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList) {
return IPCCC_GetFunctionList(ppFunctionList);
}
}

View File

@ -0,0 +1,9 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DIRS += ["dynamic-library"]
RustLibrary("ipcclientcerts-static")

View File

@ -0,0 +1,380 @@
/* -*- Mode: rust; rust-indent-offset: 4 -*- */
/* 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 pkcs11::types::*;
use rsclientcerts::error::{Error, ErrorType};
use rsclientcerts::manager::{ClientCertsBackend, CryptokiObject, Sign, SlotType};
use rsclientcerts::util::*;
use sha2::{Digest, Sha256};
use std::ffi::c_void;
use std::thread;
use crate::FindObjectsFunction;
use crate::SignFunction;
pub struct Cert {
class: Vec<u8>,
token: Vec<u8>,
id: Vec<u8>,
label: Vec<u8>,
value: Vec<u8>,
issuer: Vec<u8>,
serial_number: Vec<u8>,
subject: Vec<u8>,
slot_type: SlotType,
}
impl Cert {
fn new(der: &[u8], slot_type: SlotType) -> Result<Cert, Error> {
let (serial_number, issuer, subject) = read_encoded_certificate_identifiers(der)?;
let id = Sha256::digest(der).to_vec();
Ok(Cert {
class: serialize_uint(CKO_CERTIFICATE)?,
token: serialize_uint(CK_TRUE)?,
id,
label: b"IPC certificate".to_vec(),
value: der.to_vec(),
issuer,
serial_number,
subject,
slot_type,
})
}
fn class(&self) -> &[u8] {
&self.class
}
fn token(&self) -> &[u8] {
&self.token
}
fn id(&self) -> &[u8] {
&self.id
}
fn label(&self) -> &[u8] {
&self.label
}
fn value(&self) -> &[u8] {
&self.value
}
fn issuer(&self) -> &[u8] {
&self.issuer
}
fn serial_number(&self) -> &[u8] {
&self.serial_number
}
fn subject(&self) -> &[u8] {
&self.subject
}
}
impl CryptokiObject for Cert {
fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool {
if self.slot_type != slot_type {
return false;
}
for (attr_type, attr_value) in attrs {
let comparison = match *attr_type {
CKA_CLASS => self.class(),
CKA_TOKEN => self.token(),
CKA_LABEL => self.label(),
CKA_ID => self.id(),
CKA_VALUE => self.value(),
CKA_ISSUER => self.issuer(),
CKA_SERIAL_NUMBER => self.serial_number(),
CKA_SUBJECT => self.subject(),
_ => return false,
};
if attr_value.as_slice() != comparison {
return false;
}
}
true
}
fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> {
let result = match attribute {
CKA_CLASS => self.class(),
CKA_TOKEN => self.token(),
CKA_LABEL => self.label(),
CKA_ID => self.id(),
CKA_VALUE => self.value(),
CKA_ISSUER => self.issuer(),
CKA_SERIAL_NUMBER => self.serial_number(),
CKA_SUBJECT => self.subject(),
_ => return None,
};
Some(result)
}
}
pub struct Key {
cert: Vec<u8>,
class: Vec<u8>,
token: Vec<u8>,
id: Vec<u8>,
private: Vec<u8>,
key_type: Vec<u8>,
modulus: Option<Vec<u8>>,
ec_params: Option<Vec<u8>>,
slot_type: SlotType,
sign: SignFunction,
}
impl Key {
fn new(
modulus: Option<&[u8]>,
ec_params: Option<&[u8]>,
cert: &[u8],
slot_type: SlotType,
sign: SignFunction,
) -> Result<Key, Error> {
let id = Sha256::digest(cert).to_vec();
let key_type = if modulus.is_some() { CKK_RSA } else { CKK_EC };
Ok(Key {
cert: cert.to_vec(),
class: serialize_uint(CKO_PRIVATE_KEY)?,
token: serialize_uint(CK_TRUE)?,
id,
private: serialize_uint(CK_TRUE)?,
key_type: serialize_uint(key_type)?,
modulus: modulus.map(|b| b.to_vec()),
ec_params: ec_params.map(|b| b.to_vec()),
slot_type,
sign,
})
}
fn class(&self) -> &[u8] {
&self.class
}
fn token(&self) -> &[u8] {
&self.token
}
pub fn id(&self) -> &[u8] {
&self.id
}
fn private(&self) -> &[u8] {
&self.private
}
fn key_type(&self) -> &[u8] {
&self.key_type
}
fn modulus(&self) -> Option<&[u8]> {
match &self.modulus {
Some(modulus) => Some(modulus.as_slice()),
None => None,
}
}
fn ec_params(&self) -> Option<&[u8]> {
match &self.ec_params {
Some(ec_params) => Some(ec_params.as_slice()),
None => None,
}
}
}
impl CryptokiObject for Key {
fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool {
if self.slot_type != slot_type {
return false;
}
for (attr_type, attr_value) in attrs {
let comparison = match *attr_type {
CKA_CLASS => self.class(),
CKA_TOKEN => self.token(),
CKA_ID => self.id(),
CKA_PRIVATE => self.private(),
CKA_KEY_TYPE => self.key_type(),
CKA_MODULUS => {
if let Some(modulus) = self.modulus() {
modulus
} else {
return false;
}
}
CKA_EC_PARAMS => {
if let Some(ec_params) = self.ec_params() {
ec_params
} else {
return false;
}
}
_ => return false,
};
if attr_value.as_slice() != comparison {
return false;
}
}
true
}
fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> {
match attribute {
CKA_CLASS => Some(self.class()),
CKA_TOKEN => Some(self.token()),
CKA_ID => Some(self.id()),
CKA_PRIVATE => Some(self.private()),
CKA_KEY_TYPE => Some(self.key_type()),
CKA_MODULUS => self.modulus(),
CKA_EC_PARAMS => self.ec_params(),
_ => None,
}
}
}
impl Sign for Key {
fn get_signature_length(
&mut self,
data: &[u8],
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<usize, Error> {
// Unfortunately we don't have a way of getting the length of a signature without creating
// one.
let dummy_signature_bytes = self.sign(data, params)?;
Ok(dummy_signature_bytes.len())
}
fn sign(
&mut self,
data: &[u8],
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<Vec<u8>, Error> {
let mut signature = Vec::new();
let (params_len, params) = match params {
Some(params) => (
std::mem::size_of::<CK_RSA_PKCS_PSS_PARAMS>(),
params as *const _ as *const u8,
),
None => (0, std::ptr::null()),
};
(self.sign)(
self.cert.len(),
self.cert.as_ptr(),
data.len(),
data.as_ptr(),
params_len,
params,
Some(sign_callback),
&mut signature as *mut _ as *mut c_void,
);
if signature.len() > 0 {
Ok(signature)
} else {
Err(error_here!(ErrorType::LibraryFailure))
}
}
}
unsafe extern "C" fn sign_callback(data_len: usize, data: *const u8, ctx: *mut c_void) {
let signature: &mut Vec<u8> = std::mem::transmute(ctx);
signature.clear();
signature.extend_from_slice(std::slice::from_raw_parts(data, data_len));
}
unsafe extern "C" fn find_objects_callback(
typ: u8,
data_len: usize,
data: *const u8,
extra_len: usize,
extra: *const u8,
slot_type: u32,
ctx: *mut c_void,
) {
let data = std::slice::from_raw_parts(data, data_len);
let extra = std::slice::from_raw_parts(extra, extra_len);
let slot_type = match slot_type {
1 => SlotType::Modern,
2 => SlotType::Legacy,
_ => return,
};
let find_objects_context: &mut FindObjectsContext = std::mem::transmute(ctx);
match typ {
1 => match Cert::new(data, slot_type) {
Ok(cert) => find_objects_context.certs.push(cert),
Err(e) => {
log_with_thread_id!(error, "find_objects_callback: couldn't create Cert: {}", e)
}
},
2 => match Key::new(
Some(data),
None,
extra,
slot_type,
find_objects_context.sign,
) {
Ok(key) => find_objects_context.keys.push(key),
Err(e) => {
log_with_thread_id!(error, "find_objects_callback: couldn't create Key: {}", e)
}
},
3 => match Key::new(
None,
Some(data),
extra,
slot_type,
find_objects_context.sign,
) {
Ok(key) => find_objects_context.keys.push(key),
Err(e) => {
log_with_thread_id!(error, "find_objects_callback: couldn't create Key: {}", e)
}
},
_ => log_with_thread_id!(error, "find_objects_callback: unknown type {}", typ),
}
}
struct FindObjectsContext {
certs: Vec<Cert>,
keys: Vec<Key>,
sign: SignFunction,
}
impl FindObjectsContext {
fn new(sign: SignFunction) -> FindObjectsContext {
FindObjectsContext {
certs: Vec::new(),
keys: Vec::new(),
sign,
}
}
}
pub struct Backend {
find_objects: FindObjectsFunction,
sign: SignFunction,
}
impl Backend {
pub fn new(find_objects: FindObjectsFunction, sign: SignFunction) -> Backend {
Backend { find_objects, sign }
}
}
impl ClientCertsBackend for Backend {
type Cert = Cert;
type Key = Key;
fn find_objects(&self) -> Result<(Vec<Cert>, Vec<Key>), Error> {
let mut find_objects_context = FindObjectsContext::new(self.sign);
(self.find_objects)(
Some(find_objects_callback),
&mut find_objects_context as *mut _ as *mut c_void,
);
Ok((find_objects_context.certs, find_objects_context.keys))
}
}

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,8 @@ if (CONFIG["OS_ARCH"] == "WINNT" and CONFIG["CPU_ARCH"] != "aarch64") or CONFIG[
] == "Darwin":
DIRS += ["osclientcerts"]
DIRS += ["ipcclientcerts"]
TEST_DIRS += ["tests"]
XPIDL_SOURCES += [
@ -93,6 +95,8 @@ EXPORTS.mozilla += [
]
EXPORTS.mozilla.psm += [
"IPCClientCertsChild.h",
"IPCClientCertsParent.h",
"PSMIPCCommon.h",
"TransportSecurityInfo.h",
"VerifySSLServerCertChild.h",
@ -111,6 +115,8 @@ UNIFIED_SOURCES += [
"CSTrustDomain.cpp",
"DataStorage.cpp",
"EnterpriseRoots.cpp",
"IPCClientCertsChild.cpp",
"IPCClientCertsParent.cpp",
"LocalCertService.cpp",
"nsCertOverrideService.cpp",
"nsClientAuthRemember.cpp",
@ -189,6 +195,7 @@ if CONFIG["OS_ARCH"] == "WINNT":
]
IPDL_SOURCES += [
"PIPCClientCerts.ipdl",
"PSMIPCTypes.ipdlh",
"PVerifySSLServerCert.ipdl",
]
@ -209,6 +216,7 @@ LOCAL_INCLUDES += [
"/dom/crypto",
"/netwerk/base",
"/security/certverifier",
"/xpcom/build",
]
LOCAL_INCLUDES += [

View File

@ -13,6 +13,7 @@
#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/Logging.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Span.h"
#include "mozilla/Telemetry.h"

View File

@ -6,6 +6,7 @@
#include "nsNSSComponent.h"
#include "BinaryPath.h"
#include "CryptoTask.h"
#include "EnterpriseRoots.h"
#include "ExtendedValidation.h"
@ -21,6 +22,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/FilePreferences.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
@ -100,6 +102,38 @@ int nsNSSComponent::mInstanceCount = 0;
// Forward declaration.
nsresult CommonInit();
// Take an nsIFile and get a c-string representation of the location of that
// file (encapsulated in an nsACString). This function handles a
// platform-specific issue on Windows where Unicode characters that cannot be
// mapped to the system's codepage will be dropped, resulting in a c-string
// that is useless to describe the location of the file in question.
// This operation is generally to be avoided, except when interacting with
// third-party or legacy libraries that cannot handle `nsIFile`s (such as NSS).
nsresult FileToCString(const nsCOMPtr<nsIFile>& file, nsACString& result) {
#ifdef XP_WIN
// Native path will drop Unicode characters that cannot be mapped to system's
// codepage, using short (canonical) path as workaround.
nsCOMPtr<nsILocalFileWin> fileWin = do_QueryInterface(file);
if (!fileWin) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get nsILocalFileWin"));
return NS_ERROR_FAILURE;
}
return fileWin->GetNativeCanonicalPath(result);
#else
return file->GetNativePath(result);
#endif
}
void TruncateFromLastDirectorySeparator(nsCString& path) {
static const nsAutoCString kSeparatorString(
mozilla::FilePreferences::kPathSeparator);
int32_t index = path.RFind(kSeparatorString);
if (index == kNotFound) {
return;
}
path.Truncate(index);
}
// This function can be called from chrome or content or socket processes
// to ensure that NSS is initialized.
bool EnsureNSSInitializedChromeOrContent() {
@ -149,6 +183,36 @@ bool EnsureNSSInitializedChromeOrContent() {
if (NS_FAILED(CommonInit())) {
return false;
}
// This returns the path to the binary currently running, which in most
// cases is "plugin-container".
UniqueFreePtr<char> pluginContainerPath(BinaryPath::Get());
if (!pluginContainerPath) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("failed to get get plugin-container path"));
return false;
}
nsAutoCString ipcClientCertsDirString(pluginContainerPath.get());
// On most platforms, ipcclientcerts is in the same directory as
// plugin-container. To obtain the path to that directory, truncate from
// the last directory separator.
// On macOS, plugin-container is in
// Firefox.app/Contents/MacOS/plugin-container.app/Contents/MacOS/,
// whereas ipcclientcerts is in Firefox.app/Contents/MacOS/. Consequently,
// this truncation from the last directory separator has to happen 4 times
// total. Normally this would be done using nsIFile APIs, but due to when
// this is initialized in the socket process, those aren't available.
TruncateFromLastDirectorySeparator(ipcClientCertsDirString);
#ifdef XP_MACOSX
TruncateFromLastDirectorySeparator(ipcClientCertsDirString);
TruncateFromLastDirectorySeparator(ipcClientCertsDirString);
TruncateFromLastDirectorySeparator(ipcClientCertsDirString);
#endif
if (!LoadIPCClientCertsModule(ipcClientCertsDirString)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("failed to load ipcclientcerts from '%s'",
ipcClientCertsDirString.get()));
return false;
}
initialized = true;
return true;
}
@ -750,18 +814,7 @@ static nsresult GetDirectoryPath(const char* directoryKey, nsCString& result) {
("could not get '%s' from directory service", directoryKey));
return rv;
}
#ifdef XP_WIN
// Native path will drop Unicode characters that cannot be mapped to system's
// codepage, using short (canonical) path as workaround.
nsCOMPtr<nsILocalFileWin> directoryWin = do_QueryInterface(directory);
if (!directoryWin) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get nsILocalFileWin"));
return NS_ERROR_FAILURE;
}
return directoryWin->GetNativeCanonicalPath(result);
#else
return directory->GetNativePath(result);
#endif
return FileToCString(directory, result);
}
class BackgroundLoadOSClientCertsModuleTask final : public CryptoTask {
@ -841,8 +894,6 @@ NS_IMETHODIMP
nsNSSComponent::HasUserCertsInstalled(bool* result) {
NS_ENSURE_ARG_POINTER(result);
BlockUntilLoadableCertsLoaded();
// FindClientCertificatesWithPrivateKeys won't ever return an empty list, so
// all we need to do is check if this is null or not.
UniqueCERTCertList certList(FindClientCertificatesWithPrivateKeys());
@ -941,18 +992,7 @@ static nsresult GetNSS3Directory(nsCString& result) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get parent directory?"));
return rv;
}
#ifdef XP_WIN
// Native path will drop Unicode characters that cannot be mapped to system's
// codepage, using short (canonical) path as workaround.
nsCOMPtr<nsILocalFileWin> nss3DirectoryWin = do_QueryInterface(nss3Directory);
if (!nss3DirectoryWin) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get nsILocalFileWin"));
return NS_ERROR_FAILURE;
}
return nss3DirectoryWin->GetNativeCanonicalPath(result);
#else
return nss3Directory->GetNativePath(result);
#endif
return FileToCString(nss3Directory, result);
}
// The loadable roots library is probably in the same directory we loaded the
@ -2620,6 +2660,9 @@ UniqueCERTCertList FindClientCertificatesWithPrivateKeys() {
});
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("FindClientCertificatesWithPrivateKeys"));
BlockUntilLoadableCertsLoaded();
UniqueCERTCertList certsWithPrivateKeys(CERT_NewCertList());
if (!certsWithPrivateKeys) {
return nullptr;

View File

@ -25,8 +25,12 @@
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/net/SSLTokensCache.h"
#include "mozilla/net/SocketProcessChild.h"
#include "mozilla/psm/IPCClientCertsChild.h"
#include "mozilla/psm/PIPCClientCertsChild.h"
#include "mozpkix/pkixnss.h"
#include "mozpkix/pkixtypes.h"
#include "mozpkix/pkixutil.h"
@ -54,6 +58,7 @@
#include "sslproto.h"
using namespace mozilla::psm;
using namespace mozilla::ipc;
//#define DEBUG_SSL_VERBOSE //Enable this define to get minimal
// reports when doing SSL read/write
@ -1826,8 +1831,7 @@ class ClientAuthDataRunnable : public SyncRunnableBase {
: mInfo(std::move(info)),
mServerCert(serverCert.get()),
mCollectedCANames(std::move(collectedCANames)),
mSelectedCertificate(nullptr),
mSelectedKey(nullptr) {}
mSelectedCertificate(nullptr) {}
virtual mozilla::pkix::Result BuildChainForCertificate(
CERTCertificate* cert, UniqueCERTCertList& builtChain);
@ -1837,10 +1841,6 @@ class ClientAuthDataRunnable : public SyncRunnableBase {
UniqueCERTCertificate TakeSelectedCertificate() {
return std::move(mSelectedCertificate);
}
// Take the private key for the selected certificate. Will be null if no
// certificate was selected or an error prevented selecting one or getting
// the corresponding key.
UniqueSECKEYPrivateKey TakeSelectedKey() { return std::move(mSelectedKey); }
protected:
virtual void RunOnTargetThread() override;
@ -1850,7 +1850,6 @@ class ClientAuthDataRunnable : public SyncRunnableBase {
nsTArray<nsTArray<uint8_t>> mCollectedCANames;
nsTArray<nsTArray<uint8_t>> mEnterpriseCertificates;
UniqueCERTCertificate mSelectedCertificate;
UniqueSECKEYPrivateKey mSelectedKey;
};
class RemoteClientAuthDataRunnable : public ClientAuthDataRunnable {
@ -1946,30 +1945,42 @@ SECStatus nsNSS_SSLGetClientAuthData(void* arg, PRFileDesc* socket,
nsTArray<nsTArray<uint8_t>> collectedCANames(CollectCANames(caNames));
UniqueCERTCertificate selectedCertificate;
UniqueSECKEYPrivateKey selectedKey;
UniqueCERTCertList builtChain;
SECStatus status = DoGetClientAuthData(
std::move(authInfo), serverCert, std::move(collectedCANames),
selectedCertificate, selectedKey, builtChain);
SECStatus status = DoGetClientAuthData(std::move(authInfo), serverCert,
std::move(collectedCANames),
selectedCertificate, builtChain);
if (status != SECSuccess) {
return status;
}
if (selectedCertificate && selectedKey) {
if (builtChain) {
info->SetClientCertChain(std::move(builtChain));
} else {
MOZ_LOG(
gPIPNSSLog, LogLevel::Debug,
("[%p] couldn't determine chain for selected client cert", socket));
}
*pRetCert = selectedCertificate.release();
*pRetKey = selectedKey.release();
// Make joinConnection prohibit joining after we've sent a client cert
info->SetSentClientCert();
if (info->GetSSLVersionUsed() == nsISSLSocketControl::TLS_VERSION_1_3) {
Telemetry::Accumulate(Telemetry::TLS_1_3_CLIENT_AUTH_USES_PHA,
info->IsHandshakeCompleted());
// Currently, the IPC client certs module only refreshes its view of
// available certificates and keys if the platform issues a search for all
// certificates or keys. In the socket process, such a search may not have
// happened, so this ensures it has.
if (XRE_IsSocketProcess()) {
UniqueCERTCertList certList(FindClientCertificatesWithPrivateKeys());
Unused << certList;
}
if (selectedCertificate) {
UniqueSECKEYPrivateKey selectedKey(
PK11_FindKeyByAnyCert(selectedCertificate.get(), nullptr));
if (selectedKey) {
if (builtChain) {
info->SetClientCertChain(std::move(builtChain));
} else {
MOZ_LOG(
gPIPNSSLog, LogLevel::Debug,
("[%p] couldn't determine chain for selected client cert", socket));
}
*pRetCert = selectedCertificate.release();
*pRetKey = selectedKey.release();
// Make joinConnection prohibit joining after we've sent a client cert
info->SetSentClientCert();
if (info->GetSSLVersionUsed() == nsISSLSocketControl::TLS_VERSION_1_3) {
Telemetry::Accumulate(Telemetry::TLS_1_3_CLIENT_AUTH_USES_PHA,
info->IsHandshakeCompleted());
}
}
}
@ -1980,7 +1991,6 @@ SECStatus DoGetClientAuthData(ClientAuthInfo&& info,
const UniqueCERTCertificate& serverCert,
nsTArray<nsTArray<uint8_t>>&& collectedCANames,
UniqueCERTCertificate& outCert,
UniqueSECKEYPrivateKey& outKey,
UniqueCERTCertList& outBuiltChain) {
// XXX: This should be done asynchronously; see bug 696976
RefPtr<ClientAuthDataRunnable> runnable =
@ -1997,8 +2007,7 @@ SECStatus DoGetClientAuthData(ClientAuthInfo&& info,
}
outCert = runnable->TakeSelectedCertificate();
outKey = runnable->TakeSelectedKey();
if (outCert && outKey) {
if (outCert) {
mozilla::pkix::Result result =
runnable->BuildChainForCertificate(outCert.get(), outBuiltChain);
if (result != Success) {
@ -2033,7 +2042,7 @@ class ClientAuthCertNonverifyingTrustDomain final : public TrustDomain {
virtual mozilla::pkix::Result CheckRevocation(
EndEntityOrCA endEntityOrCA, const CertID& certID, Time time,
Duration validityDuration,
mozilla::pkix::Duration validityDuration,
/*optional*/ const Input* stapledOCSPresponse,
/*optional*/ const Input* aiaExtension,
/*optional*/ const Input* sctExtension) override {
@ -2309,8 +2318,6 @@ void ClientAuthDataRunnable::RunOnTargetThread() {
if (NS_WARN_IF(!mSelectedCertificate)) {
return;
}
mSelectedKey.reset(
PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr));
return;
}
@ -2360,7 +2367,6 @@ void ClientAuthDataRunnable::RunOnTargetThread() {
} else {
// this is a good cert to present
mSelectedCertificate.reset(CERT_DupCertificate(node->cert));
mSelectedKey = std::move(tmpKey);
return;
}
}
@ -2372,8 +2378,6 @@ void ClientAuthDataRunnable::RunOnTargetThread() {
if (lowPrioNonrepCert) {
mSelectedCertificate = std::move(lowPrioNonrepCert);
mSelectedKey.reset(
PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr));
}
return;
}
@ -2422,8 +2426,6 @@ void ClientAuthDataRunnable::RunOnTargetThread() {
if (NS_WARN_IF(!mSelectedCertificate)) {
return;
}
mSelectedKey.reset(
PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr));
return;
}
}
@ -2485,8 +2487,6 @@ void ClientAuthDataRunnable::RunOnTargetThread() {
if (NS_WARN_IF(!mSelectedCertificate)) {
return;
}
mSelectedKey.reset(
PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr));
}
if (cars && wantRemember) {
@ -2550,18 +2550,19 @@ void RemoteClientAuthDataRunnable::RunOnTargetThread() {
bool succeeded = false;
ByteArray cert;
ByteArray key;
mozilla::net::SocketProcessChild::GetSingleton()->SendGetTLSClientCert(
nsCString(mInfo.HostName()), mInfo.OriginAttributesRef(), mInfo.Port(),
mInfo.ProviderFlags(), mInfo.ProviderTlsFlags(), serverCertSerialized,
clientCertSerialized, collectedCANames, &succeeded, &cert, &key,
&mBuiltChain);
clientCertSerialized, collectedCANames, &succeeded, &cert, &mBuiltChain);
if (!succeeded) {
return;
}
DeserializeClientCertAndKey(cert, key, mSelectedCertificate, mSelectedKey);
SECItem certItem = {siBuffer, const_cast<uint8_t*>(cert.data().Elements()),
static_cast<unsigned int>(cert.data().Length())};
mSelectedCertificate.reset(CERT_NewTempCertificate(
CERT_GetDefaultCertDB(), &certItem, nullptr, false, true));
}
static PRFileDesc* nsSSLIOLayerImportFD(PRFileDesc* fd,
@ -2948,3 +2949,91 @@ loser:
}
return NS_ERROR_FAILURE;
}
already_AddRefed<IPCClientCertsChild> GetIPCClientCertsActor() {
PBackgroundChild* backgroundActor =
BackgroundChild::GetOrCreateForSocketParentBridgeForCurrentThread();
if (!backgroundActor) {
return nullptr;
}
RefPtr<PIPCClientCertsChild> actor =
SingleManagedOrNull(backgroundActor->ManagedPIPCClientCertsChild());
if (!actor) {
actor = backgroundActor->SendPIPCClientCertsConstructor(
new IPCClientCertsChild());
if (!actor) {
return nullptr;
}
}
return actor.forget().downcast<IPCClientCertsChild>();
}
extern "C" {
const uint8_t kIPCClientCertsObjectTypeCert = 1;
const uint8_t kIPCClientCertsObjectTypeRSAKey = 2;
const uint8_t kIPCClientCertsObjectTypeECKey = 3;
// This function is provided to the IPC client certs module so it can cause the
// parent process to find certificates and keys and send identifying
// information about them over IPC.
void DoFindObjects(FindObjectsCallback cb, void* ctx) {
RefPtr<IPCClientCertsChild> ipcClientCertsActor(GetIPCClientCertsActor());
if (!ipcClientCertsActor) {
return;
}
nsTArray<IPCClientCertObject> objects;
if (!ipcClientCertsActor->SendFindObjects(&objects)) {
return;
}
for (const auto& object : objects) {
switch (object.type()) {
case IPCClientCertObject::TECKey:
cb(kIPCClientCertsObjectTypeECKey, object.get_ECKey().params().Length(),
object.get_ECKey().params().Elements(),
object.get_ECKey().cert().Length(),
object.get_ECKey().cert().Elements(), object.get_ECKey().slotType(),
ctx);
break;
case IPCClientCertObject::TRSAKey:
cb(kIPCClientCertsObjectTypeRSAKey,
object.get_RSAKey().modulus().Length(),
object.get_RSAKey().modulus().Elements(),
object.get_RSAKey().cert().Length(),
object.get_RSAKey().cert().Elements(),
object.get_RSAKey().slotType(), ctx);
break;
case IPCClientCertObject::TCertificate:
cb(kIPCClientCertsObjectTypeCert,
object.get_Certificate().der().Length(),
object.get_Certificate().der().Elements(), 0, nullptr,
object.get_Certificate().slotType(), ctx);
break;
default:
MOZ_ASSERT_UNREACHABLE("unhandled IPCClientCertObject type");
break;
}
}
}
// This function is provided to the IPC client certs module so it can cause the
// parent process to sign the given data using the key corresponding to the
// given certificate, using the given parameters.
void DoSign(size_t cert_len, const uint8_t* cert, size_t data_len,
const uint8_t* data, size_t params_len, const uint8_t* params,
SignCallback cb, void* ctx) {
RefPtr<IPCClientCertsChild> ipcClientCertsActor(GetIPCClientCertsActor());
if (!ipcClientCertsActor) {
return;
}
ByteArray certBytes(nsTArray<uint8_t>(cert, cert_len));
ByteArray dataBytes(nsTArray<uint8_t>(data, data_len));
ByteArray paramsBytes(nsTArray<uint8_t>(params, params_len));
ByteArray signature;
if (!ipcClientCertsActor->SendSign(certBytes, dataBytes, paramsBytes,
&signature)) {
return;
}
cb(signature.data().Length(), signature.data().Elements(), ctx);
}
} // extern "C"

View File

@ -27,6 +27,9 @@ class SharedSSLState;
} // namespace psm
} // namespace mozilla
const uint32_t kIPCClientCertsSlotTypeModern = 1;
const uint32_t kIPCClientCertsSlotTypeLegacy = 2;
using mozilla::OriginAttributes;
class nsIObserver;
@ -354,11 +357,22 @@ nsresult nsSSLIOLayerAddToSocket(int32_t family, const char* host, int32_t port,
bool forSTARTTLS, uint32_t flags,
uint32_t tlsFlags);
extern "C" {
using FindObjectsCallback = void (*)(uint8_t type, size_t id_len,
const uint8_t* id, size_t data_len,
const uint8_t* data, uint32_t slotType,
void* ctx);
void DoFindObjects(FindObjectsCallback cb, void* ctx);
using SignCallback = void (*)(size_t data_len, const uint8_t* data, void* ctx);
void DoSign(size_t cert_len, const uint8_t* cert, size_t data_len,
const uint8_t* data, size_t params_len, const uint8_t* params,
SignCallback cb, void* ctx);
}
SECStatus DoGetClientAuthData(ClientAuthInfo&& info,
const mozilla::UniqueCERTCertificate& serverCert,
nsTArray<nsTArray<uint8_t>>&& collectedCANames,
mozilla::UniqueCERTCertificate& outCert,
mozilla::UniqueSECKEYPrivateKey& outKey,
mozilla::UniqueCERTCertList& outBuiltChain);
#endif // nsNSSIOLayer_h

View File

@ -88,8 +88,7 @@ macro_rules! manager_guard_to_manager {
// Helper macro to prefix log messages with the current thread ID.
macro_rules! log_with_thread_id {
($log_level:ident, $($message:expr),*) => {
let message = format!($($message),*);
$log_level!("{:?} {}", thread::current().id(), message);
$log_level!("{:?} {}", thread::current().id(), format_args!($($message),*));
};
}

View File

@ -146,7 +146,7 @@ impl ManagerProxy {
}
ManagerArguments::StartSearch(session, attrs) => {
ManagerReturnValue::StartSearch(
real_manager.start_search(session, &attrs),
real_manager.start_search(session, attrs),
)
}
ManagerArguments::Search(session, max_objects) => {
@ -167,11 +167,11 @@ impl ManagerProxy {
}
ManagerArguments::GetSignatureLength(session, data) => {
ManagerReturnValue::GetSignatureLength(
real_manager.get_signature_length(session, &data),
real_manager.get_signature_length(session, data),
)
}
ManagerArguments::Sign(session, data) => {
ManagerReturnValue::Sign(real_manager.sign(session, &data))
ManagerReturnValue::Sign(real_manager.sign(session, data))
}
ManagerArguments::Stop => ManagerReturnValue::Stop(Ok(())),
};
@ -402,23 +402,23 @@ impl<B: ClientCertsBackend> Object<B> {
fn get_signature_length(
&mut self,
data: &[u8],
data: Vec<u8>,
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<usize, Error> {
match self {
Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)),
Object::Key(key) => key.get_signature_length(data, params),
Object::Key(key) => key.get_signature_length(&data, params),
}
}
fn sign(
&mut self,
data: &[u8],
data: Vec<u8>,
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<Vec<u8>, Error> {
match self {
Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)),
Object::Key(key) => key.sign(data, params),
Object::Key(key) => key.sign(&data, params),
}
}
}
@ -426,7 +426,7 @@ impl<B: ClientCertsBackend> Object<B> {
/// The `Manager` keeps track of the state of this module with respect to the PKCS #11
/// specification. This includes what sessions are open, which search and sign operations are
/// ongoing, and what objects are known and by what handle.
struct Manager<B: ClientCertsBackend> {
pub struct Manager<B: ClientCertsBackend> {
/// A map of session to session type (modern or legacy). Sessions can be created (opened) and
/// later closed.
sessions: BTreeMap<CK_SESSION_HANDLE, SlotType>,
@ -546,7 +546,7 @@ impl<B: ClientCertsBackend> Manager<B> {
pub fn start_search(
&mut self,
session: CK_SESSION_HANDLE,
attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)],
attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec<u8>)>,
) -> Result<(), Error> {
let slot_type = match self.sessions.get(&session) {
Some(slot_type) => *slot_type,
@ -554,7 +554,7 @@ impl<B: ClientCertsBackend> Manager<B> {
};
// If the search is for an attribute we don't support, no objects will match. This check
// saves us having to look through all of our objects.
for (attr, _) in attrs {
for (attr, _) in &attrs {
if !SUPPORTED_ATTRIBUTES.contains(attr) {
self.searches.insert(session, Vec::new());
return Ok(());
@ -565,12 +565,12 @@ impl<B: ClientCertsBackend> Manager<B> {
// indication for the backend to re-scan for new objects from tokens that may have been
// inserted or certificates that may have been imported into the OS. Since these searches
// are relatively rare, this minimizes the impact of doing these re-scans.
if search_is_for_all_certificates_or_keys(attrs)? {
if search_is_for_all_certificates_or_keys(&attrs)? {
self.maybe_find_new_objects()?;
}
let mut handles = Vec::new();
for (handle, object) in &self.objects {
if object.matches(slot_type, attrs) {
if object.matches(slot_type, &attrs) {
handles.push(*handle);
}
}
@ -651,7 +651,7 @@ impl<B: ClientCertsBackend> Manager<B> {
pub fn get_signature_length(
&mut self,
session: CK_SESSION_HANDLE,
data: &[u8],
data: Vec<u8>,
) -> Result<usize, Error> {
let (key_handle, params) = match self.signs.get(&session) {
Some((key_handle, params)) => (key_handle, params),
@ -664,7 +664,7 @@ impl<B: ClientCertsBackend> Manager<B> {
key.get_signature_length(data, params)
}
pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: &[u8]) -> Result<Vec<u8>, Error> {
pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec<u8>) -> Result<Vec<u8>, Error> {
// Performing the signature (via C_Sign, which is the only way we support) finishes the sign
// operation, so it needs to be removed here.
let (key_handle, params) = match self.signs.remove(&session) {