1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2016 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 #include "config.h"
18 #include "cbsasl/scram-sha/scram-sha.h"
19 #include "cbsasl/scram-sha/stringutils.h"
20 #include "cbsasl/pwfile.h"
21 #include "cbsasl/cbsasl.h"
22 #include "cbsasl/util.h"
23 
24 #include <cbsasl/mechanismfactory.h>
25 #include <platform/base64.h>
26 #include <platform/random.h>
27 #include <cstring>
28 #include <gsl/gsl>
29 #include <iomanip>
30 #include <iostream>
31 #include <map>
32 #include <memory>
33 #include <set>
34 #include <sstream>
35 #include <string>
36 
37 typedef std::map<char, std::string> AttributeMap;
38 
hex_encode_nonce(const std::array<char, 8>& nonce)39 static std::string hex_encode_nonce(const std::array<char, 8>& nonce) {
40     std::stringstream ss;
41     ss << std::hex << std::setfill('0');
42     for (const auto& c : nonce) {
43         ss << std::setw(2) << uint32_t(c);
44     }
45 
46     return ss.str();
47 }
48 
49 /**
50  * Decode the attribute list into a set. The attribute list looks like:
51  * "k=value,y=value" etc
52  *
53  * @param list the list to parse
54  * @param attributes where to store the attributes
55  * @return true if success, false otherwise
56  */
decodeAttributeList(cbsasl_conn_t& conn, const std::string& list, AttributeMap& attributes)57 static bool decodeAttributeList(cbsasl_conn_t& conn, const std::string& list,
58                                 AttributeMap& attributes) {
59     size_t pos = 0;
60 
61     logging::log(conn,
62                  logging::Level::Debug,
63                  "Decoding attribute list [" + list + "]");
64 
65     while (pos < list.length()) {
66         auto equal = list.find('=', pos);
67         if (equal == std::string::npos) {
68             // syntax error!!
69             logging::log(conn,
70                          logging::Level::Error,
71                          "Decode attribute list [" + list + "] failed: no '='");
72             return false;
73         }
74 
75         if ((equal - pos) != 1) {
76             logging::log(conn,
77                          logging::Level::Error,
78                          "Decode attribute list [" + list +
79                                  "] failed: " + "key is multichar");
80             return false;
81         }
82 
83         char key = list.at(pos);
84         pos = equal + 1;
85 
86         // Make sure we haven't seen this key before..
87         if (attributes.find(key) != attributes.end()) {
88             logging::log(conn,
89                          logging::Level::Error,
90                          "Decode attribute list [" + list + "] failed: " +
91                                  "key [" + key + "] is multichar");
92             return false;
93         }
94 
95         auto comma = list.find(',', pos);
96         if (comma == std::string::npos) {
97             attributes.insert(std::make_pair(key, list.substr(pos)));
98             pos = list.length();
99         } else {
100             attributes.insert(
101                 std::make_pair(key, list.substr(pos, comma - pos)));
102             pos = comma + 1;
103         }
104     }
105 
106     return true;
107 }
108 
109 /********************************************************************
110  * Common API
111  *******************************************************************/
getAuthMessage()112 std::string ScramShaBackend::getAuthMessage() {
113     if (client_first_message_bare.empty()) {
114         throw std::logic_error(
115             "can't call getAuthMessage without client_first_message_bare is set");
116     }
117     if (server_first_message.empty()) {
118         throw std::logic_error(
119             "can't call getAuthMessage without server_first_message is set");
120     }
121     if (client_final_message_without_proof.empty()) {
122         throw std::logic_error(
123             "can't call getAuthMessage without client_final_message_without_proof is set");
124     }
125     return client_first_message_bare + "," + server_first_message + "," +
126            client_final_message_without_proof;
127 }
128 
addAttribute(std::ostream& out, char key, const std::string& value, bool more)129 void ScramShaBackend::addAttribute(std::ostream& out, char key,
130                                    const std::string& value, bool more) {
131     out << key << '=';
132 
133     switch (key) {
134     case 'n' : // username ..
135         out << encodeUsername(SASLPrep(value));
136         break;
137 
138     case 'r' : // client nonce.. printable characters
139         for (auto iter = value.begin(); iter != value.end(); ++iter) {
140             if (*iter == ',' || !isprint(*iter)) {
141                 throw std::invalid_argument("ScramShaBackend::addAttribute: "
142                                                 "Invalid character in client"
143                                                 " nonce");
144             }
145         }
146         out << value;
147         break;
148 
149     case 'c' : // base64 encoded GS2 header and channel binding data
150     case 's' : // base64 encoded salt
151     case 'p' : // base64 encoded client proof
152     case 'v' : // base64 encoded server signature
153         out << Couchbase::Base64::encode(value);
154         break;
155 
156     case 'i' : // iterator count
157         // validate that it is an integer value
158         try {
159             std::stoi(value);
160         } catch (...) {
161             throw std::invalid_argument("ScramShaBackend::addAttribute: "
162                                             "Iteration count must be a numeric"
163                                             " value");
164 
165         }
166         out << value;
167         break;
168 
169     case 'e':
170         for (auto iter = value.begin(); iter != value.end(); ++iter) {
171             if (*iter == ',' || !isprint(*iter)) {
172                 throw std::invalid_argument("ScramShaBackend::addAttribute: "
173                                                 "Invalid character in error"
174                                                 " message");
175             }
176         }
177         out << value;
178         break;
179 
180     default:
181         throw std::invalid_argument("ScramShaBackend::addAttribute:"
182                                         " Invalid key");
183     }
184 
185     if (more) {
186         out << ',';
187     }
188 }
189 
addAttribute(std::ostream& out, char key, int value, bool more)190 void ScramShaBackend::addAttribute(std::ostream& out, char key, int value,
191                                    bool more) {
192     out << key << '=';
193 
194     std::string base64_encoded;
195 
196     switch (key) {
197     case 'n' : // username ..
198     case 'r' : // client nonce.. printable characters
199     case 'c' : // base64 encoded GS2 header and channel binding data
200     case 's' : // base64 encoded salt
201     case 'p' : // base64 encoded client proof
202     case 'v' : // base64 encoded server signature
203     case 'e' : // error message
204         throw std::invalid_argument("ScramShaBackend::addAttribute:"
205                                         " Invalid value (should not be int)");
206         break;
207 
208     case 'i' : // iterator count
209         out << value;
210         break;
211 
212     default:
213         throw std::invalid_argument("ScramShaBackend::addAttribute:"
214                                         " Invalid key");
215     }
216 
217     if (more) {
218         out << ',';
219     }
220 }
221 
222 /**
223  * Generate the Server Signature. It is computed as:
224  *
225  * SaltedPassword  := Hi(Normalize(password), salt, i)
226  * ServerKey       := HMAC(SaltedPassword, "Server Key")
227  * ServerSignature := HMAC(ServerKey, AuthMessage)
228  */
getServerSignature()229 std::string ScramShaBackend::getServerSignature() {
230     auto serverKey =
231             cb::crypto::HMAC(algorithm, getSaltedPassword(), "Server Key");
232 
233     return cb::crypto::HMAC(algorithm, serverKey, getAuthMessage());
234 }
235 
236 /**
237  * Generate the Client Proof. It is computed as:
238  *
239  * SaltedPassword  := Hi(Normalize(password), salt, i)
240  * ClientKey       := HMAC(SaltedPassword, "Client Key")
241  * StoredKey       := H(ClientKey)
242  * AuthMessage     := client-first-message-bare + "," +
243  *                    server-first-message + "," +
244  *                    client-final-message-without-proof
245  * ClientSignature := HMAC(StoredKey, AuthMessage)
246  * ClientProof     := ClientKey XOR ClientSignature
247  */
getClientProof()248 std::string ScramShaBackend::getClientProof() {
249     auto clientKey =
250             cb::crypto::HMAC(algorithm, getSaltedPassword(), "Client Key");
251     auto storedKey = cb::crypto::digest(algorithm, clientKey);
252     std::string authMessage = getAuthMessage();
253     auto clientSignature = cb::crypto::HMAC(algorithm, storedKey, authMessage);
254 
255     // Client Proof is ClientKey XOR ClientSignature
256     const auto* ck = clientKey.data();
257     const auto* cs = clientSignature.data();
258 
259     std::string proof;
260     proof.resize(clientKey.size());
261 
262     auto total = proof.size();
263     for (unsigned int ii = 0; ii < total; ++ii) {
264         proof[ii] = ck[ii] ^ cs[ii];
265     }
266 
267     return proof;
268 }
269 
270 /********************************************************************
271  * Generic SHA Server API
272  *******************************************************************/
ScramShaServerBackend(const std::string& mech_name, cbsasl_conn_t& conn, const Mechanism& mech, const cb::crypto::Algorithm algo)273 ScramShaServerBackend::ScramShaServerBackend(const std::string& mech_name,
274                                              cbsasl_conn_t& conn,
275                                              const Mechanism& mech,
276                                              const cb::crypto::Algorithm algo)
277     : ScramShaBackend(mech_name, conn, mech, algo) {
278     /* Generate a challenge */
279     Couchbase::RandomGenerator randomGenerator(true);
280 
281     std::array<char, 8> nonce;
282     if (!randomGenerator.getBytes(nonce.data(), nonce.size())) {
283         logging::log(
284                 conn, logging::Level::Error, "Failed to generate server nonce");
285         throw std::bad_alloc();
286     }
287 
288     serverNonce = hex_encode_nonce(nonce);
289 }
290 
start(const char* input, unsigned inputlen, const char** output, unsigned* outputlen)291 cbsasl_error_t ScramShaServerBackend::start(const char* input,
292                                             unsigned inputlen,
293                                             const char** output,
294                                             unsigned* outputlen) {
295     if (inputlen == 0 || output == nullptr || outputlen == nullptr) {
296         logging::log(conn,
297                      logging::Level::Error,
298                      "Invalid arguments provided to "
299                      "ScramShaServerBackend::start");
300         return CBSASL_BADPARAM;
301     }
302 
303     logging::log(conn,
304                  logging::Level::Trace,
305                  "ScramShaServerBackend::start (" +
306                          MechanismFactory::toString(mechanism) + ")");
307 
308     if (conn.get_cnonce_fn != nullptr) {
309         // Allow the user to override the nonce
310         const char* nonce = nullptr;
311         unsigned int len;
312 
313         if (conn.get_cnonce_fn(conn.get_cnonce_ctx, CBSASL_CB_CNONCE,
314                                 &nonce, &len) != 0) {
315             logging::log(conn,
316                          logging::Level::Error,
317                          "CBSASL_CB_CNONCE callback returned failure");
318             return CBSASL_FAIL;
319         }
320         serverNonce.assign(nonce, len);
321 
322         // verify that the provided nonce consists of printable characters
323         // and no ,
324         for (const auto& c : serverNonce) {
325             if (c == ',' || !isprint(c)) {
326                 logging::log(conn,
327                              logging::Level::Error,
328                              "Invalid character specified in nonce");
329                 return CBSASL_BADPARAM;
330             }
331         }
332 
333         logging::log(conn,
334                      logging::Level::Trace,
335                      "Using provided "
336                      "nonce [" +
337                              serverNonce + "]");
338     }
339 
340     // the "client-first-message" message should contain a gs2-header
341     //   gs2-bind-flag,[authzid],client-first-message-bare
342     client_first_message.assign(input, inputlen);
343 
344     // according to the RFC the client should not send 'y' unless the
345     // server advertised SCRAM-SHA[n]-PLUS (which we don't)
346     if (client_first_message.find("n,") != 0) {
347         // We don't support the p= to do channel bindings (that should
348         // be advertised with SCRAM-SHA[n]-PLUS)
349         logging::log(conn,
350                      logging::Level::Error,
351                      "SCRAM: client should not try to ask for channel binding");
352         return CBSASL_BADPARAM;
353     }
354 
355     // next up is an optional authzid which we completely ignore...
356     auto idx = client_first_message.find(',', 2);
357     if (idx == std::string::npos) {
358         logging::log(conn,
359                      logging::Level::Error,
360                      "SCRAM: Format error on client-first-message");
361         return CBSASL_BADPARAM;
362     }
363 
364     client_first_message_bare = client_first_message.substr(idx + 1);
365 
366     AttributeMap attributes;
367     if (!decodeAttributeList(conn, client_first_message_bare, attributes)) {
368         logging::log(conn,
369                      logging::Level::Error,
370                      "SCRAM: Failed to decode client-first-message-bare");
371         return CBSASL_BADPARAM;
372     }
373 
374     for (const auto& attribute : attributes) {
375         switch (attribute.first) {
376             // @todo at a later stage we might want to add support for the
377             // @todo 'a' attribute that we'll use from n1ql/indexing etc
378             // @todo note that they will then use n=@xdcr etc)
379         case 'n' :
380             username = attribute.second;
381             logging::log(conn,
382                          logging::Level::Trace,
383                          "Using username [" + username + "]");
384             break;
385         case 'r' :
386             clientNonce = attribute.second;
387             logging::log(conn,
388                          logging::Level::Trace,
389                          "Using client nonce [" + clientNonce + "]");
390             break;
391         default:
392             logging::log(
393                     conn, logging::Level::Error, "Unsupported key supplied");
394             return CBSASL_BADPARAM;
395         }
396     }
397 
398     if (username.empty() || clientNonce.empty()) {
399         // mandatory fields!!!
400         logging::log(conn, logging::Level::Error, "Unsupported key supplied");
401         return CBSASL_BADPARAM;
402     }
403 
404     try {
405         username = decodeUsername(username);
406     } catch (std::runtime_error&) {
407         logging::log(conn,
408                      logging::Level::Error,
409                      "Invalid character in username detected");
410         return CBSASL_BADPARAM;
411     }
412 
413     if (!find_user(username, user)) {
414         logging::log(conn,
415                      logging::Level::Debug,
416                      "User [" + username + "] doesn't exist.. using dummy");
417         user = cb::sasl::UserFactory::createDummy(username, mechanism);
418     }
419 
420     const auto& passwordMeta = user.getPassword(mechanism);
421 
422     conn.server->username.assign(username);
423     nonce = clientNonce + std::string(serverNonce.data(), serverNonce.size());
424 
425     // build up the server-first-message
426     std::ostringstream out;
427     addAttribute(out, 'r', nonce, true);
428     addAttribute(out, 's', Couchbase::Base64::decode(passwordMeta.getSalt()),
429                  true);
430     addAttribute(out, 'i', passwordMeta.getIterationCount(), false);
431     server_first_message = out.str();
432 
433     *output = server_first_message.data();
434     *outputlen = unsigned(server_first_message.size());
435 
436     logging::log(conn, logging::Level::Trace, server_first_message);
437 
438     return CBSASL_CONTINUE;
439 }
440 
step(const char* input, unsigned inputlen, const char** output, unsigned* outputlen)441 cbsasl_error_t ScramShaServerBackend::step(const char* input,
442                                            unsigned inputlen,
443                                            const char** output,
444                                            unsigned* outputlen) {
445 
446     if (inputlen == 0) {
447         logging::log(conn, logging::Level::Error, "Invalid input");
448         return CBSASL_BADPARAM;
449     }
450 
451     std::string client_final_message(input, inputlen);
452     AttributeMap attributes;
453     if (!decodeAttributeList(conn, client_final_message, attributes)) {
454         logging::log(conn,
455                      logging::Level::Error,
456                      "SCRAM: Failed to decode client_final_message");
457         return CBSASL_BADPARAM;
458     }
459 
460     auto iter = attributes.find('p');
461     if (iter == attributes.end()) {
462         logging::log(
463                 conn,
464                 logging::Level::Error,
465                 "SCRAM: client_final_message does not contain client proof");
466         return CBSASL_BADPARAM;
467     }
468 
469     auto idx = client_final_message.find(",p=");
470     client_final_message_without_proof = client_final_message.substr(0, idx);
471 
472 
473     // Generate the server signature
474 
475     std::stringstream out;
476 
477     if (user.isDummy() && cb::sasl::saslauthd::is_configured()) {
478         addAttribute(out, 'e', "scram-not-supported-for-ldap-users", false);
479     } else {
480         auto serverSignature = getServerSignature();
481         addAttribute(out, 'v', serverSignature, false);
482     }
483 
484     server_final_message = out.str();
485     (*output) = server_final_message.data();
486     (*outputlen) = gsl::narrow<unsigned>(server_final_message.length());
487 
488     std::string clientproof = iter->second;
489     std::string my_clientproof = Couchbase::Base64::encode(getClientProof());
490 
491     int fail = cbsasl_secure_compare(clientproof.c_str(),
492                                      clientproof.length(),
493                                      my_clientproof.c_str(),
494                                      my_clientproof.length()) ^
495                gsl::narrow_cast<int>(user.isDummy());
496 
497     if (fail != 0) {
498         if (user.isDummy()) {
499             logging::log(conn,
500                          logging::Level::Fail,
501                          "No such user [" + username + "]");
502             return CBSASL_NOUSER;
503         } else {
504             logging::log(conn,
505                          logging::Level::Fail,
506                          "Authentication fail for [" + username + "]");
507             return CBSASL_PWERR;
508         }
509     }
510 
511     logging::log(conn, logging::Level::Trace, server_final_message);
512     return CBSASL_OK;
513 }
514 
515 /********************************************************************
516  * Client API
517  *******************************************************************/
518 // The iterationCount is initialized to 4k to mute Coverty from reporting
519 // the variable to be used without initialization. The actual value
520 // being used is received from the server as part of the first message
521 // sent from the server (I picked 4k as a default value because thats
522 // what the examples in the RFC used ;-)
ScramShaClientBackend(const std::string& mech_name, cbsasl_conn_t& conn, const Mechanism& mech, const cb::crypto::Algorithm algo)523 ScramShaClientBackend::ScramShaClientBackend(const std::string& mech_name,
524                                              cbsasl_conn_t& conn,
525                                              const Mechanism& mech,
526                                              const cb::crypto::Algorithm algo)
527     : ScramShaBackend(mech_name, conn, mech, algo),
528       iterationCount(4096) {
529     Couchbase::RandomGenerator randomGenerator(true);
530 
531     std::array<char, 8> nonce;
532     if (!randomGenerator.getBytes(nonce.data(), nonce.size())) {
533         logging::log(
534                 conn, logging::Level::Error, "Failed to generate server nonce");
535         throw std::bad_alloc();
536     }
537 
538     clientNonce = hex_encode_nonce(nonce);
539 }
540 
start(const char* input, unsigned inputlen, const char** output, unsigned* outputlen)541 cbsasl_error_t ScramShaClientBackend::start(const char* input,
542                                             unsigned inputlen,
543                                             const char** output,
544                                             unsigned* outputlen) {
545 
546     if (inputlen != 0 || output == nullptr || outputlen == nullptr) {
547         logging::log(
548                 conn, logging::Level::Error, "Invalid parameters provided");
549         return CBSASL_BADPARAM;
550     }
551 
552     logging::log(conn,
553                  logging::Level::Trace,
554                  "ScramShaClientBackend::start (" +
555                          MechanismFactory::toString(mechanism) + ")");
556 
557     if (conn.get_cnonce_fn != nullptr) {
558         // Allow the user to override the nonce
559         const char* nonce = nullptr;
560         unsigned int len;
561 
562         if (conn.get_cnonce_fn(conn.get_cnonce_ctx, CBSASL_CB_CNONCE,
563                                 &nonce, &len) != 0) {
564             logging::log(conn,
565                          logging::Level::Error,
566                          "CBSASL_CB_CNONCE callback returned failure");
567             return CBSASL_FAIL;
568         }
569         clientNonce.assign(nonce, len);
570 
571         // verify that the provided nonce consists of printable characters
572         // and no ,
573         for (const auto& c : clientNonce) {
574             if (c == ',' || !isprint(c)) {
575                 logging::log(conn,
576                              logging::Level::Error,
577                              "Invalid character specified in nonce");
578                 return CBSASL_BADPARAM;
579             }
580         }
581 
582         logging::log(conn,
583                      logging::Level::Trace,
584                      "Using provided "
585                      "nonce [" +
586                              clientNonce + "]");
587     }
588 
589     const char* usernm = nullptr;
590     unsigned int usernmlen;
591     auto* client = conn.client.get();
592 
593     if (cbsasl_get_username(client->get_username, client->get_username_ctx,
594                             &usernm, &usernmlen) != CBSASL_OK) {
595         logging::log(conn, logging::Level::Error, "Failed to get username");
596         return CBSASL_FAIL;
597     }
598 
599     username.assign(usernm, usernmlen);
600 
601     std::stringstream out;
602     out << "n,,";
603     addAttribute(out, 'n', username, true);
604     addAttribute(out, 'r', clientNonce, false);
605 
606     client_first_message = out.str();
607     client_first_message_bare = client_first_message.substr(3); // skip n,,
608 
609     *output = client_first_message.data();
610     *outputlen = unsigned(client_first_message.length());
611 
612     logging::log(conn, logging::Level::Trace, client_first_message);
613     return CBSASL_OK;
614 }
615 
step(const char* input, unsigned inputlen, const char** output, unsigned* outputlen)616 cbsasl_error_t ScramShaClientBackend::step(const char* input,
617                                            unsigned inputlen,
618                                            const char** output,
619                                            unsigned* outputlen) {
620     if (inputlen == 0 || output == nullptr || outputlen == nullptr) {
621         logging::log(
622                 conn, logging::Level::Error, "Invalid parameters provided");
623         return CBSASL_FAIL;
624     }
625 
626     if (server_first_message.empty()) {
627         server_first_message.assign(input, inputlen);
628 
629         AttributeMap attributes;
630         if (!decodeAttributeList(conn, server_first_message, attributes)) {
631             logging::log(conn,
632                          logging::Level::Error,
633                          "SCRAM: Failed to decode server-first-message");
634             return CBSASL_BADPARAM;
635         }
636 
637         for (const auto& attribute : attributes) {
638             switch (attribute.first) {
639             case 'r' : // combined nonce
640                 nonce = attribute.second;
641                 break;
642             case 's' :
643                 salt = Couchbase::Base64::decode(attribute.second);
644                 break;
645             case 'i' :
646                 try {
647                     iterationCount = (unsigned int)std::stoul(attribute.second);
648                 } catch (...) {
649                     return CBSASL_BADPARAM;
650                 }
651                 break;
652             default:
653                 return CBSASL_BADPARAM;
654             }
655         }
656 
657         if (attributes.find('r') == attributes.end() ||
658             attributes.find('s') == attributes.end() ||
659             attributes.find('i') == attributes.end()) {
660             logging::log(conn,
661                          logging::Level::Error,
662                          "Missing r/s/i in server message");
663             return CBSASL_BADPARAM;
664         }
665 
666         // I've got the SALT, lets generate the salted password
667         cbsasl_secret_t* pass;
668         if (cbsasl_get_password(conn.client->get_password,
669                                 &conn,
670                                 conn.client->get_password_ctx,
671                                 &pass) != CBSASL_OK) {
672             logging::log(conn, logging::Level::Error, "Failed to get password");
673             return CBSASL_FAIL;
674         }
675 
676         if (!generateSaltedPassword(reinterpret_cast<const char*>(pass->data),
677                                     static_cast<int>(pass->len))) {
678             logging::log(conn,
679                          logging::Level::Error,
680                          "Failed to generate salted passwod");
681             return CBSASL_FAIL;
682         }
683 
684         // Ok so we have salted hased password :D
685 
686         std::stringstream out;
687         addAttribute(out, 'c', "n,,", true);
688         addAttribute(out, 'r', nonce, false);
689         client_final_message_without_proof = out.str();
690         out << ",";
691 
692         addAttribute(out, 'p', getClientProof(), false);
693 
694         client_final_message = out.str();
695 
696         *output = client_final_message.data();
697         *outputlen = gsl::narrow<unsigned>(client_final_message.length());
698         logging::log(conn, logging::Level::Trace, client_final_message);
699         return CBSASL_CONTINUE;
700     } else {
701         server_final_message.assign(input, inputlen);
702 
703         AttributeMap attributes;
704         if (!decodeAttributeList(conn, server_final_message, attributes)) {
705             logging::log(conn,
706                          logging::Level::Error,
707                          "SCRAM: Failed to decode server-final-message");
708             return CBSASL_BADPARAM;
709         }
710 
711         if (attributes.find('e') != attributes.end()) {
712             logging::log(conn,
713                          logging::Level::Fail,
714                          "Failed to authenticate: " + attributes['e']);
715             return CBSASL_FAIL;
716         }
717 
718         if (attributes.find('v') == attributes.end()) {
719             logging::log(conn,
720                          logging::Level::Trace,
721                          "Syntax error server final message is missing 'v'");
722             return CBSASL_BADPARAM;
723         }
724 
725         auto encoded = Couchbase::Base64::encode(getServerSignature());
726         if (encoded != attributes['v']) {
727             logging::log(conn,
728                          logging::Level::Trace,
729                          "Incorrect ServerKey received");
730             return CBSASL_FAIL;
731         }
732 
733         return CBSASL_OK;
734     }
735 }
736 
generateSaltedPassword(const char* ptr, int len)737 bool ScramShaClientBackend::generateSaltedPassword(const char* ptr, int len) {
738     std::string secret(ptr, len);
739     try {
740         saltedPassword = cb::crypto::PBKDF2_HMAC(
741                 algorithm, secret, salt, iterationCount);
742         return true;
743     } catch (...) {
744         return false;
745     }
746 }
747