/* -*- 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/.
 *
 */

#include <algorithm>
#include <oox/crypto/AgileEngine.hxx>

#include <oox/helper/binaryinputstream.hxx>
#include <oox/helper/binaryoutputstream.hxx>

#include <sax/tools/converter.hxx>

#include <comphelper/hash.hxx>
#include <comphelper/docpasswordhelper.hxx>
#include <comphelper/random.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/base64.hxx>
#include <comphelper/sequence.hxx>

#include <filter/msfilter/mscodec.hxx>
#include <tools/stream.hxx>
#include <tools/XmlWriter.hxx>
#include <sax/fastattribs.hxx>

#include <com/sun/star/xml/sax/XFastParser.hpp>
#include <com/sun/star/xml/sax/XFastTokenHandler.hpp>
#include <com/sun/star/xml/sax/FastParser.hpp>
#include <com/sun/star/xml/sax/FastToken.hpp>

using namespace css;
using namespace css::beans;
using namespace css::io;
using namespace css::lang;
using namespace css::uno;
using namespace css::xml::sax;
using namespace css::xml;

namespace oox::crypto {

namespace {

std::u16string_view stripNamespacePrefix(std::u16string_view rsInputName)
{
    size_t idx = rsInputName.find(':');
    if (idx == std::u16string_view::npos)
        return rsInputName;
    return rsInputName.substr(idx + 1);
}

class AgileTokenHandler : public sax_fastparser::FastTokenHandlerBase
{
public:
    virtual sal_Int32 SAL_CALL getTokenFromUTF8(const Sequence< sal_Int8 >& /*nIdentifier*/) override
    {
        return FastToken::DONTKNOW;
    }

    virtual Sequence<sal_Int8> SAL_CALL getUTF8Identifier(sal_Int32 /*nToken*/) override
    {
        return Sequence<sal_Int8>();
    }

    virtual sal_Int32 getTokenDirect(std::string_view /* token */) const override
    {
        return -1;
    }
};

class AgileDocumentHandler : public ::cppu::WeakImplHelper<XFastDocumentHandler>
{
    AgileEncryptionInfo& mInfo;

public:
    explicit AgileDocumentHandler(AgileEncryptionInfo& rInfo) :
        mInfo(rInfo)
    {}

    void SAL_CALL startDocument() override {}
    void SAL_CALL endDocument() override {}
    void SAL_CALL processingInstruction( const OUString& /*rTarget*/, const OUString& /*rData*/ ) override {}
    void SAL_CALL setDocumentLocator( const Reference< XLocator >& /*xLocator*/ ) override {}
    void SAL_CALL startFastElement( sal_Int32 /*Element*/, const Reference< XFastAttributeList >& /*Attribs*/ ) override {}

    void SAL_CALL startUnknownElement( const OUString& /*aNamespace*/, const OUString& rName, const Reference< XFastAttributeList >& aAttributeList ) override
    {
        std::u16string_view rLocalName = stripNamespacePrefix(rName);

        const css::uno::Sequence<Attribute> aUnknownAttributes = aAttributeList->getUnknownAttributes();
        for (const Attribute& rAttribute : aUnknownAttributes)
        {
            std::u16string_view rAttrLocalName = stripNamespacePrefix(rAttribute.Name);

            if (rAttrLocalName == u"spinCount")
            {
                ::sax::Converter::convertNumber(mInfo.spinCount, rAttribute.Value);
            }
            else if (rAttrLocalName == u"saltSize")
            {
                ::sax::Converter::convertNumber(mInfo.saltSize, rAttribute.Value);
            }
            else if (rAttrLocalName == u"blockSize")
            {
                ::sax::Converter::convertNumber(mInfo.blockSize, rAttribute.Value);
            }
            else if (rAttrLocalName == u"keyBits")
            {
                ::sax::Converter::convertNumber(mInfo.keyBits, rAttribute.Value);
            }
            else if (rAttrLocalName == u"hashSize")
            {
                ::sax::Converter::convertNumber(mInfo.hashSize, rAttribute.Value);
            }
            else if (rAttrLocalName == u"cipherAlgorithm")
            {
                mInfo.cipherAlgorithm = rAttribute.Value;
            }
            else if (rAttrLocalName == u"cipherChaining")
            {
                mInfo.cipherChaining = rAttribute.Value;
            }
            else if (rAttrLocalName == u"hashAlgorithm")
            {
                mInfo.hashAlgorithm = rAttribute.Value;
            }
            else if (rAttrLocalName == u"saltValue")
            {
                Sequence<sal_Int8> saltValue;
                comphelper::Base64::decode(saltValue, rAttribute.Value);
                if (rLocalName == u"encryptedKey")
                    mInfo.saltValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(saltValue);
                else if (rLocalName == u"keyData")
                    mInfo.keyDataSalt = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(saltValue);
            }
            else if (rAttrLocalName == u"encryptedVerifierHashInput")
            {
                Sequence<sal_Int8> encryptedVerifierHashInput;
                comphelper::Base64::decode(encryptedVerifierHashInput, rAttribute.Value);
                mInfo.encryptedVerifierHashInput = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(encryptedVerifierHashInput);
            }
            else if (rAttrLocalName == u"encryptedVerifierHashValue")
            {
                Sequence<sal_Int8> encryptedVerifierHashValue;
                comphelper::Base64::decode(encryptedVerifierHashValue, rAttribute.Value);
                mInfo.encryptedVerifierHashValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(encryptedVerifierHashValue);
            }
            else if (rAttrLocalName == u"encryptedKeyValue")
            {
                Sequence<sal_Int8> encryptedKeyValue;
                comphelper::Base64::decode(encryptedKeyValue, rAttribute.Value);
                mInfo.encryptedKeyValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(encryptedKeyValue);
            }
            if (rAttrLocalName == u"encryptedHmacKey")
            {
                Sequence<sal_Int8> aValue;
                comphelper::Base64::decode(aValue, rAttribute.Value);
                mInfo.hmacEncryptedKey = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(aValue);
            }
            if (rAttrLocalName == u"encryptedHmacValue")
            {
                Sequence<sal_Int8> aValue;
                comphelper::Base64::decode(aValue, rAttribute.Value);
                mInfo.hmacEncryptedValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(aValue);
            }
        }
    }

    void SAL_CALL endFastElement( sal_Int32 /*aElement*/ ) override
    {}
    void SAL_CALL endUnknownElement( const OUString& /*aNamespace*/, const OUString& /*aName*/ ) override
    {}

    Reference< XFastContextHandler > SAL_CALL createFastChildContext( sal_Int32 /*aElement*/, const Reference< XFastAttributeList >& /*aAttribs*/ ) override
    {
        return nullptr;
    }

    Reference< XFastContextHandler > SAL_CALL createUnknownChildContext( const OUString& /*aNamespace*/, const OUString& /*aName*/, const Reference< XFastAttributeList >& /*aAttribs*/ ) override
    {
        return this;
    }

    void SAL_CALL characters( const OUString& /*aChars*/ ) override
    {}
};

constexpr const sal_uInt32 constSegmentLength = 4096;

const std::vector<sal_uInt8> constBlock1 { 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79 };
const std::vector<sal_uInt8> constBlock2 { 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e };
const std::vector<sal_uInt8> constBlock3 { 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6 };
const std::vector<sal_uInt8> constBlockHmac1 { 0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6 };
const std::vector<sal_uInt8> constBlockHmac2 { 0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33 };

bool hashCalc(std::vector<sal_uInt8>& output,
              std::vector<sal_uInt8>& input,
              std::u16string_view sAlgorithm )
{
    if (sAlgorithm == u"SHA1")
    {
        output = comphelper::Hash::calculateHash(input.data(), input.size(), comphelper::HashType::SHA1);
        return true;
    }
    else if (sAlgorithm == u"SHA384")
    {
        output = comphelper::Hash::calculateHash(input.data(), input.size(), comphelper::HashType::SHA384);
        return true;
    }
    else if (sAlgorithm == u"SHA512")
    {
        output = comphelper::Hash::calculateHash(input.data(), input.size(), comphelper::HashType::SHA512);
        return true;
    }
    return false;
}

comphelper::CryptoHashType cryptoHashTypeFromString(std::u16string_view sAlgorithm)
{
    if (sAlgorithm == u"SHA512")
        return comphelper::CryptoHashType::SHA512;
    else if (sAlgorithm == u"SHA384")
        return comphelper::CryptoHashType::SHA384;
    else
        return comphelper::CryptoHashType::SHA1;
}

} // namespace

AgileEngine::AgileEngine()
    : meEncryptionPreset(AgileEncryptionPreset::AES_256_SHA512)
{}

comphelper::CryptoType AgileEngine::cryptoType(const AgileEncryptionInfo& rInfo)
{
    if (rInfo.keyBits == 128 && rInfo.cipherAlgorithm == "AES" && rInfo.cipherChaining == "ChainingModeCBC")
        return comphelper::CryptoType::AES_128_CBC;
    else if (rInfo.keyBits == 192 && rInfo.cipherAlgorithm == "AES" && rInfo.cipherChaining == "ChainingModeCBC")
        return comphelper::CryptoType::AES_192_CBC;
    else if (rInfo.keyBits == 256 && rInfo.cipherAlgorithm == "AES" && rInfo.cipherChaining == "ChainingModeCBC")
        return comphelper::CryptoType::AES_256_CBC;
    return comphelper::CryptoType::UNKNOWN;
}

static std::vector<sal_uInt8> calculateIV(comphelper::HashType eType,
                             std::vector<sal_uInt8> const & rSalt,
                             std::vector<sal_uInt8> const & rBlock,
                             sal_Int32 nCipherBlockSize)
{
    comphelper::Hash aHasher(eType);
    aHasher.update(rSalt.data(), rSalt.size());
    aHasher.update(rBlock.data(), rBlock.size());
    std::vector<sal_uInt8> aIV = aHasher.finalize();
    aIV.resize(comphelper::roundUp(sal_Int32(aIV.size()), nCipherBlockSize), 0x36);
    return aIV;
}

void AgileEngine::calculateBlock(
    std::vector<sal_uInt8> const & rBlock,
    std::vector<sal_uInt8>& rHashFinal,
    std::vector<sal_uInt8>& rInput,
    std::vector<sal_uInt8>& rOutput)
{
    std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
    std::vector<sal_uInt8> dataFinal(mInfo.hashSize + rBlock.size(), 0);
    std::copy(rHashFinal.begin(), rHashFinal.end(), dataFinal.begin());
    std::copy(rBlock.begin(), rBlock.end(), dataFinal.begin() + mInfo.hashSize);

    hashCalc(hash, dataFinal, mInfo.hashAlgorithm);

    sal_Int32 keySize = mInfo.keyBits / 8;
    std::vector<sal_uInt8> key(keySize, 0);

    std::copy(hash.begin(), hash.begin() + keySize, key.begin());

    comphelper::Decrypt aDecryptor(key, mInfo.saltValue, cryptoType(mInfo));
    aDecryptor.update(rOutput, rInput);
}

void AgileEngine::encryptBlock(
    std::vector<sal_uInt8> const & rBlock,
    std::vector<sal_uInt8> & rHashFinal,
    std::vector<sal_uInt8> & rInput,
    std::vector<sal_uInt8> & rOutput)
{
    std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
    std::vector<sal_uInt8> dataFinal(mInfo.hashSize + rBlock.size(), 0);
    std::copy(rHashFinal.begin(), rHashFinal.end(), dataFinal.begin());
    std::copy(rBlock.begin(), rBlock.end(), dataFinal.begin() + mInfo.hashSize);

    hashCalc(hash, dataFinal, mInfo.hashAlgorithm);

    sal_Int32 keySize = mInfo.keyBits / 8;
    std::vector<sal_uInt8> key(keySize, 0);

    std::copy(hash.begin(), hash.begin() + keySize, key.begin());

    comphelper::Encrypt aEncryptor(key, mInfo.saltValue, cryptoType(mInfo));

    aEncryptor.update(rOutput, rInput);
}

void AgileEngine::calculateHashFinal(std::u16string_view rPassword, std::vector<sal_uInt8>& aHashFinal)
{
    aHashFinal = comphelper::DocPasswordHelper::GetOoxHashAsVector(
                    rPassword, mInfo.saltValue, mInfo.spinCount,
                    comphelper::Hash::IterCount::PREPEND, mInfo.hashAlgorithm);
}

namespace
{

bool generateBytes(std::vector<sal_uInt8> & rBytes, sal_Int32 nSize)
{
    size_t nMax = std::min(rBytes.size(), size_t(nSize));

    for (size_t i = 0; i < nMax; ++i)
    {
        rBytes[i] = sal_uInt8(comphelper::rng::uniform_uint_distribution(0, 0xFF));
    }

    return true;
}

} // end anonymous namespace

bool AgileEngine::decryptAndCheckVerifierHash(std::u16string_view rPassword)
{
    std::vector<sal_uInt8>& encryptedHashValue = mInfo.encryptedVerifierHashValue;
    size_t encryptedHashValueSize = encryptedHashValue.size();
    size_t nHashValueSize = mInfo.hashSize;
    if (nHashValueSize > encryptedHashValueSize)
        return false;

    std::vector<sal_uInt8> hashFinal(nHashValueSize, 0);
    calculateHashFinal(rPassword, hashFinal);

    std::vector<sal_uInt8>& encryptedHashInput = mInfo.encryptedVerifierHashInput;
    sal_uInt32 nSaltSize = std::max<sal_uInt32>(comphelper::roundUp(mInfo.saltSize, mInfo.blockSize), encryptedHashInput.size());
    std::vector<sal_uInt8> hashInput(nSaltSize, 0);
    calculateBlock(constBlock1, hashFinal, encryptedHashInput, hashInput);

    std::vector<sal_uInt8> hashValue(encryptedHashValueSize, 0);
    calculateBlock(constBlock2, hashFinal, encryptedHashValue, hashValue);

    std::vector<sal_uInt8> hash(nHashValueSize, 0);
    hashCalc(hash, hashInput, mInfo.hashAlgorithm);

    return std::equal(hash.begin(), hash.end(), hashValue.begin());
}

void AgileEngine::decryptEncryptionKey(std::u16string_view rPassword)
{
    sal_Int32 nKeySize = mInfo.keyBits / 8;

    mKey.clear();
    // tdf#166241: for AES 192
    // mKey is the outbuf and in that moment, mInfo.encryptedKeyValue length is 32, while mKey size is 24.
    // so the end result is: we simply need to reserve mKey for mInfo.encryptedKeyValue.size() before resizing.
    mKey.reserve(mInfo.encryptedKeyValue.size());
    mKey.resize(nKeySize, 0);

    std::vector<sal_uInt8> aPasswordHash(mInfo.hashSize, 0);
    calculateHashFinal(rPassword, aPasswordHash);

    calculateBlock(constBlock3, aPasswordHash, mInfo.encryptedKeyValue, mKey);
}

// TODO: Rename
bool AgileEngine::generateEncryptionKey(std::u16string_view rPassword)
{
    bool bResult = decryptAndCheckVerifierHash(rPassword);

    if (bResult)
    {
        decryptEncryptionKey(rPassword);
        decryptHmacKey();
        decryptHmacValue();
    }
    return bResult;
}

bool AgileEngine::decryptHmacKey()
{
    // Initialize hmacKey
    mInfo.hmacKey.clear();
    mInfo.hmacKey.resize(mInfo.hmacEncryptedKey.size(), 0);

    // Calculate IV
    comphelper::HashType eType;
    if (mInfo.hashAlgorithm == "SHA1")
        eType = comphelper::HashType::SHA1;
    else if (mInfo.hashAlgorithm == "SHA384")
        eType = comphelper::HashType::SHA384;
    else if (mInfo.hashAlgorithm == "SHA512")
        eType = comphelper::HashType::SHA512;
    else
        return false;

    std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac1, mInfo.blockSize);

    // Decrypt without key, calculated iv
    comphelper::Decrypt aDecrypt(mKey, iv, cryptoType(mInfo));
    aDecrypt.update(mInfo.hmacKey, mInfo.hmacEncryptedKey);

    mInfo.hmacKey.resize(mInfo.hashSize, 0);

    return true;
}

bool AgileEngine::decryptHmacValue()
{
    // Initialize hmacHash
    mInfo.hmacHash.clear();
    mInfo.hmacHash.resize(mInfo.hmacEncryptedValue.size(), 0);

    // Calculate IV
    comphelper::HashType eType;
    if (mInfo.hashAlgorithm == "SHA1")
        eType = comphelper::HashType::SHA1;
    else if (mInfo.hashAlgorithm == "SHA384")
        eType = comphelper::HashType::SHA384;
    else if (mInfo.hashAlgorithm == "SHA512")
        eType = comphelper::HashType::SHA512;
    else
        return false;
    std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac2, mInfo.blockSize);

    // Decrypt without key, calculated iv
    comphelper::Decrypt aDecrypt(mKey, iv, cryptoType(mInfo));
    aDecrypt.update(mInfo.hmacHash, mInfo.hmacEncryptedValue);

    mInfo.hmacHash.resize(mInfo.hashSize, 0);

    return true;
}

bool AgileEngine::checkDataIntegrity()
{
    bool bResult = (mInfo.hmacHash.size() == mInfo.hmacCalculatedHash.size() &&
               std::equal(mInfo.hmacHash.begin(), mInfo.hmacHash.end(), mInfo.hmacCalculatedHash.begin()));

    return bResult;
}

bool AgileEngine::decrypt(BinaryXInputStream& aInputStream,
                          BinaryXOutputStream& aOutputStream)
{
    comphelper::CryptoHash aCryptoHash(mInfo.hmacKey, cryptoHashTypeFromString(mInfo.hashAlgorithm));

    sal_uInt32 totalSize = aInputStream.readuInt32(); // Document unencrypted size - 4 bytes
    // account for size in HMAC
    std::vector<sal_uInt8> aSizeBytes(sizeof(sal_uInt32));
    ByteOrderConverter::writeLittleEndian(aSizeBytes.data(), totalSize);
    aCryptoHash.update(aSizeBytes);

    aInputStream.skip(4);  // Reserved 4 Bytes
    // account for reserved 4 bytes (must be 0)
    std::vector<sal_uInt8> aReserved{0,0,0,0};
    aCryptoHash.update(aReserved);

    // setup decryption
    std::vector<sal_uInt8>& keyDataSalt = mInfo.keyDataSalt;

    sal_uInt32 saltSize = mInfo.saltSize;
    sal_uInt32 keySize = mInfo.keyBits / 8;

    sal_uInt32 segment = 0;

    std::vector<sal_uInt8> saltWithBlockKey(saltSize + sizeof(segment), 0);
    std::copy(keyDataSalt.begin(), keyDataSalt.end(), saltWithBlockKey.begin());

    std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
    std::vector<sal_uInt8> iv(keySize, 0);

    std::vector<sal_uInt8> inputBuffer(constSegmentLength);
    std::vector<sal_uInt8> outputBuffer(constSegmentLength);
    sal_uInt32 inputLength;
    sal_uInt32 outputLength;
    sal_uInt32 remaining = totalSize;

    while ((inputLength = aInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
    {
        auto p = saltWithBlockKey.begin() + saltSize;
        p[0] = segment & 0xFF;
        p[1] = (segment >> 8) & 0xFF;
        p[2] = (segment >> 16) & 0xFF;
        p[3] = segment >> 24;

        hashCalc(hash, saltWithBlockKey, mInfo.hashAlgorithm);

        // Only if hash > keySize
        std::copy(hash.begin(), hash.begin() + keySize, iv.begin());

        comphelper::Decrypt aDecryptor(mKey, iv, cryptoType(mInfo));
        outputLength = aDecryptor.update(outputBuffer, inputBuffer, inputLength);

        sal_uInt32 writeLength = std::min(outputLength, remaining);

        aCryptoHash.update(inputBuffer, inputLength);

        aOutputStream.writeMemory(outputBuffer.data(), writeLength);

        remaining -= outputLength;
        segment++;
    }

    mInfo.hmacCalculatedHash = aCryptoHash.finalize();

    return true;
}

bool AgileEngine::readEncryptionInfo(uno::Reference<io::XInputStream> & rxInputStream)
{
    // Check reserved value
    std::vector<sal_uInt8> aExpectedReservedBytes(sizeof(sal_uInt32));
    ByteOrderConverter::writeLittleEndian(aExpectedReservedBytes.data(), msfilter::AGILE_ENCRYPTION_RESERVED);

    uno::Sequence<sal_Int8> aReadReservedBytes(sizeof(sal_uInt32));
    rxInputStream->readBytes(aReadReservedBytes, aReadReservedBytes.getLength());

    if (!std::equal(std::cbegin(aReadReservedBytes), std::cend(aReadReservedBytes), aExpectedReservedBytes.begin()))
        return false;

    mInfo.spinCount = 0;
    mInfo.saltSize = 0;
    mInfo.keyBits = 0;
    mInfo.hashSize = 0;
    mInfo.blockSize = 0;

    Reference<XFastDocumentHandler> xFastDocumentHandler(new AgileDocumentHandler(mInfo));
    Reference<XFastTokenHandler> xFastTokenHandler(new AgileTokenHandler);

    Reference<XFastParser> xParser(css::xml::sax::FastParser::create(comphelper::getProcessComponentContext()));

    xParser->setFastDocumentHandler(xFastDocumentHandler);
    xParser->setTokenHandler(xFastTokenHandler);

    InputSource aInputSource;
    aInputSource.aInputStream = rxInputStream;
    xParser->parseStream(aInputSource);

    // CHECK info data
    if (2 > mInfo.blockSize || mInfo.blockSize > 4096)
        return false;

    if (0 > mInfo.spinCount || mInfo.spinCount > 10000000)
        return false;

    if (1 > mInfo.saltSize|| mInfo.saltSize > 65536) // Check
        return false;

    // AES 128 CBC with SHA1
    if (mInfo.keyBits         == 128 &&
        mInfo.cipherAlgorithm == "AES" &&
        mInfo.cipherChaining  == "ChainingModeCBC" &&
        mInfo.hashAlgorithm   == "SHA1" &&
        mInfo.hashSize        == comphelper::SHA1_HASH_LENGTH)
    {
        return true;
    }

    // AES 128 CBC with SHA384
    if (mInfo.keyBits         == 128 &&
        mInfo.cipherAlgorithm == "AES" &&
        mInfo.cipherChaining  == "ChainingModeCBC" &&
        mInfo.hashAlgorithm   == "SHA384" &&
        mInfo.hashSize        == comphelper::SHA384_HASH_LENGTH)
    {
        return true;
    }

    // AES 192 CBC with SHA384
    if (mInfo.keyBits         == 192 &&
        mInfo.cipherAlgorithm == "AES" &&
        mInfo.cipherChaining  == "ChainingModeCBC" &&
        mInfo.hashAlgorithm   == "SHA384" &&
        mInfo.hashSize        == comphelper::SHA384_HASH_LENGTH)
    {
        return true;
    }

    // AES 256 CBC with SHA512
    if (mInfo.keyBits         == 256 &&
        mInfo.cipherAlgorithm == "AES" &&
        mInfo.cipherChaining  == "ChainingModeCBC" &&
        mInfo.hashAlgorithm   == "SHA512" &&
        mInfo.hashSize        == comphelper::SHA512_HASH_LENGTH)
    {
        return true;
    }

    return false;
}

bool AgileEngine::generateAndEncryptVerifierHash(std::u16string_view rPassword)
{
    if (!generateBytes(mInfo.saltValue, mInfo.saltSize))
        return false;

    std::vector<sal_uInt8> unencryptedVerifierHashInput(mInfo.saltSize);
    if (!generateBytes(unencryptedVerifierHashInput, mInfo.saltSize))
        return false;

    // HASH - needs to be modified to be multiple of block size
    sal_Int32 nVerifierHash = comphelper::roundUp(mInfo.hashSize, mInfo.blockSize);
    std::vector<sal_uInt8> unencryptedVerifierHashValue;
    if (!hashCalc(unencryptedVerifierHashValue, unencryptedVerifierHashInput, mInfo.hashAlgorithm))
        return false;
    unencryptedVerifierHashValue.resize(nVerifierHash, 0);

    std::vector<sal_uInt8> hashFinal(mInfo.hashSize, 0);
    calculateHashFinal(rPassword, hashFinal);

    encryptBlock(constBlock1, hashFinal, unencryptedVerifierHashInput, mInfo.encryptedVerifierHashInput);

    encryptBlock(constBlock2, hashFinal, unencryptedVerifierHashValue, mInfo.encryptedVerifierHashValue);

    return true;
}

bool AgileEngine::encryptHmacKey()
{
    // Initialize hmacKey
    mInfo.hmacKey.clear();
    mInfo.hmacKey.resize(mInfo.hashSize, 0);

    if (!generateBytes(mInfo.hmacKey, mInfo.hashSize))
        return false;

    // Encrypted salt must be multiple of block size
    sal_Int32 nEncryptedSaltSize = comphelper::roundUp(mInfo.hashSize, mInfo.blockSize);

    // We need to extend hmacSalt to multiple of block size, padding with 0x36
    std::vector<sal_uInt8> extendedSalt(mInfo.hmacKey);
    extendedSalt.resize(nEncryptedSaltSize, 0x36);

    // Initialize hmacEncryptedKey
    mInfo.hmacEncryptedKey.clear();
    mInfo.hmacEncryptedKey.resize(nEncryptedSaltSize, 0);

    // Calculate IV
    comphelper::HashType eType;
    if (mInfo.hashAlgorithm == "SHA1")
        eType = comphelper::HashType::SHA1;
    else if (mInfo.hashAlgorithm == "SHA384")
        eType = comphelper::HashType::SHA384;
    else if (mInfo.hashAlgorithm == "SHA512")
        eType = comphelper::HashType::SHA512;
    else
        return false;

    std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac1, mInfo.blockSize);

    // Encrypt without key, calculated iv
    comphelper::Encrypt aEncryptor(mKey, iv, cryptoType(mInfo));
    aEncryptor.update(mInfo.hmacEncryptedKey, extendedSalt);

    return true;
}

bool AgileEngine::encryptHmacValue()
{
    sal_Int32 nEncryptedValueSize = comphelper::roundUp(mInfo.hashSize, mInfo.blockSize);
    mInfo.hmacEncryptedValue.clear();
    mInfo.hmacEncryptedValue.resize(nEncryptedValueSize, 0);

    std::vector<sal_uInt8> extendedHash(mInfo.hmacHash);
    extendedHash.resize(nEncryptedValueSize, 0x36);

    // Calculate IV
    comphelper::HashType eType;
    if (mInfo.hashAlgorithm == "SHA1")
        eType = comphelper::HashType::SHA1;
    else if (mInfo.hashAlgorithm == "SHA384")
        eType = comphelper::HashType::SHA384;
    else if (mInfo.hashAlgorithm == "SHA512")
        eType = comphelper::HashType::SHA512;
    else
        return false;

    std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac2, mInfo.blockSize);

    // Encrypt without key, calculated iv
    comphelper::Encrypt aEncryptor(mKey, iv, cryptoType(mInfo));
    aEncryptor.update(mInfo.hmacEncryptedValue, extendedHash);

    return true;
}

bool AgileEngine::encryptEncryptionKey(std::u16string_view rPassword)
{
    sal_Int32 nKeySize = mInfo.keyBits / 8;

    mKey.clear();
    mKey.resize(nKeySize, 0);

    mInfo.encryptedKeyValue.clear();
    mInfo.encryptedKeyValue.resize(nKeySize, 0);

    if (!generateBytes(mKey, nKeySize))
        return false;

    std::vector<sal_uInt8> aPasswordHash(mInfo.hashSize, 0);
    calculateHashFinal(rPassword, aPasswordHash);

    encryptBlock(constBlock3, aPasswordHash, mKey, mInfo.encryptedKeyValue);

    return true;
}

bool AgileEngine::setupEncryption(OUString const & rPassword)
{
    if (meEncryptionPreset == AgileEncryptionPreset::AES_128_SHA1)
        setupEncryptionParameters({ 100000, 16, 128, 20, 16, u"AES"_ustr, u"ChainingModeCBC"_ustr, u"SHA1"_ustr });
    else if (meEncryptionPreset == AgileEncryptionPreset::AES_128_SHA384)
        setupEncryptionParameters({ 100000, 16, 128, 48, 16, u"AES"_ustr, u"ChainingModeCBC"_ustr, u"SHA384"_ustr });
    else if (meEncryptionPreset == AgileEncryptionPreset::AES_192_SHA384)
        setupEncryptionParameters({ 100000, 16, 192, 48, 16, u"AES"_ustr, u"ChainingModeCBC"_ustr, u"SHA384"_ustr });
    else
        setupEncryptionParameters({ 100000, 16, 256, 64, 16, u"AES"_ustr, u"ChainingModeCBC"_ustr, u"SHA512"_ustr });

    return setupEncryptionKey(rPassword);
}

void AgileEngine::setupEncryptionParameters(AgileEncryptionParameters const & rAgileEncryptionParameters)
{
    mInfo.spinCount = rAgileEncryptionParameters.spinCount;
    mInfo.saltSize = rAgileEncryptionParameters.saltSize;
    mInfo.keyBits = rAgileEncryptionParameters.keyBits;
    mInfo.hashSize = rAgileEncryptionParameters.hashSize;
    mInfo.blockSize = rAgileEncryptionParameters.blockSize;

    mInfo.cipherAlgorithm = rAgileEncryptionParameters.cipherAlgorithm;
    mInfo.cipherChaining = rAgileEncryptionParameters.cipherChaining;
    mInfo.hashAlgorithm = rAgileEncryptionParameters.hashAlgorithm;

    mInfo.keyDataSalt.resize(mInfo.saltSize);
    mInfo.saltValue.resize(mInfo.saltSize);
    mInfo.encryptedVerifierHashInput.resize(mInfo.saltSize);
    mInfo.encryptedVerifierHashValue.resize(comphelper::roundUp(mInfo.hashSize, mInfo.blockSize), 0);
}

bool AgileEngine::setupEncryptionKey(std::u16string_view rPassword)
{
    if (!generateAndEncryptVerifierHash(rPassword))
        return false;
    if (!encryptEncryptionKey(rPassword))
        return false;
    if (!generateBytes(mInfo.keyDataSalt, mInfo.saltSize))
        return false;
    if (!encryptHmacKey())
        return false;
    return true;
}

void AgileEngine::writeEncryptionInfo(BinaryXOutputStream & rStream)
{
    rStream.WriteUInt32(msfilter::VERSION_INFO_AGILE);
    rStream.WriteUInt32(msfilter::AGILE_ENCRYPTION_RESERVED);

    SvMemoryStream aMemStream;
    tools::XmlWriter aXmlWriter(&aMemStream);

    if (aXmlWriter.startDocument(0/*nIndent*/))
    {
        aXmlWriter.startElement(""_ostr, "encryption"_ostr, "http://schemas.microsoft.com/office/2006/encryption"_ostr);
        aXmlWriter.attribute("xmlns:p", "http://schemas.microsoft.com/office/2006/keyEncryptor/password");

        aXmlWriter.startElement("keyData");
        aXmlWriter.attribute("saltSize", mInfo.saltSize);
        aXmlWriter.attribute("blockSize", mInfo.blockSize);
        aXmlWriter.attribute("keyBits", mInfo.keyBits);
        aXmlWriter.attribute("hashSize", mInfo.hashSize);
        aXmlWriter.attribute("cipherAlgorithm", mInfo.cipherAlgorithm);
        aXmlWriter.attribute("cipherChaining", mInfo.cipherChaining);
        aXmlWriter.attribute("hashAlgorithm", mInfo.hashAlgorithm);
        aXmlWriter.attributeBase64("saltValue", mInfo.keyDataSalt);
        aXmlWriter.endElement();

        aXmlWriter.startElement("dataIntegrity");
        aXmlWriter.attributeBase64("encryptedHmacKey", mInfo.hmacEncryptedKey);
        aXmlWriter.attributeBase64("encryptedHmacValue", mInfo.hmacEncryptedValue);
        aXmlWriter.endElement();

        aXmlWriter.startElement("keyEncryptors");
        aXmlWriter.startElement("keyEncryptor");
        aXmlWriter.attribute("uri", "http://schemas.microsoft.com/office/2006/keyEncryptor/password");

        aXmlWriter.startElement("p"_ostr, "encryptedKey"_ostr, ""_ostr);
        aXmlWriter.attribute("spinCount", mInfo.spinCount);
        aXmlWriter.attribute("saltSize", mInfo.saltSize);
        aXmlWriter.attribute("blockSize", mInfo.blockSize);
        aXmlWriter.attribute("keyBits", mInfo.keyBits);
        aXmlWriter.attribute("hashSize", mInfo.hashSize);
        aXmlWriter.attribute("cipherAlgorithm", mInfo.cipherAlgorithm);
        aXmlWriter.attribute("cipherChaining", mInfo.cipherChaining);
        aXmlWriter.attribute("hashAlgorithm", mInfo.hashAlgorithm);
        aXmlWriter.attributeBase64("saltValue", mInfo.saltValue);
        aXmlWriter.attributeBase64("encryptedVerifierHashInput", mInfo.encryptedVerifierHashInput);
        aXmlWriter.attributeBase64("encryptedVerifierHashValue", mInfo.encryptedVerifierHashValue);
        aXmlWriter.attributeBase64("encryptedKeyValue", mInfo.encryptedKeyValue);
        aXmlWriter.endElement();

        aXmlWriter.endElement();
        aXmlWriter.endElement();

        aXmlWriter.endElement();
        aXmlWriter.endDocument();
    }
    rStream.writeMemory(aMemStream.GetData(), aMemStream.GetSize());
}

void AgileEngine::encrypt(const css::uno::Reference<css::io::XInputStream> &  rxInputStream,
                          css::uno::Reference<css::io::XOutputStream> & rxOutputStream,
                          sal_uInt32 nSize)
{
    comphelper::CryptoHash aCryptoHash(mInfo.hmacKey, cryptoHashTypeFromString(mInfo.hashAlgorithm));

    BinaryXOutputStream aBinaryOutputStream(rxOutputStream, false);
    BinaryXInputStream aBinaryInputStream(rxInputStream, false);

    std::vector<sal_uInt8> aSizeBytes(sizeof(sal_uInt32));
    ByteOrderConverter::writeLittleEndian(aSizeBytes.data(), nSize);
    aBinaryOutputStream.writeMemory(aSizeBytes.data(), aSizeBytes.size()); // size
    aCryptoHash.update(aSizeBytes, aSizeBytes.size());

    std::vector<sal_uInt8> aNull{0,0,0,0};
    aBinaryOutputStream.writeMemory(aNull.data(), aNull.size()); // reserved
    aCryptoHash.update(aNull, aNull.size());

    std::vector<sal_uInt8>& keyDataSalt = mInfo.keyDataSalt;

    sal_uInt32 saltSize = mInfo.saltSize;
    sal_uInt32 keySize = mInfo.keyBits / 8;

    sal_uInt32 nSegment = 0;
    sal_uInt32 nSegmentByteSize = sizeof(nSegment);

    std::vector<sal_uInt8> saltWithBlockKey(saltSize + nSegmentByteSize, 0);
    std::copy(keyDataSalt.begin(), keyDataSalt.end(), saltWithBlockKey.begin());

    std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
    std::vector<sal_uInt8> iv(keySize, 0);

    std::vector<sal_uInt8> inputBuffer(constSegmentLength);
    std::vector<sal_uInt8> outputBuffer(constSegmentLength);
    sal_uInt32 inputLength;
    sal_uInt32 outputLength;

    while ((inputLength = aBinaryInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
    {
        sal_uInt32 correctedInputLength = inputLength % mInfo.blockSize == 0 ?
                        inputLength : comphelper::roundUp(inputLength, sal_uInt32(mInfo.blockSize));

        // Update Key
        auto p = saltWithBlockKey.begin() + saltSize;
        p[0] = nSegment & 0xFF;
        p[1] = (nSegment >> 8) & 0xFF;
        p[2] = (nSegment >> 16) & 0xFF;
        p[3] = nSegment >> 24;

        hashCalc(hash, saltWithBlockKey, mInfo.hashAlgorithm);

        // Only if hash > keySize
        std::copy(hash.begin(), hash.begin() + keySize, iv.begin());

        comphelper::Encrypt aEncryptor(mKey, iv, AgileEngine::cryptoType(mInfo));
        outputLength = aEncryptor.update(outputBuffer, inputBuffer, correctedInputLength);
        aBinaryOutputStream.writeMemory(outputBuffer.data(), outputLength);
        aCryptoHash.update(outputBuffer, outputLength);

        nSegment++;
    }
    mInfo.hmacHash = aCryptoHash.finalize();
    encryptHmacValue();
}

} // namespace oox::crypto

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
