/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * 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/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include "passwordcontainer.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace osl; using namespace utl; using namespace com::sun::star; using namespace com::sun::star::uno; using namespace com::sun::star::registry; using namespace com::sun::star::lang; using namespace com::sun::star::task; using namespace com::sun::star::ucb; static OUString createIndex(const std::vector< OUString >& lines) { OUStringBuffer aResult; for( size_t i = 0; i < lines.size(); i++ ) { if( i ) aResult.append("__"); OString line = OUStringToOString( lines[i], RTL_TEXTENCODING_UTF8 ); const char* pLine = line.getStr(); while( *pLine ) { if (rtl::isAsciiAlphanumeric(static_cast(*pLine))) { aResult.append(*pLine); } else { aResult.append("_" + OUString::number(*pLine, 16) ); } pLine++; } } return aResult.makeStringAndClear(); } static std::vector< OUString > getInfoFromInd( std::u16string_view aInd ) { std::vector< OUString > aResult; bool aStart = true; OString line = OUStringToOString( aInd, RTL_TEXTENCODING_ASCII_US ); const char* pLine = line.getStr(); do { OUStringBuffer newItem; if( !aStart ) pLine += 2; else aStart = false; while( *pLine && ( pLine[0] != '_' || pLine[1] != '_' )) if( *pLine != '_' ) { newItem.append( *pLine ); pLine++; } else { OUString aNum; for( int i = 1; i < 3; i++ ) { if( !pLine[i] || ( ( pLine[i] < '0' || pLine[i] > '9' ) && ( pLine[i] < 'a' || pLine[i] > 'f' ) && ( pLine[i] < 'A' || pLine[i] > 'F' ) ) ) { OSL_FAIL( "Wrong index syntax!" ); return aResult; } aNum += OUStringChar( pLine[i] ); } newItem.append( sal_Unicode( aNum.toUInt32( 16 ) ) ); pLine += 3; } aResult.push_back( newItem.makeStringAndClear() ); } while( pLine[0] == '_' && pLine[1] == '_' ); if( *pLine ) OSL_FAIL( "Wrong index syntax!" ); return aResult; } static bool shorterUrl( OUString& aURL ) { sal_Int32 aInd = aURL.lastIndexOf( '/' ); if( aInd > 0 && aURL.indexOf( "://" ) != aInd-2 ) { aURL = aURL.copy( 0, aInd ); return true; } return false; } static OUString getAsciiLine( const ::rtl::ByteSequence& buf ) { OUString aResult; ::rtl::ByteSequence outbuf( buf.getLength()*2+1 ); for( int ind = 0; ind < buf.getLength(); ind++ ) { outbuf[ind*2] = ( static_cast(buf[ind]) >> 4 ) + 'a'; outbuf[ind*2+1] = ( static_cast(buf[ind]) & 0x0f ) + 'a'; } outbuf[buf.getLength()*2] = '\0'; aResult = OUString::createFromAscii( reinterpret_cast(outbuf.getArray()) ); return aResult; } static ::rtl::ByteSequence getBufFromAsciiLine( const OUString& line ) { OSL_ENSURE( line.getLength() % 2 == 0, "Wrong syntax!" ); OString tmpLine = OUStringToOString( line, RTL_TEXTENCODING_ASCII_US ); ::rtl::ByteSequence aResult(line.getLength()/2); for( int ind = 0; ind < tmpLine.getLength()/2; ind++ ) { aResult[ind] = ( static_cast( tmpLine[ind*2] - 'a' ) << 4 ) | static_cast( tmpLine[ind*2+1] - 'a' ); } return aResult; } PassMap StorageItem::getInfo() { PassMap aResult; Sequence< OUString > aNodeNames = ConfigItem::GetNodeNames( "Store" ); sal_Int32 aNodeCount = aNodeNames.getLength(); Sequence< OUString > aPropNames( aNodeCount * 2); std::transform(aNodeNames.begin(), aNodeNames.end(), aPropNames.begin(), [](const OUString& rName) -> OUString { return "Store/Passwordstorage['" + rName + "']/Password"; }); std::transform(aNodeNames.begin(), aNodeNames.end(), aPropNames.getArray() + aNodeCount, [](const OUString& rName) -> OUString { return "Store/Passwordstorage['" + rName + "']/InitializationVector"; }); Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aPropNames ); if( aPropertyValues.getLength() != aNodeCount * 2) { OSL_FAIL( "Problems during reading" ); return aResult; } for( sal_Int32 aNodeInd = 0; aNodeInd < aNodeCount; ++aNodeInd ) { std::vector< OUString > aUrlUsr = getInfoFromInd( aNodeNames[aNodeInd] ); if( aUrlUsr.size() == 2 ) { OUString aUrl = aUrlUsr[0]; OUString aName = aUrlUsr[1]; OUString aEPasswd; OUString aIV; aPropertyValues[aNodeInd] >>= aEPasswd; aPropertyValues[aNodeInd + aNodeCount] >>= aIV; PassMap::iterator aIter = aResult.find( aUrl ); if( aIter != aResult.end() ) aIter->second.emplace_back( aName, aEPasswd, aIV ); else { NamePassRecord aNewRecord( aName, aEPasswd, aIV ); std::vector< NamePassRecord > listToAdd( 1, aNewRecord ); aResult.insert( PairUrlRecord( aUrl, listToAdd ) ); } } else OSL_FAIL( "Wrong index syntax!" ); } return aResult; } void StorageItem::setUseStorage( bool bUse ) { ConfigItem::SetModified(); ConfigItem::PutProperties( { "UseStorage" }, { uno::Any(bUse) } ); } bool StorageItem::useStorage() { Sequence aNodeNames { "UseStorage" }; Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aNodeNames ); if( aPropertyValues.getLength() != aNodeNames.getLength() ) { OSL_FAIL( "Problems during reading" ); return false; } bool aResult = false; aPropertyValues[0] >>= aResult; return aResult; } sal_Int32 StorageItem::getStorageVersion() { Sequence aNodeNames { "StorageVersion" }; Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aNodeNames ); if( aPropertyValues.getLength() != aNodeNames.getLength() ) { OSL_FAIL( "Problems during reading" ); return 0; } sal_Int32 nResult = 0; aPropertyValues[0] >>= nResult; return nResult; } bool StorageItem::getEncodedMP( OUString& aResult, OUString& aResultIV ) { if( hasEncoded ) { aResult = mEncoded; aResultIV = mEncodedIV; return true; } Sequence< OUString > aNodeNames( 3 ); aNodeNames[0] = "HasMaster"; aNodeNames[1] = "Master"; aNodeNames[2] = "MasterInitializationVector"; Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aNodeNames ); if( aPropertyValues.getLength() != aNodeNames.getLength() ) { OSL_FAIL( "Problems during reading" ); return false; } aPropertyValues[0] >>= hasEncoded; aPropertyValues[1] >>= mEncoded; aPropertyValues[2] >>= mEncodedIV; aResult = mEncoded; aResultIV = mEncodedIV; return hasEncoded; } void StorageItem::setEncodedMP( const OUString& aEncoded, const OUString& aEncodedIV, bool bAcceptEmpty ) { bool bHasMaster = ( !aEncoded.isEmpty() || bAcceptEmpty ); ConfigItem::SetModified(); ConfigItem::PutProperties( { "HasMaster", "Master", "MasterInitializationVector", "StorageVersion" }, { uno::Any(bHasMaster), uno::Any(aEncoded), uno::Any(aEncodedIV), uno::Any(nCurrentStorageVersion) } ); hasEncoded = bHasMaster; mEncoded = aEncoded; mEncodedIV = aEncodedIV; } void StorageItem::remove( const OUString& aURL, const OUString& aName ) { Sequence< OUString > sendSeq { createIndex( { aURL, aName } ) }; ConfigItem::ClearNodeElements( "Store", sendSeq ); } void StorageItem::clear() { ConfigItem::ClearNodeSet( "Store" ); } void StorageItem::update( const OUString& aURL, const NamePassRecord& aRecord ) { if ( !aRecord.HasPasswords( PERSISTENT_RECORD ) ) { OSL_FAIL( "Unexpected storing of a record!" ); return; } Sequence< beans::PropertyValue > sendSeq(2); sendSeq[0].Name = "Store/Passwordstorage['" + createIndex( { aURL, aRecord.GetUserName() } ) + "']/InitializationVector"; sendSeq[0].Value <<= aRecord.GetPersistentIV(); sendSeq[1].Name = "Store/Passwordstorage['" + createIndex( { aURL, aRecord.GetUserName() } ) + "']/Password"; sendSeq[1].Value <<= aRecord.GetPersPasswords(); ConfigItem::SetModified(); ConfigItem::SetSetProperties( "Store", sendSeq ); } void StorageItem::Notify( const Sequence< OUString >& ) { // this feature still should not be used if( mainCont ) mainCont->Notify(); } void StorageItem::ImplCommit() { // Do nothing, we stored everything we want already } PasswordContainer::PasswordContainer( const Reference& rxContext ) { // m_pStorageFile->Notify() can be called ::osl::MutexGuard aGuard( mMutex ); mComponent.set( rxContext->getServiceManager(), UNO_QUERY ); mComponent->addEventListener( this ); m_pStorageFile.reset( new StorageItem( this, "Office.Common/Passwords" ) ); if( m_pStorageFile->useStorage() ) m_aContainer = m_pStorageFile->getInfo(); } PasswordContainer::~PasswordContainer() { ::osl::MutexGuard aGuard( mMutex ); m_pStorageFile.reset(); if( mComponent.is() ) { mComponent->removeEventListener(this); mComponent.clear(); } } void SAL_CALL PasswordContainer::disposing( const EventObject& ) { ::osl::MutexGuard aGuard( mMutex ); m_pStorageFile.reset(); if( mComponent.is() ) { //mComponent->removeEventListener(this); mComponent.clear(); } } std::vector< OUString > PasswordContainer::DecodePasswords( const OUString& aLine, const OUString& aIV, const OUString& aMasterPasswd, css::task::PasswordRequestMode mode ) { if( !aMasterPasswd.isEmpty() ) { rtlCipher aDecoder = rtl_cipher_create (rtl_Cipher_AlgorithmBF, rtl_Cipher_ModeStream ); OSL_ENSURE( aDecoder, "Can't create decoder" ); if( aDecoder ) { OSL_ENSURE( aMasterPasswd.getLength() == RTL_DIGEST_LENGTH_MD5 * 2, "Wrong master password format!" ); unsigned char code[RTL_DIGEST_LENGTH_MD5]; for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ ) code[ ind ] = static_cast(aMasterPasswd.copy( ind*2, 2 ).toUInt32(16)); unsigned char iv[RTL_DIGEST_LENGTH_MD5] = {0}; if (!aIV.isEmpty()) { for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ ) iv[ ind ] = static_cast(aIV.copy( ind*2, 2 ).toUInt32(16)); } rtlCipherError result = rtl_cipher_init ( aDecoder, rtl_Cipher_DirectionDecode, code, RTL_DIGEST_LENGTH_MD5, iv, RTL_DIGEST_LENGTH_MD5 ); if( result == rtl_Cipher_E_None ) { ::rtl::ByteSequence aSeq = getBufFromAsciiLine( aLine ); ::rtl::ByteSequence resSeq( aSeq.getLength() ); rtl_cipher_decode ( aDecoder, aSeq.getArray(), aSeq.getLength(), reinterpret_cast(resSeq.getArray()), resSeq.getLength() ); OUString aPasswd( reinterpret_cast(resSeq.getArray()), resSeq.getLength(), RTL_TEXTENCODING_UTF8 ); rtl_cipher_destroy (aDecoder); return getInfoFromInd( aPasswd ); } rtl_cipher_destroy (aDecoder); } } else { OSL_FAIL( "No master password provided!" ); // throw special exception } // problems with decoding OSL_FAIL( "Problem with decoding" ); throw css::task::NoMasterException( "Can't decode!", css::uno::Reference(), mode); } OUString PasswordContainer::EncodePasswords(const std::vector< OUString >& lines, const OUString& aIV, const OUString& aMasterPasswd) { if( !aMasterPasswd.isEmpty() ) { OString aSeq = OUStringToOString( createIndex( lines ), RTL_TEXTENCODING_UTF8 ); rtlCipher aEncoder = rtl_cipher_create (rtl_Cipher_AlgorithmBF, rtl_Cipher_ModeStream ); OSL_ENSURE( aEncoder, "Can't create encoder" ); if( aEncoder ) { OSL_ENSURE( aMasterPasswd.getLength() == RTL_DIGEST_LENGTH_MD5 * 2, "Wrong master password format!" ); unsigned char code[RTL_DIGEST_LENGTH_MD5]; for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ ) code[ ind ] = static_cast(aMasterPasswd.copy( ind*2, 2 ).toUInt32(16)); unsigned char iv[RTL_DIGEST_LENGTH_MD5] = {0}; if (!aIV.isEmpty()) { for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ ) iv[ ind ] = static_cast(aIV.copy( ind*2, 2 ).toUInt32(16)); } rtlCipherError result = rtl_cipher_init ( aEncoder, rtl_Cipher_DirectionEncode, code, RTL_DIGEST_LENGTH_MD5, iv, RTL_DIGEST_LENGTH_MD5 ); if( result == rtl_Cipher_E_None ) { ::rtl::ByteSequence resSeq(aSeq.getLength()+1); result = rtl_cipher_encode ( aEncoder, aSeq.getStr(), aSeq.getLength()+1, reinterpret_cast(resSeq.getArray()), resSeq.getLength() ); /* //test rtlCipherError result = rtl_cipher_init ( aEncoder, rtl_Cipher_DirectionDecode, code, RTL_DIGEST_LENGTH_MD5, NULL, 0 ); if( result == rtl_Cipher_E_None ) { OUString testOU = getAsciiLine( resSeq ); ::rtl::ByteSequence aSeq1 = getBufFromAsciiLine( testOU ); ::rtl::ByteSequence resSeq1( aSeq1.getLength() ); if( resSeq.getLength() == aSeq1.getLength() ) { for( int ind = 0; ind < aSeq1.getLength(); ind++ ) if( resSeq[ind] != aSeq1[ind] ) testOU = ""; } result = rtl_cipher_decode ( aEncoder, (sal_uInt8*)aSeq1.getArray(), aSeq1.getLength(), (sal_uInt8*)resSeq1.getArray(), resSeq1.getLength() ); OUString aPasswd( ( char* )resSeq1.getArray(), resSeq1.getLength(), RTL_TEXTENCODING_UTF8 ); } */ rtl_cipher_destroy (aEncoder); if( result == rtl_Cipher_E_None ) return getAsciiLine( resSeq ); } rtl_cipher_destroy (aEncoder); } } else { OSL_FAIL( "No master password provided!" ); // throw special exception } // problems with encoding OSL_FAIL( "Problem with encoding" ); throw RuntimeException("Can't encode!" ); } void PasswordContainer::UpdateVector( const OUString& aURL, std::vector< NamePassRecord >& toUpdate, NamePassRecord const & aRecord, bool writeFile ) { for (auto & aNPIter : toUpdate) if( aNPIter.GetUserName() == aRecord.GetUserName() ) { if( aRecord.HasPasswords( MEMORY_RECORD ) ) aNPIter.SetMemPasswords( aRecord.GetMemPasswords() ); if( aRecord.HasPasswords( PERSISTENT_RECORD ) ) { aNPIter.SetPersPasswords( aRecord.GetPersPasswords(), aRecord.GetPersistentIV() ); if( writeFile ) { // the password must be already encoded m_pStorageFile->update( aURL, aRecord ); // change existing ( aURL, aName ) record in the configfile } } return; } if( aRecord.HasPasswords( PERSISTENT_RECORD ) && writeFile ) { // the password must be already encoded m_pStorageFile->update( aURL, aRecord ); // add new aName to the existing url } toUpdate.insert( toUpdate.begin(), aRecord ); } UserRecord PasswordContainer::CopyToUserRecord( const NamePassRecord& aRecord, bool& io_bTryToDecode, const Reference< XInteractionHandler >& aHandler ) { ::std::vector< OUString > aPasswords; if( aRecord.HasPasswords( MEMORY_RECORD ) ) aPasswords = aRecord.GetMemPasswords(); if( io_bTryToDecode && aRecord.HasPasswords( PERSISTENT_RECORD ) ) { try { ::std::vector< OUString > aDecodedPasswords = DecodePasswords( aRecord.GetPersPasswords(), aRecord.GetPersistentIV(), GetMasterPassword( aHandler ), css::task::PasswordRequestMode_PASSWORD_ENTER ); aPasswords.insert( aPasswords.end(), aDecodedPasswords.begin(), aDecodedPasswords.end() ); } catch( NoMasterException& ) { // if master password could not be detected the entry will be just ignored io_bTryToDecode = false; } } return UserRecord( aRecord.GetUserName(), comphelper::containerToSequence( aPasswords ) ); } Sequence< UserRecord > PasswordContainer::CopyToUserRecordSequence( const std::vector< NamePassRecord >& original, const Reference< XInteractionHandler >& aHandler ) { Sequence< UserRecord > aResult( original.size() ); sal_uInt32 nInd = 0; bool bTryToDecode = true; for (auto const& aNPIter : original) { aResult[nInd] = CopyToUserRecord( aNPIter, bTryToDecode, aHandler ); ++nInd; } return aResult; } void SAL_CALL PasswordContainer::add( const OUString& Url, const OUString& UserName, const Sequence< OUString >& Passwords, const Reference< XInteractionHandler >& aHandler ) { ::osl::MutexGuard aGuard( mMutex ); PrivateAdd( Url, UserName, Passwords, MEMORY_RECORD, aHandler ); } void SAL_CALL PasswordContainer::addPersistent( const OUString& Url, const OUString& UserName, const Sequence< OUString >& Passwords, const Reference< XInteractionHandler >& aHandler ) { ::osl::MutexGuard aGuard( mMutex ); PrivateAdd( Url, UserName, Passwords, PERSISTENT_RECORD, aHandler ); } OUString PasswordContainer::createIV() { rtlRandomPool randomPool = mRandomPool.get(); unsigned char iv[RTL_DIGEST_LENGTH_MD5]; rtl_random_getBytes(randomPool, iv, RTL_DIGEST_LENGTH_MD5); OUStringBuffer aBuffer; for (sal_uInt8 i : iv) { aBuffer.append(OUString::number(i >> 4, 16)); aBuffer.append(OUString::number(i & 15, 16)); } return aBuffer.makeStringAndClear(); } void PasswordContainer::PrivateAdd( const OUString& Url, const OUString& UserName, const Sequence< OUString >& Passwords, char Mode, const Reference< XInteractionHandler >& aHandler ) { NamePassRecord aRecord( UserName ); ::std::vector< OUString > aStorePass = comphelper::sequenceToContainer< std::vector >( Passwords ); if( Mode == PERSISTENT_RECORD ) { OUString sIV = createIV(); OUString sEncodedPasswords = EncodePasswords( aStorePass, sIV, GetMasterPassword( aHandler ) ); aRecord.SetPersPasswords( sEncodedPasswords, sIV ); } else if( Mode == MEMORY_RECORD ) aRecord.SetMemPasswords( aStorePass ); else { OSL_FAIL( "Unexpected persistence status!" ); return; } if( !m_aContainer.empty() ) { PassMap::iterator aIter = m_aContainer.find( Url ); if( aIter != m_aContainer.end() ) { UpdateVector( aIter->first, aIter->second, aRecord, true ); return; } } std::vector< NamePassRecord > listToAdd( 1, aRecord ); m_aContainer.insert( PairUrlRecord( Url, listToAdd ) ); if( Mode == PERSISTENT_RECORD && m_pStorageFile && m_pStorageFile->useStorage() ) m_pStorageFile->update( Url, aRecord ); } UrlRecord SAL_CALL PasswordContainer::find( const OUString& aURL, const Reference< XInteractionHandler >& aHandler ) { return find( aURL, u"", false, aHandler ); } UrlRecord SAL_CALL PasswordContainer::findForName( const OUString& aURL, const OUString& aName, const Reference< XInteractionHandler >& aHandler ) { return find( aURL, aName, true, aHandler ); } Sequence< UserRecord > PasswordContainer::FindUsr( const std::vector< NamePassRecord >& userlist, std::u16string_view aName, const Reference< XInteractionHandler >& aHandler ) { for (auto const& aNPIter : userlist) { if( aNPIter.GetUserName() == aName ) { bool bTryToDecode = true; Sequence< UserRecord > aResult { CopyToUserRecord( aNPIter, bTryToDecode, aHandler ) }; return aResult; } } return Sequence< UserRecord >(); } bool PasswordContainer::createUrlRecord( const PassMap::iterator & rIter, bool bName, std::u16string_view aName, const Reference< XInteractionHandler >& aHandler, UrlRecord & rRec ) { if ( bName ) { Sequence< UserRecord > aUsrRec = FindUsr( rIter->second, aName, aHandler ); if( aUsrRec.hasElements() ) { rRec = UrlRecord( rIter->first, aUsrRec ); return true; } } else { rRec = UrlRecord( rIter->first, CopyToUserRecordSequence( rIter->second, aHandler ) ); return true; } return false; } UrlRecord PasswordContainer::find( const OUString& aURL, std::u16string_view aName, bool bName, // only needed to support empty user names const Reference< XInteractionHandler >& aHandler ) { ::osl::MutexGuard aGuard( mMutex ); if( !m_aContainer.empty() && !aURL.isEmpty() ) { OUString aUrl( aURL ); // each iteration remove last '/...' section from the aUrl // while it's possible, up to the most left '://' do { // first look for /somename and then look for /somename/... PassMap::iterator aIter = m_aContainer.find( aUrl ); if( aIter != m_aContainer.end() ) { UrlRecord aRec; if ( createUrlRecord( aIter, bName, aName, aHandler, aRec ) ) return aRec; } else { OUString tmpUrl( aUrl ); if ( !tmpUrl.endsWith("/") ) tmpUrl += "/"; aIter = m_aContainer.lower_bound( tmpUrl ); if( aIter != m_aContainer.end() && aIter->first.match( tmpUrl ) ) { UrlRecord aRec; if ( createUrlRecord( aIter, bName, aName, aHandler, aRec ) ) return aRec; } } } while( shorterUrl( aUrl ) && !aUrl.isEmpty() ); } return UrlRecord(); } OUString PasswordContainer::GetDefaultMasterPassword() { OUStringBuffer aResult; for ( sal_Int32 nInd = 0; nInd < RTL_DIGEST_LENGTH_MD5; nInd++ ) aResult.append("aa"); return aResult.makeStringAndClear(); } OUString PasswordContainer::RequestPasswordFromUser( PasswordRequestMode aRMode, const uno::Reference< task::XInteractionHandler >& xHandler ) { // empty string means that the call was cancelled or just failed OUString aResult; if ( xHandler.is() ) { ::rtl::Reference< MasterPasswordRequest_Impl > xRequest = new MasterPasswordRequest_Impl( aRMode ); xHandler->handle( xRequest ); ::rtl::Reference< ucbhelper::InteractionContinuation > xSelection = xRequest->getSelection(); if ( xSelection.is() ) { Reference< XInteractionAbort > xAbort( xSelection.get(), UNO_QUERY ); if ( !xAbort.is() ) { const ::rtl::Reference< ucbhelper::InteractionSupplyAuthentication > & xSupp = xRequest->getAuthenticationSupplier(); aResult = xSupp->getPassword(); } } } return aResult; } // Mangle the key to match an old bug static OUString ReencodeAsOldHash(const OUString& rPass) { OUStringBuffer aBuffer; for (int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ++ind) { unsigned char i = static_cast(rPass.copy(ind * 2, 2).toUInt32(16)); aBuffer.append(static_cast< sal_Unicode >('a' + (i >> 4))); aBuffer.append(static_cast< sal_Unicode >('a' + (i & 15))); } return aBuffer.makeStringAndClear(); } OUString const & PasswordContainer::GetMasterPassword( const Reference< XInteractionHandler >& aHandler ) { PasswordRequestMode aRMode = PasswordRequestMode_PASSWORD_ENTER; if( !m_pStorageFile || !m_pStorageFile->useStorage() ) throw NoMasterException("Password storing is not active!", Reference< XInterface >(), aRMode ); if( m_aMasterPasswd.isEmpty() && aHandler.is() ) { OUString aEncodedMP, aEncodedMPIV; bool bDefaultPassword = false; if( !m_pStorageFile->getEncodedMP( aEncodedMP, aEncodedMPIV ) ) aRMode = PasswordRequestMode_PASSWORD_CREATE; else if ( aEncodedMP.isEmpty() ) { m_aMasterPasswd = GetDefaultMasterPassword(); bDefaultPassword = true; } if ( !bDefaultPassword ) { bool bAskAgain = false; do { bAskAgain = false; OUString aPass = RequestPasswordFromUser( aRMode, aHandler ); if ( !aPass.isEmpty() ) { if( aRMode == PasswordRequestMode_PASSWORD_CREATE ) { m_aMasterPasswd = aPass; std::vector< OUString > aMaster( 1, m_aMasterPasswd ); OUString sIV = createIV(); m_pStorageFile->setEncodedMP( EncodePasswords( aMaster, sIV, m_aMasterPasswd ), sIV ); } else { if (m_pStorageFile->getStorageVersion() == 0) aPass = ReencodeAsOldHash(aPass); std::vector< OUString > aRM( DecodePasswords( aEncodedMP, aEncodedMPIV, aPass, aRMode ) ); if( aRM.empty() || aPass != aRM[0] ) { bAskAgain = true; aRMode = PasswordRequestMode_PASSWORD_REENTER; } else m_aMasterPasswd = aPass; } } } while( bAskAgain ); } } if ( m_aMasterPasswd.isEmpty() ) throw NoMasterException("No master password!", Reference< XInterface >(), aRMode ); return m_aMasterPasswd; } void SAL_CALL PasswordContainer::remove( const OUString& aURL, const OUString& aName ) { ::osl::MutexGuard aGuard( mMutex ); OUString aUrl( aURL ); if( m_aContainer.empty() ) return; PassMap::iterator aIter = m_aContainer.find( aUrl ); if( aIter == m_aContainer.end() ) { if( aUrl.endsWith("/") ) aUrl = aUrl.copy( 0, aUrl.getLength() - 1 ); else aUrl += "/"; aIter = m_aContainer.find( aUrl ); } if( aIter == m_aContainer.end() ) return; auto aNPIter = std::find_if(aIter->second.begin(), aIter->second.end(), [&aName](const NamePassRecord& rNPRecord) { return rNPRecord.GetUserName() == aName; }); if (aNPIter != aIter->second.end()) { if( aNPIter->HasPasswords( PERSISTENT_RECORD ) && m_pStorageFile ) m_pStorageFile->remove( aURL, aName ); // remove record ( aURL, aName ) // the iterator will not be used any more so it can be removed directly aIter->second.erase( aNPIter ); if( aIter->second.empty() ) m_aContainer.erase( aIter ); } } void SAL_CALL PasswordContainer::removePersistent( const OUString& aURL, const OUString& aName ) { ::osl::MutexGuard aGuard( mMutex ); OUString aUrl( aURL ); if( m_aContainer.empty() ) return; PassMap::iterator aIter = m_aContainer.find( aUrl ); if( aIter == m_aContainer.end() ) { if( aUrl.endsWith("/") ) aUrl = aUrl.copy( 0, aUrl.getLength() - 1 ); else aUrl += "/"; aIter = m_aContainer.find( aUrl ); } if( aIter == m_aContainer.end() ) return; auto aNPIter = std::find_if(aIter->second.begin(), aIter->second.end(), [&aName](const NamePassRecord& rNPRecord) { return rNPRecord.GetUserName() == aName; }); if (aNPIter == aIter->second.end()) return; if( aNPIter->HasPasswords( PERSISTENT_RECORD ) ) { // TODO/LATER: should the password be converted to MemoryPassword? aNPIter->RemovePasswords( PERSISTENT_RECORD ); if ( m_pStorageFile ) m_pStorageFile->remove( aURL, aName ); // remove record ( aURL, aName ) } if( !aNPIter->HasPasswords( MEMORY_RECORD ) ) aIter->second.erase( aNPIter ); if( aIter->second.empty() ) m_aContainer.erase( aIter ); } void SAL_CALL PasswordContainer::removeAllPersistent() { ::osl::MutexGuard aGuard( mMutex ); if( m_pStorageFile ) m_pStorageFile->clear(); for( PassMap::iterator aIter = m_aContainer.begin(); aIter != m_aContainer.end(); ) { for( std::vector< NamePassRecord >::iterator aNPIter = aIter->second.begin(); aNPIter != aIter->second.end(); ) { if( aNPIter->HasPasswords( PERSISTENT_RECORD ) ) { // TODO/LATER: should the password be converted to MemoryPassword? aNPIter->RemovePasswords( PERSISTENT_RECORD ); if ( m_pStorageFile ) m_pStorageFile->remove( aIter->first, aNPIter->GetUserName() ); // remove record ( aURL, aName ) } if( !aNPIter->HasPasswords( MEMORY_RECORD ) ) { aNPIter = aIter->second.erase(aNPIter); } else ++aNPIter; } if( aIter->second.empty() ) { aIter = m_aContainer.erase(aIter); } else ++aIter; } } Sequence< UrlRecord > SAL_CALL PasswordContainer::getAllPersistent( const Reference< XInteractionHandler >& xHandler ) { Sequence< UrlRecord > aResult; ::osl::MutexGuard aGuard( mMutex ); for( const auto& rEntry : m_aContainer ) { Sequence< UserRecord > aUsers; for (auto const& aNP : rEntry.second) if( aNP.HasPasswords( PERSISTENT_RECORD ) ) { sal_Int32 oldLen = aUsers.getLength(); aUsers.realloc( oldLen + 1 ); aUsers[ oldLen ] = UserRecord( aNP.GetUserName(), comphelper::containerToSequence( DecodePasswords( aNP.GetPersPasswords(), aNP.GetPersistentIV(), GetMasterPassword( xHandler ), css::task::PasswordRequestMode_PASSWORD_ENTER ) ) ); } if( aUsers.hasElements() ) { sal_Int32 oldLen = aResult.getLength(); aResult.realloc( oldLen + 1 ); aResult[ oldLen ] = UrlRecord( rEntry.first, aUsers ); } } return aResult; } sal_Bool SAL_CALL PasswordContainer::authorizateWithMasterPassword( const uno::Reference< task::XInteractionHandler >& xHandler ) { bool bResult = false; OUString aEncodedMP, aEncodedMPIV; uno::Reference< task::XInteractionHandler > xTmpHandler = xHandler; ::osl::MutexGuard aGuard( mMutex ); // the method should fail if there is no master password if( m_pStorageFile && m_pStorageFile->useStorage() && m_pStorageFile->getEncodedMP( aEncodedMP, aEncodedMPIV ) ) { if ( aEncodedMP.isEmpty() ) { // this is a default master password // no UI is necessary bResult = true; } else { if ( !xTmpHandler.is() ) { uno::Reference< lang::XMultiServiceFactory > xFactory( mComponent, uno::UNO_QUERY_THROW ); uno::Reference< uno::XComponentContext > xContext( comphelper::getComponentContext(xFactory) ); xTmpHandler.set( InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW ); } if ( !m_aMasterPasswd.isEmpty() ) { // there is a password, it should be just rechecked PasswordRequestMode aRMode = PasswordRequestMode_PASSWORD_ENTER; OUString aPass; do { aPass = RequestPasswordFromUser( aRMode, xTmpHandler ); if (m_pStorageFile->getStorageVersion() == 0) aPass = ReencodeAsOldHash(aPass); bResult = ( !aPass.isEmpty() && aPass == m_aMasterPasswd ); aRMode = PasswordRequestMode_PASSWORD_REENTER; // further questions with error notification } while( !bResult && !aPass.isEmpty() ); } else { try { // ask for the password, if user provide no correct password an exception will be thrown bResult = !GetMasterPassword( xTmpHandler ).isEmpty(); } catch( uno::Exception& ) {} } } } return bResult; } sal_Bool SAL_CALL PasswordContainer::changeMasterPassword( const uno::Reference< task::XInteractionHandler >& xHandler ) { bool bResult = false; uno::Reference< task::XInteractionHandler > xTmpHandler = xHandler; ::osl::MutexGuard aGuard( mMutex ); if ( m_pStorageFile && m_pStorageFile->useStorage() ) { if ( !xTmpHandler.is() ) { uno::Reference< lang::XMultiServiceFactory > xFactory( mComponent, uno::UNO_QUERY_THROW ); uno::Reference< uno::XComponentContext > xContext( comphelper::getComponentContext(xFactory) ); xTmpHandler.set( InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW ); } bool bCanChangePassword = true; // if there is already a stored master password it should be entered by the user before the change happen OUString aEncodedMP, aEncodedMPIV; if( !m_aMasterPasswd.isEmpty() || m_pStorageFile->getEncodedMP( aEncodedMP, aEncodedMPIV ) ) bCanChangePassword = authorizateWithMasterPassword( xTmpHandler ); if ( bCanChangePassword ) { // ask for the new password, but do not set it OUString aPass = RequestPasswordFromUser( PasswordRequestMode_PASSWORD_CREATE, xTmpHandler ); if ( !aPass.isEmpty() ) { // get all the persistent entries if it is possible const Sequence< UrlRecord > aPersistent = getAllPersistent( uno::Reference< task::XInteractionHandler >() ); // remove the master password and the entries persistence removeMasterPassword(); // store the new master password m_aMasterPasswd = aPass; std::vector< OUString > aMaster( 1, m_aMasterPasswd ); OUString aIV = createIV(); m_pStorageFile->setEncodedMP( EncodePasswords( aMaster, aIV, m_aMasterPasswd ), aIV ); // store all the entries with the new password for ( const auto& rURL : aPersistent ) for ( const auto& rUser : rURL.UserList ) addPersistent( rURL.Url, rUser.UserName, rUser.Passwords, uno::Reference< task::XInteractionHandler >() ); bResult = true; } } } return bResult; } void SAL_CALL PasswordContainer::removeMasterPassword() { // remove all the stored passwords and the master password removeAllPersistent(); ::osl::MutexGuard aGuard( mMutex ); if ( m_pStorageFile ) { m_aMasterPasswd.clear(); m_pStorageFile->setEncodedMP( OUString(), OUString() ); // let the master password be removed from configuration } } sal_Bool SAL_CALL PasswordContainer::hasMasterPassword( ) { ::osl::MutexGuard aGuard( mMutex ); if ( !m_pStorageFile ) throw uno::RuntimeException(); OUString aEncodedMP, aEncodedMPIV; return ( m_pStorageFile->useStorage() && m_pStorageFile->getEncodedMP( aEncodedMP, aEncodedMPIV ) ); } sal_Bool SAL_CALL PasswordContainer::allowPersistentStoring( sal_Bool bAllow ) { ::osl::MutexGuard aGuard( mMutex ); if ( !m_pStorageFile ) throw uno::RuntimeException(); if ( !bAllow ) removeMasterPassword(); if (m_pStorageFile->useStorage() == static_cast(bAllow)) return bAllow; m_pStorageFile->setUseStorage( bAllow ); return !bAllow; } sal_Bool SAL_CALL PasswordContainer::isPersistentStoringAllowed() { ::osl::MutexGuard aGuard( mMutex ); if ( !m_pStorageFile ) throw uno::RuntimeException(); return m_pStorageFile->useStorage(); } sal_Bool SAL_CALL PasswordContainer::useDefaultMasterPassword( const uno::Reference< task::XInteractionHandler >& xHandler ) { bool bResult = false; uno::Reference< task::XInteractionHandler > xTmpHandler = xHandler; ::osl::MutexGuard aGuard( mMutex ); if ( m_pStorageFile && m_pStorageFile->useStorage() ) { if ( !xTmpHandler.is() ) { uno::Reference< lang::XMultiServiceFactory > xFactory( mComponent, uno::UNO_QUERY_THROW ); uno::Reference< uno::XComponentContext > xContext( comphelper::getComponentContext(xFactory) ); xTmpHandler.set( InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW ); } bool bCanChangePassword = true; // if there is already a stored nondefault master password it should be entered by the user before the change happen OUString aEncodedMP, aEncodedMPIV; if( m_pStorageFile->getEncodedMP( aEncodedMP, aEncodedMPIV ) && !aEncodedMP.isEmpty() ) bCanChangePassword = authorizateWithMasterPassword( xTmpHandler ); if ( bCanChangePassword ) { // generate the default password OUString aPass = GetDefaultMasterPassword(); if ( !aPass.isEmpty() ) { // get all the persistent entries if it is possible const Sequence< UrlRecord > aPersistent = getAllPersistent( uno::Reference< task::XInteractionHandler >() ); // remove the master password and the entries persistence removeMasterPassword(); // store the empty string to flag the default master password m_aMasterPasswd = aPass; m_pStorageFile->setEncodedMP( OUString(), OUString(), true ); // store all the entries with the new password for ( const auto& rURL : aPersistent ) for ( const auto& rUser : rURL.UserList ) addPersistent( rURL.Url, rUser.UserName, rUser.Passwords, uno::Reference< task::XInteractionHandler >() ); bResult = true; } } } return bResult; } sal_Bool SAL_CALL PasswordContainer::isDefaultMasterPasswordUsed() { ::osl::MutexGuard aGuard( mMutex ); if ( !m_pStorageFile ) throw uno::RuntimeException(); OUString aEncodedMP, aEncodedMPIV; return ( m_pStorageFile->useStorage() && m_pStorageFile->getEncodedMP( aEncodedMP, aEncodedMPIV ) && aEncodedMP.isEmpty() ); } void SAL_CALL PasswordContainer::addUrl( const OUString& Url, sal_Bool MakePersistent ) { mUrlContainer.add( Url, MakePersistent ); } OUString SAL_CALL PasswordContainer::findUrl( const OUString& Url ) { return mUrlContainer.find( Url ); } void SAL_CALL PasswordContainer::removeUrl( const OUString& Url ) { mUrlContainer.remove( Url ); } uno::Sequence< OUString > SAL_CALL PasswordContainer::getUrls( sal_Bool OnlyPersistent ) { return mUrlContainer.list( OnlyPersistent ); } void PasswordContainer::Notify() { ::osl::MutexGuard aGuard( mMutex ); // remove the cached persistent values in the memory for( auto& rEntry : m_aContainer ) { for( std::vector< NamePassRecord >::iterator aNPIter = rEntry.second.begin(); aNPIter != rEntry.second.end(); ) { if( aNPIter->HasPasswords( PERSISTENT_RECORD ) ) { aNPIter->RemovePasswords( PERSISTENT_RECORD ); if ( m_pStorageFile ) m_pStorageFile->remove( rEntry.first, aNPIter->GetUserName() ); // remove record ( aURL, aName ) } if( !aNPIter->HasPasswords( MEMORY_RECORD ) ) { aNPIter = rEntry.second.erase(aNPIter); } else ++aNPIter; } } PassMap addon; if( m_pStorageFile ) addon = m_pStorageFile->getInfo(); for( const auto& rEntry : addon ) { PassMap::iterator aSearchIter = m_aContainer.find( rEntry.first ); if( aSearchIter != m_aContainer.end() ) for (auto const& aNP : rEntry.second) UpdateVector( aSearchIter->first, aSearchIter->second, aNP, false ); else m_aContainer.insert( PairUrlRecord( rEntry.first, rEntry.second ) ); } } OUString SAL_CALL PasswordContainer::getImplementationName( ) { return "stardiv.svl.PasswordContainer"; } sal_Bool SAL_CALL PasswordContainer::supportsService( const OUString& ServiceName ) { return cppu::supportsService( this, ServiceName ); } Sequence< OUString > SAL_CALL PasswordContainer::getSupportedServiceNames( ) { return { "com.sun.star.task.PasswordContainer" }; } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* svl_PasswordContainer_get_implementation( css::uno::XComponentContext* context, css::uno::Sequence const&) { return cppu::acquire(new PasswordContainer(context)); } MasterPasswordRequest_Impl::MasterPasswordRequest_Impl( PasswordRequestMode Mode ) { MasterPasswordRequest aRequest; aRequest.Classification = InteractionClassification_ERROR; aRequest.Mode = Mode; setRequest( makeAny( aRequest ) ); // Fill continuations... Sequence< RememberAuthentication > aRememberModes( 1 ); aRememberModes[ 0 ] = RememberAuthentication_NO; m_xAuthSupplier = new ::ucbhelper::InteractionSupplyAuthentication( this, false, // bCanSetRealm false, // bCanSetUserName true, // bCanSetPassword false, // bCanSetAccount aRememberModes, // rRememberPasswordModes RememberAuthentication_NO, // eDefaultRememberPasswordMode aRememberModes, // rRememberAccountModes RememberAuthentication_NO, // eDefaultRememberAccountMode false // bCanUseSystemCredentials ); Sequence< Reference< XInteractionContinuation > > aContinuations( 3 ); aContinuations[ 0 ] = new ::ucbhelper::InteractionAbort( this ); aContinuations[ 1 ] = new ::ucbhelper::InteractionRetry( this ); aContinuations[ 2 ] = m_xAuthSupplier.get(); setContinuations( aContinuations ); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */