1/* 2 * Copyright 2010 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 <cstdlib> 13#include <openssl/md5.h> 14 15#include <cstdio> 16#define PRINT(x) printf x 17 18 19static const char* kBase64Symbols 20 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; 21 22 23BHttpAuthentication::BHttpAuthentication() 24 : 25 fAuthenticationMethod(B_HTTP_AUTHENTICATION_NONE) 26{ 27} 28 29 30BHttpAuthentication::BHttpAuthentication(const BString& username, const BString& password) 31 : 32 fAuthenticationMethod(B_HTTP_AUTHENTICATION_NONE), 33 fUserName(username), 34 fPassword(password) 35{ 36} 37 38 39// #pragma mark Field modification 40 41 42void 43BHttpAuthentication::SetUserName(const BString& username) 44{ 45 fUserName = username; 46} 47 48 49void 50BHttpAuthentication::SetPassword(const BString& password) 51{ 52 fPassword = password; 53} 54 55 56void 57BHttpAuthentication::SetMethod(BHttpAuthenticationMethod method) 58{ 59 fAuthenticationMethod = method; 60} 61 62 63status_t 64BHttpAuthentication::Initialize(const BString& wwwAuthenticate) 65{ 66 fAuthenticationMethod = B_HTTP_AUTHENTICATION_NONE; 67 fDigestQop = B_HTTP_QOP_NONE; 68 69 if (wwwAuthenticate.Length() == 0) 70 return B_BAD_VALUE; 71 72 BString authRequired; 73 BString additionalData; 74 int32 firstSpace = wwwAuthenticate.FindFirst(' '); 75 76 if (firstSpace == -1) 77 wwwAuthenticate.CopyInto(authRequired, 0, wwwAuthenticate.Length()); 78 else { 79 wwwAuthenticate.CopyInto(authRequired, 0, firstSpace); 80 wwwAuthenticate.CopyInto(additionalData, firstSpace+1, 81 wwwAuthenticate.Length()); 82 } 83 84 authRequired.ToLower(); 85 if (authRequired == "basic") 86 fAuthenticationMethod = B_HTTP_AUTHENTICATION_BASIC; 87 else if (authRequired == "digest") { 88 fAuthenticationMethod = B_HTTP_AUTHENTICATION_DIGEST; 89 fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5; 90 } else 91 return B_ERROR; 92 93 94 while (additionalData.Length()) { 95 int32 firstColon = additionalData.FindFirst(','); 96 if (firstColon == -1) 97 firstColon = additionalData.Length(); 98 99 BString value; 100 additionalData.MoveInto(value, 0, firstColon); 101 additionalData.Remove(0, 1); 102 additionalData.Trim(); 103 104 int32 equal = value.FindFirst('='); 105 if (equal == -1) 106 continue; 107 108 BString name; 109 value.MoveInto(name, 0, equal); 110 value.Remove(0, 1); 111 name.ToLower(); 112 113 if (value[0] == '"') { 114 value.Remove(0, 1); 115 value.Remove(value.Length()-1, 1); 116 } 117 118 PRINT(("HttpAuth: name=%s, value=%s\n", name.String(), 119 value.String())); 120 121 if (name == "realm") 122 fRealm = value; 123 else if (name == "nonce") 124 fDigestNonce = value; 125 else if (name == "opaque") 126 fDigestOpaque = value; 127 else if (name == "stale") { 128 value.ToLower(); 129 fDigestStale = (value == "true"); 130 } else if (name == "algorithm") { 131 value.ToLower(); 132 133 if (value == "md5") 134 fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5; 135 else if (value == "md5-sess") 136 fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS; 137 else 138 fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_NONE; 139 } else if (name == "qop") 140 fDigestQop = B_HTTP_QOP_AUTH; 141 } 142 143 if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_BASIC) 144 return B_OK; 145 else if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_DIGEST 146 && fDigestNonce.Length() > 0 147 && fDigestAlgorithm != B_HTTP_AUTHENTICATION_ALGORITHM_NONE) 148 return B_OK; 149 else 150 return B_ERROR; 151} 152 153 154// #pragma mark Field access 155 156 157const BString& 158BHttpAuthentication::UserName() const 159{ 160 return fUserName; 161} 162 163 164const BString& 165BHttpAuthentication::Password() const 166{ 167 return fPassword; 168} 169 170 171BHttpAuthenticationMethod 172BHttpAuthentication::Method() const 173{ 174 return fAuthenticationMethod; 175} 176 177 178BString 179BHttpAuthentication::Authorization(const BUrl& url, const BString& method) const 180{ 181 BString authorizationString; 182 183 switch (fAuthenticationMethod) { 184 case B_HTTP_AUTHENTICATION_NONE: 185 break; 186 187 case B_HTTP_AUTHENTICATION_BASIC: 188 { 189 BString basicEncode; 190 basicEncode << fUserName << ':' << fPassword; 191 authorizationString << "Basic " << Base64Encode(basicEncode); 192 break; 193 } 194 195 case B_HTTP_AUTHENTICATION_DIGEST: 196 case B_HTTP_AUTHENTICATION_IE_DIGEST: 197 authorizationString << "Digest " << "username=\"" << fUserName 198 << "\", realm=\"" << fRealm << "\", nonce=\"" << fDigestNonce 199 << "\", algorithm="; 200 201 if (fDigestAlgorithm == B_HTTP_AUTHENTICATION_ALGORITHM_MD5) 202 authorizationString << "MD5"; 203 else 204 authorizationString << "MD5-sess"; 205 206 if (fDigestOpaque.Length() > 0) 207 authorizationString << ", opaque=\"" << fDigestOpaque << "\""; 208 209 if (fDigestQop != B_HTTP_QOP_NONE) { 210 if (fDigestCnonce.Length() == 0) { 211 fDigestCnonce = _H(fDigestOpaque); 212 //fDigestCnonce = "03c6790a055cbbac"; 213 fDigestNc = 0; 214 } 215 216 authorizationString << ", uri=\"" << url.Path() << "\""; 217 authorizationString << ", qop=auth, cnonce=\"" << fDigestCnonce 218 << "\""; 219 220 char strNc[9]; 221 snprintf(strNc, 9, "%08x", ++fDigestNc); 222 authorizationString << ", nc=" << strNc; 223 224 } 225 226 authorizationString << ", response=\"" 227 << _DigestResponse(url.Path(), method) << "\""; 228 break; 229 } 230 231 return authorizationString; 232} 233 234 235// #pragma mark Base64 encoding 236 237 238/*static*/ BString 239BHttpAuthentication::Base64Encode(const BString& string) 240{ 241 BString result; 242 BString tmpString = string; 243 244 while (tmpString.Length()) { 245 char in[3] = { 0, 0, 0 }; 246 char out[4] = { 0, 0, 0, 0 }; 247 int8 remaining = tmpString.Length(); 248 249 tmpString.MoveInto(in, 0, 3); 250 251 out[0] = (in[0] & 0xFC) >> 2; 252 out[1] = ((in[0] & 0x03) << 4) | ((in[1] & 0xF0) >> 4); 253 out[2] = ((in[1] & 0x0F) << 2) | ((in[2] & 0xC0) >> 6); 254 out[3] = in[2] & 0x3F; 255 256 for (int i = 0; i < 4; i++) 257 out[i] = kBase64Symbols[(int)out[i]]; 258 259 // Add padding if the input length is not a multiple 260 // of 3 261 switch (remaining) { 262 case 1: 263 out[2] = '='; 264 // Fall through 265 case 2: 266 out[3] = '='; 267 break; 268 } 269 270 result.Append(out, 4); 271 } 272 273 return result; 274} 275 276 277/*static*/ BString 278BHttpAuthentication::Base64Decode(const BString& string) 279{ 280 BString base64Reverse(kBase64Symbols); 281 BString result; 282 283 // Invalid input 284 if (string.Length() % 4 != 0) 285 return BString(""); 286 287 288 BString tmpString(string); 289 while (tmpString.Length()) { 290 char in[4] = { 0, 0, 0, 0 }; 291 char out[3] = { 0, 0, 0 }; 292 293 tmpString.MoveInto(in, 0, 4); 294 295 for (int i = 0; i < 4; i++) { 296 if (in[i] == '=') 297 in[i] = 0; 298 else 299 in[i] = base64Reverse.FindFirst(in[i], 0); 300 } 301 302 out[0] = (in[0] << 2) | ((in[1] & 0x30) >> 4); 303 out[1] = ((in[1] & 0x0F) << 4) | ((in[2] & 0x3C) >> 2); 304 out[2] = ((in[2] & 0x03) << 6) | in[3]; 305 306 result.Append(out, 3); 307 } 308 309 return result; 310} 311 312 313BString 314BHttpAuthentication::_DigestResponse(const BString& uri, const BString& method) const 315{ 316 PRINT(("HttpAuth: Computing digest response: \n")); 317 PRINT(("HttpAuth: > username = %s\n", fUserName.String())); 318 PRINT(("HttpAuth: > password = %s\n", fPassword.String())); 319 PRINT(("HttpAuth: > realm = %s\n", fRealm.String())); 320 PRINT(("HttpAuth: > nonce = %s\n", fDigestNonce.String())); 321 PRINT(("HttpAuth: > cnonce = %s\n", fDigestCnonce.String())); 322 PRINT(("HttpAuth: > nc = %08x\n", fDigestNc)); 323 PRINT(("HttpAuth: > uri = %s\n", uri.String())); 324 PRINT(("HttpAuth: > method = %s\n", method.String())); 325 PRINT(("HttpAuth: > algorithm = %d (MD5:%d, MD5-sess:%d)\n", 326 fDigestAlgorithm, B_HTTP_AUTHENTICATION_ALGORITHM_MD5, 327 B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS)); 328 329 BString A1; 330 A1 << fUserName << ':' << fRealm << ':' << fPassword; 331 332 if (fDigestAlgorithm == B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS) { 333 A1 = _H(A1); 334 A1 << ':' << fDigestNonce << ':' << fDigestCnonce; 335 } 336 337 338 BString A2; 339 A2 << method << ':' << uri; 340 341 PRINT(("HttpAuth: > A1 = %s\n", A1.String())); 342 PRINT(("HttpAuth: > A2 = %s\n", A2.String())); 343 PRINT(("HttpAuth: > H(A1) = %s\n", _H(A1).String())); 344 PRINT(("HttpAuth: > H(A2) = %s\n", _H(A2).String())); 345 346 char strNc[9]; 347 snprintf(strNc, 9, "%08x", fDigestNc); 348 349 BString secretResp; 350 secretResp << fDigestNonce << ':' << strNc << ':' << fDigestCnonce 351 << ":auth:" << _H(A2); 352 353 PRINT(("HttpAuth: > R2 = %s\n", secretResp.String())); 354 355 BString response = _KD(_H(A1), secretResp); 356 PRINT(("HttpAuth: > response = %s\n", response.String())); 357 358 return response; 359} 360 361 362BString 363BHttpAuthentication::_H(const BString& value) const 364{ 365 unsigned char* hashResult 366 = MD5(reinterpret_cast<const unsigned char*>(value.String()), 367 value.Length(), NULL); 368 369 BString result; 370 // TODO: This is slower than it needs to be. If we already know the 371 // final hash string length, we can use 372 // BString::LockBuffer(MD5_DIGEST_LENGTH * 2) to preallocate it. 373 // I am not making the change since I can not test it right now (stippi). 374 for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { 375 char c = ((hashResult[i] & 0xF0) >> 4); 376 c += (c > 9) ? 'a' - 10 : '0'; 377 result << c; 378 379 c = hashResult[i] & 0x0F; 380 c += (c > 9) ? 'a' - 10 : '0'; 381 result << c; 382 } 383 384 return result; 385} 386 387 388BString 389BHttpAuthentication::_KD(const BString& secret, const BString& data) const 390{ 391 BString encode; 392 encode << secret << ':' << data; 393 394 return _H(encode); 395} 396