1 /* 2 * Copyright 2010-2023 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Christophe Huriaux, c.huriaux@gmail.com 7 */ 8 9 10 #include <HttpAuthentication.h> 11 12 #include <stdlib.h> 13 #include <stdio.h> 14 15 #include <AutoLocker.h> 16 17 using namespace BPrivate::Network; 18 19 20 #if DEBUG > 0 21 #define PRINT(x) printf x 22 #else 23 #define PRINT(x) 24 #endif 25 26 #ifdef OPENSSL_ENABLED 27 extern "C" { 28 #include <openssl/md5.h> 29 }; 30 #else 31 #include "md5.h" 32 #endif 33 34 #ifndef MD5_DIGEST_LENGTH 35 #define MD5_DIGEST_LENGTH 16 36 #endif 37 38 static const char* kBase64Symbols 39 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; 40 41 42 BHttpAuthentication::BHttpAuthentication() 43 : 44 fAuthenticationMethod(B_HTTP_AUTHENTICATION_NONE) 45 { 46 } 47 48 49 BHttpAuthentication::BHttpAuthentication(const BString& username, const BString& password) 50 : 51 fAuthenticationMethod(B_HTTP_AUTHENTICATION_NONE), 52 fUserName(username), 53 fPassword(password) 54 { 55 } 56 57 58 BHttpAuthentication::BHttpAuthentication(const BHttpAuthentication& other) 59 : 60 fAuthenticationMethod(other.fAuthenticationMethod), 61 fUserName(other.fUserName), 62 fPassword(other.fPassword), 63 fToken(other.fToken), 64 fRealm(other.fRealm), 65 fDigestNonce(other.fDigestNonce), 66 fDigestCnonce(other.fDigestCnonce), 67 fDigestNc(other.fDigestNc), 68 fDigestOpaque(other.fDigestOpaque), 69 fDigestStale(other.fDigestStale), 70 fDigestAlgorithm(other.fDigestAlgorithm), 71 fDigestQop(other.fDigestQop), 72 fAuthorizationString(other.fAuthorizationString) 73 { 74 } 75 76 77 BHttpAuthentication& BHttpAuthentication::operator=( 78 const BHttpAuthentication& other) 79 { 80 fAuthenticationMethod = other.fAuthenticationMethod; 81 fUserName = other.fUserName; 82 fPassword = other.fPassword; 83 fToken = other.fToken; 84 fRealm = other.fRealm; 85 fDigestNonce = other.fDigestNonce; 86 fDigestCnonce = other.fDigestCnonce; 87 fDigestNc = other.fDigestNc; 88 fDigestOpaque = other.fDigestOpaque; 89 fDigestStale = other.fDigestStale; 90 fDigestAlgorithm = other.fDigestAlgorithm; 91 fDigestQop = other.fDigestQop; 92 fAuthorizationString = other.fAuthorizationString; 93 return *this; 94 } 95 96 97 // #pragma mark Field modification 98 99 100 void 101 BHttpAuthentication::SetUserName(const BString& username) 102 { 103 fLock.Lock(); 104 fUserName = username; 105 fLock.Unlock(); 106 } 107 108 109 void 110 BHttpAuthentication::SetPassword(const BString& password) 111 { 112 fLock.Lock(); 113 fPassword = password; 114 fLock.Unlock(); 115 } 116 117 118 void 119 BHttpAuthentication::SetToken(const BString& token) 120 { 121 fLock.Lock(); 122 fToken = token; 123 fLock.Unlock(); 124 } 125 126 127 void 128 BHttpAuthentication::SetMethod(BHttpAuthenticationMethod method) 129 { 130 fLock.Lock(); 131 fAuthenticationMethod = method; 132 fLock.Unlock(); 133 } 134 135 136 status_t 137 BHttpAuthentication::Initialize(const BString& wwwAuthenticate) 138 { 139 BPrivate::AutoLocker<BLocker> lock(fLock); 140 141 fAuthenticationMethod = B_HTTP_AUTHENTICATION_NONE; 142 fDigestQop = B_HTTP_QOP_NONE; 143 144 if (wwwAuthenticate.Length() == 0) 145 return B_BAD_VALUE; 146 147 BString authRequired; 148 BString additionalData; 149 int32 firstSpace = wwwAuthenticate.FindFirst(' '); 150 151 if (firstSpace == -1) 152 wwwAuthenticate.CopyInto(authRequired, 0, wwwAuthenticate.Length()); 153 else { 154 wwwAuthenticate.CopyInto(authRequired, 0, firstSpace); 155 wwwAuthenticate.CopyInto(additionalData, firstSpace + 1, 156 wwwAuthenticate.Length() - (firstSpace + 1)); 157 } 158 159 authRequired.ToLower(); 160 if (authRequired == "basic") 161 fAuthenticationMethod = B_HTTP_AUTHENTICATION_BASIC; 162 else if (authRequired == "digest") { 163 fAuthenticationMethod = B_HTTP_AUTHENTICATION_DIGEST; 164 fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5; 165 } else if (authRequired == "bearer") 166 fAuthenticationMethod = B_HTTP_AUTHENTICATION_BEARER; 167 else 168 return B_ERROR; 169 170 171 while (additionalData.Length()) { 172 int32 firstComma = additionalData.FindFirst(','); 173 if (firstComma == -1) 174 firstComma = additionalData.Length(); 175 176 BString value; 177 additionalData.MoveInto(value, 0, firstComma); 178 additionalData.Remove(0, 1); 179 additionalData.Trim(); 180 181 int32 equal = value.FindFirst('='); 182 if (equal <= 0) 183 continue; 184 185 BString name; 186 value.MoveInto(name, 0, equal); 187 value.Remove(0, 1); 188 name.ToLower(); 189 190 if (value.Length() > 0 && value[0] == '"') { 191 value.Remove(0, 1); 192 value.Remove(value.Length() - 1, 1); 193 } 194 195 PRINT(("HttpAuth: name=%s, value=%s\n", name.String(), 196 value.String())); 197 198 if (name == "realm") 199 fRealm = value; 200 else if (name == "nonce") 201 fDigestNonce = value; 202 else if (name == "opaque") 203 fDigestOpaque = value; 204 else if (name == "stale") { 205 value.ToLower(); 206 fDigestStale = (value == "true"); 207 } else if (name == "algorithm") { 208 value.ToLower(); 209 210 if (value == "md5") 211 fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5; 212 else if (value == "md5-sess") 213 fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS; 214 else 215 fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_NONE; 216 } else if (name == "qop") 217 fDigestQop = B_HTTP_QOP_AUTH; 218 } 219 220 if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_BASIC) 221 return B_OK; 222 else if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_BEARER) 223 return B_OK; 224 else if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_DIGEST 225 && fDigestNonce.Length() > 0 226 && fDigestAlgorithm != B_HTTP_AUTHENTICATION_ALGORITHM_NONE) { 227 return B_OK; 228 } else 229 return B_ERROR; 230 } 231 232 233 // #pragma mark Field access 234 235 236 const BString& 237 BHttpAuthentication::UserName() const 238 { 239 BPrivate::AutoLocker<BLocker> lock(fLock); 240 return fUserName; 241 } 242 243 244 const BString& 245 BHttpAuthentication::Password() const 246 { 247 BPrivate::AutoLocker<BLocker> lock(fLock); 248 return fPassword; 249 } 250 251 252 BHttpAuthenticationMethod 253 BHttpAuthentication::Method() const 254 { 255 BPrivate::AutoLocker<BLocker> lock(fLock); 256 return fAuthenticationMethod; 257 } 258 259 260 BString 261 BHttpAuthentication::Authorization(const BUrl& url, const BString& method) const 262 { 263 BPrivate::AutoLocker<BLocker> lock(fLock); 264 BString authorizationString; 265 266 switch (fAuthenticationMethod) { 267 case B_HTTP_AUTHENTICATION_NONE: 268 break; 269 270 case B_HTTP_AUTHENTICATION_BASIC: 271 { 272 BString basicEncode; 273 basicEncode << fUserName << ':' << fPassword; 274 authorizationString << "Basic " << Base64Encode(basicEncode); 275 break; 276 } 277 278 case B_HTTP_AUTHENTICATION_BEARER: 279 authorizationString << "Bearer " << fToken; 280 break; 281 282 case B_HTTP_AUTHENTICATION_DIGEST: 283 case B_HTTP_AUTHENTICATION_IE_DIGEST: 284 authorizationString << "Digest " << "username=\"" << fUserName 285 << "\", realm=\"" << fRealm << "\", nonce=\"" << fDigestNonce 286 << "\", algorithm="; 287 288 if (fDigestAlgorithm == B_HTTP_AUTHENTICATION_ALGORITHM_MD5) 289 authorizationString << "MD5"; 290 else 291 authorizationString << "MD5-sess"; 292 293 if (fDigestOpaque.Length() > 0) 294 authorizationString << ", opaque=\"" << fDigestOpaque << "\""; 295 296 if (fDigestQop != B_HTTP_QOP_NONE) { 297 if (fDigestCnonce.Length() == 0) { 298 fDigestCnonce = _H(fDigestOpaque); 299 //fDigestCnonce = "03c6790a055cbbac"; 300 fDigestNc = 0; 301 } 302 303 authorizationString << ", uri=\"" << url.Path() << "\""; 304 authorizationString << ", qop=auth, cnonce=\"" << fDigestCnonce 305 << "\""; 306 307 char strNc[9]; 308 snprintf(strNc, 9, "%08x", ++fDigestNc); 309 authorizationString << ", nc=" << strNc; 310 311 } 312 313 authorizationString << ", response=\"" 314 << _DigestResponse(url.Path(), method) << "\""; 315 break; 316 } 317 318 return authorizationString; 319 } 320 321 322 // #pragma mark Base64 encoding 323 324 325 /*static*/ BString 326 BHttpAuthentication::Base64Encode(const BString& string) 327 { 328 BString result; 329 BString tmpString = string; 330 331 while (tmpString.Length()) { 332 char in[3] = { 0, 0, 0 }; 333 char out[4] = { 0, 0, 0, 0 }; 334 int8 remaining = tmpString.Length(); 335 336 tmpString.MoveInto(in, 0, 3); 337 338 out[0] = (in[0] & 0xFC) >> 2; 339 out[1] = ((in[0] & 0x03) << 4) | ((in[1] & 0xF0) >> 4); 340 out[2] = ((in[1] & 0x0F) << 2) | ((in[2] & 0xC0) >> 6); 341 out[3] = in[2] & 0x3F; 342 343 for (int i = 0; i < 4; i++) 344 out[i] = kBase64Symbols[(int)out[i]]; 345 346 // Add padding if the input length is not a multiple 347 // of 3 348 switch (remaining) { 349 case 1: 350 out[2] = '='; 351 // Fall through 352 case 2: 353 out[3] = '='; 354 break; 355 } 356 357 result.Append(out, 4); 358 } 359 360 return result; 361 } 362 363 364 /*static*/ BString 365 BHttpAuthentication::Base64Decode(const BString& string) 366 { 367 BString result; 368 369 // Check for invalid input 370 if (string.Length() % 4 != 0) 371 return result; 372 373 BString base64Reverse(kBase64Symbols); 374 375 BString tmpString(string); 376 while (tmpString.Length()) { 377 char in[4] = { 0, 0, 0, 0 }; 378 char out[3] = { 0, 0, 0 }; 379 380 tmpString.MoveInto(in, 0, 4); 381 382 for (int i = 0; i < 4; i++) { 383 if (in[i] == '=') 384 in[i] = 0; 385 else 386 in[i] = base64Reverse.FindFirst(in[i], 0); 387 } 388 389 out[0] = (in[0] << 2) | ((in[1] & 0x30) >> 4); 390 out[1] = ((in[1] & 0x0F) << 4) | ((in[2] & 0x3C) >> 2); 391 out[2] = ((in[2] & 0x03) << 6) | in[3]; 392 393 result.Append(out, 3); 394 } 395 396 return result; 397 } 398 399 400 BString 401 BHttpAuthentication::_DigestResponse(const BString& uri, const BString& method) const 402 { 403 PRINT(("HttpAuth: Computing digest response: \n")); 404 PRINT(("HttpAuth: > username = %s\n", fUserName.String())); 405 PRINT(("HttpAuth: > password = %s\n", fPassword.String())); 406 PRINT(("HttpAuth: > token = %s\n", fToken.String())); 407 PRINT(("HttpAuth: > realm = %s\n", fRealm.String())); 408 PRINT(("HttpAuth: > nonce = %s\n", fDigestNonce.String())); 409 PRINT(("HttpAuth: > cnonce = %s\n", fDigestCnonce.String())); 410 PRINT(("HttpAuth: > nc = %08x\n", fDigestNc)); 411 PRINT(("HttpAuth: > uri = %s\n", uri.String())); 412 PRINT(("HttpAuth: > method = %s\n", method.String())); 413 PRINT(("HttpAuth: > algorithm = %d (MD5:%d, MD5-sess:%d)\n", 414 fDigestAlgorithm, B_HTTP_AUTHENTICATION_ALGORITHM_MD5, 415 B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS)); 416 417 BString A1; 418 A1 << fUserName << ':' << fRealm << ':' << fPassword; 419 420 if (fDigestAlgorithm == B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS) { 421 A1 = _H(A1); 422 A1 << ':' << fDigestNonce << ':' << fDigestCnonce; 423 } 424 425 426 BString A2; 427 A2 << method << ':' << uri; 428 429 PRINT(("HttpAuth: > A1 = %s\n", A1.String())); 430 PRINT(("HttpAuth: > A2 = %s\n", A2.String())); 431 PRINT(("HttpAuth: > H(A1) = %s\n", _H(A1).String())); 432 PRINT(("HttpAuth: > H(A2) = %s\n", _H(A2).String())); 433 434 char strNc[9]; 435 snprintf(strNc, 9, "%08x", fDigestNc); 436 437 BString secretResp; 438 secretResp << fDigestNonce << ':' << strNc << ':' << fDigestCnonce 439 << ":auth:" << _H(A2); 440 441 PRINT(("HttpAuth: > R2 = %s\n", secretResp.String())); 442 443 BString response = _KD(_H(A1), secretResp); 444 PRINT(("HttpAuth: > response = %s\n", response.String())); 445 446 return response; 447 } 448 449 450 BString 451 BHttpAuthentication::_H(const BString& value) const 452 { 453 MD5_CTX context; 454 uchar hashResult[MD5_DIGEST_LENGTH]; 455 MD5_Init(&context); 456 MD5_Update(&context, (void *)(value.String()), value.Length()); 457 MD5_Final(hashResult, &context); 458 459 BString result; 460 // Preallocate the string 461 char* resultChar = result.LockBuffer(MD5_DIGEST_LENGTH * 2); 462 if (resultChar == NULL) 463 return BString(); 464 465 for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { 466 char c = ((hashResult[i] & 0xF0) >> 4); 467 c += (c > 9) ? 'a' - 10 : '0'; 468 resultChar[0] = c; 469 resultChar++; 470 471 c = hashResult[i] & 0x0F; 472 c += (c > 9) ? 'a' - 10 : '0'; 473 resultChar[0] = c; 474 resultChar++; 475 } 476 result.UnlockBuffer(MD5_DIGEST_LENGTH * 2); 477 478 return result; 479 } 480 481 482 BString 483 BHttpAuthentication::_KD(const BString& secret, const BString& data) const 484 { 485 BString encode; 486 encode << secret << ':' << data; 487 488 return _H(encode); 489 } 490