differential code coverage report with master
Current view: top level - source/detail/cryptography - xlsx_crypto_producer.cpp (source / functions) Coverage Total Hit UBC CBC
Current: coverage.info Lines: 72.3 % 184 133 51 133
Current Date: 2025-12-07 02:01:22 Functions: 66.7 % 12 8 4 8
Baseline: coverage_master.info Branches: 75.0 % 184 138 92 276
Baseline Date: 2025-12-07 02:01:21

             Branch data    TLA  Line data    Source code
       1                 :                : // Copyright (c) 2014-2022 Thomas Fussell
       2                 :                : // Copyright (c) 2024-2025 xlnt-community
       3                 :                : //
       4                 :                : // Permission is hereby granted, free of charge, to any person obtaining a copy
       5                 :                : // of this software and associated documentation files (the "Software"), to deal
       6                 :                : // in the Software without restriction, including without limitation the rights
       7                 :                : // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       8                 :                : // copies of the Software, and to permit persons to whom the Software is
       9                 :                : // furnished to do so, subject to the following conditions:
      10                 :                : //
      11                 :                : // The above copyright notice and this permission notice shall be included in
      12                 :                : // all copies or substantial portions of the Software.
      13                 :                : //
      14                 :                : // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      15                 :                : // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      16                 :                : // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      17                 :                : // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      18                 :                : // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      19                 :                : // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      20                 :                : // THE SOFTWARE
      21                 :                : //
      22                 :                : // @license: http://www.opensource.org/licenses/mit-license.php
      23                 :                : // @author: see AUTHORS file
      24                 :                : 
      25                 :                : #include <xlnt/utils/exceptions.hpp>
      26                 :                : #include <detail/constants.hpp>
      27                 :                : #include <detail/cryptography/aes.hpp>
      28                 :                : #include <detail/cryptography/base64.hpp>
      29                 :                : #include <detail/cryptography/compound_document.hpp>
      30                 :                : #include <detail/cryptography/encryption_info.hpp>
      31                 :                : #include <detail/cryptography/value_traits.hpp>
      32                 :                : #include <detail/cryptography/xlsx_crypto_producer.hpp>
      33                 :                : #include <detail/external/include_libstudxml.hpp>
      34                 :                : #include <detail/serialization/vector_streambuf.hpp>
      35                 :                : #include <detail/serialization/xlsx_producer.hpp>
      36                 :                : #include <detail/serialization/zstream.hpp>
      37                 :                : #include <detail/unicode.hpp>
      38                 :                : #include <detail/utils/string_helpers.hpp>
      39                 :                : 
      40                 :                : namespace {
      41                 :                : 
      42                 :                : using xlnt::detail::encryption_info;
      43                 :                : 
      44                 :CBC           4 : encryption_info generate_encryption_info(const std::u16string & /*password*/)
      45                 :                : {
      46                 :              4 :     encryption_info result;
      47                 :                : 
      48                 :              4 :     result.is_agile = true;
      49                 :                : 
      50                 :              4 :     result.agile = encryption_info::agile_encryption_info();
      51                 :                : 
      52                 :              4 :     result.agile.key_data.block_size = 16;
      53            [ + ]:              4 :     result.agile.key_data.cipher_algorithm = "AES";
      54            [ + ]:              4 :     result.agile.key_data.cipher_chaining = "ChainingModeCBC";
      55            [ + ]:              4 :     result.agile.key_data.hash_algorithm = "SHA512";
      56                 :              4 :     result.agile.key_data.hash_size = 64;
      57                 :              4 :     result.agile.key_data.key_bits = 256;
      58                 :              4 :     result.agile.key_data.salt_size = 16;
      59                 :                :     result.agile.key_data.salt_value =
      60                 :                :         {
      61            [ + ]:              8 :             {40, 183, 193, 64, 115, 97, 10, 177, 122, 50, 243, 123, 229, 145, 162, 247}};
      62                 :                : 
      63                 :                :     result.agile.data_integrity.hmac_key =
      64                 :                :         {
      65                 :                :             {90, 206, 203, 147, 102, 81, 82, 14, 118, 94, 168, 38, 200, 79, 13, 147, 60,
      66                 :                :                 123, 167, 220, 17, 165, 124, 188, 206, 74, 98, 33, 156, 63, 220, 152, 180, 201,
      67                 :                :                 167, 183, 141, 252, 182, 55, 90, 189, 187, 167, 230, 186, 61, 239, 80, 49, 54,
      68            [ + ]:              8 :                 208, 52, 133, 232, 187, 117, 136, 213, 48, 133, 15, 7, 126}};
      69                 :                :     result.agile.data_integrity.hmac_value =
      70                 :                :         {
      71                 :                :             {49, 128, 174, 178, 161, 48, 1, 82, 241, 103, 72, 223, 103, 111, 204, 73,
      72                 :                :                 210, 70, 254, 43, 12, 134, 180, 201, 124, 153, 214, 115, 82, 184, 78, 2,
      73                 :                :                 166, 106, 69, 18, 173, 177, 40, 238, 243, 240, 3, 86, 145, 218, 223, 177,
      74            [ + ]:              8 :                 36, 34, 44, 159, 104, 163, 217, 42, 203, 135, 173, 14, 218, 172, 72, 224}};
      75                 :                : 
      76                 :              4 :     result.agile.key_encryptor.spin_count = 100000;
      77                 :              4 :     result.agile.key_encryptor.block_size = 16;
      78            [ + ]:              4 :     result.agile.key_encryptor.cipher_algorithm = "AES";
      79            [ + ]:              4 :     result.agile.key_encryptor.cipher_chaining = "ChainingModeCBC";
      80                 :              4 :     result.agile.key_encryptor.hash = xlnt::detail::hash_algorithm::sha512;
      81                 :              4 :     result.agile.key_encryptor.hash_size = 64;
      82                 :              4 :     result.agile.key_encryptor.key_bits = 256;
      83                 :              4 :     result.agile.key_encryptor.salt_size = 16;
      84                 :                :     result.agile.key_encryptor.salt_value =
      85                 :                :         {
      86            [ + ]:              8 :             {98, 169, 85, 224, 173, 253, 2, 52, 199, 108, 195, 73, 116, 112, 72, 165}};
      87                 :                :     result.agile.key_encryptor.verifier_hash_input =
      88                 :                :         {
      89            [ + ]:              8 :             {179, 105, 118, 193, 217, 180, 248, 7, 174, 45, 186, 17, 202, 101, 178, 12}};
      90                 :                :     result.agile.key_encryptor.verifier_hash_value =
      91                 :                :         {
      92                 :                :             {82, 190, 235, 102, 30, 33, 103, 191, 3, 160, 153, 30, 127, 117, 8, 195, 65,
      93                 :                :                 245, 77, 219, 85, 28, 206, 236, 55, 86, 243, 49, 104, 128, 243, 138, 227, 113,
      94                 :                :                 82, 88, 88, 73, 243, 108, 193, 11, 84, 162, 235, 189, 9, 137, 151, 97, 43,
      95            [ + ]:              8 :                 137, 197, 72, 164, 192, 65, 252, 253, 227, 236, 242, 252, 179}};
      96                 :                :     result.agile.key_encryptor.encrypted_key_value =
      97                 :                :         {
      98                 :                :             {220, 6, 106, 218, 31, 210, 9, 75, 28, 154, 173, 232, 190, 109, 112, 203, 25,
      99            [ + ]:              8 :                 5, 45, 152, 75, 131, 122, 17, 166, 95, 117, 124, 121, 123, 32, 133}};
     100                 :                : 
     101                 :              4 :     return result;
     102                 :UBC           0 : }
     103                 :                : 
     104                 :CBC           4 : void write_agile_encryption_info(
     105                 :                :     const encryption_info &info,
     106                 :                :     std::ostream &info_stream)
     107                 :                : {
     108                 :              4 :     const auto version_major = std::uint16_t(4);
     109                 :              4 :     const auto version_minor = std::uint16_t(4);
     110                 :              4 :     const auto encryption_flags = std::uint32_t(0x40);
     111                 :                : 
     112            [ + ]:              4 :     info_stream.write(reinterpret_cast<const char *>(&version_major), sizeof(std::uint16_t));
     113            [ + ]:              4 :     info_stream.write(reinterpret_cast<const char *>(&version_minor), sizeof(std::uint16_t));
     114            [ + ]:              4 :     info_stream.write(reinterpret_cast<const char *>(&encryption_flags), sizeof(std::uint32_t));
     115                 :                : 
     116   [ +  +  +  -  :              6 :     static const auto &xmlns = xlnt::constants::ns("encryption");
             +  +  -  - ]
     117   [ +  +  +  -  :              6 :     static const auto &xmlns_p = xlnt::constants::ns("encryption-password");
             +  +  -  - ]
     118                 :                : 
     119         [ +  + ]:              8 :     xml::serializer serializer(info_stream, "EncryptionInfo");
     120                 :                : 
     121         [ +  + ]:              4 :     serializer.start_element(xmlns, "encryption");
     122                 :                : 
     123            [ + ]:              4 :     const auto key_data = info.agile.key_data;
     124         [ +  + ]:              8 :     serializer.start_element(xmlns, "keyData");
     125         [ +  + ]:              8 :     serializer.attribute("saltSize", key_data.salt_size);
     126         [ +  + ]:              8 :     serializer.attribute("blockSize", key_data.block_size);
     127         [ +  + ]:              8 :     serializer.attribute("keyBits", key_data.key_bits);
     128         [ +  + ]:              8 :     serializer.attribute("hashSize", key_data.hash_size);
     129         [ +  + ]:              8 :     serializer.attribute("cipherAlgorithm", key_data.cipher_algorithm);
     130         [ +  + ]:              8 :     serializer.attribute("cipherChaining", key_data.cipher_chaining);
     131         [ +  + ]:              4 :     serializer.attribute("hashAlgorithm", key_data.hash_algorithm);
     132         [ +  + ]:              4 :     serializer.attribute("saltValue",
     133            [ + ]:              8 :         xlnt::detail::encode_base64(key_data.salt_value));
     134         [ +  + ]:              4 :     serializer.end_element(xmlns, "keyData");
     135                 :                : 
     136            [ + ]:              4 :     const auto data_integrity = info.agile.data_integrity;
     137         [ +  + ]:              4 :     serializer.start_element(xmlns, "dataIntegrity");
     138         [ +  + ]:              4 :     serializer.attribute("encryptedHmacKey",
     139            [ + ]:              8 :         xlnt::detail::encode_base64(data_integrity.hmac_key));
     140         [ +  + ]:              4 :     serializer.attribute("encryptedHmacValue",
     141            [ + ]:              8 :         xlnt::detail::encode_base64(data_integrity.hmac_value));
     142         [ +  + ]:              4 :     serializer.end_element(xmlns, "dataIntegrity");
     143                 :                : 
     144            [ + ]:              4 :     const auto key_encryptor = info.agile.key_encryptor;
     145         [ +  + ]:              8 :     serializer.start_element(xmlns, "keyEncryptors");
     146         [ +  + ]:              8 :     serializer.start_element(xmlns, "keyEncryptor");
     147         [ +  + ]:              8 :     serializer.attribute("uri", "");
     148         [ +  + ]:              8 :     serializer.start_element(xmlns_p, "encryptedKey");
     149         [ +  + ]:              8 :     serializer.attribute("spinCount", key_encryptor.spin_count);
     150         [ +  + ]:              8 :     serializer.attribute("saltSize", key_encryptor.salt_size);
     151         [ +  + ]:              8 :     serializer.attribute("blockSize", key_encryptor.block_size);
     152         [ +  + ]:              8 :     serializer.attribute("keyBits", key_encryptor.key_bits);
     153         [ +  + ]:              8 :     serializer.attribute("hashSize", key_encryptor.hash_size);
     154         [ +  + ]:              8 :     serializer.attribute("cipherAlgorithm", key_encryptor.cipher_algorithm);
     155         [ +  + ]:              8 :     serializer.attribute("cipherChaining", key_encryptor.cipher_chaining);
     156         [ +  + ]:              4 :     serializer.attribute("hashAlgorithm", key_encryptor.hash);
     157         [ +  + ]:              4 :     serializer.attribute("saltValue",
     158            [ + ]:              8 :         xlnt::detail::encode_base64(key_encryptor.salt_value));
     159         [ +  + ]:              4 :     serializer.attribute("encryptedVerifierHashInput",
     160            [ + ]:              8 :         xlnt::detail::encode_base64(key_encryptor.verifier_hash_input));
     161         [ +  + ]:              4 :     serializer.attribute("encryptedVerifierHashValue",
     162            [ + ]:              8 :         xlnt::detail::encode_base64(key_encryptor.verifier_hash_value));
     163         [ +  + ]:              4 :     serializer.attribute("encryptedKeyValue",
     164            [ + ]:              8 :         xlnt::detail::encode_base64(key_encryptor.encrypted_key_value));
     165         [ +  + ]:              8 :     serializer.end_element(xmlns_p, "encryptedKey");
     166         [ +  + ]:              8 :     serializer.end_element(xmlns, "keyEncryptor");
     167         [ +  + ]:              8 :     serializer.end_element(xmlns, "keyEncryptors");
     168                 :                : 
     169         [ +  + ]:              4 :     serializer.end_element(xmlns, "encryption");
     170                 :              4 : }
     171                 :                : 
     172                 :UBC           0 : void write_standard_encryption_info(const encryption_info &info, std::ostream &info_stream)
     173                 :                : {
     174                 :              0 :     auto result = std::vector<std::uint8_t>();
     175                 :              0 :     auto writer = xlnt::detail::binary_writer<std::uint8_t>(result);
     176                 :                : 
     177                 :              0 :     const auto version_major = std::uint16_t(4);
     178                 :              0 :     const auto version_minor = std::uint16_t(2);
     179                 :              0 :     const auto encryption_flags = std::uint32_t(0x10 & 0x20);
     180                 :                : 
     181            [ # ]:              0 :     writer.write(version_major);
     182            [ # ]:              0 :     writer.write(version_minor);
     183            [ # ]:              0 :     writer.write(encryption_flags);
     184                 :                : 
     185                 :              0 :     const auto header_length = std::uint32_t(32); // calculate this!
     186                 :                : 
     187            [ # ]:              0 :     writer.write(header_length);
     188            [ # ]:              0 :     writer.write(std::uint32_t(0)); // skip_flags
     189            [ # ]:              0 :     writer.write(std::uint32_t(0)); // size_extra
     190            [ # ]:              0 :     writer.write(std::uint32_t(0x0000660E));
     191            [ # ]:              0 :     writer.write(std::uint32_t(0x00008004));
     192            [ # ]:              0 :     writer.write(std::uint32_t(info.standard.key_bits));
     193            [ # ]:              0 :     writer.write(std::uint32_t(0x00000018));
     194            [ # ]:              0 :     writer.write(std::uint32_t(0));
     195            [ # ]:              0 :     writer.write(std::uint32_t(0));
     196                 :                : 
     197            [ # ]:              0 :     const auto provider = std::u16string(u"Microsoft Enhanced RSA and AES Cryptographic Provider");
     198         [ #  # ]:              0 :     writer.append(xlnt::detail::string_to_bytes(provider));
     199                 :                : 
     200            [ # ]:              0 :     writer.write(std::uint32_t(info.standard.salt.size()));
     201            [ # ]:              0 :     writer.append(info.standard.salt);
     202                 :                : 
     203            [ # ]:              0 :     writer.append(info.standard.encrypted_verifier);
     204                 :                : 
     205            [ # ]:              0 :     writer.write(std::uint32_t(20));
     206            [ # ]:              0 :     writer.append(info.standard.encrypted_verifier_hash);
     207                 :                : 
     208            [ # ]:              0 :     info_stream.write(reinterpret_cast<char *>(result.data()),
     209                 :              0 :         static_cast<std::streamsize>(result.size()));
     210                 :              0 : }
     211                 :                : 
     212                 :CBC           4 : void encrypt_xlsx_agile(
     213                 :                :     const encryption_info &info,
     214                 :                :     const std::vector<std::uint8_t> &plaintext,
     215                 :                :     std::ostream &ciphertext_stream)
     216                 :                : {
     217                 :              4 :     const auto length = static_cast<std::uint64_t>(plaintext.size());
     218            [ + ]:              4 :     ciphertext_stream.write(reinterpret_cast<const char *>(&length), sizeof(std::uint64_t));
     219                 :                : 
     220            [ + ]:              4 :     auto key = info.calculate_key();
     221                 :                : 
     222                 :              4 :     auto salt_size = info.agile.key_data.salt_size;
     223            [ + ]:              4 :     auto salt_with_block_key = info.agile.key_data.salt_value;
     224            [ + ]:              4 :     salt_with_block_key.resize(salt_size + sizeof(std::uint32_t), 0);
     225                 :              4 :     auto &segment_index = *reinterpret_cast<std::uint32_t *>(salt_with_block_key.data() + salt_size);
     226                 :                : 
     227            [ + ]:              4 :     auto segment = std::vector<std::uint8_t>(4096, 0);
     228                 :                : 
     229         [ +  + ]:            132 :     for (auto i = std::size_t(0); i < length; i += 4096)
     230                 :                :     {
     231            [ + ]:            128 :         auto iv = hash(info.agile.key_encryptor.hash, salt_with_block_key);
     232            [ + ]:            128 :         iv.resize(16);
     233                 :                : 
     234                 :            128 :         auto start = plaintext.begin() + static_cast<std::ptrdiff_t>(i);
     235                 :            128 :         auto bytes = std::min(std::size_t(length - i), std::size_t(4096));
     236            [ + ]:            128 :         std::copy(start, start + static_cast<std::ptrdiff_t>(bytes), segment.begin());
     237            [ + ]:            128 :         auto encrypted_segment = xlnt::detail::aes_cbc_encrypt(segment, key, iv);
     238            [ + ]:            128 :         ciphertext_stream.write(reinterpret_cast<char *>(encrypted_segment.data()),
     239                 :                :             static_cast<std::streamsize>(bytes));
     240                 :                : 
     241                 :            128 :         ++segment_index;
     242                 :            128 :     }
     243                 :              4 : }
     244                 :                : 
     245                 :UBC           0 : void encrypt_xlsx_standard(
     246                 :                :     const encryption_info &info,
     247                 :                :     const std::vector<std::uint8_t> &plaintext,
     248                 :                :     std::ostream &ciphertext_stream)
     249                 :                : {
     250                 :              0 :     const auto length = static_cast<std::uint64_t>(plaintext.size());
     251            [ # ]:              0 :     ciphertext_stream.write(reinterpret_cast<const char *>(&length), sizeof(std::uint64_t));
     252                 :                : 
     253            [ # ]:              0 :     auto key = info.calculate_key();
     254            [ # ]:              0 :     auto segment = std::vector<std::uint8_t>(4096, 0);
     255                 :                : 
     256         [ #  # ]:              0 :     for (auto i = std::size_t(0); i < length; ++i)
     257                 :                :     {
     258                 :              0 :         auto start = plaintext.begin() + static_cast<std::ptrdiff_t>(i);
     259                 :              0 :         auto bytes = std::min(std::size_t(length - i), std::size_t(4096));
     260            [ # ]:              0 :         std::copy(start, start + static_cast<std::ptrdiff_t>(bytes), segment.begin());
     261            [ # ]:              0 :         auto encrypted_segment = xlnt::detail::aes_ecb_encrypt(segment, key);
     262            [ # ]:              0 :         ciphertext_stream.write(reinterpret_cast<char *>(encrypted_segment.data()),
     263                 :                :             static_cast<std::streamsize>(bytes));
     264                 :              0 :     }
     265                 :              0 : }
     266                 :                : 
     267                 :CBC           4 : std::vector<std::uint8_t> encrypt_xlsx(
     268                 :                :     const std::vector<std::uint8_t> &plaintext,
     269                 :                :     const std::u16string &password)
     270                 :                : {
     271            [ + ]:              4 :     auto encryption_info = generate_encryption_info(password);
     272            [ + ]:              4 :     encryption_info.password = u"secret";
     273                 :                : 
     274                 :              4 :     auto ciphertext = std::vector<std::uint8_t>();
     275                 :                : 
     276            [ + ]:              4 :     xlnt::detail::vector_ostreambuf buffer(ciphertext);
     277            [ + ]:              4 :     std::ostream stream(&buffer);
     278            [ + ]:              4 :     xlnt::detail::compound_document document(stream);
     279                 :                : 
     280         [ +  - ]:              4 :     if (encryption_info.is_agile)
     281                 :                :     {
     282         [ +  + ]:              4 :         write_agile_encryption_info(encryption_info,
     283            [ + ]:             12 :             document.open_write_stream("/EncryptionInfo"));
     284         [ +  + ]:              4 :         encrypt_xlsx_agile(encryption_info, plaintext,
     285            [ + ]:             12 :             document.open_write_stream("/EncryptedPackage"));
     286                 :                :     }
     287                 :                :     else
     288                 :                :     {
     289         [ #  # ]:UBC           0 :         write_standard_encryption_info(encryption_info,
     290            [ # ]:              0 :             document.open_write_stream("/EncryptionInfo"));
     291         [ #  # ]:              0 :         encrypt_xlsx_standard(encryption_info, plaintext,
     292            [ # ]:              0 :             document.open_write_stream("/EncryptedPackage"));
     293                 :                :     }
     294                 :                : 
     295                 :CBC           4 :     return ciphertext;
     296                 :              4 : }
     297                 :                : 
     298                 :                : } // namespace
     299                 :                : 
     300                 :                : namespace xlnt {
     301                 :                : namespace detail {
     302                 :                : 
     303                 :UBC           0 : std::vector<std::uint8_t> encrypt_xlsx(
     304                 :                :     const std::vector<std::uint8_t> &plaintext,
     305                 :                :     const std::string &password)
     306                 :                : {
     307         [ #  # ]:              0 :     return ::encrypt_xlsx(plaintext, utf8_to_utf16(password));
     308                 :                : }
     309                 :                : 
     310                 :                : #if XLNT_HAS_FEATURE(U8_STRING_VIEW)
     311                 :              0 : std::vector<std::uint8_t> encrypt_xlsx(
     312                 :                :     const std::vector<std::uint8_t> &plaintext,
     313                 :                :     std::u8string_view password)
     314                 :                : {
     315         [ #  # ]:              0 :     return ::encrypt_xlsx(plaintext, utf8_to_utf16(password));
     316                 :                : }
     317                 :                : #endif
     318                 :                : 
     319                 :                : template <typename T>
     320                 :CBC           4 : void xlsx_producer::write_internal(std::ostream &destination, const T &password)
     321                 :                : {
     322                 :              4 :     std::vector<std::uint8_t> plaintext;
     323            [ + ]:              4 :     vector_ostreambuf plaintext_buffer(plaintext);
     324            [ + ]:              4 :     std::ostream decrypted_stream(&plaintext_buffer);
     325            [ + ]:              4 :     write(decrypted_stream);
     326                 :              4 :     archive_.reset();
     327                 :                : 
     328         [ +  + ]:              4 :     const auto ciphertext = ::encrypt_xlsx(plaintext, utf8_to_utf16(password));
     329            [ + ]:              4 :     vector_istreambuf encrypted_buffer(ciphertext);
     330                 :                : 
     331            [ + ]:              4 :     destination << &encrypted_buffer;
     332                 :              4 : }
     333                 :                : 
     334                 :              3 : void xlsx_producer::write(std::ostream &destination, const std::string &password)
     335                 :                : {
     336                 :              3 :     write_internal(destination, password);
     337                 :              3 : }
     338                 :                : 
     339                 :                : #if XLNT_HAS_FEATURE(U8_STRING_VIEW)
     340                 :              1 : void xlsx_producer::write(std::ostream &destination, std::u8string_view password)
     341                 :                : {
     342                 :              1 :     write_internal(destination, password);
     343                 :              1 : }
     344                 :                : #endif
     345                 :                : 
     346                 :                : } // namespace detail
     347                 :                : } // namespace xlnt
        

Generated by: LCOV version 2.3.1-beta