1 /*
2 * (C) 2016 Philipp Weber
3 * (C) 2016 Daniel Neus
4 *
5 * Botan is released under the Simplified BSD License (see license.txt)
6 */
7 
8 #include "tests.h"
9 
10 #if defined(BOTAN_HAS_ECIES)
11    #include <botan/ecies.h>
12    #include <botan/ecdh.h>
13 #endif
14 
15 namespace Botan_Tests {
16 
17 namespace {
18 
19 #if defined(BOTAN_HAS_ECIES) && defined(BOTAN_HAS_AES) && defined(BOTAN_HAS_MODE_CBC)
20 
21 using Flags = Botan::ECIES_Flags;
22 
get_compression_type(const std::string & format)23 Botan::PointGFp::Compression_Type get_compression_type(const std::string& format)
24    {
25    if(format == "uncompressed")
26       {
27       return Botan::PointGFp::UNCOMPRESSED;
28       }
29    else if(format == "compressed")
30       {
31       return Botan::PointGFp::COMPRESSED;
32       }
33    else if(format == "hybrid")
34       {
35       return Botan::PointGFp::HYBRID;
36       }
37    throw Botan::Invalid_Argument("invalid compression format");
38    }
39 
ecies_flags(bool cofactor_mode,bool old_cofactor_mode,bool check_mode,bool single_hash_mode)40 Flags ecies_flags(bool cofactor_mode, bool old_cofactor_mode, bool check_mode, bool single_hash_mode)
41    {
42    return (cofactor_mode ? Flags::COFACTOR_MODE : Flags::NONE)
43           | (single_hash_mode ? Flags::SINGLE_HASH_MODE : Flags::NONE)
44           | (old_cofactor_mode ? Flags::OLD_COFACTOR_MODE : Flags::NONE)
45           | (check_mode ? Flags::CHECK_MODE : Flags::NONE);
46    }
47 
check_encrypt_decrypt(Test::Result & result,const Botan::ECDH_PrivateKey & private_key,const Botan::ECDH_PrivateKey & other_private_key,const Botan::ECIES_System_Params & ecies_params,const Botan::InitializationVector & iv,const std::string & label,const std::vector<uint8_t> & plaintext,const std::vector<uint8_t> & ciphertext)48 void check_encrypt_decrypt(Test::Result& result, const Botan::ECDH_PrivateKey& private_key,
49                            const Botan::ECDH_PrivateKey& other_private_key,
50                            const Botan::ECIES_System_Params& ecies_params,
51                            const Botan::InitializationVector& iv, const std::string& label,
52                            const std::vector<uint8_t>& plaintext, const std::vector<uint8_t>& ciphertext)
53    {
54    try
55       {
56       Botan::ECIES_Encryptor ecies_enc(private_key, ecies_params, Test::rng());
57       ecies_enc.set_other_key(other_private_key.public_point());
58       Botan::ECIES_Decryptor ecies_dec(other_private_key, ecies_params, Test::rng());
59       if(!iv.bits_of().empty())
60          {
61          ecies_enc.set_initialization_vector(iv);
62          ecies_dec.set_initialization_vector(iv);
63          }
64       if(!label.empty())
65          {
66          ecies_enc.set_label(label);
67          ecies_dec.set_label(label);
68          }
69 
70       const std::vector<uint8_t> encrypted = ecies_enc.encrypt(plaintext, Test::rng());
71       if(!ciphertext.empty())
72          {
73          result.test_eq("encrypted data", encrypted, ciphertext);
74          }
75       const Botan::secure_vector<uint8_t> decrypted = ecies_dec.decrypt(encrypted);
76       result.test_eq("decrypted data equals plaintext", decrypted, plaintext);
77 
78       std::vector<uint8_t> invalid_encrypted = encrypted;
79       uint8_t& last_byte = invalid_encrypted[invalid_encrypted.size() - 1];
80       last_byte = ~last_byte;
81       result.test_throws("throw on invalid ciphertext", [&ecies_dec, &invalid_encrypted]
82          {
83          ecies_dec.decrypt(invalid_encrypted);
84          });
85       }
86    catch(Botan::Lookup_Error& e)
87       {
88       result.test_note(std::string("Test not executed: ") + e.what());
89       }
90    }
91 
check_encrypt_decrypt(Test::Result & result,const Botan::ECDH_PrivateKey & private_key,const Botan::ECDH_PrivateKey & other_private_key,const Botan::ECIES_System_Params & ecies_params,size_t iv_length=0)92 void check_encrypt_decrypt(Test::Result& result, const Botan::ECDH_PrivateKey& private_key,
93                            const Botan::ECDH_PrivateKey& other_private_key,
94                            const Botan::ECIES_System_Params& ecies_params, size_t iv_length = 0)
95    {
96    const std::vector<uint8_t> plaintext { 1, 2, 3 };
97    check_encrypt_decrypt(result, private_key, other_private_key, ecies_params, std::vector<uint8_t>(iv_length, 0), "",
98                          plaintext, std::vector<uint8_t>());
99    }
100 
101 #if defined(BOTAN_HAS_KDF1_18033) && defined(BOTAN_HAS_SHA1)
102 
103 class ECIES_ISO_Tests final : public Text_Based_Test
104    {
105    public:
ECIES_ISO_Tests()106       ECIES_ISO_Tests() : Text_Based_Test(
107             "pubkey/ecies-18033.vec",
108             "format,p,a,b,mu,nu,gx,gy,hx,hy,x,r,C0,K") {}
109 
run_one_test(const std::string &,const VarMap & vars)110       Test::Result run_one_test(const std::string&, const VarMap& vars) override
111          {
112          Test::Result result("ECIES-ISO");
113 
114          // get test vectors defined by ISO 18033
115          const Botan::PointGFp::Compression_Type compression_type = get_compression_type(vars.get_req_str("format"));
116          const Botan::BigInt p = vars.get_req_bn("p");
117          const Botan::BigInt a = vars.get_req_bn("a");
118          const Botan::BigInt b = vars.get_req_bn("b");
119          const Botan::BigInt mu = vars.get_req_bn("mu");   // order
120          const Botan::BigInt nu = vars.get_req_bn("nu");   // cofactor
121          const Botan::BigInt gx = vars.get_req_bn("gx");   // base point x
122          const Botan::BigInt gy = vars.get_req_bn("gy");   // base point y
123          const Botan::BigInt hx = vars.get_req_bn("hx");   // x of public point of bob
124          const Botan::BigInt hy = vars.get_req_bn("hy");   // y of public point of bob
125          const Botan::BigInt x = vars.get_req_bn("x");   // private key of bob
126          const Botan::BigInt r = vars.get_req_bn("r");   // (ephemeral) private key of alice
127          const std::vector<uint8_t> c0 = vars.get_req_bin("C0");   // expected encoded (ephemeral) public key
128          const std::vector<uint8_t> k = vars.get_req_bin("K");   // expected derived secret
129 
130          const Botan::EC_Group domain(p, a, b, gx, gy, mu, nu);
131 
132          // keys of bob
133          const Botan::ECDH_PrivateKey other_private_key(Test::rng(), domain, x);
134          const Botan::PointGFp other_public_key_point = domain.point(hx, hy);
135          const Botan::ECDH_PublicKey other_public_key(domain, other_public_key_point);
136 
137          // (ephemeral) keys of alice
138          const Botan::ECDH_PrivateKey eph_private_key(Test::rng(), domain, r);
139          const Botan::PointGFp eph_public_key_point = eph_private_key.public_point();
140          const std::vector<uint8_t> eph_public_key_bin = eph_public_key_point.encode(compression_type);
141          result.test_eq("encoded (ephemeral) public key", eph_public_key_bin, c0);
142 
143          // test secret derivation: ISO 18033 test vectors use KDF1 from ISO 18033
144          // no cofactor-/oldcofactor-/singlehash-/check-mode and 128 byte secret length
145          Botan::ECIES_KA_Params ka_params(eph_private_key.domain(), "KDF1-18033(SHA-1)", 128, compression_type, Flags::NONE);
146          const Botan::ECIES_KA_Operation ka(eph_private_key, ka_params, true, Test::rng());
147          const Botan::SymmetricKey secret_key = ka.derive_secret(eph_public_key_bin, other_public_key_point);
148          result.test_eq("derived secret key", secret_key.bits_of(), k);
149 
150          // test encryption / decryption
151 
152          for(auto comp_type : { Botan::PointGFp::UNCOMPRESSED, Botan::PointGFp::COMPRESSED, Botan::PointGFp::HYBRID })
153             {
154             for(bool cofactor_mode : { true, false })
155                {
156                for(bool single_hash_mode : { true, false })
157                   {
158                   for(bool old_cofactor_mode : { true, false })
159                      {
160                      for(bool check_mode : { true, false })
161                         {
162                         Flags flags = ecies_flags(cofactor_mode, old_cofactor_mode, check_mode, single_hash_mode);
163 
164                         if(size_t(cofactor_mode) + size_t(check_mode) + size_t(old_cofactor_mode) > 1)
165                            {
166                            auto onThrow =  [&]()
167                               {
168                               Botan::ECIES_System_Params(eph_private_key.domain(),
169                                  "KDF2(SHA-1)", "AES-256/CBC", 32, "HMAC(SHA-1)", 20,
170                                  comp_type, flags);
171                               };
172                            result.test_throws("throw on invalid ECIES_Flags", onThrow);
173                            continue;
174                            }
175 
176                         Botan::ECIES_System_Params ecies_params(eph_private_key.domain(), "KDF2(SHA-1)", "AES-256/CBC",
177                                                                 32, "HMAC(SHA-1)", 20, comp_type, flags);
178                         check_encrypt_decrypt(result, eph_private_key, other_private_key, ecies_params, 16);
179                         }
180                      }
181                   }
182                }
183             }
184 
185          return result;
186          }
187    };
188 
189 BOTAN_REGISTER_TEST("pubkey", "ecies_iso", ECIES_ISO_Tests);
190 
191 #endif
192 
193 class ECIES_Tests final : public Text_Based_Test
194    {
195    public:
ECIES_Tests()196       ECIES_Tests()
197          : Text_Based_Test(
198               "pubkey/ecies.vec",
199               "Curve,PrivateKey,OtherPrivateKey,Kdf,Dem,DemKeyLen,Mac,MacKeyLen,Format,"
200               "CofactorMode,OldCofactorMode,CheckMode,SingleHashMode,Label,Plaintext,Ciphertext",
201               "Iv") {}
202 
run_one_test(const std::string &,const VarMap & vars)203       Test::Result run_one_test(const std::string&, const VarMap& vars) override
204          {
205          Test::Result result("ECIES");
206 
207          const std::string curve = vars.get_req_str("Curve");
208          const Botan::BigInt private_key_value = vars.get_req_bn("PrivateKey");
209          const Botan::BigInt other_private_key_value = vars.get_req_bn("OtherPrivateKey");
210          const std::string kdf = vars.get_req_str("Kdf");
211          const std::string dem = vars.get_req_str("Dem");
212          const size_t dem_key_len = vars.get_req_sz("DemKeyLen");
213          const std::vector<uint8_t> iv = vars.get_opt_bin("Iv");
214          const std::string mac = vars.get_req_str("Mac");
215          const size_t mac_key_len = vars.get_req_sz("MacKeyLen");
216          const Botan::PointGFp::Compression_Type compression_type = get_compression_type(vars.get_req_str("Format"));
217          const bool cofactor_mode = vars.get_req_sz("CofactorMode") != 0;
218          const bool old_cofactor_mode = vars.get_req_sz("OldCofactorMode") != 0;
219          const bool check_mode = vars.get_req_sz("CheckMode") != 0;
220          const bool single_hash_mode = vars.get_req_sz("SingleHashMode") != 0;
221          const std::string label = vars.get_req_str("Label");
222          const std::vector<uint8_t> plaintext = vars.get_req_bin("Plaintext");
223          const std::vector<uint8_t> ciphertext = vars.get_req_bin("Ciphertext");
224 
225          const Flags flags = ecies_flags(cofactor_mode, old_cofactor_mode, check_mode, single_hash_mode);
226          const Botan::EC_Group domain(curve);
227          const Botan::ECDH_PrivateKey private_key(Test::rng(), domain, private_key_value);
228          const Botan::ECDH_PrivateKey other_private_key(Test::rng(), domain, other_private_key_value);
229 
230          const Botan::ECIES_System_Params ecies_params(private_key.domain(), kdf, dem, dem_key_len, mac, mac_key_len,
231                compression_type, flags);
232          check_encrypt_decrypt(result, private_key, other_private_key, ecies_params, iv, label, plaintext, ciphertext);
233 
234          return result;
235          }
236 
237    };
238 
239 BOTAN_REGISTER_TEST("pubkey", "ecies", ECIES_Tests);
240 
241 #if defined(BOTAN_HAS_KDF1_18033) && defined(BOTAN_HAS_HMAC) && defined(BOTAN_HAS_AES) && defined(BOTAN_HAS_SHA2_64)
242 
test_other_key_not_set()243 Test::Result test_other_key_not_set()
244    {
245    Test::Result result("ECIES other key not set");
246 
247    const Flags flags = ecies_flags(false, false, false, true);
248    const Botan::EC_Group domain("secp521r1");
249    const Botan::BigInt private_key_value("405029866705438137604064977397053031159826489755682166267763407"
250                                          "5002761777100287880684822948852132235484464537021197213998300006"
251                                          "547176718172344447619746779823");
252 
253    const Botan::ECDH_PrivateKey private_key(Test::rng(), domain, private_key_value);
254    const Botan::ECIES_System_Params ecies_params(private_key.domain(), "KDF1-18033(SHA-512)", "AES-256/CBC", 32,
255          "HMAC(SHA-512)", 20, Botan::PointGFp::Compression_Type::COMPRESSED,
256          flags);
257 
258    Botan::ECIES_Encryptor ecies_enc(private_key, ecies_params, Test::rng());
259 
260    result.test_throws("encrypt not possible without setting other public key", [ &ecies_enc ]()
261       {
262       ecies_enc.encrypt(std::vector<uint8_t>(8), Test::rng());
263       });
264 
265    return result;
266    }
267 
test_kdf_not_found()268 Test::Result test_kdf_not_found()
269    {
270    Test::Result result("ECIES kdf not found");
271 
272    const Flags flags = ecies_flags(false, false, false, true);
273    const Botan::EC_Group domain("secp521r1");
274    const Botan::BigInt private_key_value("405029866705438137604064977397053031159826489755682166267763407"
275                                          "5002761777100287880684822948852132235484464537021197213998300006"
276                                          "547176718172344447619746779823");
277 
278    const Botan::ECDH_PrivateKey private_key(Test::rng(), domain, private_key_value);
279    const Botan::ECIES_System_Params ecies_params(private_key.domain(), "KDF-XYZ(SHA-512)", "AES-256/CBC", 32,
280          "HMAC(SHA-512)", 20, Botan::PointGFp::Compression_Type::COMPRESSED,
281          flags);
282 
283    result.test_throws("kdf not found", [&]()
284       {
285       Botan::ECIES_Encryptor ecies_enc(private_key, ecies_params, Test::rng());
286       ecies_enc.encrypt(std::vector<uint8_t>(8), Test::rng());
287       });
288 
289    return result;
290    }
291 
test_mac_not_found()292 Test::Result test_mac_not_found()
293    {
294    Test::Result result("ECIES mac not found");
295 
296    const Flags flags = ecies_flags(false, false, false, true);
297    const Botan::EC_Group domain("secp521r1");
298    const Botan::BigInt private_key_value("405029866705438137604064977397053031159826489755682166267763407"
299                                          "5002761777100287880684822948852132235484464537021197213998300006"
300                                          "547176718172344447619746779823");
301 
302    const Botan::ECDH_PrivateKey private_key(Test::rng(), domain, private_key_value);
303    const Botan::ECIES_System_Params ecies_params(private_key.domain(), "KDF1-18033(SHA-512)", "AES-256/CBC", 32,
304          "XYZMAC(SHA-512)", 20, Botan::PointGFp::Compression_Type::COMPRESSED,
305          flags);
306 
307    result.test_throws("mac not found", [&]()
308       {
309       Botan::ECIES_Encryptor ecies_enc(private_key, ecies_params, Test::rng());
310       ecies_enc.encrypt(std::vector<uint8_t>(8), Test::rng());
311       });
312 
313    return result;
314    }
315 
test_cipher_not_found()316 Test::Result test_cipher_not_found()
317    {
318    Test::Result result("ECIES cipher not found");
319 
320    const Flags flags = ecies_flags(false, false, false, true);
321    const Botan::EC_Group domain("secp521r1");
322    const Botan::BigInt private_key_value("405029866705438137604064977397053031159826489755682166267763407"
323                                          "5002761777100287880684822948852132235484464537021197213998300006"
324                                          "547176718172344447619746779823");
325 
326    const Botan::ECDH_PrivateKey private_key(Test::rng(), domain, private_key_value);
327    const Botan::ECIES_System_Params ecies_params(private_key.domain(), "KDF1-18033(SHA-512)", "AES-XYZ-256/CBC", 32,
328          "HMAC(SHA-512)", 20, Botan::PointGFp::Compression_Type::COMPRESSED,
329          flags);
330 
331    result.test_throws("cipher not found", [&]()
332       {
333       Botan::ECIES_Encryptor ecies_enc(private_key, ecies_params, Test::rng());
334       ecies_enc.encrypt(std::vector<uint8_t>(8), Test::rng());
335       });
336 
337    return result;
338    }
339 
test_system_params_short_ctor()340 Test::Result test_system_params_short_ctor()
341    {
342    Test::Result result("ECIES short system params ctor");
343 
344    const Botan::EC_Group domain("secp521r1");
345    const Botan::BigInt private_key_value("405029866705438137604064977397053031159826489755682166267763407"
346                                          "5002761777100287880684822948852132235484464537021197213998300006"
347                                          "547176718172344447619746779823");
348 
349    const Botan::BigInt other_private_key_value("2294226772740614508941417891614236736606752960073669253551166842"
350          "5866095315090327914760325168219669828915074071456176066304457448"
351          "25404691681749451640151380153");
352 
353    const Botan::ECDH_PrivateKey private_key(Test::rng(), domain, private_key_value);
354    const Botan::ECDH_PrivateKey other_private_key(Test::rng(), domain, other_private_key_value);
355 
356    const Botan::ECIES_System_Params ecies_params(private_key.domain(), "KDF1-18033(SHA-512)", "AES-256/CBC", 32,
357          "HMAC(SHA-512)", 16);
358 
359    const Botan::InitializationVector iv("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
360    const std::string label = "Test";
361 
362    const std::vector<uint8_t> plaintext = Botan::hex_decode("000102030405060708090A0B0C0D0E0F");
363 
364    // generated with botan
365    const std::vector<uint8_t> ciphertext = Botan::hex_decode("0401519EAA0489FF9D51E98E4C22349463E2001CD06F8CE47D81D4007A"
366                                            "79ACF98E92C814686477CEA666EFC277DC84E15FC95E38AFF8E16D478A"
367                                            "44CD5C5F1517F8B1F300000591317F261C3D04A7207F01EAE3EC70F2360"
368                                            "0F82C53CC0B85BE7AC9F6CE79EF2AB416E5934D61BA9D346385D7545C57F"
369                                            "77C7EA7C58E18C70CBFB0A24AE1B9943EC5A8D0657522CCDF30BA95674D81"
370                                            "B397635D215178CD13BD9504AE957A9888F4128FFC0F0D3F1CEC646AEC8CE"
371                                            "3F2463D233B22A7A12B679F4C06501F584D4DEFF6D26592A8D873398BD892"
372                                            "B477B3468813C053DA43C4F3D49009F7A12D6EF7");
373 
374    check_encrypt_decrypt(result, private_key, other_private_key, ecies_params, iv, label, plaintext, ciphertext);
375 
376    return result;
377    }
378 
test_ciphertext_too_short()379 Test::Result test_ciphertext_too_short()
380    {
381    Test::Result result("ECIES ciphertext too short");
382 
383    const Botan::EC_Group domain("secp521r1");
384    const Botan::BigInt private_key_value("405029866705438137604064977397053031159826489755682166267763407"
385                                          "5002761777100287880684822948852132235484464537021197213998300006"
386                                          "547176718172344447619746779823");
387 
388    const Botan::BigInt other_private_key_value("2294226772740614508941417891614236736606752960073669253551166842"
389          "5866095315090327914760325168219669828915074071456176066304457448"
390          "25404691681749451640151380153");
391 
392    const Botan::ECDH_PrivateKey private_key(Test::rng(), domain, private_key_value);
393    const Botan::ECDH_PrivateKey other_private_key(Test::rng(), domain, other_private_key_value);
394 
395    const Botan::ECIES_System_Params ecies_params(private_key.domain(), "KDF1-18033(SHA-512)", "AES-256/CBC", 32,
396          "HMAC(SHA-512)", 16);
397 
398    Botan::ECIES_Decryptor ecies_dec(other_private_key, ecies_params, Test::rng());
399 
400    result.test_throws("ciphertext too short", [ &ecies_dec ]()
401       {
402       ecies_dec.decrypt(Botan::hex_decode("0401519EAA0489FF9D51E98E4C22349A"));
403       });
404 
405    return result;
406    }
407 
408 class ECIES_Unit_Tests final : public Test
409    {
410    public:
run()411       std::vector<Test::Result> run() override
412          {
413          std::vector<Test::Result> results;
414 
415          std::vector<std::function<Test::Result()>> fns =
416             {
417             test_other_key_not_set,
418             test_kdf_not_found,
419             test_mac_not_found,
420             test_cipher_not_found,
421             test_system_params_short_ctor,
422             test_ciphertext_too_short
423             };
424 
425          for(size_t i = 0; i != fns.size(); ++i)
426             {
427             try
428                {
429                results.emplace_back(fns[ i ]());
430                }
431             catch(std::exception& e)
432                {
433                results.emplace_back(Test::Result::Failure("ECIES unit tests " + std::to_string(i), e.what()));
434                }
435             }
436 
437          return results;
438          }
439    };
440 
441 BOTAN_REGISTER_TEST("pubkey", "ecies_unit", ECIES_Unit_Tests);
442 
443 #endif
444 
445 #endif
446 
447 }
448 
449 }
450