/**************************************************************
 *
 * 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/encryption.hxx"
#include "oox/core/fastparser.hxx"
#include "oox/helper/attributelist.hxx"
#include "oox/helper/helper.hxx"
#include "oox/helper/openssl_wrapper.hxx"

#include <rtl/digest.h>
#include <cppuhelper/implbase1.hxx>
#include <openssl/evp.h>

#include <com/sun/star/io/XStream.hpp>



namespace oox {
namespace core {

// ============================================================================

using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::xml::sax;

using ::com::sun::star::io::XInputStream;
using ::comphelper::SequenceAsHashMap;
using ::rtl::OUString;
using ::std::vector;

// ============================================================================


/* =========================================================================== */
/*  Kudos to Caolan McNamara who provided the core decryption implementation   */
/*  of Standard Encryption (MS-OFFCRYPTO section 2.3.4.5).                     */
/* =========================================================================== */

#define ENCRYPTINFO_CRYPTOAPI        0x00000004U
#define ENCRYPTINFO_DOCPROPS         0x00000008U
#define ENCRYPTINFO_EXTERNAL         0x00000010U
#define ENCRYPTINFO_AES              0x00000020U

#define ENCRYPT_ALGO_AES128          0x0000660EU
#define ENCRYPT_ALGO_AES192          0x0000660FU
#define ENCRYPT_ALGO_AES256          0x00006610U
#define ENCRYPT_ALGO_RC4             0x00006801U

#define ENCRYPT_HASH_SHA1            0x00008004U

class StandardEncryptionInfo : public EncryptionInfo
{
public:
    StandardEncryptionInfo( BinaryInputStream& rStrm ) throw ( Exception );
    ~StandardEncryptionInfo() {}
    bool isImplemented();
    Sequence< NamedValue > verifyPassword( const OUString& rPassword ) throw ( Exception );
    bool verifyEncryptionData( const Sequence< NamedValue >& rEncryptionData ) throw ( Exception );
    bool checkEncryptionData( const sal_uInt8* pnKey, sal_uInt32 nKeySize, const sal_uInt8* pnVerifier, sal_uInt32 nVerifierSize, const sal_uInt8* pnVerifierHash, sal_uInt32 nVerifierHashSize ) throw ( Exception );
    void decryptStream( BinaryXInputStream &aEncryptedPackage, BinaryXOutputStream &aDecryptedPackage ) throw ( Exception );

private:
    sal_uInt8           mpnSalt[ 16 ];
    sal_uInt8           mpnEncrVerifier[ 16 ];
    sal_uInt8           mpnEncrVerifierHash[ 32 ];
    sal_uInt32          mnFlags;
    sal_uInt32          mnAlgorithmId;
    sal_uInt32          mnAlgorithmIdHash;
    sal_uInt32          mnKeySize;
    sal_uInt32          mnSaltSize;
    sal_uInt32          mnVerifierHashSize;
    vector< sal_uInt8>  encryptionKey;
};

StandardEncryptionInfo::StandardEncryptionInfo( BinaryInputStream& rStrm ) throw ( Exception )
{
    char msg[ 1024 ];
    rStrm >> mnFlags;
    if( getFlag( mnFlags, (sal_uInt32) ENCRYPTINFO_EXTERNAL ) )
        throw Exception( OUString::createFromAscii( "EncryptionInfo::readEncryptionInfo() error: \"Extensible encryption\" is not currently supported, please report" ), Reference< XInterface >() );

    sal_uInt32 nHeaderSize, nRepeatedFlags;
    rStrm >> nHeaderSize >> nRepeatedFlags;
    if( nHeaderSize < 20 )
    {
        snprintf( msg, sizeof( msg ), "EncryptionInfo::readEncryptionInfo() error: header size %u is too short", nHeaderSize );
        throw Exception( OUString::createFromAscii( msg ), Reference< XInterface >() );
    }
    if( nRepeatedFlags != mnFlags )
        throw Exception( OUString::createFromAscii( "EncryptionInfo::readEncryptionInfo() error: flags don't match" ), Reference< XInterface>() );

    rStrm.skip( 4 );
    rStrm >> mnAlgorithmId >> mnAlgorithmIdHash >> mnKeySize;
    rStrm.skip( nHeaderSize - 20 );
    rStrm >> mnSaltSize;
    if( mnSaltSize != 16 )
    {
        snprintf( msg, sizeof( msg ), "EncryptionInfo::readEncryptionInfo() error: salt size is %u instead of 16", mnSaltSize );
        throw Exception( OUString::createFromAscii( msg ), Reference< XInterface >() );
    }

    rStrm.readMemory( mpnSalt, 16 );
    rStrm.readMemory( mpnEncrVerifier, 16 );
    rStrm >> mnVerifierHashSize;
    rStrm.readMemory( mpnEncrVerifierHash, 32 );
    if( rStrm.isEof() )
        throw Exception( OUString::createFromAscii( "EncryptionInfo::readEncryptionInfo() error: standard encryption header too short" ), Reference< XInterface >() );
}

bool StandardEncryptionInfo::isImplemented()
{
    return getFlag( mnFlags, (sal_uInt32) ENCRYPTINFO_CRYPTOAPI ) &&
        getFlag( mnFlags, (sal_uInt32) ENCRYPTINFO_AES ) &&
        // algorithm ID 0 defaults to AES128 too, if ENCRYPTINFO_AES flag is set
       ( ( mnAlgorithmId == 0 ) || ( mnAlgorithmId == ENCRYPT_ALGO_AES128 ) ) &&
       // hash algorithm ID 0 defaults to SHA-1 too
       ( ( mnAlgorithmIdHash == 0 ) || ( mnAlgorithmIdHash == ENCRYPT_HASH_SHA1 ) ) &&
       ( mnVerifierHashSize == 20 );
}

static void deriveKey( const sal_uInt8* pnHash, sal_uInt32 nHashLen, sal_uInt8* pnKeyDerived, sal_uInt32 nRequiredKeyLen )
{
    sal_uInt8 pnBuffer[ 64 ];
    memset( pnBuffer, 0x36, sizeof( pnBuffer ) );
    for( sal_uInt32 i = 0; i < nHashLen; ++i )
        pnBuffer[ i ] ^= pnHash[ i ];

    rtlDigest aDigest = rtl_digest_create( rtl_Digest_AlgorithmSHA1 );
    rtlDigestError aError = rtl_digest_update( aDigest, pnBuffer, sizeof( pnBuffer ) );
    sal_uInt8 pnX1[ RTL_DIGEST_LENGTH_SHA1 ];
    aError = rtl_digest_get( aDigest, pnX1, RTL_DIGEST_LENGTH_SHA1 );
    rtl_digest_destroy( aDigest );

    memset( pnBuffer, 0x5C, sizeof( pnBuffer ) );
    for( sal_uInt32 i = 0; i < nHashLen; ++i )
        pnBuffer[ i ] ^= pnHash[ i ];

    aDigest = rtl_digest_create( rtl_Digest_AlgorithmSHA1 );
    aError = rtl_digest_update( aDigest, pnBuffer, sizeof( pnBuffer ) );
    sal_uInt8 pnX2[ RTL_DIGEST_LENGTH_SHA1 ];
    aError = rtl_digest_get( aDigest, pnX2, RTL_DIGEST_LENGTH_SHA1 );
    rtl_digest_destroy( aDigest );

    if( nRequiredKeyLen > RTL_DIGEST_LENGTH_SHA1 )
    {
        memcpy( pnKeyDerived + RTL_DIGEST_LENGTH_SHA1, pnX2, nRequiredKeyLen - RTL_DIGEST_LENGTH_SHA1 );
        nRequiredKeyLen = RTL_DIGEST_LENGTH_SHA1;
    }
    memcpy( pnKeyDerived, pnX1, nRequiredKeyLen );
}

Sequence< NamedValue > StandardEncryptionInfo::verifyPassword( const OUString& rPassword ) throw ( Exception )
{
    size_t nBufferSize = mnSaltSize + 2 * rPassword.getLength();
    sal_uInt8* pnBuffer = new sal_uInt8[ nBufferSize ];
    memcpy( pnBuffer, mpnSalt, mnSaltSize );

    sal_uInt8* pnPasswordLoc = pnBuffer + mnSaltSize;
    const sal_Unicode* pStr = rPassword.getStr();
    for( sal_Int32 i = 0, nLen = rPassword.getLength(); i < nLen; ++i, ++pStr, pnPasswordLoc += 2 )
        ByteOrderConverter::writeLittleEndian( pnPasswordLoc, static_cast< sal_uInt16 >( *pStr ) );

    rtlDigest aDigest = rtl_digest_create( rtl_Digest_AlgorithmSHA1 );
    rtlDigestError aError = rtl_digest_update( aDigest, pnBuffer, nBufferSize );
    delete[] pnBuffer;

    size_t nHashSize = RTL_DIGEST_LENGTH_SHA1 + 4;
    sal_uInt8* pnHash = new sal_uInt8[ nHashSize ];
    aError = rtl_digest_get( aDigest, pnHash + 4, RTL_DIGEST_LENGTH_SHA1 );
    rtl_digest_destroy( aDigest );

    for( sal_uInt32 i = 0; i < 50000; ++i )
    {
        ByteOrderConverter::writeLittleEndian( pnHash, i );
        aDigest = rtl_digest_create( rtl_Digest_AlgorithmSHA1 );
        aError = rtl_digest_update( aDigest, pnHash, nHashSize );
        aError = rtl_digest_get( aDigest, pnHash + 4, RTL_DIGEST_LENGTH_SHA1 );
        rtl_digest_destroy( aDigest );
    }

    memmove( pnHash, pnHash + 4, RTL_DIGEST_LENGTH_SHA1 );
    memset( pnHash + RTL_DIGEST_LENGTH_SHA1, 0, 4 );
    aDigest = rtl_digest_create( rtl_Digest_AlgorithmSHA1 );
    aError = rtl_digest_update( aDigest, pnHash, nHashSize );
    aError = rtl_digest_get( aDigest, pnHash, RTL_DIGEST_LENGTH_SHA1 );
    rtl_digest_destroy( aDigest );

    vector< sal_uInt8 > key( mnKeySize / 8 );
    deriveKey( pnHash, RTL_DIGEST_LENGTH_SHA1, &key[ 0 ], key.size() );
    delete[] pnHash;

    Sequence< NamedValue > aResult;
    if( checkEncryptionData( &key[ 0 ], key.size(), mpnEncrVerifier, sizeof( mpnEncrVerifier ), mpnEncrVerifierHash, sizeof( mpnEncrVerifierHash ) ) )
    {
        SequenceAsHashMap aEncryptionData;
        aEncryptionData[ CREATE_OUSTRING( "AES128EncryptionKey" ) ] <<= Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( &key[ 0 ] ), key.size() );
        aEncryptionData[ CREATE_OUSTRING( "AES128EncryptionSalt" ) ] <<= Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( mpnSalt ), mnSaltSize );
        aEncryptionData[ CREATE_OUSTRING( "AES128EncryptionVerifier" ) ] <<= Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( mpnEncrVerifier ), sizeof( mpnEncrVerifier ) );
        aEncryptionData[ CREATE_OUSTRING( "AES128EncryptionVerifierHash" ) ] <<= Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( mpnEncrVerifierHash ), sizeof( mpnEncrVerifierHash ) );
        encryptionKey = key;
        aResult = aEncryptionData.getAsConstNamedValueList();
    }

    return aResult;
}

bool StandardEncryptionInfo::verifyEncryptionData( const Sequence< NamedValue >& rEncryptionData ) throw ( Exception )
{
    SequenceAsHashMap aHashData( rEncryptionData );
    Sequence< sal_Int8 > aKey = aHashData.getUnpackedValueOrDefault( CREATE_OUSTRING( "AES128EncryptionKey" ), Sequence< sal_Int8 >() );
    Sequence< sal_Int8 > aVerifier = aHashData.getUnpackedValueOrDefault( CREATE_OUSTRING( "AES128EncryptionVerifier" ), Sequence< sal_Int8 >() );
    Sequence< sal_Int8 > aVerifierHash = aHashData.getUnpackedValueOrDefault( CREATE_OUSTRING( "AES128EncryptionVerifierHash" ), Sequence< sal_Int8 >() );
    const sal_uInt8 *pnKey = reinterpret_cast< const sal_uInt8* >( aKey.getConstArray() );
    sal_uInt32 nKeySize = aKey.getLength();
    const sal_uInt8 *pnVerifier = reinterpret_cast< const sal_uInt8* >( aVerifier.getConstArray() );
    sal_uInt32 nVerifierSize = aVerifier.getLength();
    const sal_uInt8 *pnVerifierHash = reinterpret_cast< const sal_uInt8* >( aVerifierHash.getConstArray() );
    sal_uInt32 nVerifierHashSize = aVerifierHash.getLength();
    if( checkEncryptionData( pnKey, nKeySize, pnVerifier, nVerifierSize, pnVerifierHash, nVerifierHashSize ) )
    {
        encryptionKey = vector< sal_uInt8 >( &pnKey[ 0 ], &pnKey[ nKeySize ] );
        return true;
    }
    else
        return false;
}

bool StandardEncryptionInfo::checkEncryptionData( const sal_uInt8* pnKey, sal_uInt32 nKeySize, const sal_uInt8* pnVerifier, sal_uInt32 nVerifierSize, const sal_uInt8* pnVerifierHash, sal_uInt32 nVerifierHashSize ) throw ( Exception )
{
    bool bResult = false;

    // the only currently supported algorithm needs key size 128
    if ( nKeySize == 16 && nVerifierSize == 16 && nVerifierHashSize == 32 )
    {
        // check password
        EVP_CIPHER_CTX *aes_ctx;
        aes_ctx = EVP_CIPHER_CTX_new();
        if ( aes_ctx == NULL )
            return false;
        EVP_DecryptInit_ex( aes_ctx, EVP_aes_128_ecb(), 0, pnKey, 0 );
        EVP_CIPHER_CTX_set_padding( aes_ctx, 0 );
        int nOutLen = 0;
        sal_uInt8 pnTmpVerifier[ 16 ];
        (void) memset( pnTmpVerifier, 0, sizeof(pnTmpVerifier) );

        /*int*/ EVP_DecryptUpdate( aes_ctx, pnTmpVerifier, &nOutLen, pnVerifier, nVerifierSize );
        EVP_CIPHER_CTX_free( aes_ctx );

        aes_ctx = EVP_CIPHER_CTX_new();
        if ( aes_ctx == NULL )
            return false;
        EVP_DecryptInit_ex( aes_ctx, EVP_aes_128_ecb(), 0, pnKey, 0 );
        EVP_CIPHER_CTX_set_padding( aes_ctx, 0 );
        sal_uInt8 pnTmpVerifierHash[ 32 ];
        (void) memset( pnTmpVerifierHash, 0, sizeof(pnTmpVerifierHash) );

        /*int*/ EVP_DecryptUpdate( aes_ctx, pnTmpVerifierHash, &nOutLen, pnVerifierHash, nVerifierHashSize );
        EVP_CIPHER_CTX_free( aes_ctx );

        rtlDigest aDigest = rtl_digest_create( rtl_Digest_AlgorithmSHA1 );
        rtlDigestError aError = rtl_digest_update( aDigest, pnTmpVerifier, sizeof( pnTmpVerifier ) );
        sal_uInt8 pnSha1Hash[ RTL_DIGEST_LENGTH_SHA1 ];
        aError = rtl_digest_get( aDigest, pnSha1Hash, RTL_DIGEST_LENGTH_SHA1 );
        rtl_digest_destroy( aDigest );

        bResult = ( memcmp( pnSha1Hash, pnTmpVerifierHash, RTL_DIGEST_LENGTH_SHA1 ) == 0 );
    }

    return bResult;
}

void StandardEncryptionInfo::decryptStream( BinaryXInputStream &aEncryptedPackage, BinaryXOutputStream &aDecryptedPackage ) throw ( Exception )
{
    EVP_CIPHER_CTX *aes_ctx;
    aes_ctx = EVP_CIPHER_CTX_new();
    if ( aes_ctx == NULL )
        throw Exception();
    EVP_DecryptInit_ex( aes_ctx, EVP_aes_128_ecb(), 0, &encryptionKey[ 0 ], 0 );
    EVP_CIPHER_CTX_set_padding( aes_ctx, 0 );

    sal_uInt8 pnInBuffer[ 1024 ];
    sal_uInt8 pnOutBuffer[ 1024 ];
    sal_Int32 nInLen;
    int nOutLen;
    aEncryptedPackage.skip( 8 ); // decrypted size
    while( (nInLen = aEncryptedPackage.readMemory( pnInBuffer, sizeof( pnInBuffer ) )) > 0 )
    {
        EVP_DecryptUpdate( aes_ctx, pnOutBuffer, &nOutLen, pnInBuffer, nInLen );
        aDecryptedPackage.writeMemory( pnOutBuffer, nOutLen );
    }
    EVP_DecryptFinal_ex( aes_ctx, pnOutBuffer, &nOutLen );
    aDecryptedPackage.writeMemory( pnOutBuffer, nOutLen );

    EVP_CIPHER_CTX_free( aes_ctx );
    aDecryptedPackage.flush();
}

// ============================================================================
// "Agile" encryption, 2.3.4.10 of MS-OFFCRYPTO
// ============================================================================

struct AgileKeyData
{
    sal_Int32 saltSize;
    sal_Int32 blockSize;
    sal_Int32 keyBits;
    sal_Int32 hashSize;
    OUString cipherAlgorithm;
    OUString cipherChaining;
    OUString hashAlgorithm;
    vector< sal_uInt8 > saltValue;
};

struct AgileDataIntegrity
{
    vector< sal_uInt8 > encryptedHmacKey;
    vector< sal_uInt8 > encryptedHmacValue;
};

struct AgilePasswordKeyEncryptor
{
    sal_Int32 saltSize;
    sal_Int32 blockSize;
    sal_Int32 keyBits;
    sal_Int32 hashSize;
    OUString cipherAlgorithm;
    OUString cipherChaining;
    OUString hashAlgorithm;
    vector< sal_uInt8 > saltValue;
    sal_Int32 spinCount;
    vector< sal_uInt8 > encryptedVerifierHashInput;
    vector< sal_uInt8 > encryptedVerifierHashValue;
    vector< sal_uInt8 > encryptedKeyValue;
};

static bool decodeBase64( OUString& base64, vector< sal_uInt8 >& bytes )
{
    ::rtl::OString base64Ascii = ::rtl::OUStringToOString( base64, RTL_TEXTENCODING_UTF8 );
    const sal_uInt32 len = base64Ascii.getLength();
    bytes.resize( (len + 3) / 4 * 3 );
    int decodedSize = EVP_DecodeBlock( &bytes[ 0 ], reinterpret_cast< sal_uInt8 const * >( base64Ascii.getStr() ), len );
    if ( decodedSize < 0 )
        return false;
    if ( len >= 2 && base64Ascii[ len-1 ] == '=' && base64Ascii[ len-2 ] == '=' )
        decodedSize -= 2;
    else if ( len >= 1 && base64Ascii[ len-1] == '=' )
        decodedSize--;
    bytes.resize( decodedSize );
    return true;
}

class AgileEncryptionInfo : public EncryptionInfo
{
public:
    AgileEncryptionInfo( const Reference< XComponentContext >& context, Reference< XInputStream >& inputStream ) throw ( Exception );
    ~AgileEncryptionInfo() {}
    bool isImplemented() { return true; } // FIXME
    Sequence< NamedValue > verifyPassword( const OUString& rPassword ) throw ( Exception );
    bool verifyEncryptionData( const Sequence< NamedValue >& rEncryptionData ) throw ( Exception );
    void decryptStream( BinaryXInputStream &aEncryptedPackage, BinaryXOutputStream &aDecryptedPackage ) throw ( Exception );

private:
    AgileKeyData keyData;
    AgileDataIntegrity dataIntegrity;
    AgilePasswordKeyEncryptor passwordKeyEncryptor;
    vector< sal_uInt8> encryptionKey;
    vector< sal_uInt8> hmacKey;
    vector< sal_uInt8> hmacValue;
};

// A SAX handler that parses the XML from the "XmlEncryptionDescriptor" in the EncryptionInfo stream.
class AgileEncryptionHandler : public ::cppu::WeakImplHelper1< XFastDocumentHandler >
{
public:
    AgileEncryptionHandler( AgileKeyData &aKeyData, AgileDataIntegrity &aDataIntegrity, AgilePasswordKeyEncryptor &aPasswordKeyEncryptor )
    : keyData( aKeyData ),
      dataIntegrity( aDataIntegrity ),
      passwordKeyEncryptor( aPasswordKeyEncryptor )
    {
    }

    // XFastDocumentHandler
    virtual void SAL_CALL startDocument() throw (SAXException, RuntimeException);
    virtual void SAL_CALL endDocument() throw (SAXException, RuntimeException);
    virtual void SAL_CALL setDocumentLocator( const Reference< XLocator >& xLocator ) throw (SAXException, RuntimeException);

    // XFastContextHandler
    virtual void SAL_CALL startFastElement( sal_Int32 nElement, const Reference< XFastAttributeList >& Attribs ) throw (SAXException, RuntimeException);
    virtual void SAL_CALL startUnknownElement( const OUString& Namespace, const OUString& Name, const Reference< XFastAttributeList >& Attribs ) throw (SAXException, RuntimeException);
    virtual void SAL_CALL endFastElement( sal_Int32 Element ) throw (SAXException, RuntimeException);
    virtual void SAL_CALL endUnknownElement( const OUString& Namespace, const OUString& Name ) throw (SAXException, RuntimeException);
    virtual Reference< XFastContextHandler > SAL_CALL createFastChildContext( sal_Int32 Element, const Reference< XFastAttributeList >& Attribs ) throw (SAXException, RuntimeException);
    virtual Reference< XFastContextHandler > SAL_CALL createUnknownChildContext( const OUString& Namespace, const OUString& Name, const Reference< XFastAttributeList >& Attribs ) throw (SAXException, RuntimeException);
    virtual void SAL_CALL characters( const OUString& aChars ) throw (SAXException, RuntimeException);
    virtual void SAL_CALL ignorableWhitespace( const OUString& aWhitespaces ) throw (SAXException, RuntimeException);
    virtual void SAL_CALL processingInstruction( const OUString& aTarget, const OUString& aData ) throw (SAXException, RuntimeException);

    OUString& getLastError() { return lastError; }

private:
    void parseKeyData( const AttributeList& attribs ) throw (SAXException, RuntimeException);
    void parseDataIntegrity( const AttributeList& attribs ) throw (SAXException, RuntimeException);
    void parseEncryptedKey( const AttributeList& attribs ) throw (SAXException, RuntimeException);

    vector< sal_Int32 > stack;
    OUString lastError;
    AgileKeyData &keyData;
    AgileDataIntegrity &dataIntegrity;
    AgilePasswordKeyEncryptor &passwordKeyEncryptor;
};

void AgileEncryptionHandler::startDocument()
    throw ( SAXException, RuntimeException )
{
}

void AgileEncryptionHandler::endDocument()
    throw ( SAXException, RuntimeException )
{
}

void AgileEncryptionHandler::setDocumentLocator( const Reference< XLocator >& )
    throw ( SAXException, RuntimeException )
{
}

void AgileEncryptionHandler::startFastElement( sal_Int32 nElement, const Reference< XFastAttributeList >& attribs )
    throw( SAXException, RuntimeException )
{
    switch ( nElement )
    {
        case ENCRYPTION_TOKEN( encryption ):
        break;

        case ENCRYPTION_TOKEN( keyData ):
            if ( stack.size() == 1 && (stack[ 0 ] == ENCRYPTION_TOKEN( encryption )) )
                parseKeyData( AttributeList( attribs ) );
        break;

        case ENCRYPTION_TOKEN( dataIntegrity ):
            if ( stack.size() == 1 && (stack[ 0 ] == ENCRYPTION_TOKEN( encryption )) )
                parseDataIntegrity( AttributeList ( attribs ) );
        break;

        case ENCRYPTION_TOKEN( keyEncryptors ):
        break;

        case ENCRYPTION_TOKEN( keyEncryptor ):
        break;

        case KEY_ENCRYPTOR_PASSWORD_TOKEN( encryptedKey ):
            if ( stack.size() == 3
                && (stack[ 0 ] == ENCRYPTION_TOKEN( encryption ))
                && (stack[ 1 ] == ENCRYPTION_TOKEN( keyEncryptors ))
                && (stack[ 2 ] == ENCRYPTION_TOKEN( keyEncryptor )) )
                parseEncryptedKey( AttributeList ( attribs ) );
        break;
    }
    stack.push_back( nElement );
}

void AgileEncryptionHandler::startUnknownElement( const OUString&, const OUString&, const Reference< XFastAttributeList >& )
    throw( SAXException, RuntimeException )
{
    stack.push_back( -1 );
}

void AgileEncryptionHandler::endFastElement( sal_Int32 nElement )
    throw( SAXException, RuntimeException )
{
    stack.pop_back();
}

void AgileEncryptionHandler::endUnknownElement( const OUString&, const OUString& )
    throw( SAXException, RuntimeException )
{
    stack.pop_back();
}

Reference< XFastContextHandler > AgileEncryptionHandler::createFastChildContext( sal_Int32, const Reference< XFastAttributeList >& )
    throw (SAXException, RuntimeException)
{
    return this;
}

Reference< XFastContextHandler > AgileEncryptionHandler::createUnknownChildContext( const OUString&, const OUString&, const Reference< XFastAttributeList >& )
    throw (SAXException, RuntimeException)
{
    return this;
}

void AgileEncryptionHandler::characters( const ::rtl::OUString& rStr )
    throw( SAXException, RuntimeException )
{
}

void AgileEncryptionHandler::ignorableWhitespace( const ::rtl::OUString& str )
    throw( SAXException, RuntimeException )
{
}

void AgileEncryptionHandler::processingInstruction( const ::rtl::OUString& aTarget, const ::rtl::OUString& aData )
    throw( SAXException, RuntimeException )
{
}

void AgileEncryptionHandler::parseKeyData( const AttributeList& attribs )
    throw ( SAXException, RuntimeException )
{
    keyData.saltSize = attribs.getInteger( XML_saltSize, 0 );
    keyData.blockSize = attribs.getInteger( XML_blockSize, 0 );
    keyData.keyBits = attribs.getInteger( XML_keyBits, 0 );
    keyData.hashSize = attribs.getInteger( XML_hashSize, 0 );
    keyData.cipherAlgorithm = attribs.getString( XML_cipherAlgorithm, OUString() );
    keyData.cipherChaining = attribs.getString( XML_cipherChaining, OUString() );
    keyData.hashAlgorithm = attribs.getString( XML_hashAlgorithm, OUString() );

    OUString saltValue = attribs.getString( XML_saltValue, OUString() );
    if( !decodeBase64( saltValue, keyData.saltValue ) )
        lastError = OUString::createFromAscii( "Failed to base64 decode the keyData.saltValue " ) + saltValue;
}

void AgileEncryptionHandler::parseDataIntegrity( const AttributeList& attribs )
    throw ( SAXException, RuntimeException )
{
    OUString encryptedHmacKey = attribs.getString( XML_encryptedHmacKey, OUString() );
    if( !decodeBase64( encryptedHmacKey, dataIntegrity.encryptedHmacKey ) )
        lastError = OUString::createFromAscii( "Failed to base64 decode the dataIntegrity.encryptedHmacKey " ) + encryptedHmacKey;
    OUString encryptedHmacValue = attribs.getString( XML_encryptedHmacValue, OUString() );
    if( !decodeBase64( encryptedHmacValue, dataIntegrity.encryptedHmacValue ) )
        lastError = OUString::createFromAscii( "Failed to base64 decode the dataIntegrity.encryptedHmacValue " ) + encryptedHmacValue;
}

void AgileEncryptionHandler::parseEncryptedKey( const AttributeList& attribs )
    throw ( SAXException, RuntimeException )
{
    passwordKeyEncryptor.spinCount = attribs.getInteger( XML_spinCount, 0 );
    passwordKeyEncryptor.saltSize = attribs.getInteger( XML_saltSize, 0 );
    passwordKeyEncryptor.blockSize = attribs.getInteger( XML_blockSize, 0 );
    passwordKeyEncryptor.keyBits = attribs.getInteger( XML_keyBits, 0 );
    passwordKeyEncryptor.hashSize = attribs.getInteger( XML_hashSize, 0 );
    passwordKeyEncryptor.cipherAlgorithm = attribs.getString( XML_cipherAlgorithm, OUString() );
    passwordKeyEncryptor.cipherChaining = attribs.getString( XML_cipherChaining, OUString() );
    passwordKeyEncryptor.hashAlgorithm = attribs.getString( XML_hashAlgorithm, OUString() );
    OUString saltValue = attribs.getString( XML_saltValue, OUString() );
    if( !decodeBase64( saltValue, passwordKeyEncryptor.saltValue ) )
        lastError = OUString::createFromAscii( "Failed to base64 decode the passwordKeyEncryptor.saltValue " ) + saltValue;
    OUString encryptedVerifierHashInput = attribs.getString( XML_encryptedVerifierHashInput, OUString() );
    if( !decodeBase64( encryptedVerifierHashInput, passwordKeyEncryptor.encryptedVerifierHashInput ) )
        lastError = OUString::createFromAscii( "Failed to base64 decode the passwordKeyEncryptor.encryptedVerifierHashInput " ) + encryptedVerifierHashInput;
    OUString encryptedVerifierHashValue = attribs.getString( XML_encryptedVerifierHashValue, OUString() );
    if( !decodeBase64( encryptedVerifierHashValue, passwordKeyEncryptor.encryptedVerifierHashValue ) )
        lastError = OUString::createFromAscii( "Failed to base64 decode the passwordKeyEncryptor.encryptedVerifierHashValue " ) + encryptedVerifierHashValue;
    OUString encryptedKeyValue = attribs.getString( XML_encryptedKeyValue, OUString() );
    if( !decodeBase64( encryptedKeyValue, passwordKeyEncryptor.encryptedKeyValue ) )
        lastError = OUString::createFromAscii( "Failed to base64 decode the passwordKeyEncryptor.encryptedKeyValue " ) + encryptedKeyValue;
}

static sal_uInt16 readUInt16LE( Reference< XInputStream >& inputStream ) throw ( Exception )
{
    Sequence< sal_Int8 > bytes( 2 );
    sal_Int32 bytesRead = inputStream->readBytes( bytes, 2 );
    if( bytesRead < 2 )
        throw new Exception( OUString::createFromAscii( "EncryptionInfo::readEncryptionInfo() failed, early end of file" ), Reference< XInterface >() );
    return (sal_uInt16) ( bytes[0] | (bytes[1] << 8) );
}

static sal_uInt32 readUInt32LE( Reference< XInputStream >& inputStream ) throw ( Exception )
{
    Sequence< sal_Int8 > bytes( 4 );
    sal_Int32 bytesRead = inputStream->readBytes( bytes, 4 );
    if( bytesRead < 4 )
        throw new Exception( OUString::createFromAscii( "EncryptionInfo::readEncryptionInfo() failed, early end of file" ), Reference< XInterface >() );
    return (sal_uInt32) ( bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24) );
}

AgileEncryptionInfo::AgileEncryptionInfo( const Reference< XComponentContext >& context, Reference< XInputStream >& inputStream ) throw ( Exception )
{
    sal_uInt32 nReserved = readUInt32LE( inputStream );
    if( nReserved != 0x40 )
        throw new Exception( OUString::createFromAscii( "reserved field isn't 0x40" ), Reference< XInterface >() );
    AgileEncryptionHandler *agileEncryptionHandler = new AgileEncryptionHandler( keyData, dataIntegrity, passwordKeyEncryptor );
    Reference< XFastDocumentHandler > documentHandler( agileEncryptionHandler );
    FastParser fastParser( context );
    fastParser.registerNamespace( NMSP_encryption );
    fastParser.registerNamespace( NMSP_keyEncryptorPassword );
    fastParser.setDocumentHandler( documentHandler );
    fastParser.parseStream( inputStream, OUString::createFromAscii( "EncryptionInfo" ), false );
    if( !agileEncryptionHandler->getLastError().isEmpty() )
        throw new Exception( agileEncryptionHandler->getLastError(), Reference< XInterface >() );
}

static const EVP_MD* toOpenSSLDigestAlgorithm( const OUString& hashAlgorithm  ) throw ( Exception )
{
    if( hashAlgorithm.equalsAscii( "SHA-1" ) )
        return EVP_sha1();
    else if( hashAlgorithm.equalsAscii( "SHA1" ) ) // Typical Microsoft. The specification says "SHA-1", but documents use "SHA1".
        return EVP_sha1();
    else if( hashAlgorithm.equalsAscii( "SHA256" ) )
        return EVP_sha256();
    else if( hashAlgorithm.equalsAscii( "SHA384" ) )
        return EVP_sha384();
    else if( hashAlgorithm.equalsAscii( "SHA512" ) )
        return EVP_sha512();
    else if( hashAlgorithm.equalsAscii( "MD5" ) )
        return EVP_md5();
    else if( hashAlgorithm.equalsAscii( "MD4" ) )
        return EVP_md4();
#if !defined(OPENSSL_NO_MD2)
    else if( hashAlgorithm.equalsAscii( "MD2" ) )
        return EVP_md2();
#endif
    else if( hashAlgorithm.equalsAscii( "RIPEMD-160" ) )
        return EVP_ripemd160();
    else if( hashAlgorithm.equalsAscii( "WHIRLPOOL" ) )
        return EVP_whirlpool();
    char buffer[ 256 ];
    ::rtl::OString str = ::rtl::OUStringToOString( hashAlgorithm, RTL_TEXTENCODING_UTF8 );
    snprintf( buffer, sizeof( buffer ), "Unsupported digest algorithm %s", str.getStr() );
    throw Exception( OUString::createFromAscii( buffer ), Reference< XInterface >() );
}

static const EVP_CIPHER* toOpenSSLCipherAlgorithm( const OUString& cipherName, sal_uInt32 keyBits, const OUString &chainingMode ) throw ( Exception )
{
    if( cipherName.equalsAscii( "AES" ) && keyBits == 128 && chainingMode.equalsAscii( "ChainingModeCBC" ) )
        return EVP_aes_128_cbc();
    else if( cipherName.equalsAscii( "AES" ) && keyBits == 128 && chainingMode.equalsAscii( "ChainingModeCFB" ) )
        return EVP_aes_128_cfb();
    else if( cipherName.equalsAscii( "AES" ) && keyBits == 192 && chainingMode.equalsAscii( "ChainingModeCBC" ) )
        return EVP_aes_192_cbc();
    else if( cipherName.equalsAscii( "AES" ) && keyBits == 192 && chainingMode.equalsAscii( "ChainingModeCFB" ) )
        return EVP_aes_192_cfb();
    else if( cipherName.equalsAscii( "AES" ) && keyBits == 256 && chainingMode.equalsAscii( "ChainingModeCBC" ) )
        return EVP_aes_256_cbc();
    else if( cipherName.equalsAscii( "AES" ) && keyBits == 256 && chainingMode.equalsAscii( "ChainingModeCFB" ) )
        return EVP_aes_256_cfb();
#if !defined(OPENSSL_NO_RC2)
    else if( cipherName.equalsAscii( "RC2" ) && keyBits == 128 && chainingMode.equalsAscii( "ChainingModeCBC" ) )
        return EVP_rc2_cbc();
    else if( cipherName.equalsAscii( "RC2" ) && keyBits == 128 && chainingMode.equalsAscii( "ChainingModeCFB" ) )
        return EVP_rc2_cfb();
#endif
#if !defined(OPENSSL_NO_DES)
    else if( cipherName.equalsAscii( "DES" ) && keyBits == 56 && chainingMode.equalsAscii( "ChainingModeCBC" ) )
        return EVP_des_cbc();
    else if( cipherName.equalsAscii( "DES" ) && keyBits == 56 && chainingMode.equalsAscii( "ChainingModeCFB" ) )
        return EVP_des_cfb();
    else if( cipherName.equalsAscii( "DESX" ) && keyBits == 128 && chainingMode.equalsAscii( "ChainingModeCBC" ) )
        return EVP_desx_cbc();
    else if( cipherName.equalsAscii( "3DES" ) && keyBits == 168 && chainingMode.equalsAscii( "ChainingModeCBC" ) )
        return EVP_des_ede3_cbc();
    else if( cipherName.equalsAscii( "3DES" ) && keyBits == 168 && chainingMode.equalsAscii( "ChainingModeCFB" ) )
        return EVP_des_ede3_cfb();
    else if( cipherName.equalsAscii( "3DES_112" ) && keyBits == 112 && chainingMode.equalsAscii( "ChainingModeCBC" ) )
        return EVP_des_ede_cbc();
    else if( cipherName.equalsAscii( "3DES_112" ) && keyBits == 112 && chainingMode.equalsAscii( "ChainingModeCFB" ) )
        return EVP_des_ede_cfb();
#endif
    char buffer[ 256 ];
    ::rtl::OString cipherNameUtf8 = ::rtl::OUStringToOString( cipherName, RTL_TEXTENCODING_UTF8 );
    ::rtl::OString chainingModeUtf8 = ::rtl::OUStringToOString( chainingMode, RTL_TEXTENCODING_UTF8 );
    snprintf( buffer, sizeof( buffer ), "Unsupported cipher with name=%s, keyBits=%u, chainingMode=%s", cipherNameUtf8.getStr(), keyBits, chainingModeUtf8.getStr() );
    throw Exception( OUString::createFromAscii( buffer ), Reference< XInterface >() );
}

// Ported from Apache POI's org.apache.poi.poifs.crypt.CryptoFunctions.hashPassword().
static vector< sal_uInt8 > hashPassword( const OUString& password, const EVP_MD *digestAlgorithm, vector< sal_uInt8 >& salt, sal_uInt32 spinCount ) throw ( Exception )
{
    OpenSSLDigest digest;
    digest.initialize( digestAlgorithm );
    size_t digestSize = digest.digestSize();

    // Convert to little-endian UTF-16
    vector< sal_uInt8 > passwordLE( 2 * password.getLength() );
    for ( int i = 0; i < password.getLength(); i++ )
        ByteOrderConverter::writeLittleEndian( &passwordLE[ 2 * i ], static_cast< sal_uInt16 >( password[ i ] ) );

    vector< sal_uInt8> digestBuffer( digestSize );
    digest.update( &salt[ 0 ], salt.size() );
    digest.update( &passwordLE[ 0 ], passwordLE.size() );
    digest.final( &digestBuffer[ 0 ], NULL );

    char iteratorBuffer[ 4 ];
    for (sal_uInt32 i = 0; i < spinCount; i++)
    {
        digest.initialize( digestAlgorithm );
        ByteOrderConverter::writeLittleEndian( &iteratorBuffer, i );
        digest.update( iteratorBuffer, sizeof( iteratorBuffer ) );
        digest.update( &digestBuffer[ 0 ], digestSize );
        digest.final( &digestBuffer[ 0 ], NULL );
    }
    return digestBuffer;
}

// Ported from Apache POI's org.apache.poi.poifs.crypt.CryptoFunctions.getBlock36().
static void toBlock36( vector< sal_uInt8 >& digest, sal_uInt32 size )
{
    if( digest.size() < size )
    {
        sal_uInt32 i = digest.size();
        digest.resize( size );
        for (; i < size; i++)
            digest[ i ] = 0x36;
    }
    else
        digest.resize( size );
}

// Ported from Apache POI's org.apache.poi.poifs.crypt.CryptoFunctions.getBlock0().
static void toBlock0( vector< sal_uInt8 >& digest, sal_uInt32 size )
{
    if( digest.size() < size )
    {
        sal_uInt32 i = digest.size();
        digest.resize( size );
        for (; i < size; i++)
            digest[ i ] = 0;
    }
    else
        digest.resize( size );
}

// Ported from Apache POI's org.apache.poi.poifs.crypt.CryptoFunctions.generateKey().
static vector< sal_uInt8 > generateKey( const vector< sal_uInt8 >& passwordHash,
                                        const EVP_MD *digestAlgorithm,
                                        const vector< sal_uInt8 >& blockKey,
                                        sal_uInt32 keySize )
    throw ( Exception )
{
    OpenSSLDigest digest;
    digest.initialize( digestAlgorithm );
    digest.update( &passwordHash[ 0 ], passwordHash.size() );
    digest.update( &blockKey[ 0 ], blockKey.size() );
    vector< sal_uInt8> key( digest.digestSize() );
    digest.final( &key[ 0 ], NULL );
    toBlock36( key, keySize );
    return key;
}

// Ported from Apache POI's org.apache.poi.poifs.crypt.CryptoFunctions.generateIv().
static vector< sal_uInt8> generateIv( const vector< sal_uInt8 >& salt,
                                      sal_uInt32 blockSize )
    throw ( Exception )
{
    vector< sal_uInt8> iv( salt );
    toBlock36( iv, blockSize );
    return iv;
}

// Ported from Apache POI's org.apache.poi.poifs.crypt.CryptoFunctions.generateIv().
static vector< sal_uInt8> generateIv( const EVP_MD *digestAlgorithm,
                                      const vector< sal_uInt8 >& salt,
                                      const vector< sal_uInt8 >& blockKey,
                                      sal_uInt32 blockSize )
    throw ( Exception )
{
    OpenSSLDigest digest;
    digest.initialize( digestAlgorithm );
    digest.update( &salt[ 0 ], salt.size() );
    digest.update( &blockKey[ 0 ], blockKey.size() );
    vector< sal_uInt8> iv( digest.digestSize() );
    digest.final( &iv[ 0 ], NULL );
    toBlock36( iv, blockSize );
    return iv;
}

// Ported from Apache POI's org.apache.poi.poifs.crypt.agile.AgileDecryptor.getNextBlockSize().
static sal_uInt32 getNextBlockSize( sal_uInt32 totalSize, sal_uInt32 blockSize )
{
    sal_uInt32 numberOfBlocks = ( totalSize + ( blockSize - 1 ) ) / blockSize;
    return numberOfBlocks * blockSize;
}

static vector< sal_uInt8 > decryptAll( const EVP_CIPHER* cipherAlgorithm,
                                       const sal_uInt8* iv,
                                       const sal_uInt8* key,
                                       const sal_uInt8* encryptedData,
                                       sal_uInt32 encryptedDataLength )
    throw ( Exception )
{
    OpenSSLCipher cipher;
    cipher.initialize( cipherAlgorithm, key, iv, 0 );
    cipher.setPadding( 0 );
    const int blockSize = OpenSSLCipher::blockSize( cipherAlgorithm );
    vector< sal_uInt8 > decryptedData( encryptedDataLength + 2*blockSize );

    int decryptedDataLength;
    cipher.update( encryptedData, encryptedDataLength, &decryptedData[ 0 ], &decryptedDataLength );
    int finalDataLength;
    cipher.final( &decryptedData[ decryptedDataLength ], &finalDataLength );
    decryptedDataLength += finalDataLength;
    decryptedData.resize( decryptedDataLength );
    return decryptedData;
}

// Ported from Apache POI's org.apache.poi.poifs.crypt.agile.AgileDecryptor.hashInput().
static vector< sal_uInt8 > hashInput( const vector< sal_uInt8 >& passwordHash,
                                      const vector< sal_uInt8 >& salt,
                                      const EVP_MD *digestAlgorithm,
                                      const vector< sal_uInt8 >& blockKey,
                                      const vector< sal_uInt8 >& inputKey,
                                      const EVP_CIPHER *decryptionAlgorithm,
                                      sal_uInt32 keySize,
                                      sal_uInt32 blockSize )
    throw ( Exception )
{
    vector< sal_uInt8 > intermediateKey = generateKey( passwordHash, digestAlgorithm, blockKey, keySize );
    vector< sal_uInt8> iv = generateIv( salt, blockSize );
    vector< sal_uInt8 > zeroedInput( inputKey.size() );
    zeroedInput = inputKey;
    toBlock0( zeroedInput, getNextBlockSize( zeroedInput.size(), blockSize ) );
    return decryptAll( decryptionAlgorithm, &iv[ 0 ], &intermediateKey[ 0 ], &zeroedInput[ 0 ], zeroedInput.size() );
}

// Ported from Apache POI's org.apache.poi.poifs.crypt.agile.AgileDecryptor.verifyPassword().
Sequence< NamedValue > AgileEncryptionInfo::verifyPassword( const OUString& password )
    throw ( Exception )
{
    const EVP_MD *digestAlgorithm = toOpenSSLDigestAlgorithm( passwordKeyEncryptor.hashAlgorithm );
    vector< sal_uInt8 > passwordHash = hashPassword( password, digestAlgorithm, passwordKeyEncryptor.saltValue, passwordKeyEncryptor.spinCount );

    static const sal_uInt8 verifierInputBlockData[] = { 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79 };
    vector< sal_uInt8 > verifierInputBlock( &verifierInputBlockData[ 0 ], &verifierInputBlockData[ sizeof( verifierInputBlockData ) ] );
    const EVP_CIPHER* cipher = toOpenSSLCipherAlgorithm( passwordKeyEncryptor.cipherAlgorithm, passwordKeyEncryptor.keyBits, passwordKeyEncryptor.cipherChaining );
    vector< sal_uInt8 > encryptedVerifierHash = hashInput( passwordHash, passwordKeyEncryptor.saltValue, digestAlgorithm, verifierInputBlock,
                                                           passwordKeyEncryptor.encryptedVerifierHashInput, cipher, passwordKeyEncryptor.keyBits,
                                                           passwordKeyEncryptor.blockSize );
    const EVP_MD *verifierDigestAlgorithm = toOpenSSLDigestAlgorithm( keyData.hashAlgorithm );
    OpenSSLDigest verifierDigest;
    verifierDigest.initialize( verifierDigestAlgorithm );
    verifierDigest.update( &encryptedVerifierHash[ 0 ], encryptedVerifierHash.size() );
    encryptedVerifierHash.resize( verifierDigest.digestSize() );
    verifierDigest.final( &encryptedVerifierHash[ 0 ], NULL );

    static const sal_uInt8 verifierHashBlockData[] = { 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e };
    vector< sal_uInt8 > verifierHashBlock( &verifierHashBlockData[ 0 ], &verifierHashBlockData[ sizeof( verifierHashBlockData ) ] );
    vector< sal_uInt8 > verifierHashDec = hashInput( passwordHash, passwordKeyEncryptor.saltValue, digestAlgorithm, verifierHashBlock,
                                                     passwordKeyEncryptor.encryptedVerifierHashValue, cipher, passwordKeyEncryptor.keyBits,
                                                     passwordKeyEncryptor.blockSize );
    toBlock0( verifierHashDec, verifierDigest.digestSize() );

    if( encryptedVerifierHash != verifierHashDec )
        return Sequence< NamedValue >();

    // Password is correct. Decrypt and store the encryption key.
    static const sal_uInt8 cryptoKeyBlockData[] = { 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6 };
    vector< sal_uInt8 > cryptoKeyBlock( &cryptoKeyBlockData[ 0 ], &cryptoKeyBlockData[ sizeof( cryptoKeyBlockData ) ] );
    encryptionKey = hashInput( passwordHash, passwordKeyEncryptor.saltValue, digestAlgorithm, cryptoKeyBlock,
                               passwordKeyEncryptor.encryptedKeyValue, cipher, passwordKeyEncryptor.keyBits,
                               passwordKeyEncryptor.blockSize );
    toBlock0( encryptionKey, passwordKeyEncryptor.keyBits / 8 );

    // Also decrypt the dataIntegrity fields for stream validation. Note that they are optional.
    if( !dataIntegrity.encryptedHmacKey.empty() && !dataIntegrity.encryptedHmacValue.empty() )
    {
        const EVP_MD* keyDataDigestAlgorithm = toOpenSSLDigestAlgorithm( keyData.hashAlgorithm );
        const EVP_CIPHER* keyDataCipher = toOpenSSLCipherAlgorithm( keyData.cipherAlgorithm, keyData.keyBits, keyData.cipherChaining );
        static const sal_uInt8 integrityKeyBlockData[] = { 0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6 };
        vector< sal_uInt8 > integrityKeyBlock( &integrityKeyBlockData[ 0 ], &integrityKeyBlockData[ sizeof( integrityKeyBlockData ) ] );
        vector< sal_uInt8 > integrityKeyIv = generateIv( keyDataDigestAlgorithm, keyData.saltValue, integrityKeyBlock, keyData.blockSize );
        hmacKey = decryptAll( keyDataCipher, &integrityKeyIv[ 0 ], &encryptionKey[ 0 ], &dataIntegrity.encryptedHmacKey[ 0 ], dataIntegrity.encryptedHmacKey.size() );
        toBlock0( hmacKey, OpenSSLDigest::digestSize( keyDataDigestAlgorithm ) );

        static const sal_uInt8 integrityValueBlockData[] = { 0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33 };
        vector< sal_uInt8 > integrityValueBlock( &integrityValueBlockData[ 0 ], &integrityValueBlockData[ sizeof( integrityValueBlockData ) ] );
        vector< sal_uInt8 > integrityValueIv = generateIv( keyDataDigestAlgorithm, keyData.saltValue, integrityValueBlock, keyData.blockSize );
        hmacValue = decryptAll( keyDataCipher, &integrityValueIv[ 0 ], &encryptionKey[ 0 ], &dataIntegrity.encryptedHmacValue[ 0 ], dataIntegrity.encryptedHmacValue.size() );
        toBlock0( hmacValue, OpenSSLDigest::digestSize( keyDataDigestAlgorithm ) );
    }

    // On success, MUST populate something into the encryption data, even though we'll never use it.
    SequenceAsHashMap encryptionData;
    encryptionData[ CREATE_OUSTRING( "OOXMLAgileEncryptionPasswordVerified" ) ] <<= sal_True;
    return encryptionData.getAsConstNamedValueList();
}

bool AgileEncryptionInfo::verifyEncryptionData( const Sequence< NamedValue >& rEncryptionData )
    throw ( Exception )
{
    // OpenGrok shows how only main/comphelper/source/misc/docpasswordhelper.cxx calls IDocPasswordVerifier::verifyEncryptionData(),
    // and only when the password is wrong and the rMediaEncData non-empty, which presumably allows other forms of encryption
    // (eg. by certificate) to be used. We only support password for now.
    return false;
}

// Ported from Apache POI's org.apache.poi.poifs.crypt.agile.AgileDecryptor.initCipherForBlock().
void AgileEncryptionInfo::decryptStream( BinaryXInputStream &aEncryptedPackage, BinaryXOutputStream &aDecryptedPackage )
    throw ( Exception )
{
    if( encryptionKey.empty() )
        throw Exception( OUString::createFromAscii( "Encryption key not set, was the password wrong?" ), Reference< XInterface >() );
    const EVP_CIPHER* cipherAlgorithm = toOpenSSLCipherAlgorithm( keyData.cipherAlgorithm, keyData.keyBits, keyData.cipherChaining );
    const EVP_MD* digestAlgorithm = toOpenSSLDigestAlgorithm( keyData.hashAlgorithm );
    OpenSSLCipher cipher;

    const sal_uInt64 decryptedSize = aEncryptedPackage.readuInt64();

    sal_uInt8 inputBuffer[ 4096 ];
    vector< sal_uInt8 > outputBuffer( 4096 + 2*OpenSSLCipher::blockSize( cipherAlgorithm ) );
    sal_Int32 bytesIn;
    int bytesOut;
    int finalBytesOut;
    sal_uInt64 totalBytesWritten = 0;

    vector< sal_uInt8 > blockBytes( 4 );
    bool done = false;
    for ( sal_uInt32 block = 0; !done; block++ )
    {
        ByteOrderConverter::writeLittleEndian( &blockBytes[ 0 ], block );
        vector< sal_uInt8 > iv = generateIv( digestAlgorithm, keyData.saltValue, blockBytes, keyData.blockSize );
        cipher.initialize( cipherAlgorithm, &encryptionKey[ 0 ], &iv[ 0 ], 0 );
        cipher.setPadding( 0 );

        bytesIn = aEncryptedPackage.readMemory( inputBuffer, sizeof( inputBuffer ) );
        if( bytesIn > 0 )
        {
            cipher.update( inputBuffer, bytesIn, &outputBuffer[ 0 ], &bytesOut );
            cipher.final( &outputBuffer[ bytesOut ], &finalBytesOut );
            bytesOut += finalBytesOut;
            if( decryptedSize < (totalBytesWritten + bytesOut) )
            {
                bytesOut = decryptedSize % sizeof( inputBuffer );
                done = true;
            }
            aDecryptedPackage.writeMemory( &outputBuffer[ 0 ], bytesOut );
            totalBytesWritten += bytesOut;
        } else
            done = true;
    }

    aDecryptedPackage.flush();
}

EncryptionInfo* EncryptionInfo::readEncryptionInfo( const Reference< XComponentContext >& context, Reference< XInputStream >& inputStream )
    throw ( Exception )
{
    sal_uInt16 nVersionMajor = readUInt16LE( inputStream );
    sal_uInt16 nVersionMinor = readUInt16LE( inputStream );
    if( ( nVersionMajor == 2 && nVersionMinor == 2 ) ||
        ( nVersionMajor == 3 && nVersionMinor == 2 ) ||
        ( nVersionMajor == 4 && nVersionMinor == 2 ) )
    {
        // 2.3.4.5 Standard Encryption
        BinaryXInputStream aInfoStrm( inputStream, false );
        return new StandardEncryptionInfo( aInfoStrm );
    }
    else if ( nVersionMajor == 4 && nVersionMajor == 4 )
    {
        // 2.3.4.10 Agile Encryption
        return new AgileEncryptionInfo( context, inputStream );
    }
    else
    {
        char msg[ 1024 ];
        snprintf( msg, sizeof( msg ), "EncryptionInfo::readEncryptionInfo() error: unsupported EncryptionVersionInfo header with major=%hu minor=%hu",
            nVersionMajor, nVersionMinor );
        throw Exception( OUString::createFromAscii( msg ), Reference< XInterface >() );
    }
}

// ============================================================================

} // namespace core
} // namespace oox
