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