1 /*
2   Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
3 
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2.0,
6   as published by the Free Software Foundation.
7 
8   This program is also distributed with certain software (including
9   but not limited to OpenSSL) that is licensed under separate terms,
10   as designated in a particular file or component or in included license
11   documentation.  The authors of MySQL hereby grant you an additional
12   permission to link the program and your derivative works with the
13   separately licensed software that they have included with MySQL.
14 
15   This program is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   GNU General Public License for more details.
19 
20   You should have received a copy of the GNU General Public License
21   along with this program; if not, write to the Free Software
22   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23 */
24 
25 #include "keyring/keyring_manager.h"
26 
27 #ifndef _WIN32
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #else
31 #include <windows.h>
32 #endif
33 
34 #include <fstream>
35 #include <set>
36 #include <stdexcept>
37 
38 #include "common.h"
39 #include "dim.h"
40 #include "gtest/gtest.h"
41 #include "keyring/keyring_memory.h"
42 #include "random_generator.h"
43 #include "test/helpers.h"
44 
45 using namespace testing;
46 
47 class TemporaryFileCleaner {
48  public:
~TemporaryFileCleaner()49   ~TemporaryFileCleaner() {
50     if (!getenv("TEST_DONT_DELETE_FILES")) {
51       for (auto path : tmp_files_) {
52 #ifndef _WIN32
53         ::unlink(path.c_str());
54 #else
55         DeleteFile(path.c_str()) ? 0 : -1;
56 #endif
57       }
58     }
59   }
60 
add(const std::string & path)61   const std::string &add(const std::string &path) {
62     tmp_files_.insert(path);
63     return path;
64   }
65 
66  private:
67   std::set<std::string> tmp_files_;
68 };
69 
70 #ifdef _WIN32
71 // Copied from keyring_file.cc
72 
73 // Smart pointers for WinAPI structures that use C-style memory management.
74 using SecurityDescriptorPtr =
75     std::unique_ptr<SECURITY_DESCRIPTOR,
76                     mysql_harness::StdFreeDeleter<SECURITY_DESCRIPTOR>>;
77 using SidPtr = std::unique_ptr<SID, mysql_harness::StdFreeDeleter<SID>>;
78 
79 /**
80  * Retrieves file's DACL security descriptor.
81  *
82  * @param[in] file_name File name.
83  *
84  * @return File's DACL security descriptor.
85  *
86  * @throw std::exception Failed to retrieve security descriptor.
87  */
get_security_descriptor(const std::string & file_name)88 static SecurityDescriptorPtr get_security_descriptor(
89     const std::string &file_name) {
90   static constexpr SECURITY_INFORMATION kReqInfo = DACL_SECURITY_INFORMATION;
91 
92   // Get the size of the descriptor.
93   DWORD sec_desc_size;
94 
95   if (GetFileSecurityA(file_name.c_str(), kReqInfo, nullptr, 0,
96                        &sec_desc_size) == FALSE) {
97     auto error = GetLastError();
98 
99     // We expect to receive `ERROR_INSUFFICIENT_BUFFER`.
100     if (error != ERROR_INSUFFICIENT_BUFFER) {
101       throw std::runtime_error("GetFileSecurity() failed (" + file_name +
102                                "): " + std::to_string(error));
103     }
104   }
105 
106   SecurityDescriptorPtr sec_desc(
107       static_cast<SECURITY_DESCRIPTOR *>(std::malloc(sec_desc_size)));
108 
109   if (GetFileSecurityA(file_name.c_str(), kReqInfo, sec_desc.get(),
110                        sec_desc_size, &sec_desc_size) == FALSE) {
111     throw std::runtime_error("GetFileSecurity() failed (" + file_name +
112                              "): " + std::to_string(GetLastError()));
113   }
114 
115   return sec_desc;
116 }
117 
118 /**
119  * Verifies permissions of an access ACE entry.
120  *
121  * @param[in] access_ace Access ACE entry.
122  *
123  * @throw std::exception Everyone has access to the ACE access entry or
124  *                        an error occurred.
125  */
check_ace_access_rights(ACCESS_ALLOWED_ACE * access_ace)126 static void check_ace_access_rights(ACCESS_ALLOWED_ACE *access_ace) {
127   SID *sid = reinterpret_cast<SID *>(&access_ace->SidStart);
128   DWORD sid_size = SECURITY_MAX_SID_SIZE;
129   SidPtr everyone_sid(static_cast<SID *>(std::malloc(sid_size)));
130 
131   if (CreateWellKnownSid(WinWorldSid, nullptr, everyone_sid.get(), &sid_size) ==
132       FALSE) {
133     throw std::runtime_error("CreateWellKnownSid() failed: " +
134                              std::to_string(GetLastError()));
135   }
136 
137   if (EqualSid(sid, everyone_sid.get())) {
138     if (access_ace->Mask & (FILE_EXECUTE)) {
139       throw std::runtime_error(
140           "Invalid keyring file access rights "
141           "(Execute privilege granted to Everyone).");
142     }
143     if (access_ace->Mask &
144         (FILE_WRITE_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES)) {
145       throw std::runtime_error(
146           "Invalid keyring file access rights "
147           "(Write privilege granted to Everyone).");
148     }
149     if (access_ace->Mask &
150         (FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES)) {
151       throw std::runtime_error(
152           "Invalid keyring file access rights "
153           "(Read privilege granted to Everyone).");
154     }
155   }
156 }
157 
158 /**
159  * Verifies access permissions in a DACL.
160  *
161  * @param[in] dacl DACL to be verified.
162  *
163  * @throw std::exception DACL contains an ACL entry that grants full access to
164  *                        Everyone or an error occurred.
165  */
check_acl_access_rights(ACL * dacl)166 static void check_acl_access_rights(ACL *dacl) {
167   ACL_SIZE_INFORMATION dacl_size_info;
168 
169   if (GetAclInformation(dacl, &dacl_size_info, sizeof(dacl_size_info),
170                         AclSizeInformation) == FALSE) {
171     throw std::runtime_error("GetAclInformation() failed: " +
172                              std::to_string(GetLastError()));
173   }
174 
175   for (DWORD ace_idx = 0; ace_idx < dacl_size_info.AceCount; ++ace_idx) {
176     LPVOID ace = nullptr;
177 
178     if (GetAce(dacl, ace_idx, &ace) == FALSE) {
179       throw std::runtime_error("GetAce() failed: " +
180                                std::to_string(GetLastError()));
181       continue;
182     }
183 
184     if (static_cast<ACE_HEADER *>(ace)->AceType == ACCESS_ALLOWED_ACE_TYPE)
185       check_ace_access_rights(static_cast<ACCESS_ALLOWED_ACE *>(ace));
186   }
187 }
188 
189 /**
190  * Verifies access permissions in a security descriptor.
191  *
192  * @param[in] sec_desc Security descriptor to be verified.
193  *
194  * @throw std::exception Security descriptor grants full access to
195  *                        Everyone or an error occurred.
196  */
check_security_descriptor_access_rights(SecurityDescriptorPtr sec_desc)197 static void check_security_descriptor_access_rights(
198     SecurityDescriptorPtr sec_desc) {
199   BOOL dacl_present;
200   ACL *dacl;
201   BOOL dacl_defaulted;
202 
203   if (GetSecurityDescriptorDacl(sec_desc.get(), &dacl_present, &dacl,
204                                 &dacl_defaulted) == FALSE) {
205     throw std::runtime_error("GetSecurityDescriptorDacl() failed: " +
206                              std::to_string(GetLastError()));
207   }
208 
209   if (!dacl_present) {
210     // No DACL means: no access allowed. Which is fine.
211     return;
212   }
213 
214   if (!dacl) {
215     // Empty DACL means: all access allowed.
216     throw std::runtime_error(
217         "Invalid keyring file access rights "
218         "(Everyone has full access rights).");
219   }
220 
221   check_acl_access_rights(dacl);
222 }
223 #endif  // _WIN32
224 
check_file_private(const std::string & file)225 static bool check_file_private(const std::string &file) {
226 #ifdef _WIN32
227   try {
228     check_security_descriptor_access_rights(get_security_descriptor(file));
229     return true;
230   } catch (...) {
231     return false;
232   }
233 #else
234   struct stat st;
235   if (stat(file.c_str(), &st) < 0) {
236     throw std::runtime_error(file + ": " + strerror(errno));
237   }
238   if ((st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == (S_IRUSR | S_IWUSR))
239     return true;
240   return false;
241 #endif
242 }
243 
244 class FileChangeChecker {
245  public:
FileChangeChecker(const std::string & file)246   FileChangeChecker(const std::string &file) : path_(file) {
247     std::ifstream f;
248     std::stringstream ss;
249     f.open(file, std::ifstream::binary);
250     if (f.fail())
251       throw std::runtime_error(file + " " + mysql_harness::get_strerror(errno));
252     ss << f.rdbuf();
253     contents_ = ss.str();
254     f.close();
255   }
256 
check_unchanged()257   bool check_unchanged() {
258     std::ifstream f;
259     std::stringstream ss;
260     f.open(path_, std::ifstream::binary);
261     if (f.fail())
262       throw std::runtime_error(path_ + " " +
263                                mysql_harness::get_strerror(errno));
264     ss << f.rdbuf();
265     f.close();
266     return ss.str() == contents_;
267   }
268 
269  private:
270   std::string path_;
271   std::string contents_;
272 };
273 
file_exists(const std::string & file)274 static bool file_exists(const std::string &file) {
275   return mysql_harness::Path(file).exists();
276 }
277 
278 TmpDir tmp_dir;
279 
TEST(KeyringManager,init_tests)280 TEST(KeyringManager, init_tests) {
281   mysql_harness::DIM::instance().set_RandomGenerator(
282       []() {
283         static mysql_harness::FakeRandomGenerator rg;
284         return &rg;
285       },
286       [](mysql_harness::RandomGeneratorInterface *) {}
287       // don't delete our static!
288   );
289 }
290 
TEST(KeyringManager,init_with_key)291 TEST(KeyringManager, init_with_key) {
292   TemporaryFileCleaner cleaner;
293 
294   EXPECT_TRUE(mysql_harness::get_keyring() == nullptr);
295   mysql_harness::init_keyring_with_key(cleaner.add(tmp_dir.file("keyring")),
296                                        "secret", true);
297   {
298     mysql_harness::Keyring *kr = mysql_harness::get_keyring();
299     EXPECT_NE(kr, nullptr);
300 
301     kr->store("foo", "bar", "baz");
302     mysql_harness::flush_keyring();
303     EXPECT_TRUE(check_file_private(tmp_dir.file("keyring")));
304 
305     // this key will not be saved to disk b/c of missing flush
306     kr->store("account", "password", "");
307     EXPECT_EQ(kr->fetch("foo", "bar"), "baz");
308 
309     EXPECT_EQ(kr->fetch("account", "password"), "");
310   }
311   mysql_harness::reset_keyring();
312   EXPECT_TRUE(mysql_harness::get_keyring() == nullptr);
313 
314   EXPECT_FALSE(file_exists(tmp_dir.file("badkeyring")));
315   ASSERT_THROW(mysql_harness::init_keyring_with_key(tmp_dir.file("badkeyring"),
316                                                     "secret", false),
317                std::runtime_error);
318   EXPECT_FALSE(file_exists(tmp_dir.file("badkeyring")));
319 
320 #ifndef _WIN32
321   ASSERT_THROW(
322       mysql_harness::init_keyring_with_key("/badkeyring", "secret", false),
323       std::runtime_error);
324   EXPECT_FALSE(file_exists("/badkeyring"));
325 
326   ASSERT_THROW(
327       mysql_harness::init_keyring_with_key("/badkeyring", "secret", true),
328       std::runtime_error);
329   EXPECT_FALSE(file_exists("/badkeyring"));
330 #endif
331 
332   ASSERT_THROW(mysql_harness::init_keyring_with_key(tmp_dir.file("keyring"),
333                                                     "badkey", false),
334                mysql_harness::decryption_error);
335 
336   ASSERT_THROW(
337       mysql_harness::init_keyring_with_key(tmp_dir.file("keyring"), "", false),
338       mysql_harness::decryption_error);
339 
340   EXPECT_TRUE(mysql_harness::get_keyring() == nullptr);
341 
342   mysql_harness::init_keyring_with_key(tmp_dir.file("keyring"), "secret",
343                                        false);
344   {
345     mysql_harness::Keyring *kr = mysql_harness::get_keyring();
346 
347     EXPECT_EQ(kr->fetch("foo", "bar"), "baz");
348     ASSERT_THROW(kr->fetch("account", "password"), std::out_of_range);
349   }
350 
351   mysql_harness::reset_keyring();
352   EXPECT_TRUE(mysql_harness::get_keyring() == nullptr);
353   // no key no service
354   ASSERT_THROW(mysql_harness::init_keyring_with_key(
355                    cleaner.add(tmp_dir.file("xkeyring")), "", true),
356                std::runtime_error);
357   EXPECT_FALSE(file_exists(tmp_dir.file("xkeyring")));
358 
359   // try to open non-existing keyring
360   ASSERT_THROW(
361       mysql_harness::init_keyring_with_key(
362           cleaner.add(tmp_dir.file("invalidkeyring")), "secret", false),
363       std::runtime_error);
364   EXPECT_FALSE(file_exists(tmp_dir.file("invalidkeyring")));
365 
366   // check if keyring is created even if empty
367   mysql_harness::init_keyring_with_key(
368       cleaner.add(tmp_dir.file("emptykeyring")), "secret2", true);
369   EXPECT_TRUE(file_exists(tmp_dir.file("emptykeyring")));
370   mysql_harness::reset_keyring();
371 }
372 
TEST(KeyringManager,init_with_key_file)373 TEST(KeyringManager, init_with_key_file) {
374   TemporaryFileCleaner cleaner;
375 
376   EXPECT_FALSE(file_exists(tmp_dir.file("keyring")));
377   EXPECT_FALSE(file_exists(tmp_dir.file("keyfile")));
378 
379   EXPECT_TRUE(mysql_harness::get_keyring() == nullptr);
380   mysql_harness::init_keyring(cleaner.add(tmp_dir.file("keyring")),
381                               cleaner.add(tmp_dir.file("keyfile")), true);
382   EXPECT_TRUE(file_exists(tmp_dir.file("keyring")));
383   EXPECT_TRUE(file_exists(tmp_dir.file("keyfile")));
384   {
385     mysql_harness::Keyring *kr = mysql_harness::get_keyring();
386     EXPECT_NE(kr, nullptr);
387 
388     kr->store("foo", "bar", "baz");
389     mysql_harness::flush_keyring();
390     EXPECT_TRUE(check_file_private(tmp_dir.file("keyring")));
391     EXPECT_TRUE(check_file_private(tmp_dir.file("keyfile")));
392 
393     // this key will not be saved to disk b/c of missing flush
394     kr->store("account", "password", "");
395     EXPECT_EQ(kr->fetch("foo", "bar"), "baz");
396 
397     EXPECT_EQ(kr->fetch("account", "password"), "");
398   }
399   mysql_harness::reset_keyring();
400   EXPECT_TRUE(mysql_harness::get_keyring() == nullptr);
401 
402   FileChangeChecker check_kf(tmp_dir.file("keyfile"));
403   FileChangeChecker check_kr(tmp_dir.file("keyring"));
404 
405   EXPECT_FALSE(file_exists(tmp_dir.file("badkeyring")));
406   EXPECT_TRUE(file_exists(tmp_dir.file("keyfile")));
407   ASSERT_THROW(mysql_harness::init_keyring(tmp_dir.file("badkeyring"),
408                                            tmp_dir.file("keyfile"), false),
409                std::runtime_error);
410   EXPECT_FALSE(file_exists(tmp_dir.file("badkeyring")));
411 
412 #ifndef _WIN32
413   ASSERT_THROW(mysql_harness::init_keyring("/badkeyring",
414                                            tmp_dir.file("keyfile"), false),
415                std::runtime_error);
416   EXPECT_FALSE(file_exists("/badkeyring"));
417 
418   ASSERT_THROW(
419       mysql_harness::init_keyring("/badkeyring", tmp_dir.file("keyfile"), true),
420       std::runtime_error);
421   EXPECT_FALSE(file_exists("/badkeyring"));
422   EXPECT_TRUE(check_kf.check_unchanged());
423 
424   ASSERT_THROW(
425       mysql_harness::init_keyring(tmp_dir.file("keyring"), "/keyfile", false),
426       std::runtime_error);
427   EXPECT_FALSE(file_exists("/keyfile"));
428 
429   ASSERT_THROW(mysql_harness::init_keyring("/keyring", "/keyfile", false),
430                std::runtime_error);
431   EXPECT_FALSE(file_exists("/keyring"));
432   EXPECT_FALSE(file_exists("/keyfile"));
433 #endif
434   ASSERT_THROW(mysql_harness::init_keyring(tmp_dir.file("keyring"), "", false),
435                std::invalid_argument);
436 
437   EXPECT_TRUE(mysql_harness::get_keyring() == nullptr);
438 
439   // ensure that none of the tests above touched the keyring files
440   EXPECT_TRUE(check_kf.check_unchanged());
441   EXPECT_TRUE(check_kr.check_unchanged());
442 
443   EXPECT_TRUE(file_exists(tmp_dir.file("keyring")));
444   EXPECT_TRUE(file_exists(tmp_dir.file("keyfile")));
445   // reopen it
446   mysql_harness::init_keyring(tmp_dir.file("keyring"), tmp_dir.file("keyfile"),
447                               false);
448   {
449     mysql_harness::Keyring *kr = mysql_harness::get_keyring();
450 
451     EXPECT_EQ(kr->fetch("foo", "bar"), "baz");
452 
453     ASSERT_THROW(kr->fetch("account", "password"), std::out_of_range);
454   }
455   mysql_harness::reset_keyring();
456   EXPECT_TRUE(mysql_harness::get_keyring() == nullptr);
457 
458   // try to reopen keyring with bad key file
459   ASSERT_THROW(mysql_harness::init_keyring(tmp_dir.file("keyring"),
460                                            tmp_dir.file("badkeyfile"), false),
461                std::runtime_error);
462 
463   // try to reopen bad keyring with right keyfile
464   ASSERT_THROW(mysql_harness::init_keyring(tmp_dir.file("badkeyring"),
465                                            tmp_dir.file("keyfile"), false),
466                std::runtime_error);
467 
468   ASSERT_THROW(mysql_harness::init_keyring(tmp_dir.file("badkeyring"),
469                                            tmp_dir.file("badkeyfile"), false),
470                std::runtime_error);
471   EXPECT_TRUE(mysql_harness::get_keyring() == nullptr);
472 
473   // ensure that none of the tests above touched the keyring files
474   EXPECT_TRUE(check_kf.check_unchanged());
475   EXPECT_TRUE(check_kr.check_unchanged());
476 
477   // create a new keyring reusing the same keyfile, which should result in
478   // 2 master keys stored in the same keyfile
479   EXPECT_FALSE(file_exists(tmp_dir.file("keyring2")));
480   mysql_harness::init_keyring(cleaner.add(tmp_dir.file("keyring2")),
481                               cleaner.add(tmp_dir.file("keyfile")), true);
482   EXPECT_TRUE(file_exists(tmp_dir.file("keyring2")));
483   {
484     mysql_harness::Keyring *kr = mysql_harness::get_keyring();
485     EXPECT_NE(kr, nullptr);
486 
487     kr->store("user", "pass", "hooray");
488     mysql_harness::flush_keyring();
489     EXPECT_TRUE(check_file_private(tmp_dir.file("keyring2")));
490 
491     mysql_harness::flush_keyring();
492     EXPECT_TRUE(file_exists(tmp_dir.file("keyring2")));
493   }
494   mysql_harness::reset_keyring();
495 
496   // the orignal keyring should still be unchanged, but not the keyfile
497   bool b1 = check_kf.check_unchanged();
498   bool b2 = check_kr.check_unchanged();
499   EXPECT_FALSE(b1);
500   EXPECT_TRUE(b2);
501 
502   // now try to reopen both keyrings
503   mysql_harness::init_keyring(cleaner.add(tmp_dir.file("keyring2")),
504                               cleaner.add(tmp_dir.file("keyfile")), false);
505   {
506     mysql_harness::Keyring *kr = mysql_harness::get_keyring();
507     EXPECT_EQ(kr->fetch("user", "pass"), "hooray");
508   }
509   mysql_harness::reset_keyring();
510 
511   mysql_harness::init_keyring(cleaner.add(tmp_dir.file("keyring")),
512                               cleaner.add(tmp_dir.file("keyfile")), false);
513   {
514     mysql_harness::Keyring *kr = mysql_harness::get_keyring();
515     EXPECT_EQ(kr->fetch("foo", "bar"), "baz");
516   }
517   mysql_harness::reset_keyring();
518 
519   // now try to open with bogus key file
520   ASSERT_THROW(
521       mysql_harness::init_keyring(cleaner.add(tmp_dir.file("keyring")),
522                                   cleaner.add(tmp_dir.file("keyring2")), false),
523       std::runtime_error);
524 }
525 
TEST(KeyringManager,regression)526 TEST(KeyringManager, regression) {
527   TemporaryFileCleaner cleaner;
528 
529   // init keyring with no create flag was writing to existing file on open
530   mysql_harness::init_keyring(cleaner.add(tmp_dir.file("keyring")),
531                               cleaner.add(tmp_dir.file("keyfile")), true);
532   mysql_harness::Keyring *kr = mysql_harness::get_keyring();
533   kr->store("1", "2", "3");
534   mysql_harness::flush_keyring();
535   mysql_harness::reset_keyring();
536 
537   FileChangeChecker check_kf(tmp_dir.file("keyfile"));
538   FileChangeChecker check_kr(tmp_dir.file("keyring"));
539 
540   mysql_harness::init_keyring(cleaner.add(tmp_dir.file("keyring")),
541                               cleaner.add(tmp_dir.file("keyfile")), false);
542   EXPECT_TRUE(check_kf.check_unchanged());
543   EXPECT_TRUE(check_kr.check_unchanged());
544 
545   ASSERT_THROW(
546       mysql_harness::init_keyring(cleaner.add(tmp_dir.file("bogus1")),
547                                   cleaner.add(tmp_dir.file("bogus2")), false),
548       std::runtime_error);
549   ASSERT_THROW(
550       mysql_harness::init_keyring(cleaner.add(tmp_dir.file("bogus1")),
551                                   cleaner.add(tmp_dir.file("keyfile")), false),
552       std::runtime_error);
553   EXPECT_FALSE(file_exists(tmp_dir.file("bogus1")));
554   EXPECT_FALSE(file_exists(tmp_dir.file("bogus2")));
555 
556   EXPECT_TRUE(check_kf.check_unchanged());
557   EXPECT_TRUE(check_kr.check_unchanged());
558 
559   mysql_harness::reset_keyring();
560 }
561 
main(int argc,char ** argv)562 int main(int argc, char **argv) {
563   ::testing::InitGoogleTest(&argc, argv);
564   return RUN_ALL_TESTS();
565 }
566