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