1 /*
2   Copyright (c) 2018, 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 #ifndef ROUTER_KDF_SHA_CRYPT_INCLUDED
25 #define ROUTER_KDF_SHA_CRYPT_INCLUDED
26 
27 #include <string>
28 #include <utility>  // std::pair
29 #include <vector>
30 
31 #include "digest.h"
32 #include "mcf_error.h"
33 #include "mysqlrouter/http_auth_backend_lib_export.h"
34 
35 /**
36  * sha256_crypt and sha512_crypt are SHA based crypt() key derivation functions.
37  * @see  https://www.akkadia.org/drepper/SHA-crypt.txt
38  *
39  * caching_sha2_password is key derivation function taken from an internal
40  * MySQL authentication mechanism
41  */
42 class HTTP_AUTH_BACKEND_LIB_EXPORT ShaCrypt {
43  public:
44   enum class Type { Sha256, Sha512, CachingSha2Password };
45   static std::string salt();
46   static std::string derive(Type digest, unsigned long rounds,
47                             const std::string &salt,
48                             const std::string &password);
49 
50  private:
51   /**
52    * crypt specific base64 encode.
53    *
54    * different alphabet than RFC4648
55    */
56   static std::string base64_encode(const std::vector<uint8_t> &data);
57 };
58 
59 class ShaCryptMcfType {
60  public:
61   using Type = ShaCrypt::Type;
62 
name(Type type)63   static std::pair<bool, std::string> name(Type type) noexcept {
64     switch (type) {
65       case Type::Sha256:
66         return std::make_pair(true, kTypeSha256);
67       case Type::Sha512:
68         return std::make_pair(true, kTypeSha512);
69       case Type::CachingSha2Password:
70         return std::make_pair(true, kTypeCachingSha2Password);
71     }
72 
73     return std::make_pair(false, std::string{});
74   }
75 
type(const std::string & name)76   static std::pair<bool, Type> type(const std::string &name) noexcept {
77     if (name == kTypeSha256) {
78       return std::make_pair(true, Type::Sha256);
79     } else if (name == kTypeSha512) {
80       return std::make_pair(true, Type::Sha512);
81     } else if (name == kTypeCachingSha2Password) {
82       return std::make_pair(true, Type::CachingSha2Password);
83     }
84 
85     return std::make_pair(false, Type{});
86   }
87 
supports_name(const std::string & name)88   static bool supports_name(const std::string &name) noexcept {
89     if (name == kTypeSha256) {
90       return true;
91     } else if (name == kTypeSha512) {
92       return true;
93     } else if (name == kTypeCachingSha2Password) {
94       return true;
95     }
96 
97     return false;
98   }
99 
100  private:
101   static constexpr char kTypeSha256[] = "5";
102   static constexpr char kTypeSha512[] = "6";
103   static constexpr char kTypeCachingSha2Password[] = "A";
104 };
105 
106 /**
107  * MCF reader/writer for ShaCrypt
108  */
109 class HTTP_AUTH_BACKEND_LIB_EXPORT ShaCryptMcfAdaptor {
110  public:
111   using mcf_type = ShaCryptMcfType;
112   using kdf_type = ShaCrypt;
113 
114   using Type = mcf_type::Type;
115 
116   /**
117    * number of rounds if no rounds was specified in from_mcf().
118    */
119   static constexpr unsigned long kDefaultRounds = 5000;
120   /**
121    * minimum rounds.
122    */
123   static constexpr unsigned long kMinRounds = 1000;
124   /**
125    * maximum rounds.
126    */
127   static constexpr unsigned long kMaxRounds = 999999999;
128   /**
129    * maximum length of the salt.
130    *
131    * only the first kMaxSaltLength bytes of the salt will be used.
132    */
133   static constexpr size_t kMaxSaltLength = 16;
134 
ShaCryptMcfAdaptor(Type digest,unsigned long rounds,const std::string & salt,const std::string & checksum)135   ShaCryptMcfAdaptor(Type digest, unsigned long rounds, const std::string &salt,
136                      const std::string &checksum)
137       : digest_{digest}, rounds_{rounds}, salt_{salt}, checksum_{checksum} {
138     // limit salt, for caching_sha2_password salt has a fixed length of 20
139     if (digest != Type::CachingSha2Password && salt_.size() > kMaxSaltLength) {
140       salt_.resize(kMaxSaltLength);
141     }
142 
143     // limit rounds to allowed range
144     if (rounds_ < kMinRounds) rounds_ = kMinRounds;
145     if (rounds_ > kMaxRounds) rounds_ = kMaxRounds;
146   }
147 
148   /**
149    * name of the digest according to MCF.
150    *
151    * - 5 for SHA256
152    * - 6 for SHA512
153    * - A for caching_sha2_password
154    */
mcf_digest_name()155   std::string mcf_digest_name() const {
156     auto r = mcf_type::name(digest());
157     if (r.first) return r.second;
158 
159     throw std::invalid_argument("failed to map digest to a name");
160   }
161 
162   /**
163    * checkum.
164    *
165    * in crypt-specific base64 encoding
166    */
checksum()167   std::string checksum() const { return checksum_; }
168 
169   /**
170    * salt.
171    *
172    * @pre must be [a-z0-9]*
173    */
salt()174   std::string salt() const { return salt_; }
175 
176   /**
177    *
178    */
digest()179   Type digest() const { return digest_; }
180 
181   /**
182    * rounds.
183    *
184    * number of rounds the hash will be applied
185    */
rounds()186   unsigned long rounds() const { return rounds_; }
187 
188   /**
189    * build ShaCrypt from a MCF notation.
190    *
191    * - ${prefix}$rounds={rounds}${salt}${checksum}
192    * - ${prefix}$rounds={rounds}${salt}
193    * - ${prefix}${salt}${checksum}
194    * - ${prefix}${salt}
195    *
196    * prefix
197    * :  [56] (5 is SHA256, 6 is SHA512)
198    *
199    * rounds
200    * :  [0-9]+
201    *
202    * salt
203    * :  [^$]*
204    *
205    * checksum
206    * :  [./a-zA-Z0-0]*
207    */
208   static ShaCryptMcfAdaptor from_mcf(const std::string &data);
209 
210   /**
211    * encode to MCF.
212    *
213    * MCF (Modular Crypt Format)
214    */
215   std::string to_mcf() const;
216 
217   /**
218    * hash a password into checksum.
219    *
220    * updates checksum
221    */
hash(const std::string & password)222   void hash(const std::string &password) {
223     checksum_ = kdf_type::derive(digest_, rounds_, salt_, password);
224   }
225 
supports_mcf_id(const std::string mcf_id)226   static bool supports_mcf_id(const std::string mcf_id) {
227     return mcf_type::supports_name(mcf_id);
228   }
229 
validate(const std::string & mcf_line,const std::string & password)230   static std::error_code validate(const std::string &mcf_line,
231                                   const std::string &password) {
232     try {
233       auto me = from_mcf(mcf_line);
234       if (kdf_type::derive(me.digest(), me.rounds(), me.salt(), password) ==
235           me.checksum()) {
236         return {};
237       } else {
238         return make_error_code(McfErrc::kPasswordNotMatched);
239       }
240     } catch (const std::exception &) {
241       // treat all exceptions as parse-errors
242       return make_error_code(McfErrc::kParseError);
243     }
244   }
245 
246  private:
247   Type digest_;
248   unsigned long rounds_;
249   std::string salt_;
250   std::string checksum_;
251 };
252 
253 class CachingSha2Adaptor : public ShaCryptMcfAdaptor {
254  public:
255   static ShaCryptMcfAdaptor from_mcf(const std::string &crypt_data);
256 
257   static constexpr unsigned long kCachingSha2SaltLength = 20;
258 };
259 
260 #endif
261