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/uno/XComponentContext.hpp>
25 #include <cppuhelper/supportsservice.hxx>
26 #include <officecfg/Office/Common.hxx>
27 #include <sal/types.h>
28 #include <rtl/instance.hxx>
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>
38 #include <nss/nssinitializer.hxx>
40 #include "digestcontext.hxx"
41 #include "ciphercontext.hxx"
52 namespace cssu
= css::uno
;
53 namespace cssl
= css::lang
;
55 using namespace com::sun::star
;
57 #define ROOT_CERTS "Root Certs for OpenOffice.org"
61 static void nsscrypto_finalize();
71 std::unique_ptr
<utl::TempFile
> m_pTempFileDatabaseDirectory
;
73 static void scanDirsAndFiles(OUString
const & rDirURL
, std::vector
<OUString
> & rDirs
, std::vector
<OUString
> & rFiles
)
75 if (rDirURL
.isEmpty())
77 osl::Directory
aDirectory(rDirURL
);
79 if (osl::FileBase::E_None
!= aDirectory
.open())
82 osl::DirectoryItem aDirectoryItem
;
84 while (osl::FileBase::E_None
== aDirectory
.getNextItem(aDirectoryItem
))
86 osl::FileStatus
aFileStatus(osl_FileStatus_Mask_Type
| osl_FileStatus_Mask_FileURL
| osl_FileStatus_Mask_FileName
);
88 if (osl::FileBase::E_None
== aDirectoryItem
.getFileStatus(aFileStatus
))
90 if (aFileStatus
.isDirectory())
92 const OUString
aFileName(aFileStatus
.getFileName());
93 if (!aFileName
.isEmpty())
94 rDirs
.push_back(aFileName
);
96 else if (aFileStatus
.isRegular())
98 const OUString
aFileName(aFileStatus
.getFileName());
99 if (!aFileName
.isEmpty())
100 rFiles
.push_back(aFileName
);
107 static bool deleteDirRecursively(OUString
const & rDirURL
)
109 std::vector
<OUString
> aDirs
;
110 std::vector
<OUString
> aFiles
;
113 scanDirsAndFiles(rDirURL
, aDirs
, aFiles
);
115 for (const auto& sDir
: aDirs
)
117 const OUString
aNewDirURL(rDirURL
+ "/" + sDir
);
118 bError
|= deleteDirRecursively(aNewDirURL
);
121 for (const auto& sFile
: aFiles
)
123 OUString
aNewFileURL(rDirURL
+ "/" + sFile
);
124 bError
|= (osl::FileBase::E_None
!= osl::File::remove(aNewFileURL
));
127 bError
|= (osl::FileBase::E_None
!= osl::Directory::remove(rDirURL
));
133 OUString
getTempDatabasePath()
135 if (!m_pTempFileDatabaseDirectory
)
137 m_pTempFileDatabaseDirectory
.reset(new utl::TempFile(nullptr, true));
138 m_pTempFileDatabaseDirectory
->EnableKillingFile();
140 return m_pTempFileDatabaseDirectory
->GetFileName();
145 if (m_pTempFileDatabaseDirectory
)
147 deleteDirRecursively(m_pTempFileDatabaseDirectory
->GetURL());
148 m_pTempFileDatabaseDirectory
.reset();
153 salhelper::SingletonRef
<InitNSSPrivate
>* getInitNSSPrivate()
155 static salhelper::SingletonRef
<InitNSSPrivate
> aInitNSSPrivate
;
156 return &aInitNSSPrivate
;
159 bool nsscrypto_initialize( const css::uno::Reference
< css::uno::XComponentContext
> &rxContext
, bool & out_nss_init
);
161 struct InitNSSInitialize
163 css::uno::Reference
< css::uno::XComponentContext
> m_xContext
;
165 explicit InitNSSInitialize(const css::uno::Reference
<css::uno::XComponentContext
> &rxContext
)
166 : m_xContext(rxContext
)
172 static bool bInitialized
= false;
173 bool bNSSInit
= false;
174 bInitialized
= nsscrypto_initialize( m_xContext
, bNSSInit
);
176 atexit(nsscrypto_finalize
);
177 return & bInitialized
;
181 struct GetNSSInitStaticMutex
183 ::osl::Mutex
* operator()()
185 static ::osl::Mutex aNSSInitMutex
;
186 return &aNSSInitMutex
;
190 #ifdef XMLSEC_CRYPTO_NSS
192 void deleteRootsModule()
194 SECMODModule
*RootsModule
= nullptr;
195 SECMODModuleList
*list
= SECMOD_GetDefaultModuleList();
196 SECMODListLock
*lock
= SECMOD_GetDefaultModuleListLock();
197 SECMOD_GetReadLock(lock
);
199 while (!RootsModule
&& list
)
201 SECMODModule
*module
= list
->module
;
203 for (int i
=0; i
< module
->slotCount
; i
++)
205 PK11SlotInfo
*slot
= module
->slots
[i
];
206 if (PK11_IsPresent(slot
))
208 if (PK11_HasRootCerts(slot
))
210 SAL_INFO("xmlsecurity.xmlsec", "The root certificates module \"" << module
->commonName
<< "\" is already loaded: " << module
->dllName
);
212 RootsModule
= SECMOD_ReferenceModule(module
);
219 SECMOD_ReleaseReadLock(lock
);
224 if (SECSuccess
== SECMOD_DeleteModule(RootsModule
->commonName
, &modType
))
226 SAL_INFO("xmlsecurity.xmlsec", "Deleted module \"" << RootsModule
->commonName
<< "\".");
230 SAL_INFO("xmlsecurity.xmlsec", "Failed to delete \"" << RootsModule
->commonName
<< "\": " << RootsModule
->dllName
);
232 SECMOD_DestroyModule(RootsModule
);
233 RootsModule
= nullptr;
237 OString
getMozillaCurrentProfile( const css::uno::Reference
< css::uno::XComponentContext
> &rxContext
)
239 // first, try to get the profile from "MOZILLA_CERTIFICATE_FOLDER"
240 const char* pEnv
= getenv("MOZILLA_CERTIFICATE_FOLDER");
244 "xmlsecurity.xmlsec",
245 "Using Mozilla profile from MOZILLA_CERTIFICATE_FOLDER=" << pEnv
);
249 // second, try to get saved user-preference
252 OUString sUserSetCertPath
=
253 officecfg::Office::Common::Security::Scripting::CertDir::get().get_value_or(OUString());
255 if (!sUserSetCertPath
.isEmpty())
258 "xmlsecurity.xmlsec",
259 "Using Mozilla profile from /org.openoffice.Office.Common/"
260 "Security/Scripting/CertDir: " << sUserSetCertPath
);
261 return OUStringToOString(sUserSetCertPath
, osl_getThreadTextEncoding());
264 catch (const uno::Exception
&)
266 TOOLS_WARN_EXCEPTION("xmlsecurity.xmlsec", "getMozillaCurrentProfile:");
269 // third, dig around to see if there's one available
270 mozilla::MozillaProductType productTypes
[3] = {
271 mozilla::MozillaProductType_Thunderbird
,
272 mozilla::MozillaProductType_Firefox
,
273 mozilla::MozillaProductType_Mozilla
};
275 uno::Reference
<uno::XInterface
> xInstance
= rxContext
->getServiceManager()->createInstanceWithContext("com.sun.star.mozilla.MozillaBootstrap", rxContext
);
276 OSL_ENSURE( xInstance
.is(), "failed to create instance" );
278 uno::Reference
<mozilla::XMozillaBootstrap
> xMozillaBootstrap(xInstance
,uno::UNO_QUERY
);
279 OSL_ENSURE( xMozillaBootstrap
.is(), "failed to create instance" );
281 if (xMozillaBootstrap
.is())
283 for (int i
=0; i
<int(SAL_N_ELEMENTS(productTypes
)); ++i
)
285 OUString profile
= xMozillaBootstrap
->getDefaultProfile(productTypes
[i
]);
287 if (!profile
.isEmpty())
289 OUString sProfilePath
= xMozillaBootstrap
->getProfilePath( productTypes
[i
], profile
);
291 "xmlsecurity.xmlsec",
292 "Using Mozilla profile " << sProfilePath
);
293 return OUStringToOString(sProfilePath
, osl_getThreadTextEncoding());
298 SAL_INFO("xmlsecurity.xmlsec", "No Mozilla profile found");
304 //Older versions of Firefox (FF), for example FF2, and Thunderbird (TB) 2 write
305 //the roots certificate module (libnssckbi.so), which they use, into the
306 //profile. This module will then already be loaded during NSS_Init (and the
307 //other init functions). This fails in two cases. First, FF3 was used to create
308 //the profile, or possibly used that profile before, and second the profile was
309 //used on a different platform.
311 //Then one needs to add the roots module oneself. This should be done with
312 //SECMOD_LoadUserModule rather than SECMOD_AddNewModule. The latter would write
313 //the location of the roots module to the profile, which makes FF2 and TB2 use
314 //it instead of their own module.
316 //When using SYSTEM_NSS then the libnss3.so lib is typically found in /usr/lib.
317 //This folder may, however, NOT contain the roots certificate module. That is,
318 //just providing the library name in SECMOD_LoadUserModule or
319 //SECMOD_AddNewModule will FAIL to load the mozilla unless the LD_LIBRARY_PATH
320 //contains an FF or TB installation.
321 //ATTENTION: DO NOT call this function directly instead use initNSS
322 //return true - whole initialization was successful
323 //param out_nss_init = true: at least the NSS initialization (NSS_InitReadWrite
324 //was successful and therefore NSS_Shutdown should be called when terminating.
325 bool nsscrypto_initialize(css::uno::Reference
<css::uno::XComponentContext
> const & rxContext
, bool & out_nss_init
)
327 // this method must be called only once, no need for additional lock
330 #ifdef XMLSEC_CRYPTO_NSS
331 sCertDir
= getMozillaCurrentProfile(rxContext
);
335 SAL_INFO("xmlsecurity.xmlsec", "Using profile: " << sCertDir
);
337 PR_Init( PR_USER_THREAD
, PR_PRIORITY_NORMAL
, 1 ) ;
339 bool bSuccess
= false;
340 // there might be no profile
341 if (!sCertDir
.isEmpty())
343 if (sCertDir
.indexOf(':') == -1) //might be env var with explicit prefix
345 OUString sCertDirURL
;
346 osl::FileBase::getFileURLFromSystemPath(
347 OStringToOUString(sCertDir
, osl_getThreadTextEncoding()),
349 osl::DirectoryItem item
;
350 if (osl::FileBase::E_NOENT
!= osl::DirectoryItem::get(sCertDirURL
+ "/cert8.db", item
) &&
351 osl::FileBase::E_NOENT
== osl::DirectoryItem::get(sCertDirURL
+ "/cert9.db", item
))
353 SAL_INFO("xmlsecurity.xmlsec", "nsscrypto_initialize: trying to avoid profile migration");
354 sCertDir
= "dbm:" + sCertDir
;
357 if (NSS_InitReadWrite(sCertDir
.getStr()) != SECSuccess
)
359 SAL_INFO("xmlsecurity.xmlsec", "Initializing NSS with profile failed.");
360 int errlen
= PR_GetErrorTextLength();
363 std::unique_ptr
<char[]> const error(new char[errlen
+ 1]);
364 PR_GetErrorText(error
.get());
365 SAL_INFO("xmlsecurity.xmlsec", error
.get());
374 if (!bSuccess
) // Try to create a database in temp dir
376 SAL_INFO("xmlsecurity.xmlsec", "Initializing NSS with a temporary profile.");
377 OUString rString
= (*getInitNSSPrivate())->getTempDatabasePath();
379 if (NSS_InitReadWrite(rString
.toUtf8().getStr()) != SECSuccess
)
381 SAL_INFO("xmlsecurity.xmlsec", "Initializing NSS with a temporary profile.");
382 int errlen
= PR_GetErrorTextLength();
385 std::unique_ptr
<char[]> const error(new char[errlen
+ 1]);
386 PR_GetErrorText(error
.get());
387 SAL_INFO("xmlsecurity.xmlsec", error
.get());
391 // Initialize and set empty password if needed
392 PK11SlotInfo
* pSlot
= PK11_GetInternalKeySlot();
395 if (PK11_NeedUserInit(pSlot
))
396 PK11_InitPin(pSlot
, nullptr, nullptr);
397 PK11_FreeSlot(pSlot
);
402 #ifdef XMLSEC_CRYPTO_NSS
403 bool return_value
= true;
405 #if defined SYSTEM_NSS
406 if (!SECMOD_HasRootCerts())
411 #if defined SYSTEM_NSS
412 OUString
rootModule("libnssckbi" SAL_DLLEXTENSION
);
414 OUString
rootModule("${LO_LIB_DIR}/libnssckbi" SAL_DLLEXTENSION
);
416 ::rtl::Bootstrap::expandMacros(rootModule
);
418 OUString rootModulePath
;
419 if (::osl::File::E_None
== ::osl::File::getSystemPathFromFileURL(rootModule
, rootModulePath
))
421 OString ospath
= OUStringToOString(rootModulePath
, osl_getThreadTextEncoding());
422 OString aStr
= "name=\"" ROOT_CERTS
"\" library=\"" + ospath
+ "\"";
424 SECMODModule
* RootsModule
=
425 SECMOD_LoadUserModule(
426 const_cast<char*>(aStr
.getStr()),
427 nullptr, // no parent
428 PR_FALSE
); // do not recurse
433 bool found
= RootsModule
->loaded
;
435 SECMOD_DestroyModule(RootsModule
);
436 RootsModule
= nullptr;
438 SAL_INFO("xmlsecurity.xmlsec", "Added new root certificate module " ROOT_CERTS
" contained in " << ospath
);
441 SAL_INFO("xmlsecurity.xmlsec", "FAILED to load the new root certificate module " ROOT_CERTS
"contained in " << ospath
);
442 return_value
= false;
447 SAL_INFO("xmlsecurity.xmlsec", "FAILED to add new root certificate module " ROOT_CERTS
" contained in " << ospath
);
448 return_value
= false;
454 SAL_INFO("xmlsecurity.xmlsec", "Adding new root certificate module failed.");
455 return_value
= false;
467 // must be extern "C" because we pass the function pointer to atexit
468 extern "C" void nsscrypto_finalize()
470 SECMODModule
*RootsModule
= SECMOD_FindModule(ROOT_CERTS
);
475 if (SECSuccess
== SECMOD_UnloadUserModule(RootsModule
))
477 SAL_INFO("xmlsecurity.xmlsec", "Unloaded module \"" ROOT_CERTS
"\".");
481 SAL_INFO("xmlsecurity.xmlsec", "Failed unloading module \"" ROOT_CERTS
"\".");
483 SECMOD_DestroyModule(RootsModule
);
487 SAL_INFO("xmlsecurity.xmlsec", "Unloading module \"" ROOT_CERTS
"\" failed because it was not found.");
490 (void)NSS_Shutdown();
492 (*getInitNSSPrivate())->reset();
495 ONSSInitializer::ONSSInitializer(
496 const css::uno::Reference
< css::uno::XComponentContext
> &rxContext
)
497 :m_xContext( rxContext
)
501 ONSSInitializer::~ONSSInitializer()
505 bool ONSSInitializer::initNSS( const css::uno::Reference
< css::uno::XComponentContext
> &rxContext
)
507 return *rtl_Instance
< bool, InitNSSInitialize
, ::osl::MutexGuard
, GetNSSInitStaticMutex
>
508 ::create( InitNSSInitialize( rxContext
), GetNSSInitStaticMutex() );
511 css::uno::Reference
< css::xml::crypto::XDigestContext
> SAL_CALL
ONSSInitializer::getDigestContext( ::sal_Int32 nDigestID
, const css::uno::Sequence
< css::beans::NamedValue
>& aParams
)
513 SECOidTag nNSSDigestID
= SEC_OID_UNKNOWN
;
514 sal_Int32 nDigestLength
= 0;
515 bool b1KData
= false;
516 if ( nDigestID
== css::xml::crypto::DigestID::SHA256
517 || nDigestID
== css::xml::crypto::DigestID::SHA256_1K
)
519 nNSSDigestID
= SEC_OID_SHA256
;
521 b1KData
= ( nDigestID
== css::xml::crypto::DigestID::SHA256_1K
);
523 else if ( nDigestID
== css::xml::crypto::DigestID::SHA1
524 || nDigestID
== css::xml::crypto::DigestID::SHA1_1K
)
526 nNSSDigestID
= SEC_OID_SHA1
;
528 b1KData
= ( nDigestID
== css::xml::crypto::DigestID::SHA1_1K
);
530 else if ( nDigestID
== css::xml::crypto::DigestID::SHA512
531 || nDigestID
== css::xml::crypto::DigestID::SHA512_1K
)
533 nNSSDigestID
= SEC_OID_SHA512
;
535 b1KData
= ( nDigestID
== css::xml::crypto::DigestID::SHA512_1K
);
538 throw css::lang::IllegalArgumentException("Unexpected digest requested.", css::uno::Reference
< css::uno::XInterface
>(), 1 );
540 if ( aParams
.hasElements() )
541 throw css::lang::IllegalArgumentException("Unexpected arguments provided for digest creation.", css::uno::Reference
< css::uno::XInterface
>(), 2 );
543 css::uno::Reference
< css::xml::crypto::XDigestContext
> xResult
;
544 if( initNSS( m_xContext
) )
546 PK11Context
* pContext
= PK11_CreateDigestContext( nNSSDigestID
);
547 if ( pContext
&& PK11_DigestBegin( pContext
) == SECSuccess
)
548 xResult
= new ODigestContext( pContext
, nDigestLength
, b1KData
);
554 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
)
556 CK_MECHANISM_TYPE nNSSCipherID
= 0;
557 bool bW3CPadding
= false;
558 if ( nCipherID
!= css::xml::crypto::CipherID::AES_CBC_W3C_PADDING
)
559 throw css::lang::IllegalArgumentException("Unexpected cipher requested.", css::uno::Reference
< css::uno::XInterface
>(), 1 );
561 nNSSCipherID
= CKM_AES_CBC
;
564 if ( aKey
.getLength() != 16 && aKey
.getLength() != 24 && aKey
.getLength() != 32 )
565 throw css::lang::IllegalArgumentException("Unexpected key length.", css::uno::Reference
< css::uno::XInterface
>(), 2 );
567 if ( aParams
.hasElements() )
568 throw css::lang::IllegalArgumentException("Unexpected arguments provided for cipher creation.", css::uno::Reference
< css::uno::XInterface
>(), 5 );
570 css::uno::Reference
< css::xml::crypto::XCipherContext
> xResult
;
571 if( initNSS( m_xContext
) )
573 if ( aInitializationVector
.getLength() != PK11_GetIVLength( nNSSCipherID
) )
574 throw css::lang::IllegalArgumentException("Unexpected length of initialization vector.", css::uno::Reference
< css::uno::XInterface
>(), 3 );
576 xResult
= OCipherContext::Create( nNSSCipherID
, aKey
, aInitializationVector
, bEncryption
, bW3CPadding
);
583 OUString SAL_CALL
ONSSInitializer::getImplementationName()
585 return "com.sun.star.xml.crypto.NSSInitializer";
588 sal_Bool SAL_CALL
ONSSInitializer::supportsService( const OUString
& rServiceName
)
590 return cppu::supportsService(this, rServiceName
);
593 cssu::Sequence
< OUString
> SAL_CALL
ONSSInitializer::getSupportedServiceNames( )
595 cssu::Sequence
<OUString
> aRet
{ NSS_SERVICE_NAME
};
599 #ifndef XMLSEC_CRYPTO_NSS
600 extern "C" SAL_DLLPUBLIC_EXPORT
uno::XInterface
*
601 com_sun_star_xml_crypto_NSSInitializer_get_implementation(
602 uno::XComponentContext
* pCtx
, uno::Sequence
<uno::Any
> const& /*rSeq*/)
604 return cppu::acquire(new ONSSInitializer(pCtx
));
608 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */