1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/lang/XMultiComponentFactory.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 <comphelper/diagnose_ex.hxx>
35 #include <unotools/tempfile.hxx>
36 #include <comphelper/singletonref.hxx>
37 #include <comphelper/sequence.hxx>
39 #include <nss/nssinitializer.hxx>
41 #include "digestcontext.hxx"
42 #include "ciphercontext.hxx"
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"
62 static void nsscrypto_finalize();
72 std::optional
<utl::TempFileNamed
> m_oTempFileDatabaseDirectory
;
75 OUString
getTempDatabasePath()
77 if (!m_oTempFileDatabaseDirectory
)
79 m_oTempFileDatabaseDirectory
.emplace(nullptr, true);
80 m_oTempFileDatabaseDirectory
->EnableKillingFile();
82 return m_oTempFileDatabaseDirectory
->GetFileName();
87 if (m_oTempFileDatabaseDirectory
)
89 m_oTempFileDatabaseDirectory
.reset();
94 comphelper::SingletonRef
<InitNSSPrivate
>* getInitNSSPrivate()
96 static comphelper::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
);
131 SECMOD_ReleaseReadLock(lock
);
137 if (SECSuccess
== SECMOD_DeleteModule(RootsModule
->commonName
, &modType
))
139 SAL_INFO("xmlsecurity.xmlsec", "Deleted module \"" << RootsModule
->commonName
<< "\".");
143 SAL_INFO("xmlsecurity.xmlsec", "Failed to delete \"" << RootsModule
->commonName
<< "\": " << RootsModule
->dllName
);
145 SECMOD_DestroyModule(RootsModule
);
146 RootsModule
= nullptr;
151 bool lcl_pathExists(const OUString
& sPath
)
156 ::osl::DirectoryItem aPathItem
;
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
))
171 const OUString
& ONSSInitializer::getMozillaCurrentProfile(const css::uno::Reference
< css::uno::XComponentContext
> &rxContext
, bool bSetActive
)
173 if (m_bIsNSSinitialized
)
176 m_bIsNSSinitialized
= true;
178 // first, try to get the profile from "MOZILLA_CERTIFICATE_FOLDER"
179 const char* pEnv
= getenv("MOZILLA_CERTIFICATE_FOLDER");
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
))
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(u
"com.sun.star.mozilla.MozillaBootstrap"_ustr
, 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 (auto const productTypeIter
: productTypes
)
227 OUString profile
= xMozillaBootstrap
->getDefaultProfile(productTypeIter
);
229 if (!profile
.isEmpty())
231 OUString sProfilePath
= xMozillaBootstrap
->getProfilePath(productTypeIter
, profile
);
232 if (m_sNSSPath
.isEmpty())
234 SAL_INFO("xmlsecurity.xmlsec", "Using Mozilla profile " << sProfilePath
);
235 m_sNSSPath
= sProfilePath
;
242 SAL_INFO_IF(m_sNSSPath
.isEmpty(), "xmlsecurity.xmlsec", "No Mozilla profile found");
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(u
"com.sun.star.mozilla.MozillaBootstrap"_ustr
, 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 (auto const productTypeIter
: productTypes
)
267 uno::Sequence
<OUString
> aProductProfileList
;
268 xMozillaBootstrap
->getProfileList(productTypeIter
, aProductProfileList
);
269 for (const auto& sProfile
: aProductProfileList
)
270 aProfileList
.push_back({sProfile
, xMozillaBootstrap
->getProfilePath(productTypeIter
, sProfile
), productTypeIter
});
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({u
"MANUAL"_ustr
, sUserSelect
, mozilla::MozillaProductType_Default
});
287 const char* pEnv
= getenv("MOZILLA_CERTIFICATE_FOLDER");
288 aProfileList
.push_back({u
"MOZILLA_CERTIFICATE_FOLDER"_ustr
,
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
);
304 sal_Bool SAL_CALL
ONSSInitializer::getIsNSSinitialized()
306 return m_bIsNSSinitialized
;
309 ONSSInitializer::ONSSInitializer(css::uno::Reference
< css::uno::XComponentContext
> xContext
)
310 : m_xContext(std::move(xContext
))
314 ONSSInitializer::ONSSInitializer()
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
347 #ifdef XMLSEC_CRYPTO_NSS
348 sCertDir
= OUStringToOString(ONSSInitializer::getMozillaCurrentProfile(rxContext
, true), osl_getThreadTextEncoding());
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()),
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();
380 std::unique_ptr
<char[]> const error(new char[errlen
+ 1]);
381 PR_GetErrorText(error
.get());
382 SAL_INFO("xmlsecurity.xmlsec", error
.get());
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();
402 std::unique_ptr
<char[]> const error(new char[errlen
+ 1]);
403 PR_GetErrorText(error
.get());
404 SAL_INFO("xmlsecurity.xmlsec", error
.get());
410 // Initialize and set empty password if needed
411 // note: it's possible that the first NSS_InitReadWrite() succeeds by
412 // creating a new DB; in this case it may also be necessary to call
414 PK11SlotInfo
* pSlot
= PK11_GetInternalKeySlot();
417 if (PK11_NeedUserInit(pSlot
))
418 PK11_InitPin(pSlot
, nullptr, nullptr);
419 PK11_FreeSlot(pSlot
);
424 #ifdef XMLSEC_CRYPTO_NSS
425 bool return_value
= true;
427 #if defined SYSTEM_NSS || defined IOS // The statically linked nss on iOS acts as a "system" nss in this regards
428 if (!SECMOD_HasRootCerts())
433 #ifdef IOS // Use statically linked NSS
434 OUString
rootModulePath("NSSCKBI");
438 #if defined SYSTEM_NSS || defined ANDROID
439 OUString
rootModule(u
"libnssckbi" SAL_DLLEXTENSION
""_ustr
);
441 OUString
rootModule("${LO_LIB_DIR}/libnssckbi" SAL_DLLEXTENSION
);
443 ::rtl::Bootstrap::expandMacros(rootModule
);
445 OUString rootModulePath
;
446 if (::osl::File::E_None
== ::osl::File::getSystemPathFromFileURL(rootModule
, rootModulePath
))
449 OString ospath
= OUStringToOString(rootModulePath
, osl_getThreadTextEncoding());
450 OString aStr
= "name=\"" ROOT_CERTS
"\" library=\"" + ospath
+ "\"";
452 SECMODModule
* RootsModule
=
453 SECMOD_LoadUserModule(
454 const_cast<char*>(aStr
.getStr()),
455 nullptr, // no parent
456 PR_FALSE
); // do not recurse
461 bool found
= RootsModule
->loaded
;
463 SECMOD_DestroyModule(RootsModule
);
464 RootsModule
= nullptr;
466 SAL_INFO("xmlsecurity.xmlsec", "Added new root certificate module " ROOT_CERTS
" contained in " << ospath
);
469 SAL_INFO("xmlsecurity.xmlsec", "FAILED to load the new root certificate module " ROOT_CERTS
"contained in " << ospath
);
470 return_value
= false;
475 SAL_INFO("xmlsecurity.xmlsec", "FAILED to add new root certificate module " ROOT_CERTS
" contained in " << ospath
);
476 return_value
= false;
482 SAL_INFO("xmlsecurity.xmlsec", "Adding new root certificate module failed.");
483 return_value
= false;
495 // must be extern "C" because we pass the function pointer to atexit
496 extern "C" void nsscrypto_finalize()
498 SECMODModule
*RootsModule
= SECMOD_FindModule(ROOT_CERTS
);
503 if (SECSuccess
== SECMOD_UnloadUserModule(RootsModule
))
505 SAL_INFO("xmlsecurity.xmlsec", "Unloaded module \"" ROOT_CERTS
"\".");
509 SAL_INFO("xmlsecurity.xmlsec", "Failed unloading module \"" ROOT_CERTS
"\".");
511 SECMOD_DestroyModule(RootsModule
);
515 SAL_INFO("xmlsecurity.xmlsec", "Unloading module \"" ROOT_CERTS
"\" failed because it was not found.");
518 (void)NSS_Shutdown();
520 (*getInitNSSPrivate())->reset();
524 ONSSInitializer::~ONSSInitializer()
528 bool ONSSInitializer::initNSS( const css::uno::Reference
< css::uno::XComponentContext
> &rxContext
)
530 static bool gbInitialized
= [&rxContext
]()
532 bool bNSSInit
= false;
533 bool bInitialized
= nsscrypto_initialize( rxContext
, bNSSInit
);
535 atexit(nsscrypto_finalize
);
538 return gbInitialized
;
541 css::uno::Reference
< css::xml::crypto::XDigestContext
> SAL_CALL
ONSSInitializer::getDigestContext( ::sal_Int32 nDigestID
, const css::uno::Sequence
< css::beans::NamedValue
>& aParams
)
543 SECOidTag nNSSDigestID
= SEC_OID_UNKNOWN
;
544 sal_Int32 nDigestLength
= 0;
545 bool b1KData
= false;
546 if ( nDigestID
== css::xml::crypto::DigestID::SHA256
547 || nDigestID
== css::xml::crypto::DigestID::SHA256_1K
)
549 nNSSDigestID
= SEC_OID_SHA256
;
551 b1KData
= ( nDigestID
== css::xml::crypto::DigestID::SHA256_1K
);
553 else if ( nDigestID
== css::xml::crypto::DigestID::SHA1
554 || nDigestID
== css::xml::crypto::DigestID::SHA1_1K
)
556 nNSSDigestID
= SEC_OID_SHA1
;
558 b1KData
= ( nDigestID
== css::xml::crypto::DigestID::SHA1_1K
);
560 else if ( nDigestID
== css::xml::crypto::DigestID::SHA512
561 || nDigestID
== css::xml::crypto::DigestID::SHA512_1K
)
563 nNSSDigestID
= SEC_OID_SHA512
;
565 b1KData
= ( nDigestID
== css::xml::crypto::DigestID::SHA512_1K
);
568 throw css::lang::IllegalArgumentException(u
"Unexpected digest requested."_ustr
, css::uno::Reference
< css::uno::XInterface
>(), 1 );
570 if ( aParams
.hasElements() )
571 throw css::lang::IllegalArgumentException(u
"Unexpected arguments provided for digest creation."_ustr
, css::uno::Reference
< css::uno::XInterface
>(), 2 );
573 css::uno::Reference
< css::xml::crypto::XDigestContext
> xResult
;
574 if( initNSS( m_xContext
) )
576 PK11Context
* pContext
= PK11_CreateDigestContext( nNSSDigestID
);
577 if ( pContext
&& PK11_DigestBegin( pContext
) == SECSuccess
)
578 xResult
= new ODigestContext( pContext
, nDigestLength
, b1KData
);
584 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
)
586 CK_MECHANISM_TYPE nNSSCipherID
= 0;
587 bool bW3CPadding
= false;
590 case css::xml::crypto::CipherID::AES_CBC_W3C_PADDING
:
591 nNSSCipherID
= CKM_AES_CBC
;
594 case css::xml::crypto::CipherID::AES_GCM_W3C
:
595 nNSSCipherID
= CKM_AES_GCM
;
598 throw css::lang::IllegalArgumentException(u
"Unexpected cipher requested."_ustr
, css::uno::Reference
< css::uno::XInterface
>(), 1);
601 if ( aKey
.getLength() != 16 && aKey
.getLength() != 24 && aKey
.getLength() != 32 )
602 throw css::lang::IllegalArgumentException(u
"Unexpected key length."_ustr
, css::uno::Reference
< css::uno::XInterface
>(), 2 );
604 if ( aParams
.hasElements() )
605 throw css::lang::IllegalArgumentException(u
"Unexpected arguments provided for cipher creation."_ustr
, css::uno::Reference
< css::uno::XInterface
>(), 5 );
607 css::uno::Reference
< css::xml::crypto::XCipherContext
> xResult
;
608 if( initNSS( m_xContext
) )
610 if ( aInitializationVector
.getLength() != PK11_GetIVLength( nNSSCipherID
) )
611 throw css::lang::IllegalArgumentException(u
"Unexpected length of initialization vector."_ustr
, css::uno::Reference
< css::uno::XInterface
>(), 3 );
613 xResult
= OCipherContext::Create( nNSSCipherID
, aKey
, aInitializationVector
, bEncryption
, bW3CPadding
);
614 assert(xResult
.is());
621 OUString SAL_CALL
ONSSInitializer::getImplementationName()
623 return u
"com.sun.star.xml.crypto.NSSInitializer"_ustr
;
626 sal_Bool SAL_CALL
ONSSInitializer::supportsService( const OUString
& rServiceName
)
628 return cppu::supportsService(this, rServiceName
);
631 cssu::Sequence
< OUString
> SAL_CALL
ONSSInitializer::getSupportedServiceNames( )
633 return { NSS_SERVICE_NAME
};
636 #ifndef XMLSEC_CRYPTO_NSS
637 extern "C" SAL_DLLPUBLIC_EXPORT
uno::XInterface
*
638 com_sun_star_xml_crypto_NSSInitializer_get_implementation(
639 uno::XComponentContext
* pCtx
, uno::Sequence
<uno::Any
> const& /*rSeq*/)
641 return cppu::acquire(new ONSSInitializer(pCtx
));
645 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */