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/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"
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::unique_ptr
<utl::TempFile
> m_pTempFileDatabaseDirectory
;
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();
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
);
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 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("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
;
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("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
);
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()
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());
408 // Initialize and set empty password if needed
409 PK11SlotInfo
* pSlot
= PK11_GetInternalKeySlot();
412 if (PK11_NeedUserInit(pSlot
))
413 PK11_InitPin(pSlot
, nullptr, nullptr);
414 PK11_FreeSlot(pSlot
);
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())
428 #ifdef IOS // Use statically linked NSS
429 OUString
rootModulePath("NSSCKBI");
433 #if defined SYSTEM_NSS || defined ANDROID
434 OUString
rootModule("libnssckbi" SAL_DLLEXTENSION
);
436 OUString
rootModule("${LO_LIB_DIR}/libnssckbi" SAL_DLLEXTENSION
);
438 ::rtl::Bootstrap::expandMacros(rootModule
);
440 OUString rootModulePath
;
441 if (::osl::File::E_None
== ::osl::File::getSystemPathFromFileURL(rootModule
, rootModulePath
))
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
456 bool found
= RootsModule
->loaded
;
458 SECMOD_DestroyModule(RootsModule
);
459 RootsModule
= nullptr;
461 SAL_INFO("xmlsecurity.xmlsec", "Added new root certificate module " ROOT_CERTS
" contained in " << ospath
);
464 SAL_INFO("xmlsecurity.xmlsec", "FAILED to load the new root certificate module " ROOT_CERTS
"contained in " << ospath
);
465 return_value
= false;
470 SAL_INFO("xmlsecurity.xmlsec", "FAILED to add new root certificate module " ROOT_CERTS
" contained in " << ospath
);
471 return_value
= false;
477 SAL_INFO("xmlsecurity.xmlsec", "Adding new root certificate module failed.");
478 return_value
= false;
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
);
498 if (SECSuccess
== SECMOD_UnloadUserModule(RootsModule
))
500 SAL_INFO("xmlsecurity.xmlsec", "Unloaded module \"" ROOT_CERTS
"\".");
504 SAL_INFO("xmlsecurity.xmlsec", "Failed unloading module \"" ROOT_CERTS
"\".");
506 SECMOD_DestroyModule(RootsModule
);
510 SAL_INFO("xmlsecurity.xmlsec", "Unloading module \"" ROOT_CERTS
"\" failed because it was not found.");
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
);
530 atexit(nsscrypto_finalize
);
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
;
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
;
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
;
560 b1KData
= ( nDigestID
== css::xml::crypto::DigestID::SHA512_1K
);
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
);
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
;
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
);
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
));
632 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */