/* -*- 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 <filter/msfilter/mscodec.hxx>
#include <osl/diagnose.h>
#include <algorithm>
#include <string.h>
#include <tools/solar.h>
#include <comphelper/hash.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <comphelper/docpasswordhelper.hxx>
#include <com/sun/star/beans/NamedValue.hpp>
#include <utility>
#define DEBUG_MSO_ENCRYPTION_STD97 0
#if DEBUG_MSO_ENCRYPTION_STD97
#include <stdio.h>
#endif
using namespace ::com::sun::star;
namespace msfilter {
namespace {
/** Rotates rnValue left by nBits bits. */
template< typename Type >
void lclRotateLeft( Type& rnValue, int nBits )
{
OSL_ASSERT(
nBits >= 0 &&
sal::static_int_cast< unsigned int >(nBits) < sizeof( Type ) * 8 );
rnValue = static_cast< Type >( (rnValue << nBits) | (rnValue >> (sizeof( Type ) * 8 - nBits)) );
}
/** Rotates the lower nWidth bits of rnValue left by nBits bits. */
template< typename Type >
void lclRotateLeft( Type& rnValue, sal_uInt8 nBits, sal_uInt8 nWidth )
{
OSL_ASSERT( (nBits < nWidth) && (nWidth < sizeof( Type ) * 8) );
Type nMask = static_cast< Type >( (1UL << nWidth) - 1 );
rnValue = static_cast< Type >(
((rnValue << nBits) | ((rnValue & nMask) >> (nWidth - nBits))) & nMask );
}
std::size_t lclGetLen( const sal_uInt8* pnPassData, std::size_t nBufferSize )
{
std::size_t nLen = 0;
while( (nLen < nBufferSize) && pnPassData[ nLen ] ) ++nLen;
return nLen;
}
sal_uInt16 lclGetKey( const sal_uInt8* pnPassData, std::size_t nBufferSize )
{
std::size_t nLen = lclGetLen( pnPassData, nBufferSize );
if( !nLen ) return 0;
sal_uInt16 nKey = 0;
sal_uInt16 nKeyBase = 0x8000;
sal_uInt16 nKeyEnd = 0xFFFF;
const sal_uInt8* pnChar = pnPassData + nLen - 1;
for( std::size_t nIndex = 0; nIndex < nLen; ++nIndex, --pnChar )
{
sal_uInt8 cChar = *pnChar & 0x7F;
for( sal_uInt8 nBit = 0; nBit < 8; ++nBit )
{
lclRotateLeft( nKeyBase, 1 );
if( nKeyBase & 1 ) nKeyBase ^= 0x1020;
if( cChar & 1 ) nKey ^= nKeyBase;
cChar >>= 1;
lclRotateLeft( nKeyEnd, 1 );
if( nKeyEnd & 1 ) nKeyEnd ^= 0x1020;
}
}
return nKey ^ nKeyEnd;
}
sal_uInt16 lclGetHash( const sal_uInt8* pnPassData, std::size_t nBufferSize )
{
std::size_t nLen = lclGetLen( pnPassData, nBufferSize );
sal_uInt16 nHash = static_cast< sal_uInt16 >( nLen );
if( nLen )
nHash ^= 0xCE4B;
const sal_uInt8* pnChar = pnPassData;
for( std::size_t nIndex = 0; nIndex < nLen; ++nIndex, ++pnChar )
{
sal_uInt16 cChar = *pnChar;
sal_uInt8 nRot = static_cast< sal_uInt8 >( (nIndex + 1) % 15 );
lclRotateLeft( cChar, nRot, 15 );
nHash ^= cChar;
}
return nHash;
}
} // namespace
MSCodec_Xor95::MSCodec_Xor95(int nRotateDistance) :
mnOffset( 0 ),
mnKey( 0 ),
mnHash( 0 ),
mnRotateDistance( nRotateDistance )
{
}
MSCodec_Xor95::~MSCodec_Xor95()
{
memset( mpnKey, 0, sizeof( mpnKey ) );
mnKey = mnHash = 0;
}
void MSCodec_Xor95::InitKey( const sal_uInt8 pnPassData[ 16 ] )
{
mnKey = lclGetKey( pnPassData, 16 );
mnHash = lclGetHash( pnPassData, 16 );
memcpy( mpnKey, pnPassData, 16 );
static const sal_uInt8 spnFillChars[] =
{
0xBB, 0xFF, 0xFF, 0xBA,
0xFF, 0xFF, 0xB9, 0x80,
0x00, 0xBE, 0x0F, 0x00,
0xBF, 0x0F, 0x00, 0x00
};
std::size_t nLen = lclGetLen( pnPassData, 16 );
const sal_uInt8* pnFillChar = spnFillChars;
for (std::size_t nIndex = nLen; nIndex < sizeof(mpnKey); ++nIndex, ++pnFillChar)
mpnKey[ nIndex ] = *pnFillChar;
SVBT16 pnOrigKey;
ShortToSVBT16( mnKey, pnOrigKey );
sal_uInt8* pnKeyChar = mpnKey;
for (std::size_t nIndex = 0; nIndex < sizeof(mpnKey); ++nIndex, ++pnKeyChar)
{
*pnKeyChar ^= pnOrigKey[ nIndex & 0x01 ];
lclRotateLeft( *pnKeyChar, mnRotateDistance );
}
}
bool MSCodec_Xor95::InitCodec( const uno::Sequence< beans::NamedValue >& aData )
{
bool bResult = false;
::comphelper::SequenceAsHashMap aHashData( aData );
uno::Sequence< sal_Int8 > aKey = aHashData.getUnpackedValueOrDefault(u"XOR95EncryptionKey"_ustr, uno::Sequence< sal_Int8 >() );
if ( aKey.getLength() == 16 )
{
memcpy( mpnKey, aKey.getConstArray(), 16 );
bResult = true;
mnKey = static_cast<sal_uInt16>(aHashData.getUnpackedValueOrDefault(u"XOR95BaseKey"_ustr, sal_Int16(0) ));
mnHash = static_cast<sal_uInt16>(aHashData.getUnpackedValueOrDefault(u"XOR95PasswordHash"_ustr, sal_Int16(0) ));
}
else
OSL_FAIL( "Unexpected key size!" );
return bResult;
}
uno::Sequence< beans::NamedValue > MSCodec_Xor95::GetEncryptionData()
{
::comphelper::SequenceAsHashMap aHashData;
// coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
aHashData[ u"XOR95EncryptionKey"_ustr ] <<= uno::Sequence<sal_Int8>( reinterpret_cast<sal_Int8*>(mpnKey), 16 );
aHashData[ u"XOR95BaseKey"_ustr ] <<= static_cast<sal_Int16>(mnKey);
aHashData[ u"XOR95PasswordHash"_ustr ] <<= static_cast<sal_Int16>(mnHash);
return aHashData.getAsConstNamedValueList();
}
bool MSCodec_Xor95::VerifyKey( sal_uInt16 nKey, sal_uInt16 nHash ) const
{
return (nKey == mnKey) && (nHash == mnHash);
}
void MSCodec_Xor95::InitCipher()
{
mnOffset = 0;
}
void MSCodec_XorXLS95::Decode( sal_uInt8* pnData, std::size_t nBytes )
{
const sal_uInt8* pnCurrKey = mpnKey + mnOffset;
const sal_uInt8* pnKeyLast = mpnKey + 0x0F;
for( const sal_uInt8* pnDataEnd = pnData + nBytes; pnData < pnDataEnd; ++pnData )
{
lclRotateLeft( *pnData, 3 );
*pnData ^= *pnCurrKey;
if( pnCurrKey < pnKeyLast ) ++pnCurrKey; else pnCurrKey = mpnKey;
}
// update mnOffset
Skip( nBytes );
}
void MSCodec_XorWord95::Decode( sal_uInt8* pnData, std::size_t nBytes )
{
const sal_uInt8* pnCurrKey = mpnKey + mnOffset;
const sal_uInt8* pnKeyLast = mpnKey + 0x0F;
for( const sal_uInt8* pnDataEnd = pnData + nBytes; pnData < pnDataEnd; ++pnData )
{
const sal_uInt8 cChar = *pnData ^ *pnCurrKey;
if (*pnData && cChar)
*pnData = cChar;
if( pnCurrKey < pnKeyLast )
++pnCurrKey;
else
pnCurrKey = mpnKey;
}
// update mnOffset
Skip( nBytes );
}
void MSCodec_Xor95::Skip( std::size_t nBytes )
{
mnOffset = (mnOffset + nBytes) & 0x0F;
}
MSCodec97::MSCodec97(size_t nHashLen, OUString aEncKeyName)
: m_sEncKeyName(std::move(aEncKeyName))
, m_nHashLen(nHashLen)
, m_hCipher(rtl_cipher_create(rtl_Cipher_AlgorithmARCFOUR, rtl_Cipher_ModeStream))
, m_aDocId(16, 0)
, m_aDigestValue(nHashLen, 0)
{
assert(m_hCipher != nullptr);
}
MSCodec_Std97::MSCodec_Std97()
: MSCodec97(RTL_DIGEST_LENGTH_MD5, u"STD97EncryptionKey"_ustr)
{
m_hDigest = rtl_digest_create(rtl_Digest_AlgorithmMD5);
assert(m_hDigest != nullptr);
}
MSCodec_CryptoAPI::MSCodec_CryptoAPI()
: MSCodec97(RTL_DIGEST_LENGTH_SHA1, u"CryptoAPIEncryptionKey"_ustr)
{
}
MSCodec97::~MSCodec97()
{
memset(m_aDigestValue.data(), 0, m_aDigestValue.size());
memset(m_aDocId.data(), 0, m_aDocId.size());
rtl_cipher_destroy(m_hCipher);
}
MSCodec_Std97::~MSCodec_Std97()
{
rtl_digest_destroy(m_hDigest);
}
#if DEBUG_MSO_ENCRYPTION_STD97
static void lcl_PrintDigest(const sal_uInt8* pDigest, const char* msg)
{
printf("digest: (%s)\n", msg);
for (int i = 0; i < 16; ++i)
printf("%2.2x ", pDigest[i]);
printf("\n");
}
#else
static void lcl_PrintDigest(const sal_uInt8* /*pDigest*/, const char* /*msg*/)
{
}
#endif
bool MSCodec97::InitCodec( const uno::Sequence< beans::NamedValue >& aData )
{
#if DEBUG_MSO_ENCRYPTION_STD97
fprintf(stdout, "MSCodec_Std97::InitCodec: --begin\n");fflush(stdout);
#endif
bool bResult = false;
::comphelper::SequenceAsHashMap aHashData( aData );
uno::Sequence<sal_Int8> aKey = aHashData.getUnpackedValueOrDefault(m_sEncKeyName, uno::Sequence<sal_Int8>());
const size_t nKeyLen = aKey.getLength();
if (nKeyLen == m_nHashLen)
{
assert(m_aDigestValue.size() == m_nHashLen);
memcpy(m_aDigestValue.data(), aKey.getConstArray(), m_nHashLen);
uno::Sequence< sal_Int8 > aUniqueID = aHashData.getUnpackedValueOrDefault(u"STD97UniqueID"_ustr, uno::Sequence< sal_Int8 >() );
if ( aUniqueID.getLength() == 16 )
{
assert(m_aDocId.size() == static_cast<size_t>(aUniqueID.getLength()));
memcpy(m_aDocId.data(), aUniqueID.getConstArray(), m_aDocId.size());
bResult = true;
lcl_PrintDigest(m_aDigestValue.data(), "digest value");
lcl_PrintDigest(m_aDocId.data(), "DocId value");
}
else
OSL_FAIL( "Unexpected document ID!" );
}
else
OSL_FAIL( "Unexpected key size!" );
return bResult;
}
uno::Sequence< beans::NamedValue > MSCodec97::GetEncryptionData()
{
::comphelper::SequenceAsHashMap aHashData;
assert(m_aDigestValue.size() == m_nHashLen);
aHashData[m_sEncKeyName] <<= uno::Sequence<sal_Int8>(reinterpret_cast<sal_Int8*>(m_aDigestValue.data()), m_nHashLen);
aHashData[ u"STD97UniqueID"_ustr ] <<= uno::Sequence< sal_Int8 >( reinterpret_cast<sal_Int8*>(m_aDocId.data()), m_aDocId.size() );
return aHashData.getAsConstNamedValueList();
}
void MSCodec_Std97::InitKey (
const sal_uInt16 pPassData[16],
const sal_uInt8 pDocId[16])
{
#if DEBUG_MSO_ENCRYPTION_STD97
fprintf(stdout, "MSCodec_Std97::InitKey: --begin\n");fflush(stdout);
#endif
uno::Sequence< sal_Int8 > aKey = ::comphelper::DocPasswordHelper::GenerateStd97Key(pPassData, pDocId);
// Fill raw digest of above updates into DigestValue.
const size_t nKeyLen = aKey.getLength();
if (m_aDigestValue.size() == nKeyLen)
memcpy(m_aDigestValue.data(), aKey.getConstArray(), m_aDigestValue.size());
else
memset(m_aDigestValue.data(), 0, m_aDigestValue.size());
lcl_PrintDigest(m_aDigestValue.data(), "digest value");
memcpy (m_aDocId.data(), pDocId, 16);
lcl_PrintDigest(m_aDocId.data(), "DocId value");
}
void MSCodec_CryptoAPI::InitKey (
const sal_uInt16 pPassData[16],
const sal_uInt8 pDocId[16])
{
sal_uInt32 const saltSize = 16;
// Prepare initial data -> salt + password (in 16-bit chars)
std::vector<sal_uInt8> initialData(pDocId, pDocId + saltSize);
// Fill PassData into KeyData.
for (sal_Int32 nInd = 0; nInd < 16 && pPassData[nInd]; ++nInd)
{
initialData.push_back(sal::static_int_cast<sal_uInt8>((pPassData[nInd] >> 0) & 0xff));
initialData.push_back(sal::static_int_cast<sal_uInt8>((pPassData[nInd] >> 8) & 0xff));
}
// calculate SHA1 hash of initialData
std::vector<unsigned char> const sha1(::comphelper::Hash::calculateHash(
initialData.data(), initialData.size(),
::comphelper::HashType::SHA1));
m_aDigestValue = sha1;
lcl_PrintDigest(m_aDigestValue.data(), "digest value");
memcpy(m_aDocId.data(), pDocId, 16);
lcl_PrintDigest(m_aDocId.data(), "DocId value");
//generate the old format key while we have the required data
m_aStd97Key = ::comphelper::DocPasswordHelper::GenerateStd97Key(pPassData, pDocId);
}
bool MSCodec97::VerifyKey(const sal_uInt8* pSaltData, const sal_uInt8* pSaltDigest)
{
// both the salt data and salt digest (hash) come from the document being imported.
#if DEBUG_MSO_ENCRYPTION_STD97
fprintf(stdout, "MSCodec97::VerifyKey: \n");
lcl_PrintDigest(pSaltData, "salt data");
lcl_PrintDigest(pSaltDigest, "salt hash");
#endif
bool result = false;
if (InitCipher(0))
{
std::vector<sal_uInt8> aDigest(m_nHashLen);
GetDigestFromSalt(pSaltData, aDigest.data());
std::vector<sal_uInt8> aBuffer(m_nHashLen);
// Decode original SaltDigest into Buffer.
rtl_cipher_decode(m_hCipher, pSaltDigest, m_nHashLen, aBuffer.data(), m_nHashLen);
// Compare Buffer with computed Digest.
result = (memcmp(aBuffer.data(), aDigest.data(), m_nHashLen) == 0);
// Erase Buffer and Digest arrays.
rtl_secureZeroMemory(aBuffer.data(), m_nHashLen);
rtl_secureZeroMemory(aDigest.data(), m_nHashLen);
}
return result;
}
void MSCodec_CryptoAPI::GetDigestFromSalt(const sal_uInt8* pSaltData, sal_uInt8* pDigest)
{
std::vector<sal_uInt8> verifier(16);
rtl_cipher_decode(m_hCipher,
pSaltData, 16, verifier.data(), verifier.size());
std::vector<unsigned char> const sha1(::comphelper::Hash::calculateHash(
verifier.data(), verifier.size(), ::comphelper::HashType::SHA1));
::std::copy(sha1.begin(), sha1.end(), pDigest);
}
bool MSCodec_Std97::InitCipher(sal_uInt32 nCounter)
{
sal_uInt8 pKeyData[64] = {}; // 512-bit message block
// Fill 40 bit of DigestValue into [0..4].
memcpy (pKeyData, m_aDigestValue.data(), 5);
// Fill counter into [5..8].
pKeyData[ 5] = sal_uInt8((nCounter >> 0) & 0xff);
pKeyData[ 6] = sal_uInt8((nCounter >> 8) & 0xff);
pKeyData[ 7] = sal_uInt8((nCounter >> 16) & 0xff);
pKeyData[ 8] = sal_uInt8((nCounter >> 24) & 0xff);
pKeyData[ 9] = 0x80;
pKeyData[56] = 0x48;
// Fill raw digest of KeyData into KeyData.
(void)rtl_digest_updateMD5 (
m_hDigest, pKeyData, sizeof(pKeyData));
(void)rtl_digest_rawMD5 (
m_hDigest, pKeyData, RTL_DIGEST_LENGTH_MD5);
// Initialize Cipher with KeyData (for decoding).
rtlCipherError result = rtl_cipher_init (
m_hCipher, rtl_Cipher_DirectionBoth,
pKeyData, RTL_DIGEST_LENGTH_MD5, nullptr, 0);
// Erase KeyData array and leave.
rtl_secureZeroMemory (pKeyData, sizeof(pKeyData));
return (result == rtl_Cipher_E_None);
}
bool MSCodec_CryptoAPI::InitCipher(sal_uInt32 nCounter)
{
// data = hash + iterator (4bytes)
std::vector<sal_uInt8> aKeyData(m_aDigestValue);
aKeyData.push_back(sal_uInt8((nCounter >> 0) & 0xff));
aKeyData.push_back(sal_uInt8((nCounter >> 8) & 0xff));
aKeyData.push_back(sal_uInt8((nCounter >> 16) & 0xff));
aKeyData.push_back(sal_uInt8((nCounter >> 24) & 0xff));
std::vector<unsigned char> const hash(::comphelper::Hash::calculateHash(
aKeyData.data(), aKeyData.size(), ::comphelper::HashType::SHA1));
rtlCipherError result =
rtl_cipher_init(m_hCipher, rtl_Cipher_DirectionDecode,
hash.data(), ENCRYPT_KEY_SIZE_AES_128/8, nullptr, 0);
return (result == rtl_Cipher_E_None);
}
uno::Sequence<beans::NamedValue> MSCodec_CryptoAPI::GetEncryptionData()
{
::comphelper::SequenceAsHashMap aHashData(MSCodec97::GetEncryptionData());
//add in the old encryption key as well as our new key so saving using the
//old crypto scheme can be done without reprompt for the password
aHashData[u"STD97EncryptionKey"_ustr] <<= m_aStd97Key;
return aHashData.getAsConstNamedValueList();
}
void MSCodec_Std97::CreateSaltDigest( const sal_uInt8 nSaltData[16], sal_uInt8 nSaltDigest[16] )
{
#if DEBUG_MSO_ENCRYPTION_STD97
lcl_PrintDigest(nSaltData, "salt data");
#endif
if (InitCipher(0))
{
sal_uInt8 pDigest[RTL_DIGEST_LENGTH_MD5];
GetDigestFromSalt(nSaltData, pDigest);
rtl_cipher_decode (
m_hCipher, pDigest, 16, pDigest, sizeof(pDigest));
memcpy(nSaltDigest, pDigest, 16);
}
}
bool MSCodec97::Encode (
const void *pData, std::size_t nDatLen,
sal_uInt8 *pBuffer, std::size_t nBufLen)
{
rtlCipherError result = rtl_cipher_encode(
m_hCipher, pData, nDatLen, pBuffer, nBufLen);
return (result == rtl_Cipher_E_None);
}
bool MSCodec97::Decode (
const void *pData, std::size_t nDatLen,
sal_uInt8 *pBuffer, std::size_t nBufLen)
{
rtlCipherError result = rtl_cipher_decode(
m_hCipher, pData, nDatLen, pBuffer, nBufLen);
return (result == rtl_Cipher_E_None);
}
bool MSCodec97::Skip(std::size_t nDatLen)
{
sal_uInt8 pnDummy[ 1024 ];
std::size_t nDatLeft = nDatLen;
bool bResult = true;
while (bResult && nDatLeft)
{
std::size_t nBlockLen = ::std::min< std::size_t >( nDatLeft, sizeof(pnDummy) );
bResult = Decode( pnDummy, nBlockLen, pnDummy, nBlockLen );
nDatLeft -= nBlockLen;
}
return bResult;
}
void MSCodec_Std97::GetDigestFromSalt(const sal_uInt8* pSaltData, sal_uInt8* pDigest)
{
sal_uInt8 pBuffer[64];
sal_uInt8 pDigestLocal[16];
// Decode SaltData into Buffer.
rtl_cipher_decode (
m_hCipher, pSaltData, 16, pBuffer, sizeof(pBuffer));
// set the 129th bit to make the buffer 128-bit in length.
pBuffer[16] = 0x80;
// erase the rest of the buffer with zeros.
memset (pBuffer + 17, 0, sizeof(pBuffer) - 17);
// set the 441st bit.
pBuffer[56] = 0x80;
// Fill raw digest of Buffer into Digest.
rtl_digest_updateMD5 (
m_hDigest, pBuffer, sizeof(pBuffer));
rtl_digest_rawMD5 (
m_hDigest, pDigestLocal, sizeof(pDigestLocal));
memcpy(pDigest, pDigestLocal, 16);
}
void MSCodec_Std97::GetEncryptKey (
const sal_uInt8 pSalt[16],
sal_uInt8 pSaltData[16],
sal_uInt8 pSaltDigest[16])
{
if (!InitCipher(0))
return;
sal_uInt8 pDigest[RTL_DIGEST_LENGTH_MD5];
sal_uInt8 pBuffer[64];
rtl_cipher_encode (
m_hCipher, pSalt, 16, pSaltData, sizeof(pBuffer));
memcpy( pBuffer, pSalt, 16 );
pBuffer[16] = 0x80;
memset (pBuffer + 17, 0, sizeof(pBuffer) - 17);
pBuffer[56] = 0x80;
rtl_digest_updateMD5 (
m_hDigest, pBuffer, sizeof(pBuffer));
rtl_digest_rawMD5 (
m_hDigest, pDigest, sizeof(pDigest));
rtl_cipher_encode (
m_hCipher, pDigest, 16, pSaltDigest, 16);
rtl_secureZeroMemory (pBuffer, sizeof(pBuffer));
rtl_secureZeroMemory (pDigest, sizeof(pDigest));
}
void MSCodec97::GetDocId( sal_uInt8 pDocId[16] )
{
assert(m_aDocId.size() == 16);
memcpy(pDocId, m_aDocId.data(), 16);
}
EncryptionStandardHeader::EncryptionStandardHeader()
{
flags = 0;
sizeExtra = 0;
algId = 0;
algIdHash = 0;
keyBits = 0;
providedType = 0;
reserved1 = 0;
reserved2 = 0;
}
EncryptionVerifierAES::EncryptionVerifierAES()
: saltSize(SALT_LENGTH)
, encryptedVerifierHashSize(comphelper::SHA1_HASH_LENGTH)
{
}
EncryptionVerifierRC4::EncryptionVerifierRC4()
: saltSize(SALT_LENGTH)
, encryptedVerifierHashSize(comphelper::SHA1_HASH_LENGTH)
{
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V614 Uninitialized buffer 'pnDummy' used. Consider checking the first actual argument of the 'Decode' function.
↑ V1086 A call of the 'memcpy' function will lead to underflow of the buffer 'pKeyData'.