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