1 /*
2  * Copyright (c) 2017, 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, version 2.0, 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 "plugin/x/src/sha256_password_cache.h"
26 
27 #include "plugin/x/src/helper/multithread/rw_lock.h"
28 #include "plugin/x/src/xpl_log.h"
29 
30 namespace xpl {
31 
SHA256_password_cache()32 SHA256_password_cache::SHA256_password_cache()
33     : m_cache_lock(KEY_rwlock_x_sha256_password_cache) {}
34 
35 /**
36   Start caching.
37 
38   "Upsert" operation is going to cache all account informations passed to it.
39 */
enable()40 void SHA256_password_cache::enable() {
41   RWLock_writelock guard(&m_cache_lock);
42   m_accepting_input = true;
43 }
44 
45 /**
46   Stop caching.
47 
48   Remove all already cached accounts and prevent the "Upsert" operation from
49   caching account informations.
50 */
disable()51 void SHA256_password_cache::disable() {
52   RWLock_writelock guard(&m_cache_lock);
53   m_accepting_input = false;
54 
55   m_password_cache.clear();
56 }
57 
58 /**
59   Update a cache entry or add a new entry if there is no entry with the given
60   key. Key is constucted from username and hostname.
61 
62   @param [in] user Username which will be used as a part of the cache entry key
63   @param [in] host Hostname which will be used as a part of the cache entry key
64   @param [in] value Value to be cached
65 
66   @return Result of upsert operation
67     @retval true Value added or updated successfully
68     @retval false Upsert operation failed
69 */
upsert(const std::string & user,const std::string & host,const std::string & value)70 bool SHA256_password_cache::upsert(const std::string &user,
71                                    const std::string &host,
72                                    const std::string &value) {
73   auto key = create_key(user, host);
74   auto optional_hash = create_hash(value);
75   RWLock_writelock guard(&m_cache_lock);
76 
77   if (!m_accepting_input) return false;
78 
79   if (!optional_hash.first) return false;
80 
81   m_password_cache[key] = optional_hash.second;
82   return true;
83 }
84 
85 /**
86   Remove an entry from the cache
87 
88   @param [in] user Username which will be used as a part of the cache entry key
89   @param [in] host Hostname which will be used as a part of the cache entry key
90 
91   @return result of the deletion
92     @retval true Entry successfully removed
93     @retval false Error while removing the entry
94 */
remove(const std::string & user,const std::string & host)95 bool SHA256_password_cache::remove(const std::string &user,
96                                    const std::string &host) {
97   RWLock_writelock guard(&m_cache_lock);
98   return m_password_cache.erase(create_key(user, host));
99 }
100 
101 /**
102   Try to get an entry from the cache.
103 
104   @param [in] user Username which will be used as a part of the cache entry key
105   @param [in] host Hostname which will be used as a part of the cache entry key
106 
107   @returns Pair containing flag and a string. When search was successful
108   returns true and cache entry value. Otherwise returns false and an empty
109   string
110 */
get_entry(const std::string & user,const std::string & host) const111 std::pair<bool, std::string> SHA256_password_cache::get_entry(
112     const std::string &user, const std::string &host) const {
113   RWLock_readlock guard(&m_cache_lock);
114 
115   if (!m_accepting_input) return {false, ""};
116 
117   const auto it = m_password_cache.find(create_key(user, host));
118 
119   if (it == m_password_cache.end()) return {false, ""};
120 
121   return {true, it->second};
122 }
123 
124 /**
125   Check if hash of the given value is stored in the cache.
126 
127   @param [in] user Username which will be used as a part of the cache entry key
128   @param [in] host Hostname which will be used as a part of the cache entry key
129   @param [in] value Value used as base for a hash which will be searched for
130 
131   @retval true Value hash is stored in the cache
132   @retval false Value hash is not in the cache
133 */
contains(const std::string & user,const std::string & host,const std::string & value) const134 bool SHA256_password_cache::contains(const std::string &user,
135                                      const std::string &host,
136                                      const std::string &value) const {
137   const auto search_result = get_entry(user, host);
138 
139   if (!search_result.first) return false;
140 
141   const auto optional_hash = create_hash(value);
142 
143   return !optional_hash.first ? false
144                               : search_result.second == optional_hash.second;
145 }
146 
147 /**
148   Remove all cache entries.
149 */
clear()150 void SHA256_password_cache::clear() {
151   RWLock_writelock guard(&m_cache_lock);
152 
153   m_password_cache.clear();
154 }
155 
156 /**
157   Create a key which will be used in the cache.
158 
159   @param [in] user Username which will be used as a part of the cache entry key
160   @param [in] host Hostname which will be used as a part of the cache entry key
161 
162   @returns Key string.
163 */
create_key(const std::string & user,const std::string & host) const164 std::string SHA256_password_cache::create_key(const std::string &user,
165                                               const std::string &host) const {
166   return user + '\0' + host + '\0';
167 }
168 
169 /**
170   Create SHA256(SHA256(value)) hash from the given value. It will be used as
171   a cache entry
172 
173   @param[in] value Value which will be used to calculate the hash
174 
175   @returns Pair containing flag and a cache entry. Flag is returned so that in
176   case when hash could not be generated we will not insert empty string into
177   the password cache
178 */
179 std::pair<bool, SHA256_password_cache::sha2_cache_entry_t>
create_hash(const std::string & value) const180 SHA256_password_cache::create_hash(const std::string &value) const {
181   // Locking not needed since SHA256_digest does not contain any shared state
182   sha2_password::SHA256_digest sha256_digest;
183 
184   const auto length = sha2_password::CACHING_SHA2_DIGEST_LENGTH;
185   unsigned char digest_buffer[length];
186 
187   auto one_digest_round = [&](const std::string &value) {
188     if (sha256_digest.update_digest(value.c_str(), value.length()) ||
189         sha256_digest.retrieve_digest(digest_buffer, length)) {
190       return false;
191     }
192     return true;
193   };
194 
195   // First digest round
196   if (!one_digest_round(value)) return {false, ""};
197 
198   sha256_digest.scrub();
199 
200   std::string first_digest_round = {std::begin(digest_buffer),
201                                     std::end(digest_buffer)};
202 
203   // Second digest round
204   if (!one_digest_round(first_digest_round)) return {false, ""};
205 
206   return {true, {std::begin(digest_buffer), std::end(digest_buffer)}};
207 }
208 
209 }  // namespace xpl
210