/* * Copyright (c) 2017-2021 [Ribose Inc](https://www.ribose.com). * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include "rnp_tests.h" #include "support.h" #include "utils.h" #include #include #include #include static std::vector decode_hex(const char *hex) { if (!hex) { return {}; } std::vector data(strlen(hex) / 2); if (!rnp::hex_decode(hex, data.data(), data.size())) { throw std::invalid_argument("hex"); } return data; } static void test_cipher(pgp_symm_alg_t alg, pgp_cipher_mode_t mode, size_t tag_size, bool disable_padding, const char * key_hex, const char * iv_hex, const char * ad_hex, const char * pt_hex, const char * expected_ct_hex) { const std::vector key(decode_hex(key_hex)); const std::vector iv(decode_hex(iv_hex)); const std::vector ad(decode_hex(ad_hex)); const std::vector pt(decode_hex(pt_hex)); const std::vector expected_ct(decode_hex(expected_ct_hex)); auto enc = Cipher::encryption(alg, mode, tag_size, disable_padding); assert_non_null(enc); const size_t block_size = enc->block_size(); const size_t ud = enc->update_granularity(); std::vector ct; // make room for padding ct.resize(((pt.size() + tag_size) / block_size + 1) * block_size); // set key & iv assert_true(enc->set_key(key.data(), key.size())); assert_true(enc->set_iv(iv.data(), iv.size())); if (!ad.empty()) { assert_true(enc->set_ad(ad.data(), ad.size())); } // encrypt all in one go size_t output_written, input_consumed; assert_true(enc->finish( ct.data(), ct.size(), &output_written, pt.data(), pt.size(), &input_consumed)); ct.resize(output_written); assert_memory_equal(ct.data(), expected_ct.data(), expected_ct.size()); // start over enc.reset(Cipher::encryption(alg, mode, tag_size, disable_padding).release()); assert_true(enc->set_key(key.data(), key.size())); assert_true(enc->set_iv(iv.data(), iv.size())); if (!ad.empty()) { assert_true(enc->set_ad(ad.data(), ad.size())); } ct.clear(); ct.resize(((pt.size() + tag_size) / block_size + 1) * block_size); // encrypt in pieces assert_memory_not_equal(ct.data(), expected_ct.data(), expected_ct.size()); // all except the last block size_t nonfinal_bytes = rnp_round_up(pt.size(), ud) - ud; output_written = 0; input_consumed = 0; size_t written, consumed; while (input_consumed != nonfinal_bytes) { assert_true(enc->update(ct.data() + output_written, ct.size() - output_written, &written, pt.data() + input_consumed, ud, &consumed)); output_written += written; input_consumed += consumed; } assert_true(enc->finish(ct.data() + output_written, ct.size() - output_written, &written, pt.data() + input_consumed, pt.size() - input_consumed, &consumed)); output_written += written; ct.resize(output_written); assert_int_equal(ct.size(), expected_ct.size()); assert_memory_equal(ct.data(), expected_ct.data(), expected_ct.size()); enc.reset(); // decrypt auto dec = Cipher::decryption(alg, mode, tag_size, disable_padding); assert_true(dec->set_key(key.data(), key.size())); assert_true(dec->set_iv(iv.data(), iv.size())); if (!ad.empty()) { assert_true(dec->set_ad(ad.data(), ad.size())); } // decrypt in pieces std::vector decrypted(ct.size()); // all except the last block but see below for openssl nonfinal_bytes = rnp_round_up(ct.size(), ud) - ud; #if defined(CRYPTO_BACKEND_OPENSSL) || defined(CRYPTO_BACKEND_BOTAN3) /* Since ossl backend sets tag explicitly tag bytes cannot be split between two blocks. The issue may easily occur is (for example) us = 16 ct.size() = 24 tag_size=16 */ /* Botan 3 also requires to include whole tag in the finish() call. */ if (ct.size() - nonfinal_bytes < tag_size) { nonfinal_bytes = ct.size() - tag_size; } #endif // CRYPTO_BACKEND_OPENSSL output_written = 0; input_consumed = 0; while (input_consumed != nonfinal_bytes) { size_t consume = std::min(ud, nonfinal_bytes - input_consumed); if (consume < ud) { break; } assert_true(dec->update(decrypted.data() + output_written, decrypted.size() - output_written, &written, (const uint8_t *) ct.data() + input_consumed, // ++++ ud, consume, &consumed)); output_written += written; input_consumed += consumed; } assert_true(dec->finish(decrypted.data() + output_written, decrypted.size() - output_written, &written, (const uint8_t *) ct.data() + input_consumed, ct.size() - input_consumed, &consumed)); output_written += written; decrypted.resize(output_written); assert_int_equal(decrypted.size(), pt.size()); assert_memory_equal(decrypted.data(), pt.data(), pt.size()); // decrypt with a bad tag if (tag_size != 0) { dec.reset(Cipher::decryption(alg, mode, tag_size, disable_padding).release()); assert_true(dec->set_key(key.data(), key.size())); assert_true(dec->set_iv(iv.data(), iv.size())); if (!ad.empty()) { assert_true(dec->set_ad(ad.data(), ad.size())); } // decrypt in pieces std::vector decrypted(ct.size()); // all except the last block but see above for openssl nonfinal_bytes = rnp_round_up(ct.size(), ud) - ud; #ifdef CRYPTO_BACKEND_OPENSSL if (ct.size() - nonfinal_bytes < tag_size) { nonfinal_bytes = ct.size() - tag_size; } #endif // CRYPTO_BACKEND_OPENSSL output_written = 0; input_consumed = 0; while (input_consumed != nonfinal_bytes) { assert_true(dec->update(decrypted.data() + output_written, decrypted.size() - output_written, &written, (const uint8_t *) ct.data() + input_consumed, // ++++ ud, std::min(ud, nonfinal_bytes - input_consumed), &consumed)); output_written += written; input_consumed += consumed; } // tamper with the tag ct.back() ^= 0xff; assert_false(dec->finish(decrypted.data() + output_written, decrypted.size() - output_written, &written, (const uint8_t *) ct.data() + input_consumed, ct.size() - input_consumed, &consumed)); } } TEST_F(rnp_tests, test_cipher_idea) { #if defined(ENABLE_IDEA) assert_true(idea_enabled()); // OpenSSL do_crypt man page example test_cipher(PGP_SA_IDEA, PGP_CIPHER_MODE_CBC, 0, false, "000102030405060708090a0b0c0d0e0f", "0102030405060708", NULL, "536f6d652043727970746f2054657874", "8974b718d0cb68b44e27c480546dfcc7a33895f461733219"); #else assert_false(idea_enabled()); assert_null(Cipher::encryption(PGP_SA_IDEA, PGP_CIPHER_MODE_CBC, 0, false)); #endif } TEST_F(rnp_tests, test_cipher_aes_128_ocb) { // RFC 7253 -- Appendix A -- Sample Results // ( The first ten test sets ) test_cipher(PGP_SA_AES_128, PGP_CIPHER_MODE_OCB, 16, false, "000102030405060708090A0B0C0D0E0F", // key "BBAA99887766554433221100", // nounce nullptr, // ad nullptr, // data "785407BFFFC8AD9EDCC5520AC9111EE6"); test_cipher(PGP_SA_AES_128, PGP_CIPHER_MODE_OCB, 16, false, "000102030405060708090A0B0C0D0E0F", "BBAA99887766554433221101", "0001020304050607", "0001020304050607", "6820B3657B6F615A5725BDA0D3B4EB3A257C9AF1F8F03009"); test_cipher(PGP_SA_AES_128, PGP_CIPHER_MODE_OCB, 16, false, "000102030405060708090A0B0C0D0E0F", "BBAA99887766554433221102", "0001020304050607", nullptr, "81017F8203F081277152FADE694A0A00"); test_cipher(PGP_SA_AES_128, PGP_CIPHER_MODE_OCB, 16, false, "000102030405060708090A0B0C0D0E0F", "BBAA99887766554433221103", nullptr, "0001020304050607", "45DD69F8F5AAE72414054CD1F35D82760B2CD00D2F99BFA9"); test_cipher(PGP_SA_AES_128, PGP_CIPHER_MODE_OCB, 16, false, "000102030405060708090A0B0C0D0E0F", "BBAA99887766554433221104", "000102030405060708090A0B0C0D0E0F", "000102030405060708090A0B0C0D0E0F", "571D535B60B277188BE5147170A9A22C3AD7A4FF3835B8C5701C1CCEC8FC3358"); test_cipher(PGP_SA_AES_128, PGP_CIPHER_MODE_OCB, 16, false, "000102030405060708090A0B0C0D0E0F", "BBAA99887766554433221105", "000102030405060708090A0B0C0D0E0F", nullptr, "8CF761B6902EF764462AD86498CA6B97"); test_cipher(PGP_SA_AES_128, PGP_CIPHER_MODE_OCB, 16, false, "000102030405060708090A0B0C0D0E0F", "BBAA99887766554433221106", nullptr, "000102030405060708090A0B0C0D0E0F", "5CE88EC2E0692706A915C00AEB8B2396F40E1C743F52436BDF06D8FA1ECA343D"); test_cipher( PGP_SA_AES_128, PGP_CIPHER_MODE_OCB, 16, false, "000102030405060708090A0B0C0D0E0F", "BBAA99887766554433221107", "000102030405060708090A0B0C0D0E0F1011121314151617", "000102030405060708090A0B0C0D0E0F1011121314151617", "1CA2207308C87C010756104D8840CE1952F09673A448A122C92C62241051F57356D7F3C90BB0E07F"); test_cipher(PGP_SA_AES_128, PGP_CIPHER_MODE_OCB, 16, false, "000102030405060708090A0B0C0D0E0F", "BBAA99887766554433221108", "000102030405060708090A0B0C0D0E0F1011121314151617", nullptr, "6DC225A071FC1B9F7C69F93B0F1E10DE"); test_cipher( PGP_SA_AES_128, PGP_CIPHER_MODE_OCB, 16, false, "000102030405060708090A0B0C0D0E0F", "BBAA99887766554433221109", nullptr, "000102030405060708090A0B0C0D0E0F1011121314151617", "221BD0DE7FA6FE993ECCD769460A0AF2D6CDED0C395B1C3CE725F32494B9F914D85C0B1EB38357FF"); } TEST_F(rnp_tests, test_cipher_aes_128_cbc) { // botan test vectors test_cipher(PGP_SA_AES_128, PGP_CIPHER_MODE_CBC, 0, false, "10d6f8e78c0ccf8736e4307aaf5b07ef", "3eb182d95bbd5a609aecfb59a0ca898b", NULL, "3a", "a7d290687ae325054a8691014d6821d7"); test_cipher(PGP_SA_AES_128, PGP_CIPHER_MODE_CBC, 0, false, "10d6f8e78c0ccf8736e4307aaf5b07ef", "3eb182d95bbd5a609aecfb59a0ca898b", NULL, "3a513eb569a503b4413b31fa883ddc88", "0cbaf4fa94df265fe264633a994bc25fc7654f19c282a3e2db81499c941ca2b3"); } TEST_F(rnp_tests, test_cipher_aes_128_cbc_nopadding) { // botan test vectors test_cipher(PGP_SA_AES_128, PGP_CIPHER_MODE_CBC, 0, true, "1f8e4973953f3fb0bd6b16662e9a3c17", "2fe2b333ceda8f98f4a99b40d2cd34a8", NULL, "45cf12964fc824ab76616ae2f4bf0822", "0f61c4d44c5147c03c195ad7e2cc12b2"); }