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