diff --git a/Cargo.lock b/Cargo.lock index ca3c9268985caf8b15bc14d57298328ca0b0dafd..e5b38b48b73e7f95a6a61ac59de85fb84ad12641 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 e0f622a411ed76146780a89d21df3c9c31e6242e..2ea112cc1a02ed8bcf16176e7d40a93d19cc427f 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 697cd85d2775f71ea9c76ff2084376874d9212d3..334adaa2c3ed1e5cc38977ecedb23bfb4813db09 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 ffd510fe6543ad31205d50e3c65b2349d6ebe13c..6463c5964870def34ad9e064c04e76fb54a9a789 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 4d7a4810a7f27167c2c2c23dd3bfdbb7709fd87c..0d2ff9dff23213b8599e597946ef164f31a3223c 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 25e22d1c974427350f82c71324de3e69b64a60fc..e4ecee32101300be6e4289eb3f8a27764f697ea5 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<uint8_t>* aKeyDer, nsTArray<uint8_t>* aCertDer) { - ScopedSECItem derPki( - psm::WrapPrivateKeyInfoWithEmptyPassword(private_key_.get())); + ScopedSECItem derPki(WrapPrivateKeyInfoWithEmptyPassword(private_key_.get())); if (!derPki) { return NS_ERROR_FAILURE; } @@ -50,7 +144,7 @@ RefPtr<DtlsIdentity> DtlsIdentity::Deserialize( static_cast<unsigned int>(aKeyDer.Length())}; SECKEYPrivateKey* privateKey; - if (psm::UnwrapPrivateKeyInfoWithEmptyPassword(&derPKI, cert, &privateKey) != + if (UnwrapPrivateKeyInfoWithEmptyPassword(&derPKI, cert, &privateKey) != SECSuccess) { MOZ_ASSERT(false); return nullptr; diff --git a/ipc/glue/BackgroundChildImpl.cpp b/ipc/glue/BackgroundChildImpl.cpp index e10c90b5300ef1ebae3ea904001fe05355ed1261..54728c6200e72522ff38c1a55e0c369c81ebaa97 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 32e125f8d8dcb01c4fd6abd587f19fbae16e449f..d6f189c03ef8660472123650e6437afafb1054df 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<psm::PIPCClientCertsParent> +BackgroundParentImpl::AllocPIPCClientCertsParent() { + // This should only be called in the parent process with the socket process + // as the child process, not any content processes, hence the check that the + // child ID be 0. + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(mozilla::ipc::BackgroundParent::GetChildID(this) == 0); + if (!XRE_IsParentProcess() || + mozilla::ipc::BackgroundParent::GetChildID(this) != 0) { + return nullptr; + } + RefPtr<psm::IPCClientCertsParent> result = new psm::IPCClientCertsParent(); + return result.forget(); +} + bool BackgroundParentImpl::DeallocPMessagePortParent( PMessagePortParent* aActor) { AssertIsInMainOrSocketProcess(); diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h index 4923deffc68fe23c9579a2a42900e9f9512db6f4..5865d241e75f2eddd38c49246633844e429bce1e 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<PIPCClientCertsParent> AllocPIPCClientCertsParent() override; + bool DeallocPMessagePortParent(PMessagePortParent* aActor) override; mozilla::ipc::IPCResult RecvMessagePortForceClose( diff --git a/ipc/glue/PBackground.ipdl b/ipc/glue/PBackground.ipdl index 019d6f0d9237af546e74d4c5c8087d3cac268677..03dbbb98a04b22c11e320c195441ba2d8e314c1a 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 0ba1651a7cc51d23da5de21ffbcf37cd60c67f64..60f909afef27b9a4e8cf8cec725f1b1b9c017d96 100644 --- a/ipc/ipdl/sync-messages.ini +++ b/ipc/ipdl/sync-messages.ini @@ -1070,6 +1070,10 @@ description = Reflection is cold code, but synchronous by spec. # - [PSocketProcess::GetTLSClientCert] description = Synchronously get client certificate and key from parent process. Once bug 696976 has been fixed, this can be removed. +[PIPCClientCerts::FindObjects] +description = Finds certificates and private keys in the parent process. As this is called from PKCS#11, there is no way to make this asynchronous. +[PIPCClientCerts::Sign] +description = Performs a signature on given data with a key corresponding to the given identifier. This is called from PKCS#11, so there is no way to make this asynchronous. ############################################################# # AVOID ADDING NEW MESSAGES TO THIS FILE # diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 95eaa5107f2f52d4c171d35e508c56d8921ad657..3149f3ac9c6bf03b16fd8bf972f8f6cf618ccb9e 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -66,6 +66,8 @@ @BINPATH@/@DLL_PREFIX@softokn3.chk #endif +@BINPATH@/@DLL_PREFIX@ipcclientcerts@DLL_SUFFIX@ + #ifndef MOZ_FOLD_LIBS @BINPATH@/@DLL_PREFIX@mozsqlite3@DLL_SUFFIX@ #endif diff --git a/netwerk/ipc/PSocketProcess.ipdl b/netwerk/ipc/PSocketProcess.ipdl index 11988f4976e9fcb62b50abec84ecaae75b087f02..2a332136c2565fbb612999630ceb8e3c77d06386 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 e6ea9ea56b95a2d5fac42ea37e35de82086e6e70..ee4a736372410b728e7bdbf1888381066d9b8793 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<nsIDNSService> dns = do_GetService("@mozilla.org/network/dns-service;1", &rv); - return NS_SUCCEEDED(rv); + if (NS_FAILED(rv)) { + return false; + } + + if (!EnsureNSSInitializedChromeOrContent()) { + return false; + } + + return true; } void SocketProcessChild::ActorDestroy(ActorDestroyReason aWhy) { @@ -214,6 +222,7 @@ mozilla::ipc::IPCResult SocketProcessChild::RecvInit( if (aAttributes.mInitSandbox()) { Unused << RecvInitLinuxSandbox(aAttributes.mSandboxBroker()); } + return IPC_OK(); } @@ -472,9 +481,7 @@ SocketProcessChild::GetAndRemoveDataBridge(uint64_t aChannelId) { } mozilla::ipc::IPCResult SocketProcessChild::RecvClearSessionCache() { - if (EnsureNSSInitializedChromeOrContent()) { - nsNSSComponent::DoClearSSLExternalAndInternalSessionCache(); - } + nsNSSComponent::DoClearSSLExternalAndInternalSessionCache(); return IPC_OK(); } diff --git a/netwerk/ipc/SocketProcessImpl.cpp b/netwerk/ipc/SocketProcessImpl.cpp index 6f55eb85845f9c2d59ee4252765a9ff77f3b9d2b..6a0739622f1697b411bb6b1f74f25754060338fc 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 92f1fb2fc752aef8c99aa2f0d1b57489d4feb871..6a69209be231acb611dfe0dbceb2e94682e321a8 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<ByteArray>&& aClientCert, nsTArray<ByteArray>&& aCollectedCANames, - bool* aSucceeded, ByteArray* aOutCert, ByteArray* aOutKey, - nsTArray<ByteArray>* aBuiltChain) { + bool* aSucceeded, ByteArray* aOutCert, nsTArray<ByteArray>* aBuiltChain) { *aSucceeded = false; SECItem serverCertItem = { @@ -342,16 +342,15 @@ mozilla::ipc::IPCResult SocketProcessParent::RecvGetTLSClientCert( } UniqueCERTCertificate cert; - UniqueSECKEYPrivateKey key; UniqueCERTCertList builtChain; SECStatus status = DoGetClientAuthData(std::move(info), serverCert, - std::move(collectedCANames), cert, key, builtChain); + std::move(collectedCANames), cert, builtChain); if (status != SECSuccess) { return IPC_OK(); } - SerializeClientCertAndKey(cert, key, *aOutCert, *aOutKey); + aOutCert->data().AppendElements(cert->derCert.data, cert->derCert.len); if (builtChain) { for (CERTCertListNode* n = CERT_LIST_HEAD(builtChain); diff --git a/netwerk/ipc/SocketProcessParent.h b/netwerk/ipc/SocketProcessParent.h index c51124d991e8a75bc16f6e76ace0b260e12e276a..3628a0ee449d313a47fb892b3531d6b692535521 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<ByteArray>&& aClientCert, nsTArray<ByteArray>&& aCollectedCANames, - bool* aSucceeded, ByteArray* aOutCert, ByteArray* aOutKey, - nsTArray<ByteArray>* aBuiltChain); + bool* aSucceeded, ByteArray* aOutCert, nsTArray<ByteArray>* aBuiltChain); + + mozilla::ipc::IPCResult RecvFindIPCClientCertObjects( + nsTArray<IPCClientCertObject>* aObjects); + mozilla::ipc::IPCResult RecvIPCClientCertSign(ByteArray aCert, + ByteArray aData, + ByteArray aParams, + ByteArray* aSignature); already_AddRefed<PProxyConfigLookupParent> AllocPProxyConfigLookupParent( nsIURI* aURI, const uint32_t& aProxyResolveFlags); diff --git a/security/certverifier/NSSCertDBTrustDomain.cpp b/security/certverifier/NSSCertDBTrustDomain.cpp index a26121054c5a7f3b201a9a47f11357dc75521d02..9f16736e817aafc93db3dc9db7613c91b5d75a27 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<char*>(pkcs11ModuleSpec.get()), nullptr, false)); @@ -1643,6 +1653,19 @@ bool LoadUserModuleAt(const char* moduleName, const char* libraryName, return true; } +const char* kIPCClientCertsModuleName = "IPC Client Cert Module"; + +bool LoadIPCClientCertsModule(const nsCString& dir) { + // The IPC client certs module needs to be able to call back into gecko to be + // able to communicate with the parent process over IPC. This is achieved by + // serializing the addresses of the relevant functions and passing them as an + // extra string parameter that will be available when C_Initialize is called + // on IPC client certs. + nsPrintfCString addrs("%p,%p", DoFindObjects, DoSign); + return LoadUserModuleAt(kIPCClientCertsModuleName, "ipcclientcerts", dir, + addrs.get()); +} + const char* kOSClientCertsModuleName = "OS Client Cert Module"; bool LoadOSClientCertsModule(const nsCString& dir) { @@ -1652,7 +1675,8 @@ bool LoadOSClientCertsModule(const nsCString& dir) { return false; } #endif - return LoadUserModuleAt(kOSClientCertsModuleName, "osclientcerts", dir); + return LoadUserModuleAt(kOSClientCertsModuleName, "osclientcerts", dir, + nullptr); } bool LoadLoadableRoots(const nsCString& dir) { @@ -1663,7 +1687,7 @@ bool LoadLoadableRoots(const nsCString& dir) { // "Root Certs" module allows us to load the correct one. See bug 1406396. int unusedModType; Unused << SECMOD_DeleteModule("Root Certs", &unusedModType); - return LoadUserModuleAt(kRootModuleName, "nssckbi", dir); + return LoadUserModuleAt(kRootModuleName, "nssckbi", dir, nullptr); } nsresult DefaultServerNicknameForCert(const CERTCertificate* cert, diff --git a/security/certverifier/NSSCertDBTrustDomain.h b/security/certverifier/NSSCertDBTrustDomain.h index fa24b85f2e249404753f51beed6d268496f1835d..8e354fe0184fd3fb9970b92dab7cf26bdf9a9e43 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 0000000000000000000000000000000000000000..bea0e27dffc71556f9b7ff97e832a2db16261d18 --- /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 0000000000000000000000000000000000000000..17020fce2ed3b504d1e3ddb0a1f0426078108b68 --- /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 0000000000000000000000000000000000000000..8058dbdd8365fb87dbcb5fcc3417efb770685bd8 --- /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<IPCClientCertObject>* aObjects) { + UniqueCERTCertList certList(psm::FindClientCertificatesWithPrivateKeys()); + if (!certList) { + return IPC_OK(); + } + CERTCertListNode* n = CERT_LIST_HEAD(certList); + while (!CERT_LIST_END(n, certList)) { + nsTArray<uint8_t> certDER(n->cert->derCert.data, n->cert->derCert.len); + uint32_t slotType; + UniqueSECKEYPublicKey pubkey(CERT_ExtractPublicKey(n->cert)); + if (!pubkey) { + return IPC_OK(); + } + switch (SECKEY_GetPublicKeyType(pubkey.get())) { + case rsaKey: + case rsaPssKey: { + slotType = PK11_DoesMechanism(n->cert->slot, CKM_RSA_PKCS_PSS) + ? kIPCClientCertsSlotTypeModern + : kIPCClientCertsSlotTypeLegacy; + nsTArray<uint8_t> modulus(pubkey->u.rsa.modulus.data, + pubkey->u.rsa.modulus.len); + RSAKey rsakey(modulus, certDER, slotType); + aObjects->AppendElement(std::move(rsakey)); + break; + } + case ecKey: { + slotType = kIPCClientCertsSlotTypeModern; + nsTArray<uint8_t> params(pubkey->u.ec.DEREncodedParams.data, + pubkey->u.ec.DEREncodedParams.len); + ECKey eckey(params, certDER, slotType); + aObjects->AppendElement(std::move(eckey)); + break; + } + default: + n = CERT_LIST_NEXT(n); + continue; + } + Certificate cert(certDER, slotType); + aObjects->AppendElement(std::move(cert)); + + n = CERT_LIST_NEXT(n); + } + return IPC_OK(); +} + +// When the IPC client certs module needs to sign data using a key managed by +// the parent process, it will cause this function to be called in the parent +// process. The parent process needs to find the key corresponding to the given +// certificate and sign the given data with the given parameters. +mozilla::ipc::IPCResult IPCClientCertsParent::RecvSign(ByteArray aCert, + ByteArray aData, + ByteArray aParams, + ByteArray* aSignature) { + SECItem certItem = {siBuffer, const_cast<uint8_t*>(aCert.data().Elements()), + static_cast<unsigned int>(aCert.data().Length())}; + aSignature->data().Clear(); + + UniqueCERTCertificate cert(CERT_NewTempCertificate( + CERT_GetDefaultCertDB(), &certItem, nullptr, false, true)); + if (!cert) { + return IPC_OK(); + } + UniqueSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert.get(), nullptr)); + if (!key) { + return IPC_OK(); + } + SECItem params = {siBuffer, aParams.data().Elements(), + static_cast<unsigned int>(aParams.data().Length())}; + SECItem* paramsPtr = aParams.data().Length() > 0 ? ¶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<unsigned int>(aData.data().Length())}; + SECStatus srv = + PK11_SignWithMechanism(key.get(), mechanism, paramsPtr, sig.get(), &hash); + if (srv != SECSuccess) { + return IPC_OK(); + } + aSignature->data().AppendElements(sig->data, sig->len); + return IPC_OK(); +} + +} // namespace mozilla::psm diff --git a/security/manager/ssl/IPCClientCertsParent.h b/security/manager/ssl/IPCClientCertsParent.h new file mode 100644 index 0000000000000000000000000000000000000000..09ad589aca053ec7bc525f045b45341d3d5b5d46 --- /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<IPCClientCertObject>* 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 0000000000000000000000000000000000000000..6e9d7bad61e57d8f9f87fed5b705b6e9a58c1ce4 --- /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 6e0ed62302b36852b858e82c894fbafd1a8ba968..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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<uint8_t*>(aSerializedCert.data().Elements()), - static_cast<unsigned int>(aSerializedCert.data().Length())}; - - UniqueCERTCertificate cert(CERT_NewTempCertificate( - CERT_GetDefaultCertDB(), &item, nullptr, false, true)); - - if (!cert) { - return; - } - - SECItem derPKI = {siBuffer, - const_cast<uint8_t*>(aSerializedKey.data().Elements()), - static_cast<unsigned int>(aSerializedKey.data().Length())}; - - SECKEYPrivateKey* privateKey; - if (UnwrapPrivateKeyInfoWithEmptyPassword(&derPKI, cert, &privateKey) != - SECSuccess) { - MOZ_ASSERT(false); - return; - } - - aOutCert = std::move(cert); - aOutKey.reset(privateKey); -} - -} // namespace psm -} // namespace mozilla diff --git a/security/manager/ssl/PSMIPCCommon.h b/security/manager/ssl/PSMIPCCommon.h index 7d5455dd4b3f87d25ae1c5293829d91da4d91bcb..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 73e8ca04f525ba6129ee1dc0eb8a5148306e6b7e..54bce317c2039706d475f5e444238cf5b06b6502 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 0000000000000000000000000000000000000000..8ef5bf5839d79b24bc8bb1a8c70211d2ecac4d7f --- /dev/null +++ b/security/manager/ssl/ipcclientcerts/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ipcclientcerts-static" +version = "0.1.0" +authors = ["Dana Keeler <dkeeler@mozilla.com>"] +edition = "2018" + +[dependencies] +byteorder = "1.3" +env_logger = {version = "0.8", default-features = false } # disable `regex` to reduce code size +lazy_static = "1" +log = "0.4" +pkcs11 = "0.4" +rsclientcerts = { path = "../rsclientcerts" } +sha2 = "0.8" + +[lib] +crate-type = ["staticlib"] 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 0000000000000000000000000000000000000000..562ecea21d43acd090e6c3f8f098125bd724206e --- /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 0000000000000000000000000000000000000000..afc265aef942c29e145862eac9787092e9d7ae88 --- /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 0000000000000000000000000000000000000000..e50f8675741d2171eaab798fb65532f51a15a277 --- /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 0000000000000000000000000000000000000000..1d24bdea31dab3894e013ee26a4c9cdf88219c4d --- /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 0000000000000000000000000000000000000000..46742f71e94f836a1e6d217fed5468f367ac600d --- /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<u8>, + token: Vec<u8>, + id: Vec<u8>, + label: Vec<u8>, + value: Vec<u8>, + issuer: Vec<u8>, + serial_number: Vec<u8>, + subject: Vec<u8>, + slot_type: SlotType, +} + +impl Cert { + fn new(der: &[u8], slot_type: SlotType) -> Result<Cert, Error> { + let (serial_number, issuer, subject) = read_encoded_certificate_identifiers(der)?; + let id = Sha256::digest(der).to_vec(); + Ok(Cert { + class: serialize_uint(CKO_CERTIFICATE)?, + token: serialize_uint(CK_TRUE)?, + id, + label: b"IPC certificate".to_vec(), + value: der.to_vec(), + issuer, + serial_number, + subject, + slot_type, + }) + } + + fn class(&self) -> &[u8] { + &self.class + } + + fn token(&self) -> &[u8] { + &self.token + } + + fn id(&self) -> &[u8] { + &self.id + } + + fn label(&self) -> &[u8] { + &self.label + } + + fn value(&self) -> &[u8] { + &self.value + } + + fn issuer(&self) -> &[u8] { + &self.issuer + } + + fn serial_number(&self) -> &[u8] { + &self.serial_number + } + + fn subject(&self) -> &[u8] { + &self.subject + } +} + +impl CryptokiObject for Cert { + fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool { + if self.slot_type != slot_type { + return false; + } + for (attr_type, attr_value) in attrs { + let comparison = match *attr_type { + CKA_CLASS => self.class(), + CKA_TOKEN => self.token(), + CKA_LABEL => self.label(), + CKA_ID => self.id(), + CKA_VALUE => self.value(), + CKA_ISSUER => self.issuer(), + CKA_SERIAL_NUMBER => self.serial_number(), + CKA_SUBJECT => self.subject(), + _ => return false, + }; + if attr_value.as_slice() != comparison { + return false; + } + } + true + } + + fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> { + let result = match attribute { + CKA_CLASS => self.class(), + CKA_TOKEN => self.token(), + CKA_LABEL => self.label(), + CKA_ID => self.id(), + CKA_VALUE => self.value(), + CKA_ISSUER => self.issuer(), + CKA_SERIAL_NUMBER => self.serial_number(), + CKA_SUBJECT => self.subject(), + _ => return None, + }; + Some(result) + } +} + +pub struct Key { + cert: Vec<u8>, + class: Vec<u8>, + token: Vec<u8>, + id: Vec<u8>, + private: Vec<u8>, + key_type: Vec<u8>, + modulus: Option<Vec<u8>>, + ec_params: Option<Vec<u8>>, + slot_type: SlotType, + sign: SignFunction, +} + +impl Key { + fn new( + modulus: Option<&[u8]>, + ec_params: Option<&[u8]>, + cert: &[u8], + slot_type: SlotType, + sign: SignFunction, + ) -> Result<Key, Error> { + let id = Sha256::digest(cert).to_vec(); + let key_type = if modulus.is_some() { CKK_RSA } else { CKK_EC }; + Ok(Key { + cert: cert.to_vec(), + class: serialize_uint(CKO_PRIVATE_KEY)?, + token: serialize_uint(CK_TRUE)?, + id, + private: serialize_uint(CK_TRUE)?, + key_type: serialize_uint(key_type)?, + modulus: modulus.map(|b| b.to_vec()), + ec_params: ec_params.map(|b| b.to_vec()), + slot_type, + sign, + }) + } + + fn class(&self) -> &[u8] { + &self.class + } + + fn token(&self) -> &[u8] { + &self.token + } + + pub fn id(&self) -> &[u8] { + &self.id + } + + fn private(&self) -> &[u8] { + &self.private + } + + fn key_type(&self) -> &[u8] { + &self.key_type + } + + fn modulus(&self) -> Option<&[u8]> { + match &self.modulus { + Some(modulus) => Some(modulus.as_slice()), + None => None, + } + } + + fn ec_params(&self) -> Option<&[u8]> { + match &self.ec_params { + Some(ec_params) => Some(ec_params.as_slice()), + None => None, + } + } +} + +impl CryptokiObject for Key { + fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool { + if self.slot_type != slot_type { + return false; + } + for (attr_type, attr_value) in attrs { + let comparison = match *attr_type { + CKA_CLASS => self.class(), + CKA_TOKEN => self.token(), + CKA_ID => self.id(), + CKA_PRIVATE => self.private(), + CKA_KEY_TYPE => self.key_type(), + CKA_MODULUS => { + if let Some(modulus) = self.modulus() { + modulus + } else { + return false; + } + } + CKA_EC_PARAMS => { + if let Some(ec_params) = self.ec_params() { + ec_params + } else { + return false; + } + } + _ => return false, + }; + if attr_value.as_slice() != comparison { + return false; + } + } + true + } + + fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> { + match attribute { + CKA_CLASS => Some(self.class()), + CKA_TOKEN => Some(self.token()), + CKA_ID => Some(self.id()), + CKA_PRIVATE => Some(self.private()), + CKA_KEY_TYPE => Some(self.key_type()), + CKA_MODULUS => self.modulus(), + CKA_EC_PARAMS => self.ec_params(), + _ => None, + } + } +} + +impl Sign for Key { + fn get_signature_length( + &mut self, + data: &[u8], + params: &Option<CK_RSA_PKCS_PSS_PARAMS>, + ) -> Result<usize, Error> { + // Unfortunately we don't have a way of getting the length of a signature without creating + // one. + let dummy_signature_bytes = self.sign(data, params)?; + Ok(dummy_signature_bytes.len()) + } + + fn sign( + &mut self, + data: &[u8], + params: &Option<CK_RSA_PKCS_PSS_PARAMS>, + ) -> Result<Vec<u8>, Error> { + let mut signature = Vec::new(); + let (params_len, params) = match params { + Some(params) => ( + std::mem::size_of::<CK_RSA_PKCS_PSS_PARAMS>(), + params as *const _ as *const u8, + ), + None => (0, std::ptr::null()), + }; + (self.sign)( + self.cert.len(), + self.cert.as_ptr(), + data.len(), + data.as_ptr(), + params_len, + params, + Some(sign_callback), + &mut signature as *mut _ as *mut c_void, + ); + if signature.len() > 0 { + Ok(signature) + } else { + Err(error_here!(ErrorType::LibraryFailure)) + } + } +} + +unsafe extern "C" fn sign_callback(data_len: usize, data: *const u8, ctx: *mut c_void) { + let signature: &mut Vec<u8> = std::mem::transmute(ctx); + signature.clear(); + signature.extend_from_slice(std::slice::from_raw_parts(data, data_len)); +} + +unsafe extern "C" fn find_objects_callback( + typ: u8, + data_len: usize, + data: *const u8, + extra_len: usize, + extra: *const u8, + slot_type: u32, + ctx: *mut c_void, +) { + let data = std::slice::from_raw_parts(data, data_len); + let extra = std::slice::from_raw_parts(extra, extra_len); + let slot_type = match slot_type { + 1 => SlotType::Modern, + 2 => SlotType::Legacy, + _ => return, + }; + let find_objects_context: &mut FindObjectsContext = std::mem::transmute(ctx); + match typ { + 1 => match Cert::new(data, slot_type) { + Ok(cert) => find_objects_context.certs.push(cert), + Err(e) => { + log_with_thread_id!(error, "find_objects_callback: couldn't create Cert: {}", e) + } + }, + 2 => match Key::new( + Some(data), + None, + extra, + slot_type, + find_objects_context.sign, + ) { + Ok(key) => find_objects_context.keys.push(key), + Err(e) => { + log_with_thread_id!(error, "find_objects_callback: couldn't create Key: {}", e) + } + }, + 3 => match Key::new( + None, + Some(data), + extra, + slot_type, + find_objects_context.sign, + ) { + Ok(key) => find_objects_context.keys.push(key), + Err(e) => { + log_with_thread_id!(error, "find_objects_callback: couldn't create Key: {}", e) + } + }, + _ => log_with_thread_id!(error, "find_objects_callback: unknown type {}", typ), + } +} + +struct FindObjectsContext { + certs: Vec<Cert>, + keys: Vec<Key>, + sign: SignFunction, +} + +impl FindObjectsContext { + fn new(sign: SignFunction) -> FindObjectsContext { + FindObjectsContext { + certs: Vec::new(), + keys: Vec::new(), + sign, + } + } +} + +pub struct Backend { + find_objects: FindObjectsFunction, + sign: SignFunction, +} + +impl Backend { + pub fn new(find_objects: FindObjectsFunction, sign: SignFunction) -> Backend { + Backend { find_objects, sign } + } +} + +impl ClientCertsBackend for Backend { + type Cert = Cert; + type Key = Key; + + fn find_objects(&self) -> Result<(Vec<Cert>, Vec<Key>), Error> { + let mut find_objects_context = FindObjectsContext::new(self.sign); + (self.find_objects)( + Some(find_objects_callback), + &mut find_objects_context as *mut _ as *mut c_void, + ); + Ok((find_objects_context.certs, find_objects_context.keys)) + } +} diff --git a/security/manager/ssl/ipcclientcerts/src/lib.rs b/security/manager/ssl/ipcclientcerts/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..93eb97171e7cd6892e7502c831418e708d161412 --- /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<unsafe extern "C" fn(data_len: usize, data: *const u8, ctx: *mut c_void)>; + +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<Option<Manager<Backend>>> = 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<usize> = 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::<CK_RSA_PKCS_PSS_PARAMS>() { + 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 cefed1a541443c088e509cc4562628f1a90521f5..07804ac59626887f5e5e59be9901e43677281e90 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 1695b37be95f48e3e97879456741213c2b2c3a8c..7bb3ac3efafb7d2935b68e4e3cad0c4c064513a3 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 ec98414777c02de8d90907c0fd60799e33712943..0efd03fb0adeb306b6d037942a8a5e5f7c50993f 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<nsIFile>& file, nsACString& result) { +#ifdef XP_WIN + // Native path will drop Unicode characters that cannot be mapped to system's + // codepage, using short (canonical) path as workaround. + nsCOMPtr<nsILocalFileWin> fileWin = do_QueryInterface(file); + if (!fileWin) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get nsILocalFileWin")); + return NS_ERROR_FAILURE; + } + return fileWin->GetNativeCanonicalPath(result); +#else + return file->GetNativePath(result); +#endif +} + +void TruncateFromLastDirectorySeparator(nsCString& path) { + static const nsAutoCString kSeparatorString( + mozilla::FilePreferences::kPathSeparator); + int32_t index = path.RFind(kSeparatorString); + if (index == kNotFound) { + return; + } + path.Truncate(index); +} + // This function can be called from chrome or content or socket processes // to ensure that NSS is initialized. bool EnsureNSSInitializedChromeOrContent() { @@ -149,6 +183,36 @@ bool EnsureNSSInitializedChromeOrContent() { if (NS_FAILED(CommonInit())) { return false; } + // This returns the path to the binary currently running, which in most + // cases is "plugin-container". + UniqueFreePtr<char> pluginContainerPath(BinaryPath::Get()); + if (!pluginContainerPath) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("failed to get get plugin-container path")); + return false; + } + nsAutoCString ipcClientCertsDirString(pluginContainerPath.get()); + // On most platforms, ipcclientcerts is in the same directory as + // plugin-container. To obtain the path to that directory, truncate from + // the last directory separator. + // On macOS, plugin-container is in + // Firefox.app/Contents/MacOS/plugin-container.app/Contents/MacOS/, + // whereas ipcclientcerts is in Firefox.app/Contents/MacOS/. Consequently, + // this truncation from the last directory separator has to happen 4 times + // total. Normally this would be done using nsIFile APIs, but due to when + // this is initialized in the socket process, those aren't available. + TruncateFromLastDirectorySeparator(ipcClientCertsDirString); +#ifdef XP_MACOSX + TruncateFromLastDirectorySeparator(ipcClientCertsDirString); + TruncateFromLastDirectorySeparator(ipcClientCertsDirString); + TruncateFromLastDirectorySeparator(ipcClientCertsDirString); +#endif + if (!LoadIPCClientCertsModule(ipcClientCertsDirString)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("failed to load ipcclientcerts from '%s'", + ipcClientCertsDirString.get())); + return false; + } initialized = true; return true; } @@ -750,18 +814,7 @@ static nsresult GetDirectoryPath(const char* directoryKey, nsCString& result) { ("could not get '%s' from directory service", directoryKey)); return rv; } -#ifdef XP_WIN - // Native path will drop Unicode characters that cannot be mapped to system's - // codepage, using short (canonical) path as workaround. - nsCOMPtr<nsILocalFileWin> directoryWin = do_QueryInterface(directory); - if (!directoryWin) { - MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get nsILocalFileWin")); - return NS_ERROR_FAILURE; - } - return directoryWin->GetNativeCanonicalPath(result); -#else - return directory->GetNativePath(result); -#endif + return FileToCString(directory, result); } class BackgroundLoadOSClientCertsModuleTask final : public CryptoTask { @@ -841,8 +894,6 @@ NS_IMETHODIMP nsNSSComponent::HasUserCertsInstalled(bool* result) { NS_ENSURE_ARG_POINTER(result); - BlockUntilLoadableCertsLoaded(); - // FindClientCertificatesWithPrivateKeys won't ever return an empty list, so // all we need to do is check if this is null or not. UniqueCERTCertList certList(FindClientCertificatesWithPrivateKeys()); @@ -941,18 +992,7 @@ static nsresult GetNSS3Directory(nsCString& result) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get parent directory?")); return rv; } -#ifdef XP_WIN - // Native path will drop Unicode characters that cannot be mapped to system's - // codepage, using short (canonical) path as workaround. - nsCOMPtr<nsILocalFileWin> nss3DirectoryWin = do_QueryInterface(nss3Directory); - if (!nss3DirectoryWin) { - MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get nsILocalFileWin")); - return NS_ERROR_FAILURE; - } - return nss3DirectoryWin->GetNativeCanonicalPath(result); -#else - return nss3Directory->GetNativePath(result); -#endif + return FileToCString(nss3Directory, result); } // The loadable roots library is probably in the same directory we loaded the @@ -2620,6 +2660,9 @@ UniqueCERTCertList FindClientCertificatesWithPrivateKeys() { }); MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("FindClientCertificatesWithPrivateKeys")); + + BlockUntilLoadableCertsLoaded(); + UniqueCERTCertList certsWithPrivateKeys(CERT_NewCertList()); if (!certsWithPrivateKeys) { return nullptr; diff --git a/security/manager/ssl/nsNSSIOLayer.cpp b/security/manager/ssl/nsNSSIOLayer.cpp index 81e06b2698267a07ff0aeae11764e24672328b4b..ab80a8e45c3edc75fc1aa94b2aa0f35cdecc6447 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<nsTArray<uint8_t>> mCollectedCANames; nsTArray<nsTArray<uint8_t>> mEnterpriseCertificates; UniqueCERTCertificate mSelectedCertificate; - UniqueSECKEYPrivateKey mSelectedKey; }; class RemoteClientAuthDataRunnable : public ClientAuthDataRunnable { @@ -1946,30 +1945,42 @@ SECStatus nsNSS_SSLGetClientAuthData(void* arg, PRFileDesc* socket, nsTArray<nsTArray<uint8_t>> collectedCANames(CollectCANames(caNames)); UniqueCERTCertificate selectedCertificate; - UniqueSECKEYPrivateKey selectedKey; UniqueCERTCertList builtChain; - SECStatus status = DoGetClientAuthData( - std::move(authInfo), serverCert, std::move(collectedCANames), - selectedCertificate, selectedKey, builtChain); + SECStatus status = DoGetClientAuthData(std::move(authInfo), serverCert, + std::move(collectedCANames), + selectedCertificate, builtChain); if (status != SECSuccess) { return status; } - if (selectedCertificate && selectedKey) { - if (builtChain) { - info->SetClientCertChain(std::move(builtChain)); - } else { - MOZ_LOG( - gPIPNSSLog, LogLevel::Debug, - ("[%p] couldn't determine chain for selected client cert", socket)); - } - *pRetCert = selectedCertificate.release(); - *pRetKey = selectedKey.release(); - // Make joinConnection prohibit joining after we've sent a client cert - info->SetSentClientCert(); - if (info->GetSSLVersionUsed() == nsISSLSocketControl::TLS_VERSION_1_3) { - Telemetry::Accumulate(Telemetry::TLS_1_3_CLIENT_AUTH_USES_PHA, - info->IsHandshakeCompleted()); + // Currently, the IPC client certs module only refreshes its view of + // available certificates and keys if the platform issues a search for all + // certificates or keys. In the socket process, such a search may not have + // happened, so this ensures it has. + if (XRE_IsSocketProcess()) { + UniqueCERTCertList certList(FindClientCertificatesWithPrivateKeys()); + Unused << certList; + } + + if (selectedCertificate) { + UniqueSECKEYPrivateKey selectedKey( + PK11_FindKeyByAnyCert(selectedCertificate.get(), nullptr)); + if (selectedKey) { + if (builtChain) { + info->SetClientCertChain(std::move(builtChain)); + } else { + MOZ_LOG( + gPIPNSSLog, LogLevel::Debug, + ("[%p] couldn't determine chain for selected client cert", socket)); + } + *pRetCert = selectedCertificate.release(); + *pRetKey = selectedKey.release(); + // Make joinConnection prohibit joining after we've sent a client cert + info->SetSentClientCert(); + if (info->GetSSLVersionUsed() == nsISSLSocketControl::TLS_VERSION_1_3) { + Telemetry::Accumulate(Telemetry::TLS_1_3_CLIENT_AUTH_USES_PHA, + info->IsHandshakeCompleted()); + } } } @@ -1980,7 +1991,6 @@ SECStatus DoGetClientAuthData(ClientAuthInfo&& info, const UniqueCERTCertificate& serverCert, nsTArray<nsTArray<uint8_t>>&& collectedCANames, UniqueCERTCertificate& outCert, - UniqueSECKEYPrivateKey& outKey, UniqueCERTCertList& outBuiltChain) { // XXX: This should be done asynchronously; see bug 696976 RefPtr<ClientAuthDataRunnable> runnable = @@ -1997,8 +2007,7 @@ SECStatus DoGetClientAuthData(ClientAuthInfo&& info, } outCert = runnable->TakeSelectedCertificate(); - outKey = runnable->TakeSelectedKey(); - if (outCert && outKey) { + if (outCert) { mozilla::pkix::Result result = runnable->BuildChainForCertificate(outCert.get(), outBuiltChain); if (result != Success) { @@ -2033,7 +2042,7 @@ class ClientAuthCertNonverifyingTrustDomain final : public TrustDomain { virtual mozilla::pkix::Result CheckRevocation( EndEntityOrCA endEntityOrCA, const CertID& certID, Time time, - Duration validityDuration, + mozilla::pkix::Duration validityDuration, /*optional*/ const Input* stapledOCSPresponse, /*optional*/ const Input* aiaExtension, /*optional*/ const Input* sctExtension) override { @@ -2309,8 +2318,6 @@ void ClientAuthDataRunnable::RunOnTargetThread() { if (NS_WARN_IF(!mSelectedCertificate)) { return; } - mSelectedKey.reset( - PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr)); return; } @@ -2360,7 +2367,6 @@ void ClientAuthDataRunnable::RunOnTargetThread() { } else { // this is a good cert to present mSelectedCertificate.reset(CERT_DupCertificate(node->cert)); - mSelectedKey = std::move(tmpKey); return; } } @@ -2372,8 +2378,6 @@ void ClientAuthDataRunnable::RunOnTargetThread() { if (lowPrioNonrepCert) { mSelectedCertificate = std::move(lowPrioNonrepCert); - mSelectedKey.reset( - PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr)); } return; } @@ -2422,8 +2426,6 @@ void ClientAuthDataRunnable::RunOnTargetThread() { if (NS_WARN_IF(!mSelectedCertificate)) { return; } - mSelectedKey.reset( - PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr)); return; } } @@ -2485,8 +2487,6 @@ void ClientAuthDataRunnable::RunOnTargetThread() { if (NS_WARN_IF(!mSelectedCertificate)) { return; } - mSelectedKey.reset( - PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr)); } if (cars && wantRemember) { @@ -2550,18 +2550,19 @@ void RemoteClientAuthDataRunnable::RunOnTargetThread() { bool succeeded = false; ByteArray cert; - ByteArray key; mozilla::net::SocketProcessChild::GetSingleton()->SendGetTLSClientCert( nsCString(mInfo.HostName()), mInfo.OriginAttributesRef(), mInfo.Port(), mInfo.ProviderFlags(), mInfo.ProviderTlsFlags(), serverCertSerialized, - clientCertSerialized, collectedCANames, &succeeded, &cert, &key, - &mBuiltChain); + clientCertSerialized, collectedCANames, &succeeded, &cert, &mBuiltChain); if (!succeeded) { return; } - DeserializeClientCertAndKey(cert, key, mSelectedCertificate, mSelectedKey); + SECItem certItem = {siBuffer, const_cast<uint8_t*>(cert.data().Elements()), + static_cast<unsigned int>(cert.data().Length())}; + mSelectedCertificate.reset(CERT_NewTempCertificate( + CERT_GetDefaultCertDB(), &certItem, nullptr, false, true)); } static PRFileDesc* nsSSLIOLayerImportFD(PRFileDesc* fd, @@ -2948,3 +2949,91 @@ loser: } return NS_ERROR_FAILURE; } + +already_AddRefed<IPCClientCertsChild> GetIPCClientCertsActor() { + PBackgroundChild* backgroundActor = + BackgroundChild::GetOrCreateForSocketParentBridgeForCurrentThread(); + if (!backgroundActor) { + return nullptr; + } + RefPtr<PIPCClientCertsChild> actor = + SingleManagedOrNull(backgroundActor->ManagedPIPCClientCertsChild()); + if (!actor) { + actor = backgroundActor->SendPIPCClientCertsConstructor( + new IPCClientCertsChild()); + if (!actor) { + return nullptr; + } + } + return actor.forget().downcast<IPCClientCertsChild>(); +} + +extern "C" { + +const uint8_t kIPCClientCertsObjectTypeCert = 1; +const uint8_t kIPCClientCertsObjectTypeRSAKey = 2; +const uint8_t kIPCClientCertsObjectTypeECKey = 3; + +// This function is provided to the IPC client certs module so it can cause the +// parent process to find certificates and keys and send identifying +// information about them over IPC. +void DoFindObjects(FindObjectsCallback cb, void* ctx) { + RefPtr<IPCClientCertsChild> ipcClientCertsActor(GetIPCClientCertsActor()); + if (!ipcClientCertsActor) { + return; + } + nsTArray<IPCClientCertObject> objects; + if (!ipcClientCertsActor->SendFindObjects(&objects)) { + return; + } + for (const auto& object : objects) { + switch (object.type()) { + case IPCClientCertObject::TECKey: + cb(kIPCClientCertsObjectTypeECKey, object.get_ECKey().params().Length(), + object.get_ECKey().params().Elements(), + object.get_ECKey().cert().Length(), + object.get_ECKey().cert().Elements(), object.get_ECKey().slotType(), + ctx); + break; + case IPCClientCertObject::TRSAKey: + cb(kIPCClientCertsObjectTypeRSAKey, + object.get_RSAKey().modulus().Length(), + object.get_RSAKey().modulus().Elements(), + object.get_RSAKey().cert().Length(), + object.get_RSAKey().cert().Elements(), + object.get_RSAKey().slotType(), ctx); + break; + case IPCClientCertObject::TCertificate: + cb(kIPCClientCertsObjectTypeCert, + object.get_Certificate().der().Length(), + object.get_Certificate().der().Elements(), 0, nullptr, + object.get_Certificate().slotType(), ctx); + break; + default: + MOZ_ASSERT_UNREACHABLE("unhandled IPCClientCertObject type"); + break; + } + } +} + +// This function is provided to the IPC client certs module so it can cause the +// parent process to sign the given data using the key corresponding to the +// given certificate, using the given parameters. +void DoSign(size_t cert_len, const uint8_t* cert, size_t data_len, + const uint8_t* data, size_t params_len, const uint8_t* params, + SignCallback cb, void* ctx) { + RefPtr<IPCClientCertsChild> ipcClientCertsActor(GetIPCClientCertsActor()); + if (!ipcClientCertsActor) { + return; + } + ByteArray certBytes(nsTArray<uint8_t>(cert, cert_len)); + ByteArray dataBytes(nsTArray<uint8_t>(data, data_len)); + ByteArray paramsBytes(nsTArray<uint8_t>(params, params_len)); + ByteArray signature; + if (!ipcClientCertsActor->SendSign(certBytes, dataBytes, paramsBytes, + &signature)) { + return; + } + cb(signature.data().Length(), signature.data().Elements(), ctx); +} +} // extern "C" diff --git a/security/manager/ssl/nsNSSIOLayer.h b/security/manager/ssl/nsNSSIOLayer.h index ed36348cb52a4745053c29a2d54ff8effc733e9c..af5ca0a3c3e16b53953c527d13c9f51c8ab2969f 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<nsTArray<uint8_t>>&& 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 add9880c05e2790a37b43c2ad00cd3642f632371..a1986f66bb96675ccb24ff4a9dd0bd14b9aea0cc 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 3ad026fec4f0abe1c22b6daade92db6896f5608a..b8b47dcc9b7b9de240edd379f1de740cba9f8262 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<B: ClientCertsBackend> Object<B> { fn get_signature_length( &mut self, - data: &[u8], + data: Vec<u8>, params: &Option<CK_RSA_PKCS_PSS_PARAMS>, ) -> Result<usize, Error> { match self { Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)), - Object::Key(key) => key.get_signature_length(data, params), + Object::Key(key) => key.get_signature_length(&data, params), } } fn sign( &mut self, - data: &[u8], + data: Vec<u8>, params: &Option<CK_RSA_PKCS_PSS_PARAMS>, ) -> Result<Vec<u8>, Error> { match self { Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)), - Object::Key(key) => key.sign(data, params), + Object::Key(key) => key.sign(&data, params), } } } @@ -426,7 +426,7 @@ impl<B: ClientCertsBackend> Object<B> { /// The `Manager` keeps track of the state of this module with respect to the PKCS #11 /// specification. This includes what sessions are open, which search and sign operations are /// ongoing, and what objects are known and by what handle. -struct Manager<B: ClientCertsBackend> { +pub struct Manager<B: ClientCertsBackend> { /// A map of session to session type (modern or legacy). Sessions can be created (opened) and /// later closed. sessions: BTreeMap<CK_SESSION_HANDLE, SlotType>, @@ -546,7 +546,7 @@ impl<B: ClientCertsBackend> Manager<B> { pub fn start_search( &mut self, session: CK_SESSION_HANDLE, - attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)], + attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec<u8>)>, ) -> Result<(), Error> { let slot_type = match self.sessions.get(&session) { Some(slot_type) => *slot_type, @@ -554,7 +554,7 @@ impl<B: ClientCertsBackend> Manager<B> { }; // If the search is for an attribute we don't support, no objects will match. This check // saves us having to look through all of our objects. - for (attr, _) in attrs { + for (attr, _) in &attrs { if !SUPPORTED_ATTRIBUTES.contains(attr) { self.searches.insert(session, Vec::new()); return Ok(()); @@ -565,12 +565,12 @@ impl<B: ClientCertsBackend> Manager<B> { // indication for the backend to re-scan for new objects from tokens that may have been // inserted or certificates that may have been imported into the OS. Since these searches // are relatively rare, this minimizes the impact of doing these re-scans. - if search_is_for_all_certificates_or_keys(attrs)? { + if search_is_for_all_certificates_or_keys(&attrs)? { self.maybe_find_new_objects()?; } let mut handles = Vec::new(); for (handle, object) in &self.objects { - if object.matches(slot_type, attrs) { + if object.matches(slot_type, &attrs) { handles.push(*handle); } } @@ -651,7 +651,7 @@ impl<B: ClientCertsBackend> Manager<B> { pub fn get_signature_length( &mut self, session: CK_SESSION_HANDLE, - data: &[u8], + data: Vec<u8>, ) -> Result<usize, Error> { let (key_handle, params) = match self.signs.get(&session) { Some((key_handle, params)) => (key_handle, params), @@ -664,7 +664,7 @@ impl<B: ClientCertsBackend> Manager<B> { key.get_signature_length(data, params) } - pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: &[u8]) -> Result<Vec<u8>, Error> { + pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec<u8>) -> Result<Vec<u8>, Error> { // Performing the signature (via C_Sign, which is the only way we support) finishes the sign // operation, so it needs to be removed here. let (key_handle, params) = match self.signs.remove(&session) {