| /************************************************************** |
| * |
| * 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 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| #include "oox/core/binarycodec.hxx" |
| |
| #include <algorithm> |
| #include <string.h> |
| #include "oox/helper/attributelist.hxx" |
| |
| #include <comphelper/sequenceashashmap.hxx> |
| #include <comphelper/docpasswordhelper.hxx> |
| |
| using namespace ::com::sun::star; |
| |
| namespace oox { |
| namespace core { |
| |
| // ============================================================================ |
| |
| namespace { |
| |
| /** Rotates rnValue left by nBits bits. */ |
| template< typename Type > |
| inline void lclRotateLeft( Type& rnValue, size_t nBits ) |
| { |
| OSL_ENSURE( nBits < sizeof( Type ) * 8, "lclRotateLeft - rotation count overflow" ); |
| 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 > |
| inline void lclRotateLeft( Type& rnValue, size_t nBits, size_t nWidth ) |
| { |
| OSL_ENSURE( (nBits < nWidth) && (nWidth < sizeof( Type ) * 8), "lclRotateLeft - rotation count overflow" ); |
| Type nMask = static_cast< Type >( (1UL << nWidth) - 1 ); |
| rnValue = static_cast< Type >( |
| ((rnValue << nBits) | ((rnValue & nMask) >> (nWidth - nBits))) & nMask ); |
| } |
| |
| sal_Int32 lclGetLen( const sal_uInt8* pnPassData, sal_Int32 nBufferSize ) |
| { |
| sal_Int32 nLen = 0; |
| while( (nLen < nBufferSize) && pnPassData[ nLen ] ) ++nLen; |
| return nLen; |
| } |
| |
| sal_uInt16 lclGetKey( const sal_uInt8* pnPassData, sal_Int32 nBufferSize ) |
| { |
| sal_Int32 nLen = lclGetLen( pnPassData, nBufferSize ); |
| if( nLen <= 0 ) return 0; |
| |
| sal_uInt16 nKey = 0; |
| sal_uInt16 nKeyBase = 0x8000; |
| sal_uInt16 nKeyEnd = 0xFFFF; |
| const sal_uInt8* pnChar = pnPassData + nLen - 1; |
| for( sal_Int32 nIndex = 0; nIndex < nLen; ++nIndex, --pnChar ) |
| { |
| sal_uInt8 cChar = *pnChar & 0x7F; |
| for( size_t 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, sal_Int32 nBufferSize ) |
| { |
| sal_Int32 nLen = lclGetLen( pnPassData, nBufferSize ); |
| |
| sal_uInt16 nHash = static_cast< sal_uInt16 >( nLen ); |
| if( nLen > 0 ) |
| nHash ^= 0xCE4B; |
| |
| const sal_uInt8* pnChar = pnPassData; |
| for( sal_Int32 nIndex = 0; nIndex < nLen; ++nIndex, ++pnChar ) |
| { |
| sal_uInt16 cChar = *pnChar; |
| size_t nRot = static_cast< size_t >( (nIndex + 1) % 15 ); |
| lclRotateLeft( cChar, nRot, 15 ); |
| nHash ^= cChar; |
| } |
| return nHash; |
| } |
| |
| } // namespace |
| |
| // ============================================================================ |
| |
| /*static*/ sal_uInt16 CodecHelper::getPasswordHash( const AttributeList& rAttribs, sal_Int32 nElement ) |
| { |
| sal_Int32 nPasswordHash = rAttribs.getIntegerHex( nElement, 0 ); |
| OSL_ENSURE( (0 <= nPasswordHash) && (nPasswordHash <= SAL_MAX_UINT16), "CodecHelper::getPasswordHash - invalid password hash" ); |
| return static_cast< sal_uInt16 >( ((0 <= nPasswordHash) && (nPasswordHash <= SAL_MAX_UINT16)) ? nPasswordHash : 0 ); |
| } |
| |
| // ============================================================================ |
| |
| BinaryCodec_XOR::BinaryCodec_XOR( CodecType eCodecType ) : |
| meCodecType( eCodecType ), |
| mnOffset( 0 ), |
| mnBaseKey( 0 ), |
| mnHash( 0 ) |
| { |
| (void)memset( mpnKey, 0, sizeof( mpnKey ) ); |
| } |
| |
| BinaryCodec_XOR::~BinaryCodec_XOR() |
| { |
| (void)memset( mpnKey, 0, sizeof( mpnKey ) ); |
| mnBaseKey = mnHash = 0; |
| } |
| |
| void BinaryCodec_XOR::initKey( const sal_uInt8 pnPassData[ 16 ] ) |
| { |
| // calculate base key and hash from passed password |
| mnBaseKey = lclGetKey( pnPassData, 16 ); |
| mnHash = lclGetHash( pnPassData, 16 ); |
| |
| static const sal_uInt8 spnFillChars[] = |
| { |
| 0xBB, 0xFF, 0xFF, 0xBA, |
| 0xFF, 0xFF, 0xB9, 0x80, |
| 0x00, 0xBE, 0x0F, 0x00, |
| 0xBF, 0x0F, 0x00 |
| }; |
| |
| (void)memcpy( mpnKey, pnPassData, 16 ); |
| sal_Int32 nIndex; |
| sal_Int32 nLen = lclGetLen( pnPassData, 16 ); |
| const sal_uInt8* pnFillChar = spnFillChars; |
| for( nIndex = nLen; nIndex < static_cast< sal_Int32 >( sizeof( mpnKey ) ); ++nIndex, ++pnFillChar ) |
| mpnKey[ nIndex ] = *pnFillChar; |
| |
| // rotation of key values is application dependent |
| size_t nRotateSize = 0; |
| switch( meCodecType ) |
| { |
| case CODEC_WORD: nRotateSize = 7; break; |
| case CODEC_EXCEL: nRotateSize = 2; break; |
| // compiler will warn, if new codec type is introduced and not handled here |
| } |
| |
| // use little-endian base key to create key array |
| sal_uInt8 pnBaseKeyLE[ 2 ]; |
| pnBaseKeyLE[ 0 ] = static_cast< sal_uInt8 >( mnBaseKey ); |
| pnBaseKeyLE[ 1 ] = static_cast< sal_uInt8 >( mnBaseKey >> 8 ); |
| sal_uInt8* pnKeyChar = mpnKey; |
| for( nIndex = 0; nIndex < static_cast< sal_Int32 >( sizeof( mpnKey ) ); ++nIndex, ++pnKeyChar ) |
| { |
| *pnKeyChar ^= pnBaseKeyLE[ nIndex & 1 ]; |
| lclRotateLeft( *pnKeyChar, nRotateSize ); |
| } |
| } |
| |
| bool BinaryCodec_XOR::initCodec( const uno::Sequence< beans::NamedValue >& aData ) |
| { |
| bool bResult = sal_False; |
| |
| ::comphelper::SequenceAsHashMap aHashData( aData ); |
| uno::Sequence< sal_Int8 > aKey = aHashData.getUnpackedValueOrDefault( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "XOR95EncryptionKey" ) ), uno::Sequence< sal_Int8 >() ); |
| |
| if ( aKey.getLength() == 16 ) |
| { |
| (void)memcpy( mpnKey, aKey.getConstArray(), 16 ); |
| bResult = sal_True; |
| |
| mnBaseKey = (sal_uInt16)aHashData.getUnpackedValueOrDefault( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "XOR95BaseKey" ) ), (sal_Int16)0 ); |
| mnHash = (sal_uInt16)aHashData.getUnpackedValueOrDefault( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "XOR95PasswordHash" ) ), (sal_Int16)0 ); |
| } |
| else |
| OSL_ENSURE( sal_False, "Unexpected key size!\n" ); |
| |
| return bResult; |
| } |
| |
| uno::Sequence< beans::NamedValue > BinaryCodec_XOR::getEncryptionData() |
| { |
| ::comphelper::SequenceAsHashMap aHashData; |
| aHashData[ ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "XOR95EncryptionKey" ) ) ] <<= uno::Sequence<sal_Int8>( (sal_Int8*)mpnKey, 16 ); |
| aHashData[ ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "XOR95BaseKey" ) ) ] <<= (sal_Int16)mnBaseKey; |
| aHashData[ ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "XOR95PasswordHash" ) ) ] <<= (sal_Int16)mnHash; |
| |
| return aHashData.getAsConstNamedValueList(); |
| } |
| |
| bool BinaryCodec_XOR::verifyKey( sal_uInt16 nKey, sal_uInt16 nHash ) const |
| { |
| return (nKey == mnBaseKey) && (nHash == mnHash); |
| } |
| |
| void BinaryCodec_XOR::startBlock() |
| { |
| mnOffset = 0; |
| } |
| |
| bool BinaryCodec_XOR::decode( sal_uInt8* pnDestData, const sal_uInt8* pnSrcData, sal_Int32 nBytes ) |
| { |
| const sal_uInt8* pnCurrKey = mpnKey + mnOffset; |
| const sal_uInt8* pnKeyLast = mpnKey + 0x0F; |
| |
| // switch/case outside of the for loop (performance) |
| const sal_uInt8* pnSrcDataEnd = pnSrcData + nBytes; |
| switch( meCodecType ) |
| { |
| case CODEC_WORD: |
| { |
| for( ; pnSrcData < pnSrcDataEnd; ++pnSrcData, ++pnDestData ) |
| { |
| sal_uInt8 nData = *pnSrcData ^ *pnCurrKey; |
| if( (*pnSrcData != 0) && (nData != 0) ) |
| *pnDestData = nData; |
| if( pnCurrKey < pnKeyLast ) ++pnCurrKey; else pnCurrKey = mpnKey; |
| } |
| } |
| break; |
| case CODEC_EXCEL: |
| { |
| for( ; pnSrcData < pnSrcDataEnd; ++pnSrcData, ++pnDestData ) |
| { |
| *pnDestData = *pnSrcData; |
| lclRotateLeft( *pnDestData, 3 ); |
| *pnDestData ^= *pnCurrKey; |
| if( pnCurrKey < pnKeyLast ) ++pnCurrKey; else pnCurrKey = mpnKey; |
| } |
| } |
| break; |
| // compiler will warn, if new codec type is introduced and not handled here |
| } |
| |
| // update offset and leave |
| return skip( nBytes ); |
| } |
| |
| bool BinaryCodec_XOR::skip( sal_Int32 nBytes ) |
| { |
| mnOffset = static_cast< sal_Int32 >( (mnOffset + nBytes) & 0x0F ); |
| return true; |
| } |
| |
| // ============================================================================ |
| |
| BinaryCodec_RCF::BinaryCodec_RCF() |
| { |
| mhCipher = rtl_cipher_create( rtl_Cipher_AlgorithmARCFOUR, rtl_Cipher_ModeStream ); |
| OSL_ENSURE( mhCipher != 0, "BinaryCodec_RCF::BinaryCodec_RCF - cannot create cipher" ); |
| |
| mhDigest = rtl_digest_create( rtl_Digest_AlgorithmMD5 ); |
| OSL_ENSURE( mhDigest != 0, "BinaryCodec_RCF::BinaryCodec_RCF - cannot create digest" ); |
| |
| (void)memset( mpnDigestValue, 0, sizeof( mpnDigestValue ) ); |
| (void)memset (mpnUnique, 0, sizeof(mpnUnique)); |
| } |
| |
| BinaryCodec_RCF::~BinaryCodec_RCF() |
| { |
| (void)memset( mpnDigestValue, 0, sizeof( mpnDigestValue ) ); |
| (void)memset (mpnUnique, 0, sizeof(mpnUnique)); |
| rtl_digest_destroy( mhDigest ); |
| rtl_cipher_destroy( mhCipher ); |
| } |
| |
| bool BinaryCodec_RCF::initCodec( const uno::Sequence< beans::NamedValue >& aData ) |
| { |
| bool bResult = sal_False; |
| |
| ::comphelper::SequenceAsHashMap aHashData( aData ); |
| uno::Sequence< sal_Int8 > aKey = aHashData.getUnpackedValueOrDefault( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "STD97EncryptionKey" ) ), uno::Sequence< sal_Int8 >() ); |
| |
| if ( aKey.getLength() == RTL_DIGEST_LENGTH_MD5 ) |
| { |
| (void)memcpy( mpnDigestValue, aKey.getConstArray(), RTL_DIGEST_LENGTH_MD5 ); |
| uno::Sequence< sal_Int8 > aUniqueID = aHashData.getUnpackedValueOrDefault( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "STD97UniqueID" ) ), uno::Sequence< sal_Int8 >() ); |
| if ( aUniqueID.getLength() == 16 ) |
| { |
| (void)memcpy( mpnUnique, aUniqueID.getConstArray(), 16 ); |
| bResult = sal_False; |
| } |
| else |
| OSL_ENSURE( sal_False, "Unexpected document ID!\n" ); |
| } |
| else |
| OSL_ENSURE( sal_False, "Unexpected key size!\n" ); |
| |
| return bResult; |
| } |
| |
| uno::Sequence< beans::NamedValue > BinaryCodec_RCF::getEncryptionData() |
| { |
| ::comphelper::SequenceAsHashMap aHashData; |
| aHashData[ ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "STD97EncryptionKey" ) ) ] <<= uno::Sequence< sal_Int8 >( (sal_Int8*)mpnDigestValue, RTL_DIGEST_LENGTH_MD5 ); |
| aHashData[ ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "STD97UniqueID" ) ) ] <<= uno::Sequence< sal_Int8 >( (sal_Int8*)mpnUnique, 16 ); |
| |
| return aHashData.getAsConstNamedValueList(); |
| } |
| |
| void BinaryCodec_RCF::initKey( const sal_uInt16 pnPassData[ 16 ], const sal_uInt8 pnSalt[ 16 ] ) |
| { |
| uno::Sequence< sal_Int8 > aKey = ::comphelper::DocPasswordHelper::GenerateStd97Key( pnPassData, uno::Sequence< sal_Int8 >( (sal_Int8*)pnSalt, 16 ) ); |
| // Fill raw digest of above updates into DigestValue. |
| |
| if ( aKey.getLength() == sizeof(mpnDigestValue) ) |
| (void)memcpy ( mpnDigestValue, (const sal_uInt8*)aKey.getConstArray(), sizeof(mpnDigestValue) ); |
| else |
| memset( mpnDigestValue, 0, sizeof(mpnDigestValue) ); |
| |
| (void)memcpy( mpnUnique, pnSalt, 16 ); |
| } |
| |
| bool BinaryCodec_RCF::verifyKey( const sal_uInt8 pnVerifier[ 16 ], const sal_uInt8 pnVerifierHash[ 16 ] ) |
| { |
| if( !startBlock( 0 ) ) |
| return false; |
| |
| sal_uInt8 pnDigest[ RTL_DIGEST_LENGTH_MD5 ]; |
| sal_uInt8 pnBuffer[ 64 ]; |
| |
| // decode salt data into buffer |
| rtl_cipher_decode( mhCipher, pnVerifier, 16, pnBuffer, sizeof( pnBuffer ) ); |
| |
| pnBuffer[ 16 ] = 0x80; |
| (void)memset( pnBuffer + 17, 0, sizeof( pnBuffer ) - 17 ); |
| pnBuffer[ 56 ] = 0x80; |
| |
| // fill raw digest of buffer into digest |
| rtl_digest_updateMD5( mhDigest, pnBuffer, sizeof( pnBuffer ) ); |
| rtl_digest_rawMD5( mhDigest, pnDigest, sizeof( pnDigest ) ); |
| |
| // decode original salt digest into buffer |
| rtl_cipher_decode( mhCipher, pnVerifierHash, 16, pnBuffer, sizeof( pnBuffer ) ); |
| |
| // compare buffer with computed digest |
| bool bResult = memcmp( pnBuffer, pnDigest, sizeof( pnDigest ) ) == 0; |
| |
| // erase buffer and digest arrays and leave |
| (void)memset( pnBuffer, 0, sizeof( pnBuffer ) ); |
| (void)memset( pnDigest, 0, sizeof( pnDigest ) ); |
| return bResult; |
| } |
| |
| bool BinaryCodec_RCF::startBlock( sal_Int32 nCounter ) |
| { |
| // initialize key data array |
| sal_uInt8 pnKeyData[ 64 ]; |
| (void)memset( pnKeyData, 0, sizeof( pnKeyData ) ); |
| |
| // fill 40 bit of digest value into [0..4] |
| (void)memcpy( pnKeyData, mpnDigestValue, 5 ); |
| |
| // fill little-endian counter into [5..8], static_cast masks out unneeded bits |
| pnKeyData[ 5 ] = static_cast< sal_uInt8 >( nCounter ); |
| pnKeyData[ 6 ] = static_cast< sal_uInt8 >( nCounter >> 8 ); |
| pnKeyData[ 7 ] = static_cast< sal_uInt8 >( nCounter >> 16 ); |
| pnKeyData[ 8 ] = static_cast< sal_uInt8 >( nCounter >> 24 ); |
| |
| pnKeyData[ 9 ] = 0x80; |
| pnKeyData[ 56 ] = 0x48; |
| |
| // fill raw digest of key data into key data |
| (void)rtl_digest_updateMD5( mhDigest, pnKeyData, sizeof( pnKeyData ) ); |
| (void)rtl_digest_rawMD5( mhDigest, pnKeyData, RTL_DIGEST_LENGTH_MD5 ); |
| |
| // initialize cipher with key data (for decoding) |
| rtlCipherError eResult = |
| rtl_cipher_init( mhCipher, rtl_Cipher_DirectionDecode, pnKeyData, RTL_DIGEST_LENGTH_MD5, 0, 0 ); |
| |
| // rrase key data array and leave |
| (void)memset( pnKeyData, 0, sizeof( pnKeyData ) ); |
| return eResult == rtl_Cipher_E_None; |
| } |
| |
| bool BinaryCodec_RCF::decode( sal_uInt8* pnDestData, const sal_uInt8* pnSrcData, sal_Int32 nBytes ) |
| { |
| rtlCipherError eResult = rtl_cipher_decode( mhCipher, |
| pnSrcData, static_cast< sal_Size >( nBytes ), |
| pnDestData, static_cast< sal_Size >( nBytes ) ); |
| return eResult == rtl_Cipher_E_None; |
| } |
| |
| bool BinaryCodec_RCF::skip( sal_Int32 nBytes ) |
| { |
| // decode dummy data in memory to update internal state of RC4 cipher |
| sal_uInt8 pnDummy[ 1024 ]; |
| sal_Int32 nBytesLeft = nBytes; |
| bool bResult = true; |
| while( bResult && (nBytesLeft > 0) ) |
| { |
| sal_Int32 nBlockLen = ::std::min( nBytesLeft, static_cast< sal_Int32 >( sizeof( pnDummy ) ) ); |
| bResult = decode( pnDummy, pnDummy, nBlockLen ); |
| nBytesLeft -= nBlockLen; |
| } |
| return bResult; |
| } |
| |
| // ============================================================================ |
| |
| } // namespace core |
| } // namespace oox |