xref: /haiku/src/kits/network/libnetservices/HttpAuthentication.cpp (revision a5061ecec55353a5f394759473f1fd6df04890da)
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