nss: upgrade to release 3.73
[LibreOffice.git] / xmlsecurity / source / xmlsec / nss / nssinitializer.cxx
blobb8acdc059cf8767cdb0e143d7be87129774ccddc
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <com/sun/star/lang/IllegalArgumentException.hpp>
21 #include <com/sun/star/mozilla/XMozillaBootstrap.hpp>
22 #include <com/sun/star/xml/crypto/DigestID.hpp>
23 #include <com/sun/star/xml/crypto/CipherID.hpp>
24 #include <com/sun/star/xml/crypto/NSSInitializer.hpp>
25 #include <com/sun/star/uno/XComponentContext.hpp>
26 #include <cppuhelper/supportsservice.hxx>
27 #include <officecfg/Office/Common.hxx>
28 #include <sal/types.h>
29 #include <rtl/bootstrap.hxx>
30 #include <rtl/string.hxx>
31 #include <osl/file.hxx>
32 #include <osl/thread.h>
33 #include <sal/log.hxx>
34 #include <tools/diagnose_ex.h>
35 #include <unotools/tempfile.hxx>
36 #include <salhelper/singletonref.hxx>
37 #include <comphelper/sequence.hxx>
39 #include <nss/nssinitializer.hxx>
41 #include "digestcontext.hxx"
42 #include "ciphercontext.hxx"
44 #include <memory>
45 #include <vector>
47 #include <nss.h>
48 #include <pk11pub.h>
49 #include <secmod.h>
50 #include <prerror.h>
51 #include <prinit.h>
53 namespace cssu = css::uno;
54 namespace cssl = css::lang;
56 using namespace com::sun::star;
58 #define ROOT_CERTS "Root Certs for OpenOffice.org"
60 extern "C" {
62 static void nsscrypto_finalize();
66 namespace
69 class InitNSSPrivate
71 private:
72 std::unique_ptr<utl::TempFile> m_pTempFileDatabaseDirectory;
74 public:
75 OUString getTempDatabasePath()
77 if (!m_pTempFileDatabaseDirectory)
79 m_pTempFileDatabaseDirectory.reset(new utl::TempFile(nullptr, true));
80 m_pTempFileDatabaseDirectory->EnableKillingFile();
82 return m_pTempFileDatabaseDirectory->GetFileName();
85 void reset()
87 if (m_pTempFileDatabaseDirectory)
89 m_pTempFileDatabaseDirectory.reset();
94 salhelper::SingletonRef<InitNSSPrivate>* getInitNSSPrivate()
96 static salhelper::SingletonRef<InitNSSPrivate> aInitNSSPrivate;
97 return &aInitNSSPrivate;
100 bool nsscrypto_initialize( const css::uno::Reference< css::uno::XComponentContext > &rxContext, bool & out_nss_init );
102 #ifdef XMLSEC_CRYPTO_NSS
104 void deleteRootsModule()
106 SECMODModule *RootsModule = nullptr;
107 SECMODModuleList *list = SECMOD_GetDefaultModuleList();
108 SECMODListLock *lock = SECMOD_GetDefaultModuleListLock();
109 SECMOD_GetReadLock(lock);
111 while (!RootsModule && list)
113 SECMODModule *module = list->module;
115 for (int i=0; i < module->slotCount; i++)
117 PK11SlotInfo *slot = module->slots[i];
118 if (PK11_IsPresent(slot))
120 if (PK11_HasRootCerts(slot))
122 SAL_INFO("xmlsecurity.xmlsec", "The root certificates module \"" << module->commonName << "\" is already loaded: " << module->dllName);
124 RootsModule = SECMOD_ReferenceModule(module);
125 break;
129 list = list->next;
131 SECMOD_ReleaseReadLock(lock);
133 if (!RootsModule)
134 return;
136 PRInt32 modType;
137 if (SECSuccess == SECMOD_DeleteModule(RootsModule->commonName, &modType))
139 SAL_INFO("xmlsecurity.xmlsec", "Deleted module \"" << RootsModule->commonName << "\".");
141 else
143 SAL_INFO("xmlsecurity.xmlsec", "Failed to delete \"" << RootsModule->commonName << "\": " << RootsModule->dllName);
145 SECMOD_DestroyModule(RootsModule);
146 RootsModule = nullptr;
149 #endif
151 bool lcl_pathExists(const OUString& sPath)
153 if (sPath.isEmpty())
154 return false;
156 ::osl::DirectoryItem aPathItem;
157 OUString sURL;
158 osl::FileBase::getFileURLFromSystemPath(sPath, sURL);
159 if (::osl::FileBase::E_None == ::osl::DirectoryItem::get(sURL, aPathItem))
161 ::osl::FileStatus aStatus = osl_FileStatus_Mask_Validate;
162 if (::osl::FileBase::E_None == aPathItem.getFileStatus(aStatus))
163 return true;
166 return false;
169 } // namespace
171 OUString ONSSInitializer::getMozillaCurrentProfile(const css::uno::Reference< css::uno::XComponentContext > &rxContext, bool bSetActive)
173 if (m_bIsNSSinitialized)
174 return m_sNSSPath;
175 if (bSetActive)
176 m_bIsNSSinitialized = true;
178 // first, try to get the profile from "MOZILLA_CERTIFICATE_FOLDER"
179 const char* pEnv = getenv("MOZILLA_CERTIFICATE_FOLDER");
180 if (pEnv)
182 SAL_INFO(
183 "xmlsecurity.xmlsec",
184 "Using Mozilla profile from MOZILLA_CERTIFICATE_FOLDER=" << pEnv);
185 m_sNSSPath = OStringToOUString(pEnv, osl_getThreadTextEncoding());
188 // second, try to get saved user-preference
189 if (m_sNSSPath.isEmpty())
193 OUString sUserSetCertPath =
194 officecfg::Office::Common::Security::Scripting::CertDir::get().value_or(OUString());
196 if (lcl_pathExists(sUserSetCertPath))
198 SAL_INFO(
199 "xmlsecurity.xmlsec",
200 "Using Mozilla profile from /org.openoffice.Office.Common/"
201 "Security/Scripting/CertDir: " << sUserSetCertPath);
202 m_sNSSPath = sUserSetCertPath;
205 catch (const uno::Exception &)
207 TOOLS_WARN_EXCEPTION("xmlsecurity.xmlsec", "getMozillaCurrentProfile:");
211 // third, dig around to see if there's one default available
212 mozilla::MozillaProductType productTypes[3] = {
213 mozilla::MozillaProductType_Thunderbird,
214 mozilla::MozillaProductType_Firefox,
215 mozilla::MozillaProductType_Mozilla };
217 uno::Reference<uno::XInterface> xInstance = rxContext->getServiceManager()->createInstanceWithContext("com.sun.star.mozilla.MozillaBootstrap", rxContext);
218 OSL_ENSURE( xInstance.is(), "failed to create instance" );
220 uno::Reference<mozilla::XMozillaBootstrap> xMozillaBootstrap(xInstance,uno::UNO_QUERY);
221 OSL_ENSURE( xMozillaBootstrap.is(), "failed to create instance" );
223 if (xMozillaBootstrap.is())
225 for (int i=0; i<int(SAL_N_ELEMENTS(productTypes)); ++i)
227 OUString profile = xMozillaBootstrap->getDefaultProfile(productTypes[i]);
229 if (!profile.isEmpty())
231 OUString sProfilePath = xMozillaBootstrap->getProfilePath(productTypes[i], profile);
232 if (m_sNSSPath.isEmpty())
234 SAL_INFO("xmlsecurity.xmlsec", "Using Mozilla profile " << sProfilePath);
235 m_sNSSPath = sProfilePath;
237 break;
242 SAL_INFO_IF(m_sNSSPath.isEmpty(), "xmlsecurity.xmlsec", "No Mozilla profile found");
243 return m_sNSSPath;
246 css::uno::Sequence<css::xml::crypto::NSSProfile> SAL_CALL ONSSInitializer::getNSSProfiles()
248 ONSSInitializer::getMozillaCurrentProfile(m_xContext);
250 std::vector<xml::crypto::NSSProfile> aProfileList;
251 aProfileList.reserve(10);
253 mozilla::MozillaProductType productTypes[3] = {
254 mozilla::MozillaProductType_Thunderbird,
255 mozilla::MozillaProductType_Firefox,
256 mozilla::MozillaProductType_Mozilla };
258 uno::Reference<uno::XInterface> xInstance = m_xContext->getServiceManager()->createInstanceWithContext("com.sun.star.mozilla.MozillaBootstrap", m_xContext);
259 OSL_ENSURE(xInstance.is(), "failed to create instance" );
261 uno::Reference<mozilla::XMozillaBootstrap> xMozillaBootstrap(xInstance,uno::UNO_QUERY);
263 if (xMozillaBootstrap.is())
265 for (int i=0; i<int(SAL_N_ELEMENTS(productTypes)); ++i)
267 uno::Sequence<OUString> aProductProfileList;
268 xMozillaBootstrap->getProfileList(productTypes[i], aProductProfileList);
269 for (const auto& sProfile : std::as_const(aProductProfileList))
270 aProfileList.push_back({sProfile, xMozillaBootstrap->getProfilePath(productTypes[i], sProfile), productTypes[i]});
274 OUString sUserSelect;
277 sUserSelect = officecfg::Office::Common::Security::Scripting::CertDir::get().value_or(OUString());;
278 if (!lcl_pathExists(sUserSelect))
279 sUserSelect = OUString();
281 catch (const uno::Exception &)
283 TOOLS_WARN_EXCEPTION("xmlsecurity.xmlsec", "getMozillaCurrentProfile:");
285 aProfileList.push_back({"MANUAL", sUserSelect, mozilla::MozillaProductType_Default});
287 const char* pEnv = getenv("MOZILLA_CERTIFICATE_FOLDER");
288 aProfileList.push_back({"MOZILLA_CERTIFICATE_FOLDER",
289 pEnv ? OStringToOUString(pEnv, osl_getThreadTextEncoding()) : OUString(),
290 mozilla::MozillaProductType_Default});
292 return comphelper::containerToSequence(aProfileList);
295 bool ONSSInitializer::m_bIsNSSinitialized = false;
296 OUString ONSSInitializer::m_sNSSPath;
298 OUString SAL_CALL ONSSInitializer::getNSSPath()
300 ONSSInitializer::getMozillaCurrentProfile(m_xContext);
301 return m_sNSSPath;
304 sal_Bool SAL_CALL ONSSInitializer::getIsNSSinitialized()
306 return m_bIsNSSinitialized;
309 ONSSInitializer::ONSSInitializer(const css::uno::Reference< css::uno::XComponentContext > &rxContext)
310 : m_xContext(rxContext)
314 ONSSInitializer::ONSSInitializer()
318 namespace
321 //Older versions of Firefox (FF), for example FF2, and Thunderbird (TB) 2 write
322 //the roots certificate module (libnssckbi.so), which they use, into the
323 //profile. This module will then already be loaded during NSS_Init (and the
324 //other init functions). This fails in two cases. First, FF3 was used to create
325 //the profile, or possibly used that profile before, and second the profile was
326 //used on a different platform.
328 //Then one needs to add the roots module oneself. This should be done with
329 //SECMOD_LoadUserModule rather than SECMOD_AddNewModule. The latter would write
330 //the location of the roots module to the profile, which makes FF2 and TB2 use
331 //it instead of their own module.
333 //When using SYSTEM_NSS then the libnss3.so lib is typically found in /usr/lib.
334 //This folder may, however, NOT contain the roots certificate module. That is,
335 //just providing the library name in SECMOD_LoadUserModule or
336 //SECMOD_AddNewModule will FAIL to load the mozilla unless the LD_LIBRARY_PATH
337 //contains an FF or TB installation.
338 //ATTENTION: DO NOT call this function directly instead use initNSS
339 //return true - whole initialization was successful
340 //param out_nss_init = true: at least the NSS initialization (NSS_InitReadWrite
341 //was successful and therefore NSS_Shutdown should be called when terminating.
342 bool nsscrypto_initialize(css::uno::Reference<css::uno::XComponentContext> const & rxContext, bool & out_nss_init)
344 // this method must be called only once, no need for additional lock
345 OString sCertDir;
347 #ifdef XMLSEC_CRYPTO_NSS
348 sCertDir = OUStringToOString(ONSSInitializer::getMozillaCurrentProfile(rxContext, true), osl_getThreadTextEncoding());
349 #else
350 (void) rxContext;
351 #endif
352 SAL_INFO("xmlsecurity.xmlsec", "Using profile: " << sCertDir );
354 PR_Init( PR_USER_THREAD, PR_PRIORITY_NORMAL, 1 ) ;
356 bool bSuccess = false;
357 // there might be no profile
358 if (!sCertDir.isEmpty())
360 if (sCertDir.indexOf(':') == -1) //might be env var with explicit prefix
362 OUString sCertDirURL;
363 osl::FileBase::getFileURLFromSystemPath(
364 OStringToOUString(sCertDir, osl_getThreadTextEncoding()),
365 sCertDirURL);
366 osl::DirectoryItem item;
367 if (osl::FileBase::E_NOENT != osl::DirectoryItem::get(sCertDirURL + "/cert8.db", item) &&
368 osl::FileBase::E_NOENT == osl::DirectoryItem::get(sCertDirURL + "/cert9.db", item))
370 SAL_INFO("xmlsecurity.xmlsec", "nsscrypto_initialize: trying to avoid profile migration");
371 sCertDir = "dbm:" + sCertDir;
374 if (NSS_InitReadWrite(sCertDir.getStr()) != SECSuccess)
376 SAL_INFO("xmlsecurity.xmlsec", "Initializing NSS with profile failed.");
377 int errlen = PR_GetErrorTextLength();
378 if (errlen > 0)
380 std::unique_ptr<char[]> const error(new char[errlen + 1]);
381 PR_GetErrorText(error.get());
382 SAL_INFO("xmlsecurity.xmlsec", error.get());
385 else
387 bSuccess = true;
391 if (!bSuccess) // Try to create a database in temp dir
393 SAL_INFO("xmlsecurity.xmlsec", "Initializing NSS with a temporary profile.");
394 OUString rString = (*getInitNSSPrivate())->getTempDatabasePath();
396 if (NSS_InitReadWrite(rString.toUtf8().getStr()) != SECSuccess)
398 SAL_INFO("xmlsecurity.xmlsec", "Initializing NSS with a temporary profile.");
399 int errlen = PR_GetErrorTextLength();
400 if(errlen > 0)
402 std::unique_ptr<char[]> const error(new char[errlen + 1]);
403 PR_GetErrorText(error.get());
404 SAL_INFO("xmlsecurity.xmlsec", error.get());
406 return false;
408 // Initialize and set empty password if needed
409 PK11SlotInfo* pSlot = PK11_GetInternalKeySlot();
410 if (pSlot)
412 if (PK11_NeedUserInit(pSlot))
413 PK11_InitPin(pSlot, nullptr, nullptr);
414 PK11_FreeSlot(pSlot);
417 out_nss_init = true;
419 #ifdef XMLSEC_CRYPTO_NSS
420 bool return_value = true;
422 #if defined SYSTEM_NSS || defined IOS // The statically linked nss on iOS acts as a "system" nss in this regards
423 if (!SECMOD_HasRootCerts())
424 #endif
426 deleteRootsModule();
428 #ifdef IOS // Use statically linked NSS
429 OUString rootModulePath("NSSCKBI");
431 if (true)
432 #else
433 #if defined SYSTEM_NSS || defined ANDROID
434 OUString rootModule("libnssckbi" SAL_DLLEXTENSION);
435 #else
436 OUString rootModule("${LO_LIB_DIR}/libnssckbi" SAL_DLLEXTENSION);
437 #endif
438 ::rtl::Bootstrap::expandMacros(rootModule);
440 OUString rootModulePath;
441 if (::osl::File::E_None == ::osl::File::getSystemPathFromFileURL(rootModule, rootModulePath))
442 #endif
444 OString ospath = OUStringToOString(rootModulePath, osl_getThreadTextEncoding());
445 OString aStr = "name=\"" ROOT_CERTS "\" library=\"" + ospath + "\"";
447 SECMODModule * RootsModule =
448 SECMOD_LoadUserModule(
449 const_cast<char*>(aStr.getStr()),
450 nullptr, // no parent
451 PR_FALSE); // do not recurse
453 if (RootsModule)
456 bool found = RootsModule->loaded;
458 SECMOD_DestroyModule(RootsModule);
459 RootsModule = nullptr;
460 if (found)
461 SAL_INFO("xmlsecurity.xmlsec", "Added new root certificate module " ROOT_CERTS " contained in " << ospath);
462 else
464 SAL_INFO("xmlsecurity.xmlsec", "FAILED to load the new root certificate module " ROOT_CERTS "contained in " << ospath);
465 return_value = false;
468 else
470 SAL_INFO("xmlsecurity.xmlsec", "FAILED to add new root certificate module " ROOT_CERTS " contained in " << ospath);
471 return_value = false;
475 else
477 SAL_INFO("xmlsecurity.xmlsec", "Adding new root certificate module failed.");
478 return_value = false;
482 return return_value;
483 #else
484 return true;
485 #endif
488 } // namespace
490 // must be extern "C" because we pass the function pointer to atexit
491 extern "C" void nsscrypto_finalize()
493 SECMODModule *RootsModule = SECMOD_FindModule(ROOT_CERTS);
495 if (RootsModule)
498 if (SECSuccess == SECMOD_UnloadUserModule(RootsModule))
500 SAL_INFO("xmlsecurity.xmlsec", "Unloaded module \"" ROOT_CERTS "\".");
502 else
504 SAL_INFO("xmlsecurity.xmlsec", "Failed unloading module \"" ROOT_CERTS "\".");
506 SECMOD_DestroyModule(RootsModule);
508 else
510 SAL_INFO("xmlsecurity.xmlsec", "Unloading module \"" ROOT_CERTS "\" failed because it was not found.");
512 PK11_LogoutAll();
513 (void)NSS_Shutdown();
515 (*getInitNSSPrivate())->reset();
519 ONSSInitializer::~ONSSInitializer()
523 bool ONSSInitializer::initNSS( const css::uno::Reference< css::uno::XComponentContext > &rxContext )
525 static bool gbInitialized = [&rxContext]()
527 bool bNSSInit = false;
528 bool bInitialized = nsscrypto_initialize( rxContext, bNSSInit );
529 if (bNSSInit)
530 atexit(nsscrypto_finalize);
531 return bInitialized;
532 }();
533 return gbInitialized;
536 css::uno::Reference< css::xml::crypto::XDigestContext > SAL_CALL ONSSInitializer::getDigestContext( ::sal_Int32 nDigestID, const css::uno::Sequence< css::beans::NamedValue >& aParams )
538 SECOidTag nNSSDigestID = SEC_OID_UNKNOWN;
539 sal_Int32 nDigestLength = 0;
540 bool b1KData = false;
541 if ( nDigestID == css::xml::crypto::DigestID::SHA256
542 || nDigestID == css::xml::crypto::DigestID::SHA256_1K )
544 nNSSDigestID = SEC_OID_SHA256;
545 nDigestLength = 32;
546 b1KData = ( nDigestID == css::xml::crypto::DigestID::SHA256_1K );
548 else if ( nDigestID == css::xml::crypto::DigestID::SHA1
549 || nDigestID == css::xml::crypto::DigestID::SHA1_1K )
551 nNSSDigestID = SEC_OID_SHA1;
552 nDigestLength = 20;
553 b1KData = ( nDigestID == css::xml::crypto::DigestID::SHA1_1K );
555 else if ( nDigestID == css::xml::crypto::DigestID::SHA512
556 || nDigestID == css::xml::crypto::DigestID::SHA512_1K )
558 nNSSDigestID = SEC_OID_SHA512;
559 nDigestLength = 64;
560 b1KData = ( nDigestID == css::xml::crypto::DigestID::SHA512_1K );
562 else
563 throw css::lang::IllegalArgumentException("Unexpected digest requested.", css::uno::Reference< css::uno::XInterface >(), 1 );
565 if ( aParams.hasElements() )
566 throw css::lang::IllegalArgumentException("Unexpected arguments provided for digest creation.", css::uno::Reference< css::uno::XInterface >(), 2 );
568 css::uno::Reference< css::xml::crypto::XDigestContext > xResult;
569 if( initNSS( m_xContext ) )
571 PK11Context* pContext = PK11_CreateDigestContext( nNSSDigestID );
572 if ( pContext && PK11_DigestBegin( pContext ) == SECSuccess )
573 xResult = new ODigestContext( pContext, nDigestLength, b1KData );
576 return xResult;
579 css::uno::Reference< css::xml::crypto::XCipherContext > SAL_CALL ONSSInitializer::getCipherContext( ::sal_Int32 nCipherID, const css::uno::Sequence< ::sal_Int8 >& aKey, const css::uno::Sequence< ::sal_Int8 >& aInitializationVector, sal_Bool bEncryption, const css::uno::Sequence< css::beans::NamedValue >& aParams )
581 CK_MECHANISM_TYPE nNSSCipherID = 0;
582 bool bW3CPadding = false;
583 if ( nCipherID != css::xml::crypto::CipherID::AES_CBC_W3C_PADDING )
584 throw css::lang::IllegalArgumentException("Unexpected cipher requested.", css::uno::Reference< css::uno::XInterface >(), 1 );
586 nNSSCipherID = CKM_AES_CBC;
587 bW3CPadding = true;
589 if ( aKey.getLength() != 16 && aKey.getLength() != 24 && aKey.getLength() != 32 )
590 throw css::lang::IllegalArgumentException("Unexpected key length.", css::uno::Reference< css::uno::XInterface >(), 2 );
592 if ( aParams.hasElements() )
593 throw css::lang::IllegalArgumentException("Unexpected arguments provided for cipher creation.", css::uno::Reference< css::uno::XInterface >(), 5 );
595 css::uno::Reference< css::xml::crypto::XCipherContext > xResult;
596 if( initNSS( m_xContext ) )
598 if ( aInitializationVector.getLength() != PK11_GetIVLength( nNSSCipherID ) )
599 throw css::lang::IllegalArgumentException("Unexpected length of initialization vector.", css::uno::Reference< css::uno::XInterface >(), 3 );
601 xResult = OCipherContext::Create( nNSSCipherID, aKey, aInitializationVector, bEncryption, bW3CPadding );
604 return xResult;
607 /* XServiceInfo */
608 OUString SAL_CALL ONSSInitializer::getImplementationName()
610 return "com.sun.star.xml.crypto.NSSInitializer";
613 sal_Bool SAL_CALL ONSSInitializer::supportsService( const OUString& rServiceName )
615 return cppu::supportsService(this, rServiceName);
618 cssu::Sequence< OUString > SAL_CALL ONSSInitializer::getSupportedServiceNames( )
620 return { NSS_SERVICE_NAME };
623 #ifndef XMLSEC_CRYPTO_NSS
624 extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface*
625 com_sun_star_xml_crypto_NSSInitializer_get_implementation(
626 uno::XComponentContext* pCtx, uno::Sequence<uno::Any> const& /*rSeq*/)
628 return cppu::acquire(new ONSSInitializer(pCtx));
630 #endif
632 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */