sasl_digest_md5.cc 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. #include "sasl_authenticator.h"
  19. #include "common/util.h"
  20. #include <openssl/rand.h>
  21. #include <openssl/md5.h>
  22. #include <iomanip>
  23. #include <map>
  24. #include <sstream>
  25. namespace hdfs {
  26. static std::string QuoteString(const std::string &src);
  27. static std::string GetMD5Digest(const std::string &src);
  28. static std::string BinaryToHex(const std::string &src);
  29. static const char kDigestUri[] = "hdfs/0";
  30. static const size_t kMaxBufferSize = 65536;
  31. DigestMD5Authenticator::DigestMD5Authenticator(const std::string &username, const std::string &password, bool mock_nonce)
  32. : username_(username)
  33. , password_(password)
  34. , nonce_count_(0)
  35. , TEST_mock_cnonce_(mock_nonce)
  36. {}
  37. Status DigestMD5Authenticator::EvaluateResponse(const std::string &payload, std::string *result) {
  38. Status status = ParseFirstChallenge(payload);
  39. if (status.ok()) {
  40. status = GenerateFirstResponse(result);
  41. }
  42. return status;
  43. return Status::Error("");
  44. }
  45. size_t DigestMD5Authenticator::NextToken(const std::string &payload, size_t off, std::string *tok) {
  46. tok->clear();
  47. if (off >= payload.size()) {
  48. return std::string::npos;
  49. }
  50. char c = payload[off];
  51. if (c == '=' || c == ',') {
  52. *tok = c;
  53. return off + 1;
  54. }
  55. int quote_count = 0;
  56. for (; off < payload.size(); ++off) {
  57. char c = payload[off];
  58. if (c == '"') {
  59. ++quote_count;
  60. if (quote_count == 2) {
  61. return off + 1;
  62. }
  63. continue;
  64. }
  65. if (c == '=') {
  66. if (quote_count) {
  67. tok->append(&c, 1);
  68. } else {
  69. break;
  70. }
  71. } else if (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
  72. || c == '+' || c == '/' || c == '-' || c == '_' || c == '@') {
  73. tok->append(&c, 1);
  74. } else {
  75. break;
  76. }
  77. }
  78. return off;
  79. }
  80. void DigestMD5Authenticator::GenerateCNonce() {
  81. if (!TEST_mock_cnonce_) {
  82. char buf[8];
  83. RAND_pseudo_bytes(reinterpret_cast<unsigned char*>(buf), sizeof(buf));
  84. cnonce_ = Base64Encode(std::string(buf, sizeof(buf)));
  85. }
  86. }
  87. Status DigestMD5Authenticator::ParseFirstChallenge(const std::string &payload) {
  88. std::map<std::string, std::string> props;
  89. std::string token;
  90. enum {
  91. kStateLVal,
  92. kStateEqual,
  93. kStateRVal,
  94. kStateCommaOrEnd,
  95. };
  96. int state = kStateLVal;
  97. std::string lval, rval;
  98. size_t off = 0;
  99. while (true) {
  100. off = NextToken(payload, off, &token);
  101. if (off == std::string::npos) {
  102. break;
  103. }
  104. switch (state) {
  105. case kStateLVal:
  106. lval = token;
  107. state = kStateEqual;
  108. break;
  109. case kStateEqual:
  110. state = kStateRVal;
  111. break;
  112. case kStateRVal:
  113. rval = token;
  114. props[lval] = rval;
  115. state = kStateCommaOrEnd;
  116. break;
  117. case kStateCommaOrEnd:
  118. state = kStateLVal;
  119. break;
  120. }
  121. }
  122. if (props["algorithm"] != "md5-sess"
  123. || props["charset"] != "utf-8"
  124. || props.find("nonce") == props.end()) {
  125. return Status::Error("Invalid challenge");
  126. }
  127. realm_ = props["realm"];
  128. nonce_ = props["nonce"];
  129. qop_ = props["qop"];
  130. return Status::OK();
  131. }
  132. Status DigestMD5Authenticator::GenerateFirstResponse(std::string *result) {
  133. // TODO: Support auth-int and auth-conf
  134. // Handle cipher
  135. if (qop_ != "auth") {
  136. return Status::Unimplemented();
  137. }
  138. std::stringstream ss;
  139. GenerateCNonce();
  140. ss << "charset=utf-8,username=\"" << QuoteString(username_) << "\""
  141. << ",authzid=\"" << QuoteString(username_) << "\""
  142. << ",nonce=\"" << QuoteString(nonce_) << "\""
  143. << ",digest-uri=\"" << kDigestUri << "\""
  144. << ",maxbuf=" << kMaxBufferSize
  145. << ",cnonce=\"" << cnonce_ << "\"";
  146. if (realm_.size()) {
  147. ss << ",realm=\"" << QuoteString(realm_) << "\"";
  148. }
  149. ss << ",nc=" << std::hex << std::setw(8) << std::setfill('0') << ++nonce_count_;
  150. std::string response_value;
  151. GenerateResponseValue(&response_value);
  152. ss << ",response=" << response_value;
  153. *result = ss.str();
  154. return result->size() > 4096 ? Status::Error("Response too big") : Status::OK();
  155. }
  156. /**
  157. * Generate the response value specified in S 2.1.2.1 in RFC2831.
  158. **/
  159. Status DigestMD5Authenticator::GenerateResponseValue(std::string *response_value) {
  160. std::stringstream begin_a1, a1_ss;
  161. std::string a1, a2;
  162. if (qop_ == "auth") {
  163. a2 = std::string("AUTHENTICATE:") + kDigestUri;
  164. } else {
  165. a2 = std::string("AUTHENTICATE:") + kDigestUri + ":00000000000000000000000000000000";
  166. }
  167. begin_a1 << username_ << ":" << realm_ << ":" << password_;
  168. a1_ss << GetMD5Digest(begin_a1.str()) << ":" << nonce_
  169. << ":" << cnonce_ << ":" << username_;
  170. std::stringstream combine_ss;
  171. combine_ss << BinaryToHex(GetMD5Digest(a1_ss.str()))
  172. << ":" << nonce_
  173. << ":" << std::hex << std::setw(8) << std::setfill('0') << nonce_count_
  174. << ":" << cnonce_ << ":" << qop_
  175. << ":" << BinaryToHex(GetMD5Digest(a2));
  176. *response_value = BinaryToHex(GetMD5Digest(combine_ss.str()));
  177. return Status::OK();
  178. }
  179. static std::string QuoteString(const std::string &src) {
  180. std::string dst;
  181. dst.resize(2 * src.size());
  182. size_t j = 0;
  183. for (size_t i = 0; i < src.size(); ++i) {
  184. if (src[i] == '"') {
  185. dst[j++] = '\\';
  186. }
  187. dst[j++] = src[i];
  188. }
  189. dst.resize(j);
  190. return dst;
  191. }
  192. static std::string GetMD5Digest(const std::string &src) {
  193. MD5_CTX ctx;
  194. unsigned long long res[2];
  195. MD5_Init(&ctx);
  196. MD5_Update(&ctx, src.c_str(), src.size());
  197. MD5_Final(reinterpret_cast<unsigned char*>(res), &ctx);
  198. return std::string(reinterpret_cast<char*>(res), sizeof(res));
  199. }
  200. static std::string BinaryToHex(const std::string &src) {
  201. std::stringstream ss;
  202. ss << std::hex << std::setfill('0');
  203. for (size_t i = 0; i < src.size(); ++i) {
  204. unsigned c = (unsigned)(static_cast<unsigned char>(src[i]));
  205. ss << std::setw(2) << c;
  206. }
  207. return ss.str();
  208. }
  209. }