From c02a76841ba9ced2ae1ec54d3aefe972c3582f0a Mon Sep 17 00:00:00 2001 From: Dana Keeler Date: Wed, 1 Dec 2021 18:10:34 +0000 Subject: [PATCH] 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 --- Cargo.lock | 13 + Cargo.toml | 1 + .../extensions/parent/ext-pkcs11.js | 3 +- .../xpcshell/test_ext_pkcs11_management.js | 20 + browser/installer/package-manifest.in | 2 + dom/media/webrtc/transport/dtlsidentity.cpp | 100 +- ipc/glue/BackgroundChildImpl.cpp | 1 + ipc/glue/BackgroundParentImpl.cpp | 17 + ipc/glue/BackgroundParentImpl.h | 2 + ipc/glue/PBackground.ipdl | 4 + ipc/ipdl/sync-messages.ini | 4 + netwerk/ipc/PSocketProcess.ipdl | 2 +- netwerk/ipc/SocketProcessChild.cpp | 15 +- netwerk/ipc/SocketProcessImpl.cpp | 2 + netwerk/ipc/SocketProcessParent.cpp | 9 +- netwerk/ipc/SocketProcessParent.h | 10 +- .../certverifier/NSSCertDBTrustDomain.cpp | 30 +- security/certverifier/NSSCertDBTrustDomain.h | 17 + security/manager/ssl/IPCClientCertsChild.cpp | 17 + security/manager/ssl/IPCClientCertsChild.h | 36 + security/manager/ssl/IPCClientCertsParent.cpp | 114 ++ security/manager/ssl/IPCClientCertsParent.h | 40 + security/manager/ssl/PIPCClientCerts.ipdl | 35 + security/manager/ssl/PSMIPCCommon.cpp | 161 --- security/manager/ssl/PSMIPCCommon.h | 34 - security/manager/ssl/PSMIPCTypes.ipdlh | 30 + .../manager/ssl/ipcclientcerts/Cargo.toml | 17 + .../dynamic-library/ipcclientcerts.symbols | 1 + .../ipcclientcerts/dynamic-library/moz.build | 22 + .../ipcclientcerts/dynamic-library/stub.cpp | 29 + security/manager/ssl/ipcclientcerts/moz.build | 9 + .../manager/ssl/ipcclientcerts/src/backend.rs | 380 +++++ .../manager/ssl/ipcclientcerts/src/lib.rs | 1251 +++++++++++++++++ security/manager/ssl/moz.build | 8 + security/manager/ssl/nsNSSCallbacks.cpp | 1 + security/manager/ssl/nsNSSComponent.cpp | 95 +- security/manager/ssl/nsNSSIOLayer.cpp | 175 ++- security/manager/ssl/nsNSSIOLayer.h | 16 +- security/manager/ssl/osclientcerts/src/lib.rs | 3 +- .../manager/ssl/rsclientcerts/src/manager.rs | 28 +- 40 files changed, 2454 insertions(+), 300 deletions(-) create mode 100644 security/manager/ssl/IPCClientCertsChild.cpp create mode 100644 security/manager/ssl/IPCClientCertsChild.h create mode 100644 security/manager/ssl/IPCClientCertsParent.cpp create mode 100644 security/manager/ssl/IPCClientCertsParent.h create mode 100644 security/manager/ssl/PIPCClientCerts.ipdl create mode 100644 security/manager/ssl/ipcclientcerts/Cargo.toml create mode 100644 security/manager/ssl/ipcclientcerts/dynamic-library/ipcclientcerts.symbols create mode 100644 security/manager/ssl/ipcclientcerts/dynamic-library/moz.build create mode 100644 security/manager/ssl/ipcclientcerts/dynamic-library/stub.cpp create mode 100644 security/manager/ssl/ipcclientcerts/moz.build create mode 100644 security/manager/ssl/ipcclientcerts/src/backend.rs create mode 100644 security/manager/ssl/ipcclientcerts/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index b1bbb0e51c2a..a3cba0004e5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 63a4d7178b7b..39e6dfb27fbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/browser/components/extensions/parent/ext-pkcs11.js b/browser/components/extensions/parent/ext-pkcs11.js index 697cd85d2775..334adaa2c3ed 100644 --- a/browser/components/extensions/parent/ext-pkcs11.js +++ b/browser/components/extensions/parent/ext-pkcs11.js @@ -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; } diff --git a/browser/components/extensions/test/xpcshell/test_ext_pkcs11_management.js b/browser/components/extensions/test/xpcshell/test_ext_pkcs11_management.js index ffd510fe6543..6463c5964870 100644 --- a/browser/components/extensions/test/xpcshell/test_ext_pkcs11_management.js +++ b/browser/components/extensions/test/xpcshell/test_ext_pkcs11_management.js @@ -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}`); diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 4d7a4810a7f2..0d2ff9dff232 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -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) diff --git a/dom/media/webrtc/transport/dtlsidentity.cpp b/dom/media/webrtc/transport/dtlsidentity.cpp index 25e22d1c9744..e4ecee321013 100644 --- a/dom/media/webrtc/transport/dtlsidentity.cpp +++ b/dom/media/webrtc/transport/dtlsidentity.cpp @@ -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* aKeyDer, nsTArray* 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::Deserialize( static_cast(aKeyDer.Length())}; SECKEYPrivateKey* privateKey; - if (psm::UnwrapPrivateKeyInfoWithEmptyPassword(&derPKI, cert, &privateKey) != + if (UnwrapPrivateKeyInfoWithEmptyPassword(&derPKI, cert, &privateKey) != SECSuccess) { MOZ_ASSERT(false); return nullptr; diff --git a/ipc/glue/BackgroundChildImpl.cpp b/ipc/glue/BackgroundChildImpl.cpp index e10c90b5300e..54728c6200e7 100644 --- a/ipc/glue/BackgroundChildImpl.cpp +++ b/ipc/glue/BackgroundChildImpl.cpp @@ -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" diff --git a/ipc/glue/BackgroundParentImpl.cpp b/ipc/glue/BackgroundParentImpl.cpp index 32e125f8d8dc..d6f189c03ef8 100644 --- a/ipc/glue/BackgroundParentImpl.cpp +++ b/ipc/glue/BackgroundParentImpl.cpp @@ -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 +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 result = new psm::IPCClientCertsParent(); + return result.forget(); +} + bool BackgroundParentImpl::DeallocPMessagePortParent( PMessagePortParent* aActor) { AssertIsInMainOrSocketProcess(); diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h index 4923deffc68f..5865d241e75f 100644 --- a/ipc/glue/BackgroundParentImpl.h +++ b/ipc/glue/BackgroundParentImpl.h @@ -295,6 +295,8 @@ class BackgroundParentImpl : public PBackgroundParent, PMessagePortParent* aActor, const nsID& aUUID, const nsID& aDestinationUUID, const uint32_t& aSequenceID) override; + already_AddRefed AllocPIPCClientCertsParent() override; + bool DeallocPMessagePortParent(PMessagePortParent* aActor) override; mozilla::ipc::IPCResult RecvMessagePortForceClose( diff --git a/ipc/glue/PBackground.ipdl b/ipc/glue/PBackground.ipdl index 019d6f0d9237..03dbbb98a04b 100644 --- a/ipc/glue/PBackground.ipdl +++ b/ipc/glue/PBackground.ipdl @@ -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(); diff --git a/ipc/ipdl/sync-messages.ini b/ipc/ipdl/sync-messages.ini index 3459aefdb97b..a840ea0edd1c 100644 --- a/ipc/ipdl/sync-messages.ini +++ b/ipc/ipdl/sync-messages.ini @@ -1072,6 +1072,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 # diff --git a/netwerk/ipc/PSocketProcess.ipdl b/netwerk/ipc/PSocketProcess.ipdl index 11988f4976e9..2a332136c256 100644 --- a/netwerk/ipc/PSocketProcess.ipdl +++ b/netwerk/ipc/PSocketProcess.ipdl @@ -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, diff --git a/netwerk/ipc/SocketProcessChild.cpp b/netwerk/ipc/SocketProcessChild.cpp index e6ea9ea56b95..ee4a73637241 100644 --- a/netwerk/ipc/SocketProcessChild.cpp +++ b/netwerk/ipc/SocketProcessChild.cpp @@ -165,7 +165,15 @@ bool SocketProcessChild::Init(base::ProcessId aParentPid, // Initialize DNS Service here, since it needs to be done in main thread. nsCOMPtr 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(); } diff --git a/netwerk/ipc/SocketProcessImpl.cpp b/netwerk/ipc/SocketProcessImpl.cpp index 6f55eb85845f..6a0739622f16 100644 --- a/netwerk/ipc/SocketProcessImpl.cpp +++ b/netwerk/ipc/SocketProcessImpl.cpp @@ -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 diff --git a/netwerk/ipc/SocketProcessParent.cpp b/netwerk/ipc/SocketProcessParent.cpp index 92f1fb2fc752..6a69209be231 100644 --- a/netwerk/ipc/SocketProcessParent.cpp +++ b/netwerk/ipc/SocketProcessParent.cpp @@ -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&& aClientCert, nsTArray&& aCollectedCANames, - bool* aSucceeded, ByteArray* aOutCert, ByteArray* aOutKey, - nsTArray* aBuiltChain) { + bool* aSucceeded, ByteArray* aOutCert, nsTArray* 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); diff --git a/netwerk/ipc/SocketProcessParent.h b/netwerk/ipc/SocketProcessParent.h index c51124d991e8..3628a0ee449d 100644 --- a/netwerk/ipc/SocketProcessParent.h +++ b/netwerk/ipc/SocketProcessParent.h @@ -99,8 +99,14 @@ class SocketProcessParent final const int32_t& aPort, const uint32_t& aProviderFlags, const uint32_t& aProviderTlsFlags, const ByteArray& aServerCert, Maybe&& aClientCert, nsTArray&& aCollectedCANames, - bool* aSucceeded, ByteArray* aOutCert, ByteArray* aOutKey, - nsTArray* aBuiltChain); + bool* aSucceeded, ByteArray* aOutCert, nsTArray* aBuiltChain); + + mozilla::ipc::IPCResult RecvFindIPCClientCertObjects( + nsTArray* aObjects); + mozilla::ipc::IPCResult RecvIPCClientCertSign(ByteArray aCert, + ByteArray aData, + ByteArray aParams, + ByteArray* aSignature); already_AddRefed AllocPProxyConfigLookupParent( nsIURI* aURI, const uint32_t& aProxyResolveFlags); diff --git a/security/certverifier/NSSCertDBTrustDomain.cpp b/security/certverifier/NSSCertDBTrustDomain.cpp index a26121054c5a..9f16736e817a 100644 --- a/security/certverifier/NSSCertDBTrustDomain.cpp +++ b/security/certverifier/NSSCertDBTrustDomain.cpp @@ -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(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, diff --git a/security/certverifier/NSSCertDBTrustDomain.h b/security/certverifier/NSSCertDBTrustDomain.h index fa24b85f2e24..8e354fe0184f 100644 --- a/security/certverifier/NSSCertDBTrustDomain.h +++ b/security/certverifier/NSSCertDBTrustDomain.h @@ -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); diff --git a/security/manager/ssl/IPCClientCertsChild.cpp b/security/manager/ssl/IPCClientCertsChild.cpp new file mode 100644 index 000000000000..bea0e27dffc7 --- /dev/null +++ b/security/manager/ssl/IPCClientCertsChild.cpp @@ -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 diff --git a/security/manager/ssl/IPCClientCertsChild.h b/security/manager/ssl/IPCClientCertsChild.h new file mode 100644 index 000000000000..17020fce2ed3 --- /dev/null +++ b/security/manager/ssl/IPCClientCertsChild.h @@ -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 diff --git a/security/manager/ssl/IPCClientCertsParent.cpp b/security/manager/ssl/IPCClientCertsParent.cpp new file mode 100644 index 000000000000..8058dbdd8365 --- /dev/null +++ b/security/manager/ssl/IPCClientCertsParent.cpp @@ -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* aObjects) { + UniqueCERTCertList certList(psm::FindClientCertificatesWithPrivateKeys()); + if (!certList) { + return IPC_OK(); + } + CERTCertListNode* n = CERT_LIST_HEAD(certList); + while (!CERT_LIST_END(n, certList)) { + nsTArray 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 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 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(aCert.data().Elements()), + static_cast(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(aParams.data().Length())}; + SECItem* paramsPtr = aParams.data().Length() > 0 ? ¶ms : 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(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 diff --git a/security/manager/ssl/IPCClientCertsParent.h b/security/manager/ssl/IPCClientCertsParent.h new file mode 100644 index 000000000000..09ad589aca05 --- /dev/null +++ b/security/manager/ssl/IPCClientCertsParent.h @@ -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* aObjects); + mozilla::ipc::IPCResult RecvSign(ByteArray aCert, ByteArray aData, + ByteArray aParams, ByteArray* aSignature); + + private: + IPCClientCertsParent(); + ~IPCClientCertsParent() = default; +}; + +} // namespace psm +} // namespace mozilla + +#endif diff --git a/security/manager/ssl/PIPCClientCerts.ipdl b/security/manager/ssl/PIPCClientCerts.ipdl new file mode 100644 index 000000000000..6e9d7bad61e5 --- /dev/null +++ b/security/manager/ssl/PIPCClientCerts.ipdl @@ -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 diff --git a/security/manager/ssl/PSMIPCCommon.cpp b/security/manager/ssl/PSMIPCCommon.cpp index 6e0ed62302b3..e69de29bb2d1 100644 --- a/security/manager/ssl/PSMIPCCommon.cpp +++ b/security/manager/ssl/PSMIPCCommon.cpp @@ -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(aSerializedCert.data().Elements()), - static_cast(aSerializedCert.data().Length())}; - - UniqueCERTCertificate cert(CERT_NewTempCertificate( - CERT_GetDefaultCertDB(), &item, nullptr, false, true)); - - if (!cert) { - return; - } - - SECItem derPKI = {siBuffer, - const_cast(aSerializedKey.data().Elements()), - static_cast(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 diff --git a/security/manager/ssl/PSMIPCCommon.h b/security/manager/ssl/PSMIPCCommon.h index 7d5455dd4b3f..e69de29bb2d1 100644 --- a/security/manager/ssl/PSMIPCCommon.h +++ b/security/manager/ssl/PSMIPCCommon.h @@ -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__ diff --git a/security/manager/ssl/PSMIPCTypes.ipdlh b/security/manager/ssl/PSMIPCTypes.ipdlh index 73e8ca04f525..54bce317c203 100644 --- a/security/manager/ssl/PSMIPCTypes.ipdlh +++ b/security/manager/ssl/PSMIPCTypes.ipdlh @@ -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; diff --git a/security/manager/ssl/ipcclientcerts/Cargo.toml b/security/manager/ssl/ipcclientcerts/Cargo.toml new file mode 100644 index 000000000000..8ef5bf5839d7 --- /dev/null +++ b/security/manager/ssl/ipcclientcerts/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ipcclientcerts-static" +version = "0.1.0" +authors = ["Dana Keeler "] +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"] diff --git a/security/manager/ssl/ipcclientcerts/dynamic-library/ipcclientcerts.symbols b/security/manager/ssl/ipcclientcerts/dynamic-library/ipcclientcerts.symbols new file mode 100644 index 000000000000..562ecea21d43 --- /dev/null +++ b/security/manager/ssl/ipcclientcerts/dynamic-library/ipcclientcerts.symbols @@ -0,0 +1 @@ +C_GetFunctionList diff --git a/security/manager/ssl/ipcclientcerts/dynamic-library/moz.build b/security/manager/ssl/ipcclientcerts/dynamic-library/moz.build new file mode 100644 index 000000000000..afc265aef942 --- /dev/null +++ b/security/manager/ssl/ipcclientcerts/dynamic-library/moz.build @@ -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" diff --git a/security/manager/ssl/ipcclientcerts/dynamic-library/stub.cpp b/security/manager/ssl/ipcclientcerts/dynamic-library/stub.cpp new file mode 100644 index 000000000000..e50f8675741d --- /dev/null +++ b/security/manager/ssl/ipcclientcerts/dynamic-library/stub.cpp @@ -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); +} +} diff --git a/security/manager/ssl/ipcclientcerts/moz.build b/security/manager/ssl/ipcclientcerts/moz.build new file mode 100644 index 000000000000..1d24bdea31da --- /dev/null +++ b/security/manager/ssl/ipcclientcerts/moz.build @@ -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") diff --git a/security/manager/ssl/ipcclientcerts/src/backend.rs b/security/manager/ssl/ipcclientcerts/src/backend.rs new file mode 100644 index 000000000000..46742f71e94f --- /dev/null +++ b/security/manager/ssl/ipcclientcerts/src/backend.rs @@ -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, + token: Vec, + id: Vec, + label: Vec, + value: Vec, + issuer: Vec, + serial_number: Vec, + subject: Vec, + slot_type: SlotType, +} + +impl Cert { + fn new(der: &[u8], slot_type: SlotType) -> Result { + 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)]) -> 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, + class: Vec, + token: Vec, + id: Vec, + private: Vec, + key_type: Vec, + modulus: Option>, + ec_params: Option>, + slot_type: SlotType, + sign: SignFunction, +} + +impl Key { + fn new( + modulus: Option<&[u8]>, + ec_params: Option<&[u8]>, + cert: &[u8], + slot_type: SlotType, + sign: SignFunction, + ) -> Result { + 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)]) -> 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, + ) -> Result { + // 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, + ) -> Result, Error> { + let mut signature = Vec::new(); + let (params_len, params) = match params { + Some(params) => ( + std::mem::size_of::(), + 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 = 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, + keys: Vec, + 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, Vec), 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)) + } +} diff --git a/security/manager/ssl/ipcclientcerts/src/lib.rs b/security/manager/ssl/ipcclientcerts/src/lib.rs new file mode 100644 index 000000000000..93eb97171e7c --- /dev/null +++ b/security/manager/ssl/ipcclientcerts/src/lib.rs @@ -0,0 +1,1251 @@ +/* -*- 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/. */ + +#![allow(non_snake_case)] + +extern crate byteorder; +extern crate env_logger; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate log; +extern crate pkcs11; +#[macro_use] +extern crate rsclientcerts; +extern crate sha2; + +use pkcs11::types::*; +use rsclientcerts::manager::{Manager, SlotType}; +use std::ffi::{c_void, CStr}; +use std::sync::Mutex; +use std::thread; + +// Helper macro to prefix log messages with the current thread ID. +macro_rules! log_with_thread_id { + ($log_level:ident, $($message:expr),*) => {{ + $log_level!("{:?} {}", thread::current().id(), format_args!($($message),*)); + }}; +} + +mod backend; + +use backend::Backend; + +type FindObjectsCallback = Option< + unsafe extern "C" fn( + typ: u8, + data_len: usize, + data: *const u8, + extra_len: usize, + extra: *const u8, + slot_type: u32, + ctx: *mut c_void, + ), +>; + +type FindObjectsFunction = extern "C" fn(callback: FindObjectsCallback, ctx: *mut c_void); + +type SignCallback = + Option; + +type SignFunction = extern "C" fn( + cert_len: usize, + cert: *const u8, + data_len: usize, + data: *const u8, + params_len: usize, + params: *const u8, + callback: SignCallback, + ctx: *mut c_void, +); + +lazy_static! { + /// The singleton `Manager` that handles state with respect to PKCS #11. Only one thread + /// may use it at a time, but there is no restriction on which threads may use it. + static ref MANAGER: Mutex>> = Mutex::new(None); +} + +// Obtaining a handle on the manager is a two-step process. First the mutex must be locked, which +// (if successful), results in a mutex guard object. We must then get a mutable refence to the +// underlying manager (if set - otherwise we return an error). This can't happen all in one macro +// without dropping a reference that needs to live long enough for this to be safe. In +// practice, this looks like: +// let mut manager_guard = try_to_get_manager_guard!(); +// let manager = manager_guard_to_manager!(manager_guard); +macro_rules! try_to_get_manager_guard { + () => { + match MANAGER.lock() { + Ok(maybe_manager) => maybe_manager, + Err(poison_error) => { + log_with_thread_id!( + error, + "previous thread panicked acquiring manager lock: {}", + poison_error + ); + return CKR_DEVICE_ERROR; + } + } + }; +} + +macro_rules! manager_guard_to_manager { + ($manager_guard:ident) => { + match $manager_guard.as_mut() { + Some(manager) => manager, + None => { + log_with_thread_id!(error, "manager expected to be set, but it is not"); + return CKR_DEVICE_ERROR; + } + } + }; +} + +/// This gets called to initialize the module. For this implementation, this consists of +/// instantiating the `Manager`. +extern "C" fn C_Initialize(pInitArgs: CK_C_INITIALIZE_ARGS_PTR) -> CK_RV { + // This will fail if this has already been called, but this isn't a problem because either way, + // logging has been initialized. + let _ = env_logger::try_init(); + + // pInitArgs.pReserved will be a c-string containing the base-16 + // stringification of the addresses of the functions to call to communicate + // with the main process. + if pInitArgs.is_null() { + log_with_thread_id!(error, "pInitArgs is null?"); + return CKR_DEVICE_ERROR; + } + let serialized_addresses_ptr = unsafe { (*pInitArgs).pReserved }; + if serialized_addresses_ptr.is_null() { + log_with_thread_id!(error, "pInitArgs.pReserved is null?"); + return CKR_DEVICE_ERROR; + } + let serialized_addresses_cstr = + unsafe { CStr::from_ptr(serialized_addresses_ptr as *mut std::os::raw::c_char) }; + let serialized_addresses = match serialized_addresses_cstr.to_str() { + Ok(serialized_addresses) => serialized_addresses, + Err(_) => { + log_with_thread_id!(error, "pInitArgs.pReserved isn't a valid utf-8 string?"); + return CKR_DEVICE_ERROR; + } + }; + let function_addresses: Vec = serialized_addresses + .split(',') + .filter_map(|serialized_address| usize::from_str_radix(serialized_address, 16).ok()) + .collect(); + if function_addresses.len() != 2 { + log_with_thread_id!( + error, + "expected 2 hex addresses, found {}", + function_addresses.len() + ); + return CKR_DEVICE_ERROR; + } + let find_objects: FindObjectsFunction = unsafe { std::mem::transmute(function_addresses[0]) }; + let sign: SignFunction = unsafe { std::mem::transmute(function_addresses[1]) }; + let mut manager_guard = try_to_get_manager_guard!(); + match manager_guard.replace(Manager::new(Backend::new(find_objects, sign))) { + Some(_unexpected_previous_manager) => { + #[cfg(target_os = "macos")] + { + log_with_thread_id!(info, "C_Initialize: manager previously set (this is expected on macOS - replacing it)"); + } + #[cfg(target_os = "windows")] + { + log_with_thread_id!(warn, "C_Initialize: manager unexpectedly previously set (bravely continuing by replacing it)"); + } + } + None => {} + } + log_with_thread_id!(debug, "C_Initialize: CKR_OK"); + CKR_OK +} + +extern "C" fn C_Finalize(_pReserved: CK_VOID_PTR) -> CK_RV { + log_with_thread_id!(debug, "C_Finalize: CKR_OK"); + CKR_OK +} + +// The specification mandates that these strings be padded with spaces to the appropriate length. +// Since the length of fixed-size arrays in rust is part of the type, the compiler enforces that +// these byte strings are of the correct length. +const MANUFACTURER_ID_BYTES: &[u8; 32] = b"Mozilla Corporation "; +const LIBRARY_DESCRIPTION_BYTES: &[u8; 32] = b"IPC Client Cert Module "; + +/// This gets called to gather some information about the module. In particular, this implementation +/// supports (portions of) cryptoki (PKCS #11) version 2.2. +extern "C" fn C_GetInfo(pInfo: CK_INFO_PTR) -> CK_RV { + if pInfo.is_null() { + log_with_thread_id!(error, "C_GetInfo: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + log_with_thread_id!(debug, "C_GetInfo: CKR_OK"); + let mut info = CK_INFO::default(); + info.cryptokiVersion.major = 2; + info.cryptokiVersion.minor = 2; + info.manufacturerID = *MANUFACTURER_ID_BYTES; + info.libraryDescription = *LIBRARY_DESCRIPTION_BYTES; + unsafe { + *pInfo = info; + } + CKR_OK +} + +/// This module has two slots. +const SLOT_COUNT: CK_ULONG = 2; +/// The slot with ID 1 supports modern mechanisms like RSA-PSS. +const SLOT_ID_MODERN: CK_SLOT_ID = 1; +/// The slot with ID 2 only supports legacy mechanisms. +const SLOT_ID_LEGACY: CK_SLOT_ID = 2; + +/// This gets called twice: once with a null `pSlotList` to get the number of slots (returned via +/// `pulCount`) and a second time to get the ID for each slot. +extern "C" fn C_GetSlotList( + _tokenPresent: CK_BBOOL, + pSlotList: CK_SLOT_ID_PTR, + pulCount: CK_ULONG_PTR, +) -> CK_RV { + if pulCount.is_null() { + log_with_thread_id!(error, "C_GetSlotList: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + if !pSlotList.is_null() { + if unsafe { *pulCount } < SLOT_COUNT { + log_with_thread_id!(error, "C_GetSlotList: CKR_BUFFER_TOO_SMALL"); + return CKR_BUFFER_TOO_SMALL; + } + unsafe { + *pSlotList = SLOT_ID_MODERN; + *pSlotList.offset(1) = SLOT_ID_LEGACY; + } + }; + unsafe { + *pulCount = SLOT_COUNT; + } + log_with_thread_id!(debug, "C_GetSlotList: CKR_OK"); + CKR_OK +} + +const SLOT_DESCRIPTION_MODERN_BYTES: &[u8; 64] = + b"IPC Client Cert Slot (Modern) "; +const SLOT_DESCRIPTION_LEGACY_BYTES: &[u8; 64] = + b"IPC Client Cert Slot (Legacy) "; + +/// This gets called to obtain information about slots. In this implementation, the tokens are +/// always present in the slots. +extern "C" fn C_GetSlotInfo(slotID: CK_SLOT_ID, pInfo: CK_SLOT_INFO_PTR) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || pInfo.is_null() { + log_with_thread_id!(error, "C_GetSlotInfo: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let description = if slotID == SLOT_ID_MODERN { + SLOT_DESCRIPTION_MODERN_BYTES + } else { + SLOT_DESCRIPTION_LEGACY_BYTES + }; + let slot_info = CK_SLOT_INFO { + slotDescription: *description, + manufacturerID: *MANUFACTURER_ID_BYTES, + flags: CKF_TOKEN_PRESENT, + hardwareVersion: CK_VERSION::default(), + firmwareVersion: CK_VERSION::default(), + }; + unsafe { + *pInfo = slot_info; + } + log_with_thread_id!(debug, "C_GetSlotInfo: CKR_OK"); + CKR_OK +} + +const TOKEN_LABEL_MODERN_BYTES: &[u8; 32] = b"IPC Client Cert Token (Modern) "; +const TOKEN_LABEL_LEGACY_BYTES: &[u8; 32] = b"IPC Client Cert Token (Legacy) "; +const TOKEN_MODEL_BYTES: &[u8; 16] = b"ipcclientcerts "; +const TOKEN_SERIAL_NUMBER_BYTES: &[u8; 16] = b"0000000000000000"; + +/// This gets called to obtain some information about tokens. This implementation has two slots, +/// so it has two tokens. This information is primarily for display purposes. +extern "C" fn C_GetTokenInfo(slotID: CK_SLOT_ID, pInfo: CK_TOKEN_INFO_PTR) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || pInfo.is_null() { + log_with_thread_id!(error, "C_GetTokenInfo: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut token_info = CK_TOKEN_INFO::default(); + let label = if slotID == SLOT_ID_MODERN { + TOKEN_LABEL_MODERN_BYTES + } else { + TOKEN_LABEL_LEGACY_BYTES + }; + token_info.label = *label; + token_info.manufacturerID = *MANUFACTURER_ID_BYTES; + token_info.model = *TOKEN_MODEL_BYTES; + token_info.serialNumber = *TOKEN_SERIAL_NUMBER_BYTES; + unsafe { + *pInfo = token_info; + } + log_with_thread_id!(debug, "C_GetTokenInfo: CKR_OK"); + CKR_OK +} + +/// This gets called to determine what mechanisms a slot supports. The modern slot supports ECDSA, +/// RSA PKCS, and RSA PSS. The legacy slot only supports RSA PKCS. +extern "C" fn C_GetMechanismList( + slotID: CK_SLOT_ID, + pMechanismList: CK_MECHANISM_TYPE_PTR, + pulCount: CK_ULONG_PTR, +) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || pulCount.is_null() { + log_with_thread_id!(error, "C_GetMechanismList: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mechanisms = if slotID == SLOT_ID_MODERN { + vec![CKM_ECDSA, CKM_RSA_PKCS, CKM_RSA_PKCS_PSS] + } else { + vec![CKM_RSA_PKCS] + }; + if !pMechanismList.is_null() { + if unsafe { *pulCount as usize } < mechanisms.len() { + log_with_thread_id!(error, "C_GetMechanismList: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + for i in 0..mechanisms.len() { + unsafe { + *pMechanismList.offset(i as isize) = mechanisms[i]; + } + } + } + unsafe { + *pulCount = mechanisms.len() as CK_ULONG; + } + log_with_thread_id!(debug, "C_GetMechanismList: CKR_OK"); + CKR_OK +} + +extern "C" fn C_GetMechanismInfo( + _slotID: CK_SLOT_ID, + _type: CK_MECHANISM_TYPE, + _pInfo: CK_MECHANISM_INFO_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GetMechanismInfo: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_InitToken( + _slotID: CK_SLOT_ID, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, + _pLabel: CK_UTF8CHAR_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_InitToken: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_InitPIN( + _hSession: CK_SESSION_HANDLE, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_InitPIN: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SetPIN( + _hSession: CK_SESSION_HANDLE, + _pOldPin: CK_UTF8CHAR_PTR, + _ulOldLen: CK_ULONG, + _pNewPin: CK_UTF8CHAR_PTR, + _ulNewLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SetPIN: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// This gets called to create a new session. This module defers to the `ManagerProxy` to implement +/// this. +extern "C" fn C_OpenSession( + slotID: CK_SLOT_ID, + _flags: CK_FLAGS, + _pApplication: CK_VOID_PTR, + _Notify: CK_NOTIFY, + phSession: CK_SESSION_HANDLE_PTR, +) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || phSession.is_null() { + log_with_thread_id!(error, "C_OpenSession: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + let slot_type = if slotID == SLOT_ID_MODERN { + SlotType::Modern + } else { + SlotType::Legacy + }; + let session_handle = match manager.open_session(slot_type) { + Ok(session_handle) => session_handle, + Err(e) => { + log_with_thread_id!(error, "C_OpenSession: open_session failed: {}", e); + return CKR_DEVICE_ERROR; + } + }; + unsafe { + *phSession = session_handle; + } + log_with_thread_id!(debug, "C_OpenSession: CKR_OK"); + CKR_OK +} + +/// This gets called to close a session. This is handled by the `ManagerProxy`. +extern "C" fn C_CloseSession(hSession: CK_SESSION_HANDLE) -> CK_RV { + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + if manager.close_session(hSession).is_err() { + log_with_thread_id!(error, "C_CloseSession: CKR_SESSION_HANDLE_INVALID"); + return CKR_SESSION_HANDLE_INVALID; + } + log_with_thread_id!(debug, "C_CloseSession: CKR_OK"); + CKR_OK +} + +/// This gets called to close all open sessions at once. This is handled by the `ManagerProxy`. +extern "C" fn C_CloseAllSessions(slotID: CK_SLOT_ID) -> CK_RV { + if slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY { + log_with_thread_id!(error, "C_CloseAllSessions: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + let slot_type = if slotID == SLOT_ID_MODERN { + SlotType::Modern + } else { + SlotType::Legacy + }; + match manager.close_all_sessions(slot_type) { + Ok(()) => { + log_with_thread_id!(debug, "C_CloseAllSessions: CKR_OK"); + CKR_OK + } + Err(e) => { + log_with_thread_id!( + error, + "C_CloseAllSessions: close_all_sessions failed: {}", + e + ); + CKR_DEVICE_ERROR + } + } +} + +extern "C" fn C_GetSessionInfo(_hSession: CK_SESSION_HANDLE, _pInfo: CK_SESSION_INFO_PTR) -> CK_RV { + log_with_thread_id!(error, "C_GetSessionInfo: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetOperationState( + _hSession: CK_SESSION_HANDLE, + _pOperationState: CK_BYTE_PTR, + _pulOperationStateLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GetOperationState: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SetOperationState( + _hSession: CK_SESSION_HANDLE, + _pOperationState: CK_BYTE_PTR, + _ulOperationStateLen: CK_ULONG, + _hEncryptionKey: CK_OBJECT_HANDLE, + _hAuthenticationKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_SetOperationState: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Login( + _hSession: CK_SESSION_HANDLE, + _userType: CK_USER_TYPE, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_Login: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// This gets called to log out and drop any authenticated resources. Because this module does not +/// hold on to authenticated resources, this module "implements" this by doing nothing and +/// returning a success result. +extern "C" fn C_Logout(_hSession: CK_SESSION_HANDLE) -> CK_RV { + log_with_thread_id!(debug, "C_Logout: CKR_OK"); + CKR_OK +} + +extern "C" fn C_CreateObject( + _hSession: CK_SESSION_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phObject: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_CreateObject: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_CopyObject( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phNewObject: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_CopyObject: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DestroyObject(_hSession: CK_SESSION_HANDLE, _hObject: CK_OBJECT_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_DestroyObject: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetObjectSize( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pulSize: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GetObjectSize: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// This gets called to obtain the values of a number of attributes of an object identified by the +/// given handle. This module implements this by requesting that the `ManagerProxy` find the object +/// and attempt to get the value of each attribute. If a specified attribute is not defined on the +/// object, the length of that attribute is set to -1 to indicate that it is not available. +/// This gets called twice: once to obtain the lengths of the attributes and again to get the +/// values. +extern "C" fn C_GetAttributeValue( + _hSession: CK_SESSION_HANDLE, + hObject: CK_OBJECT_HANDLE, + pTemplate: CK_ATTRIBUTE_PTR, + ulCount: CK_ULONG, +) -> CK_RV { + if pTemplate.is_null() { + log_with_thread_id!(error, "C_GetAttributeValue: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut attr_types = Vec::with_capacity(ulCount as usize); + for i in 0..ulCount { + let attr = unsafe { &*pTemplate.offset(i as isize) }; + attr_types.push(attr.attrType); + } + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + let values = match manager.get_attributes(hObject, attr_types) { + Ok(values) => values, + Err(e) => { + log_with_thread_id!(error, "C_GetAttributeValue: CKR_ARGUMENTS_BAD ({})", e); + return CKR_ARGUMENTS_BAD; + } + }; + if values.len() != ulCount as usize { + log_with_thread_id!( + error, + "C_GetAttributeValue: manager.get_attributes didn't return the right number of values" + ); + return CKR_DEVICE_ERROR; + } + for i in 0..ulCount as usize { + let mut attr = unsafe { &mut *pTemplate.offset(i as isize) }; + // NB: the safety of this array access depends on the length check above + if let Some(attr_value) = &values[i] { + if attr.pValue.is_null() { + attr.ulValueLen = attr_value.len() as CK_ULONG; + } else { + let ptr: *mut u8 = attr.pValue as *mut u8; + if attr_value.len() != attr.ulValueLen as usize { + log_with_thread_id!(error, "C_GetAttributeValue: incorrect attr size"); + return CKR_ARGUMENTS_BAD; + } + unsafe { + std::ptr::copy_nonoverlapping(attr_value.as_ptr(), ptr, attr_value.len()); + } + } + } else { + attr.ulValueLen = (0 - 1) as CK_ULONG; + } + } + log_with_thread_id!(debug, "C_GetAttributeValue: CKR_OK"); + CKR_OK +} + +extern "C" fn C_SetAttributeValue( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SetAttributeValue: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +fn trace_attr(prefix: &str, attr: &CK_ATTRIBUTE) { + let typ = match unsafe_packed_field_access!(attr.attrType) { + CKA_CLASS => "CKA_CLASS".to_string(), + CKA_TOKEN => "CKA_TOKEN".to_string(), + CKA_LABEL => "CKA_LABEL".to_string(), + CKA_ID => "CKA_ID".to_string(), + CKA_VALUE => "CKA_VALUE".to_string(), + CKA_ISSUER => "CKA_ISSUER".to_string(), + CKA_SERIAL_NUMBER => "CKA_SERIAL_NUMBER".to_string(), + CKA_SUBJECT => "CKA_SUBJECT".to_string(), + CKA_PRIVATE => "CKA_PRIVATE".to_string(), + CKA_KEY_TYPE => "CKA_KEY_TYPE".to_string(), + CKA_MODULUS => "CKA_MODULUS".to_string(), + CKA_EC_PARAMS => "CKA_EC_PARAMS".to_string(), + _ => format!("0x{:x}", unsafe_packed_field_access!(attr.attrType)), + }; + let value = + unsafe { std::slice::from_raw_parts(attr.pValue as *const u8, attr.ulValueLen as usize) }; + log_with_thread_id!( + trace, + "{}CK_ATTRIBUTE {{ attrType: {}, pValue: {:?}, ulValueLen: {} }}", + prefix, + typ, + value, + unsafe_packed_field_access!(attr.ulValueLen) + ); +} + +/// This gets called to initialize a search for objects matching a given list of attributes. This +/// module implements this by gathering the attributes and passing them to the `ManagerProxy` to +/// start the search. +extern "C" fn C_FindObjectsInit( + hSession: CK_SESSION_HANDLE, + pTemplate: CK_ATTRIBUTE_PTR, + ulCount: CK_ULONG, +) -> CK_RV { + if pTemplate.is_null() { + log_with_thread_id!(error, "C_FindObjectsInit: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut attrs = Vec::new(); + log_with_thread_id!(trace, "C_FindObjectsInit:"); + for i in 0..ulCount { + let attr = unsafe { &*pTemplate.offset(i as isize) }; + trace_attr(" ", &attr); + let slice = unsafe { + std::slice::from_raw_parts(attr.pValue as *const u8, attr.ulValueLen as usize) + }; + attrs.push((attr.attrType, slice.to_owned())); + } + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.start_search(hSession, attrs) { + Ok(()) => {} + Err(e) => { + log_with_thread_id!(error, "C_FindObjectsInit: CKR_ARGUMENTS_BAD: {}", e); + return CKR_ARGUMENTS_BAD; + } + } + log_with_thread_id!(debug, "C_FindObjectsInit: CKR_OK"); + CKR_OK +} + +/// This gets called after `C_FindObjectsInit` to get the results of a search. This module +/// implements this by looking up the search in the `ManagerProxy` and copying out the matching +/// object handles. +extern "C" fn C_FindObjects( + hSession: CK_SESSION_HANDLE, + phObject: CK_OBJECT_HANDLE_PTR, + ulMaxObjectCount: CK_ULONG, + pulObjectCount: CK_ULONG_PTR, +) -> CK_RV { + if phObject.is_null() || pulObjectCount.is_null() || ulMaxObjectCount == 0 { + log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + let handles = match manager.search(hSession, ulMaxObjectCount as usize) { + Ok(handles) => handles, + Err(e) => { + log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD: {}", e); + return CKR_ARGUMENTS_BAD; + } + }; + log_with_thread_id!(debug, "C_FindObjects: found handles {:?}", handles); + if handles.len() > ulMaxObjectCount as usize { + log_with_thread_id!(error, "C_FindObjects: manager returned too many handles"); + return CKR_DEVICE_ERROR; + } + unsafe { + *pulObjectCount = handles.len() as CK_ULONG; + } + for (index, handle) in handles.iter().enumerate() { + if index < ulMaxObjectCount as usize { + unsafe { + *(phObject.add(index)) = *handle; + } + } + } + log_with_thread_id!(debug, "C_FindObjects: CKR_OK"); + CKR_OK +} + +/// This gets called after `C_FindObjectsInit` and `C_FindObjects` to finish a search. The module +/// tells the `ManagerProxy` to clear the search. +extern "C" fn C_FindObjectsFinal(hSession: CK_SESSION_HANDLE) -> CK_RV { + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + // It would be an error if there were no search for this session, but we can be permissive here. + match manager.clear_search(hSession) { + Ok(()) => { + log_with_thread_id!(debug, "C_FindObjectsFinal: CKR_OK"); + CKR_OK + } + Err(e) => { + log_with_thread_id!(error, "C_FindObjectsFinal: clear_search failed: {}", e); + CKR_DEVICE_ERROR + } + } +} + +extern "C" fn C_EncryptInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_EncryptInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Encrypt( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pEncryptedData: CK_BYTE_PTR, + _pulEncryptedDataLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_Encrypt: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_EncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_EncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_EncryptFinal( + _hSession: CK_SESSION_HANDLE, + _pLastEncryptedPart: CK_BYTE_PTR, + _pulLastEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_EncryptFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Decrypt( + _hSession: CK_SESSION_HANDLE, + _pEncryptedData: CK_BYTE_PTR, + _ulEncryptedDataLen: CK_ULONG, + _pData: CK_BYTE_PTR, + _pulDataLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_Decrypt: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptFinal( + _hSession: CK_SESSION_HANDLE, + _pLastPart: CK_BYTE_PTR, + _pulLastPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestInit(_hSession: CK_SESSION_HANDLE, _pMechanism: CK_MECHANISM_PTR) -> CK_RV { + log_with_thread_id!(error, "C_DigestInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Digest( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pDigest: CK_BYTE_PTR, + _pulDigestLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_Digest: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_DigestUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestKey(_hSession: CK_SESSION_HANDLE, _hKey: CK_OBJECT_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_DigestKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestFinal( + _hSession: CK_SESSION_HANDLE, + _pDigest: CK_BYTE_PTR, + _pulDigestLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DigestFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// This gets called to set up a sign operation. The module essentially defers to the +/// `ManagerProxy`. +extern "C" fn C_SignInit( + hSession: CK_SESSION_HANDLE, + pMechanism: CK_MECHANISM_PTR, + hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + if pMechanism.is_null() { + log_with_thread_id!(error, "C_SignInit: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + // Presumably we should validate the mechanism against hKey, but the specification doesn't + // actually seem to require this. + let mechanism = unsafe { *pMechanism }; + log_with_thread_id!(debug, "C_SignInit: mechanism is {:?}", mechanism); + let mechanism_params = if mechanism.mechanism == CKM_RSA_PKCS_PSS { + if mechanism.ulParameterLen as usize != std::mem::size_of::() { + log_with_thread_id!( + error, + "C_SignInit: bad ulParameterLen for CKM_RSA_PKCS_PSS: {}", + unsafe_packed_field_access!(mechanism.ulParameterLen) + ); + return CKR_ARGUMENTS_BAD; + } + Some(unsafe { *(mechanism.pParameter as *const CK_RSA_PKCS_PSS_PARAMS) }) + } else { + None + }; + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.start_sign(hSession, hKey, mechanism_params) { + Ok(()) => {} + Err(e) => { + log_with_thread_id!(error, "C_SignInit: CKR_GENERAL_ERROR: {}", e); + return CKR_GENERAL_ERROR; + } + }; + log_with_thread_id!(debug, "C_SignInit: CKR_OK"); + CKR_OK +} + +/// NSS calls this after `C_SignInit` (there are more ways in the PKCS #11 specification to sign +/// data, but this is the only way supported by this module). The module essentially defers to the +/// `ManagerProxy` and copies out the resulting signature. +extern "C" fn C_Sign( + hSession: CK_SESSION_HANDLE, + pData: CK_BYTE_PTR, + ulDataLen: CK_ULONG, + pSignature: CK_BYTE_PTR, + pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + if pData.is_null() || pulSignatureLen.is_null() { + log_with_thread_id!(error, "C_Sign: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let data = unsafe { std::slice::from_raw_parts(pData, ulDataLen as usize) }; + if pSignature.is_null() { + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.get_signature_length(hSession, data.to_vec()) { + Ok(signature_length) => unsafe { + *pulSignatureLen = signature_length as CK_ULONG; + }, + Err(e) => { + log_with_thread_id!(error, "C_Sign: get_signature_length failed: {}", e); + return CKR_GENERAL_ERROR; + } + } + } else { + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.sign(hSession, data.to_vec()) { + Ok(signature) => { + let signature_capacity = unsafe { *pulSignatureLen } as usize; + if signature_capacity < signature.len() { + log_with_thread_id!(error, "C_Sign: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let ptr: *mut u8 = pSignature as *mut u8; + unsafe { + std::ptr::copy_nonoverlapping(signature.as_ptr(), ptr, signature.len()); + *pulSignatureLen = signature.len() as CK_ULONG; + } + } + Err(e) => { + log_with_thread_id!(error, "C_Sign: sign failed: {}", e); + return CKR_GENERAL_ERROR; + } + } + } + log_with_thread_id!(debug, "C_Sign: CKR_OK"); + CKR_OK +} + +extern "C" fn C_SignUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SignUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignFinal( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_SignFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignRecoverInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_SignRecoverInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignRecover( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pSignature: CK_BYTE_PTR, + _pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_SignRecover: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Verify( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_Verify: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyFinal( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyRecoverInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyRecoverInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyRecover( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, + _pData: CK_BYTE_PTR, + _pulDataLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyRecover: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestEncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DigestEncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptDigestUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptDigestUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignEncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_SignEncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptVerifyUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptVerifyUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GenerateKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateKeyPair( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _pPublicKeyTemplate: CK_ATTRIBUTE_PTR, + _ulPublicKeyAttributeCount: CK_ULONG, + _pPrivateKeyTemplate: CK_ATTRIBUTE_PTR, + _ulPrivateKeyAttributeCount: CK_ULONG, + _phPublicKey: CK_OBJECT_HANDLE_PTR, + _phPrivateKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GenerateKeyPair: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_WrapKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hWrappingKey: CK_OBJECT_HANDLE, + _hKey: CK_OBJECT_HANDLE, + _pWrappedKey: CK_BYTE_PTR, + _pulWrappedKeyLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_WrapKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_UnwrapKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hUnwrappingKey: CK_OBJECT_HANDLE, + _pWrappedKey: CK_BYTE_PTR, + _ulWrappedKeyLen: CK_ULONG, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulAttributeCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_UnwrapKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DeriveKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hBaseKey: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulAttributeCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DeriveKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SeedRandom( + _hSession: CK_SESSION_HANDLE, + _pSeed: CK_BYTE_PTR, + _ulSeedLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SeedRandom: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateRandom( + _hSession: CK_SESSION_HANDLE, + _RandomData: CK_BYTE_PTR, + _ulRandomLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_GenerateRandom: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetFunctionStatus(_hSession: CK_SESSION_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_GetFunctionStatus: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_CancelFunction(_hSession: CK_SESSION_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_CancelFunction: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_WaitForSlotEvent( + _flags: CK_FLAGS, + _pSlot: CK_SLOT_ID_PTR, + _pRserved: CK_VOID_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_WaitForSlotEvent: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// To be a valid PKCS #11 module, this list of functions must be supported. At least cryptoki 2.2 +/// must be supported for this module to work in NSS. +static mut FUNCTION_LIST: CK_FUNCTION_LIST = CK_FUNCTION_LIST { + version: CK_VERSION { major: 2, minor: 2 }, + C_Initialize: Some(C_Initialize), + C_Finalize: Some(C_Finalize), + C_GetInfo: Some(C_GetInfo), + C_GetFunctionList: None, + C_GetSlotList: Some(C_GetSlotList), + C_GetSlotInfo: Some(C_GetSlotInfo), + C_GetTokenInfo: Some(C_GetTokenInfo), + C_GetMechanismList: Some(C_GetMechanismList), + C_GetMechanismInfo: Some(C_GetMechanismInfo), + C_InitToken: Some(C_InitToken), + C_InitPIN: Some(C_InitPIN), + C_SetPIN: Some(C_SetPIN), + C_OpenSession: Some(C_OpenSession), + C_CloseSession: Some(C_CloseSession), + C_CloseAllSessions: Some(C_CloseAllSessions), + C_GetSessionInfo: Some(C_GetSessionInfo), + C_GetOperationState: Some(C_GetOperationState), + C_SetOperationState: Some(C_SetOperationState), + C_Login: Some(C_Login), + C_Logout: Some(C_Logout), + C_CreateObject: Some(C_CreateObject), + C_CopyObject: Some(C_CopyObject), + C_DestroyObject: Some(C_DestroyObject), + C_GetObjectSize: Some(C_GetObjectSize), + C_GetAttributeValue: Some(C_GetAttributeValue), + C_SetAttributeValue: Some(C_SetAttributeValue), + C_FindObjectsInit: Some(C_FindObjectsInit), + C_FindObjects: Some(C_FindObjects), + C_FindObjectsFinal: Some(C_FindObjectsFinal), + C_EncryptInit: Some(C_EncryptInit), + C_Encrypt: Some(C_Encrypt), + C_EncryptUpdate: Some(C_EncryptUpdate), + C_EncryptFinal: Some(C_EncryptFinal), + C_DecryptInit: Some(C_DecryptInit), + C_Decrypt: Some(C_Decrypt), + C_DecryptUpdate: Some(C_DecryptUpdate), + C_DecryptFinal: Some(C_DecryptFinal), + C_DigestInit: Some(C_DigestInit), + C_Digest: Some(C_Digest), + C_DigestUpdate: Some(C_DigestUpdate), + C_DigestKey: Some(C_DigestKey), + C_DigestFinal: Some(C_DigestFinal), + C_SignInit: Some(C_SignInit), + C_Sign: Some(C_Sign), + C_SignUpdate: Some(C_SignUpdate), + C_SignFinal: Some(C_SignFinal), + C_SignRecoverInit: Some(C_SignRecoverInit), + C_SignRecover: Some(C_SignRecover), + C_VerifyInit: Some(C_VerifyInit), + C_Verify: Some(C_Verify), + C_VerifyUpdate: Some(C_VerifyUpdate), + C_VerifyFinal: Some(C_VerifyFinal), + C_VerifyRecoverInit: Some(C_VerifyRecoverInit), + C_VerifyRecover: Some(C_VerifyRecover), + C_DigestEncryptUpdate: Some(C_DigestEncryptUpdate), + C_DecryptDigestUpdate: Some(C_DecryptDigestUpdate), + C_SignEncryptUpdate: Some(C_SignEncryptUpdate), + C_DecryptVerifyUpdate: Some(C_DecryptVerifyUpdate), + C_GenerateKey: Some(C_GenerateKey), + C_GenerateKeyPair: Some(C_GenerateKeyPair), + C_WrapKey: Some(C_WrapKey), + C_UnwrapKey: Some(C_UnwrapKey), + C_DeriveKey: Some(C_DeriveKey), + C_SeedRandom: Some(C_SeedRandom), + C_GenerateRandom: Some(C_GenerateRandom), + C_GetFunctionStatus: Some(C_GetFunctionStatus), + C_CancelFunction: Some(C_CancelFunction), + C_WaitForSlotEvent: Some(C_WaitForSlotEvent), +}; + +/// This is the only function this module exposes. The C stub calls it when NSS +/// calls its exposed C_GetFunctionList function to obtain the list of functions +/// comprising this module. +#[no_mangle] +pub extern "C" fn IPCCC_GetFunctionList(ppFunctionList: CK_FUNCTION_LIST_PTR_PTR) -> CK_RV { + if ppFunctionList.is_null() { + return CKR_ARGUMENTS_BAD; + } + unsafe { + *ppFunctionList = &mut FUNCTION_LIST; + } + CKR_OK +} + +#[cfg_attr(target_os = "macos", link(name = "Security", kind = "framework"))] +extern "C" {} diff --git a/security/manager/ssl/moz.build b/security/manager/ssl/moz.build index cefed1a54144..07804ac59626 100644 --- a/security/manager/ssl/moz.build +++ b/security/manager/ssl/moz.build @@ -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 += [ diff --git a/security/manager/ssl/nsNSSCallbacks.cpp b/security/manager/ssl/nsNSSCallbacks.cpp index f7d834c4816d..8058426998d0 100644 --- a/security/manager/ssl/nsNSSCallbacks.cpp +++ b/security/manager/ssl/nsNSSCallbacks.cpp @@ -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" diff --git a/security/manager/ssl/nsNSSComponent.cpp b/security/manager/ssl/nsNSSComponent.cpp index ec98414777c0..0efd03fb0ade 100644 --- a/security/manager/ssl/nsNSSComponent.cpp +++ b/security/manager/ssl/nsNSSComponent.cpp @@ -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& 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 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 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 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 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; diff --git a/security/manager/ssl/nsNSSIOLayer.cpp b/security/manager/ssl/nsNSSIOLayer.cpp index 81e06b269826..ab80a8e45c3e 100644 --- a/security/manager/ssl/nsNSSIOLayer.cpp +++ b/security/manager/ssl/nsNSSIOLayer.cpp @@ -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> mCollectedCANames; nsTArray> mEnterpriseCertificates; UniqueCERTCertificate mSelectedCertificate; - UniqueSECKEYPrivateKey mSelectedKey; }; class RemoteClientAuthDataRunnable : public ClientAuthDataRunnable { @@ -1946,30 +1945,42 @@ SECStatus nsNSS_SSLGetClientAuthData(void* arg, PRFileDesc* socket, nsTArray> 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>&& collectedCANames, UniqueCERTCertificate& outCert, - UniqueSECKEYPrivateKey& outKey, UniqueCERTCertList& outBuiltChain) { // XXX: This should be done asynchronously; see bug 696976 RefPtr 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(cert.data().Elements()), + static_cast(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 GetIPCClientCertsActor() { + PBackgroundChild* backgroundActor = + BackgroundChild::GetOrCreateForSocketParentBridgeForCurrentThread(); + if (!backgroundActor) { + return nullptr; + } + RefPtr actor = + SingleManagedOrNull(backgroundActor->ManagedPIPCClientCertsChild()); + if (!actor) { + actor = backgroundActor->SendPIPCClientCertsConstructor( + new IPCClientCertsChild()); + if (!actor) { + return nullptr; + } + } + return actor.forget().downcast(); +} + +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 ipcClientCertsActor(GetIPCClientCertsActor()); + if (!ipcClientCertsActor) { + return; + } + nsTArray 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 ipcClientCertsActor(GetIPCClientCertsActor()); + if (!ipcClientCertsActor) { + return; + } + ByteArray certBytes(nsTArray(cert, cert_len)); + ByteArray dataBytes(nsTArray(data, data_len)); + ByteArray paramsBytes(nsTArray(params, params_len)); + ByteArray signature; + if (!ipcClientCertsActor->SendSign(certBytes, dataBytes, paramsBytes, + &signature)) { + return; + } + cb(signature.data().Length(), signature.data().Elements(), ctx); +} +} // extern "C" diff --git a/security/manager/ssl/nsNSSIOLayer.h b/security/manager/ssl/nsNSSIOLayer.h index ed36348cb52a..af5ca0a3c3e1 100644 --- a/security/manager/ssl/nsNSSIOLayer.h +++ b/security/manager/ssl/nsNSSIOLayer.h @@ -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>&& collectedCANames, mozilla::UniqueCERTCertificate& outCert, - mozilla::UniqueSECKEYPrivateKey& outKey, mozilla::UniqueCERTCertList& outBuiltChain); #endif // nsNSSIOLayer_h diff --git a/security/manager/ssl/osclientcerts/src/lib.rs b/security/manager/ssl/osclientcerts/src/lib.rs index add9880c05e2..a1986f66bb96 100644 --- a/security/manager/ssl/osclientcerts/src/lib.rs +++ b/security/manager/ssl/osclientcerts/src/lib.rs @@ -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),*)); }; } diff --git a/security/manager/ssl/rsclientcerts/src/manager.rs b/security/manager/ssl/rsclientcerts/src/manager.rs index 3ad026fec4f0..b8b47dcc9b7b 100644 --- a/security/manager/ssl/rsclientcerts/src/manager.rs +++ b/security/manager/ssl/rsclientcerts/src/manager.rs @@ -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 Object { fn get_signature_length( &mut self, - data: &[u8], + data: Vec, params: &Option, ) -> Result { 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, params: &Option, ) -> Result, 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 Object { /// 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 { +pub struct Manager { /// A map of session to session type (modern or legacy). Sessions can be created (opened) and /// later closed. sessions: BTreeMap, @@ -546,7 +546,7 @@ impl Manager { pub fn start_search( &mut self, session: CK_SESSION_HANDLE, - attrs: &[(CK_ATTRIBUTE_TYPE, Vec)], + attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec)>, ) -> Result<(), Error> { let slot_type = match self.sessions.get(&session) { Some(slot_type) => *slot_type, @@ -554,7 +554,7 @@ impl Manager { }; // 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 Manager { // 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 Manager { pub fn get_signature_length( &mut self, session: CK_SESSION_HANDLE, - data: &[u8], + data: Vec, ) -> Result { let (key_handle, params) = match self.signs.get(&session) { Some((key_handle, params)) => (key_handle, params), @@ -664,7 +664,7 @@ impl Manager { key.get_signature_length(data, params) } - pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: &[u8]) -> Result, Error> { + pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec) -> Result, 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) {