/* -*- 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 .
*/
uno::Sequence< sal_Int8 > aDecryptBuffer;
uno::Sequence< sal_Int8 > aDecryptBuffer2; try
{
aDecryptBuffer = xCipher->convertWithCipherContext( aReadBuffer );
aDecryptBuffer2 = xCipher->finalizeCipherContextAndDispose();
} catch( uno::Exception& )
{ // decryption with padding will throw the exception in finalizing if the buffer represent only part of the stream // it is no problem, actually this is why we read 32 additional bytes ( two of maximal possible encryption blocks )
}
// If we don't have a digest, then we have to assume that the password is correct if ( rData->m_aDigest.hasElements() &&
( aDigestSeq.getLength() != rData->m_aDigest.getLength() ||
0 != memcmp ( aDigestSeq.getConstArray(),
rData->m_aDigest.getConstArray(),
aDigestSeq.getLength() ) ) )
{ // We should probably tell the user that the password they entered was wrong
} else
bRet = true;
return bRet;
}
uno::Reference<io::XInputStream> ZipFile::checkValidPassword(
ZipEntry const& rEntry, ::rtl::Reference<EncryptionData> const& rData,
sal_Int64 const nDecryptedSize,
rtl::Reference<comphelper::RefCountedMutex> const& rMutex)
{ if (rData.is() && rData->m_nEncAlg == xml::crypto::CipherID::AES_GCM_W3C)
{ try// the only way to find out: decrypt the whole stream, which will
{ // check the tag
uno::Reference<io::XInputStream> const xRet =
createStreamForZipEntry(rMutex, rEntry, rData, UNBUFF_STREAM_DATA, nDecryptedSize); // currently XBufferedStream reads the whole stream in its ctor (to // verify the tag) - in case this gets changed, explicitly seek here
uno::Reference<io::XSeekable> const xSeek(xRet, uno::UNO_QUERY_THROW);
xSeek->seek(xSeek->getLength());
xSeek->seek(0); return xRet;
} catch (uno::Exception const&)
{ return {};
}
} elseif (rData.is() && rData->m_aKey.hasElements())
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
class XBufferedStream : public cppu::WeakImplHelper<css::io::XInputStream, css::io::XSeekable>, public comphelper::ByteReader
{
std::vector<sal_Int8> maBytes;
size_t mnPos;
// "encrypted-package" is the only data stream, no point in threading it if (nThreadingThreshold < xSrcStream->available()
&& rEntry.sPath != "encrypted-package" // tdf#160888 no threading for AEAD streams: // 1. the whole stream must be read immediately to verify tag // 2. XBufferedThreadedStream uses same m_aMutexHolder->GetMutex() // => caller cannot read without deadlock
&& (nStreamMode != UNBUFF_STREAM_DATA
|| !rData.is()
|| rData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C))
{ returnnew XBufferedThreadedStream(xSrcStream, xSrcStream->getSize());
} #endif
if (!rData->m_aKey.hasElements()) throw packages::WrongPasswordException(THROW_WHERE);
uno::Reference<XSeekable> xSeek(xStream, UNO_QUERY); if (!xSeek.is()) throw ZipIOException(u"The stream must be seekable!"_ustr);
// if we have a digest, then this file is an encrypted one and we should // check if we can decrypt it or not
SAL_WARN_IF(rData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C && !rData->m_aDigest.hasElements(), "package", "Can't detect password correctness without digest!"); if (rData->m_nEncAlg == xml::crypto::CipherID::AES_GCM_W3C)
{ // skip header
xSeek->seek(n_ConstHeaderSize + rData->m_aInitVector.getLength()
+ rData->m_aSalt.getLength() + rData->m_aDigest.getLength());
try
{ // XUnbufferedStream does not support XSeekable so wrap it
::rtl::Reference<XBufferedStream> const pRet( new XBufferedStream(new XUnbufferedStream(rMutexHolder, xStream, rData))); // currently XBufferedStream reads the whole stream in its ctor (to // verify the tag) - in case this gets changed, explicitly seek here
pRet->seek(pRet->getLength());
pRet->seek(0); return pRet;
} catch (uno::Exception const&)
{ throw packages::WrongPasswordException(THROW_WHERE);
}
} elseif (rData->m_aDigest.hasElements())
{
sal_Int32 nSize = sal::static_int_cast<sal_Int32>(xSeek->getLength()); if (nSize > n_ConstDigestLength + 32)
nSize = n_ConstDigestLength + 32;
// An exception must be thrown in case stream is encrypted and // there is no key or the key is wrong bool bNeedRawStream = false; if (oDecryptedSize)
{ // in case no digest is provided there is no way // to detect password correctness if ( !rData.is() ) throw ZipException(u"Encrypted stream without encryption data!"_ustr );
// if we have a digest, then this file is an encrypted one and we should // check if we can decrypt it or not
SAL_WARN_IF(rData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C && !rData->m_aDigest.hasElements(), "package", "Can't detect password correctness without digest!");
uno::Reference<XInputStream> const xRet(checkValidPassword(rEntry, rData, *oDecryptedSize, aMutexHolder)); if (!xRet.is())
{ throw packages::WrongPasswordException(THROW_WHERE);
} return xRet;
} else
bNeedRawStream = ( rEntry.nMethod == STORED );
// Pass in a shared name buffer to reduce the number of allocations // we do when reading the CEN.
sal_uInt64 ZipFile::readLOC_Impl(ZipEntry &rEntry, std::vector<sal_Int8>& rNameBuffer, std::vector<sal_Int8>& rExtraBuffer)
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
sal_Int32 nTestSig = headerMemGrabber.ReadInt32(); if (nTestSig != LOCSIG) throw ZipIOException(u"Invalid LOC header (bad signature)"_ustr );
// Ignore all (duplicated) information from the local file header. // various programs produced "broken" zip files; even LO at some point. // Just verify the path and calculate the data offset and otherwise // rely on the central directory info.
// version - ignore any mismatch (Maven created JARs)
sal_uInt16 const nVersion = headerMemGrabber.ReadUInt16();
sal_uInt16 const nLocFlag = headerMemGrabber.ReadUInt16(); // general purpose bit flag
sal_uInt16 const nLocMethod = headerMemGrabber.ReadUInt16(); // compression method // Do *not* compare timestamps, since MSO 2010 can produce documents // with timestamp difference in the central directory entry and local // file header.
headerMemGrabber.ReadInt32(); //time
sal_uInt32 nLocCrc = headerMemGrabber.ReadUInt32(); //crc
sal_uInt64 nLocCompressedSize = headerMemGrabber.ReadUInt32(); //compressed size
sal_uInt64 nLocSize = headerMemGrabber.ReadUInt32(); //size
sal_Int16 nPathLen = headerMemGrabber.ReadInt16();
sal_Int16 nExtraLen = headerMemGrabber.ReadInt16();
if (nPathLen < 0)
{
SAL_WARN("package", "bogus path len of: " << nPathLen);
nPathLen = 0;
}
try
{ // read always in UTF8, some tools seem not to set UTF8 bit // coverity[tainted_data] - we've checked negative lens, and up to max short is ok here
rNameBuffer.resize(nPathLen);
sal_Int32 nRead = aGrabber.readBytes(rNameBuffer.data(), nPathLen);
std::string_view aNameView(reinterpret_cast<constchar *>(rNameBuffer.data()), nRead);
// Just plain ignore bits 1 & 2 of the flag field - they are either // purely informative, or even fully undefined (depending on method). // Also ignore bit 11 ("Language encoding flag"): tdf125300.docx is // example with mismatch - and the actual file names are compared in // any case and required to be UTF-8. if ((rEntry.nFlag & ~0x806U) != (nLocFlag & ~0x806U))
{
SAL_INFO("package", "LOC inconsistent flag: \"" << rEntry.sPath << "\"");
bBroken = true;
}
if ( aEntry.nExtraLen < 0 ) throw ZipException(u"unexpected extra header info length"_ustr );
if (aEntry.nPathLen > aMemGrabber.remainingSize()) throw ZipException(u"name too long"_ustr);
// read always in UTF8, some tools seem not to set UTF8 bit
std::string_view aPathView(reinterpret_cast<charconst *>(aMemGrabber.getCurrentPos()), aEntry.nPathLen);
aEntry.sPath = OUString( aPathView.data(), aPathView.size(), RTL_TEXTENCODING_UTF8 );
if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( aEntry.sPath, true ) ) throw ZipException(u"Zip entry has an invalid name."_ustr );
// Is this a FAT-compatible empty entry? if (aEntry.nSize == 0 && (versionMadeBy & 0xff00) == 0)
{
constexpr sal_uInt32 FILE_ATTRIBUTE_DIRECTORY = 16; if (externalFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; // This is a directory entry, not a stream - skip it
}
if (aEntries.find(aEntry.sPath) != aEntries.end())
{
SAL_INFO("package", "Duplicate CEN entry: \"" << aEntry.sPath << "\""); throw ZipException(u"Duplicate CEN entry"_ustr);
} if (aEntries.empty() && m_Checks == Checks::TryCheckInsensitive)
{ if (aEntry.sPath == "mimetype" && aEntry.nSize == 0)
{ // tdf#162866 AutoCorrect uses ODF package, directories are
m_Checks = Checks::Default; // user-defined => ignore!
} else
{
m_Checks = Checks::CheckInsensitive;
}
} // this is required for OOXML, but not for ODF autoconst lowerPath(aEntry.sPath.toAsciiLowerCase()); if (!m_EntriesInsensitive.insert(lowerPath).second && m_Checks == Checks::CheckInsensitive)
{
SAL_INFO("package", "Duplicate CEN entry (case insensitive): \"" << aEntry.sPath << "\""); throw ZipException(u"Duplicate CEN entry (case insensitive)"_ustr);
}
aEntries[aEntry.sPath] = aEntry;
}
if (nCount != nTotal) throw ZipException(u"Count != Total"_ustr ); if (!unallocated.empty())
{ if (std::all_of(unallocated.begin(), unallocated.end(), [](autoconst& it) { return it.second - it.first == 12 || it.second - it.first == 16;
}))
{ throw ZipException(u"Zip file has holes the size of data descriptors; producer forgot to set flag bit 3?"_ustr);
} throw ZipException(u"Zip file has holes! It will leak!"_ustr);
}
} catch ( IllegalArgumentException & )
{ // seek can throw this...
nCenPos = -1; // make sure we return -1 to indicate an error
} return nCenPos;
}
bool ZipFile::readExtraFields(MemoryByteGrabber& aMemGrabber, sal_Int16 nExtraLen,
sal_uInt64& nSize, sal_uInt64& nCompressedSize,
std::optional<sal_uInt64> & roOffset,
std::string_view const * pCENFilenameToCheck)
{ bool isZip64{false}; while (nExtraLen > 0) // Extensible data fields
{
sal_Int16 nheaderID = aMemGrabber.ReadInt16();
sal_uInt16 dataSize = aMemGrabber.ReadUInt16(); if (nheaderID == 1) // Load Zip64 Extended Information Extra Field
{ // Datasize should be 28byte but some files have less (maybe non standard?)
nSize = aMemGrabber.ReadUInt64();
sal_uInt16 nReadSize = 8; if (dataSize >= 16)
{
nCompressedSize = aMemGrabber.ReadUInt64();
nReadSize = 16; if (dataSize >= 24)
{
roOffset.emplace(aMemGrabber.ReadUInt64());
nReadSize = 24; // 4 byte should be "Disk Start Number" but we not need it
}
} if (dataSize > nReadSize)
aMemGrabber.skipBytes(dataSize - nReadSize);
isZip64 = true;
} // Info-ZIP Unicode Path Extra Field - pointless as we expect UTF-8 in CEN already elseif (nheaderID == 0x7075 && pCENFilenameToCheck) // ignore in recovery mode
{ if (aMemGrabber.remainingSize() < dataSize)
{
SAL_INFO("package", "Invalid Info-ZIP Unicode Path Extra Field: invalid TSize"); throw ZipException(u"Invalid Info-ZIP Unicode Path Extra Field"_ustr);
} autoconst nVersion = aMemGrabber.ReadUInt8(); if (nVersion != 1)
{
SAL_INFO("package", "Invalid Info-ZIP Unicode Path Extra Field: unexpected Version"); throw ZipException(u"Invalid Info-ZIP Unicode Path Extra Field"_ustr);
} // this CRC32 is actually of the pCENFilenameToCheck // so it's pointless to check it if we require the UnicodeName // to be equal to the CEN name anyway (and pCENFilenameToCheck // is already converted to UTF-16 here)
(void) aMemGrabber.ReadUInt32(); // this is required to be UTF-8
std::string_view unicodePath(reinterpret_cast<charconst *>(aMemGrabber.getCurrentPos()),
dataSize - 5);
aMemGrabber.skipBytes(dataSize - 5); if (unicodePath != *pCENFilenameToCheck)
{
SAL_INFO("package", "Invalid Info-ZIP Unicode Path Extra Field: unexpected UnicodeName"); throw ZipException(u"Invalid Info-ZIP Unicode Path Extra Field"_ustr);
}
} else
{
aMemGrabber.skipBytes(dataSize);
}
nExtraLen -= dataSize + 4;
} return isZip64;
}
// Do not add this entry, if it is empty and is a directory of an already existing entry if (aEntry.nSize == 0 && aEntry.nCompressedSize == 0
&& std::find_if(aEntries.begin(), aEntries.end(),
[path = OUString(aEntry.sPath + "/")](constauto& r)
{ return r.first.startsWith(path); })
!= aEntries.end()) returnfalse;
autoconst lowerPath(aEntry.sPath.toAsciiLowerCase()); if (m_EntriesInsensitive.find(lowerPath) != m_EntriesInsensitive.end())
{ // this is required for OOXML, but not for ODF returnfalse;
}
m_EntriesInsensitive.insert(lowerPath);
aEntries.emplace(aEntry.sPath, aEntry);
// Drop any "directory" entry corresponding to this one's path; since we don't use // central directory, we don't see external file attributes, so sanitize here
sal_Int32 i = 0; for (OUString subdir = aEntry.sPath.getToken(0, '/', i); i >= 0;
subdir += OUString::Concat("/") + o3tl::getToken(aEntry.sPath, 0, '/', i))
{ if (auto it = aEntries.find(subdir); it != aEntries.end())
{ // if not empty, let it fail later in ZipPackage::getZipFileContents if (it->second.nSize == 0 && it->second.nCompressedSize == 0)
aEntries.erase(it);
}
} return (aEntry.nFlag & 8) && (aEntry.nCompressedSize == 0);
}
// FIXME64: find a better way to recognize if Zip64 mode is used // Now we check if the memory at +16 byte seems to be a signature // if not, then probably Zip64 mode is used here, except // if memory at +24 byte seems not to be a signature. // Normally Data Descriptor should followed by the next Local File header // that should start with PK34, except for the last file, then it may // followed by Central directory that start with PK12, or // followed by "archive decryption header" that don't have a signature. if ((data[16] == 'P' && data[17] == 'K' && data[19] == data[18] + 1
&& (data[18] == 3 || data[18] == 1))
|| !(data[24] == 'P' && data[25] == 'K' && data[27] == data[26] + 1
&& (data[26] == 3 || data[26] == 1)))
{
nCompressedSize = aMemGrabber.ReadUInt32();
nSize = aMemGrabber.ReadUInt32();
} else
{
nCompressedSize = aMemGrabber.ReadUInt64();
nSize = aMemGrabber.ReadUInt64();
}
TryDDImpl(dataOffset, nCRC32, nCompressedSize, nSize);
}
bool ZipFile::TryDDImpl(sal_Int64 const dataOffset, sal_Int32 const nCRC32,
sal_Int64 const nCompressedSize, sal_Int64 const nSize)
{ for (auto& rEntry : aEntries)
{ // this is a broken package, accept this block not only for DEFLATED streams if ((rEntry.second.nFlag & 8) == 0) continue;
sal_Int64 nStreamOffset = dataOffset - nCompressedSize; if (nStreamOffset == rEntry.second.nOffset
&& nCompressedSize > rEntry.second.nCompressedSize)
{ // only DEFLATED blocks need to be checked bool bAcceptBlock = (rEntry.second.nMethod == STORED && nCompressedSize == nSize);
sal_Int64 nPos = 0; // the buffer should contain at least one header, // or if it is end of the file, at least the postheader with sizes and hash while( nPos < nBufSize - 30
|| ( nBufSize < nToRead && nPos < nBufSize - 16 ) )
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.