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