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
17using 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
27extern "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
38static const char* kBase64Symbols
39	= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
40
41
42BHttpAuthentication::BHttpAuthentication()
43	:
44	fAuthenticationMethod(B_HTTP_AUTHENTICATION_NONE)
45{
46}
47
48
49BHttpAuthentication::BHttpAuthentication(const BString& username, const BString& password)
50	:
51	fAuthenticationMethod(B_HTTP_AUTHENTICATION_NONE),
52	fUserName(username),
53	fPassword(password)
54{
55}
56
57
58BHttpAuthentication::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
77BHttpAuthentication& 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
100void
101BHttpAuthentication::SetUserName(const BString& username)
102{
103	fLock.Lock();
104	fUserName = username;
105	fLock.Unlock();
106}
107
108
109void
110BHttpAuthentication::SetPassword(const BString& password)
111{
112	fLock.Lock();
113	fPassword = password;
114	fLock.Unlock();
115}
116
117
118void
119BHttpAuthentication::SetToken(const BString& token)
120{
121	fLock.Lock();
122	fToken = token;
123	fLock.Unlock();
124}
125
126
127void
128BHttpAuthentication::SetMethod(BHttpAuthenticationMethod method)
129{
130	fLock.Lock();
131	fAuthenticationMethod = method;
132	fLock.Unlock();
133}
134
135
136status_t
137BHttpAuthentication::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
236const BString&
237BHttpAuthentication::UserName() const
238{
239	BPrivate::AutoLocker<BLocker> lock(fLock);
240	return fUserName;
241}
242
243
244const BString&
245BHttpAuthentication::Password() const
246{
247	BPrivate::AutoLocker<BLocker> lock(fLock);
248	return fPassword;
249}
250
251
252BHttpAuthenticationMethod
253BHttpAuthentication::Method() const
254{
255	BPrivate::AutoLocker<BLocker> lock(fLock);
256	return fAuthenticationMethod;
257}
258
259
260BString
261BHttpAuthentication::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
326BHttpAuthentication::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
365BHttpAuthentication::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
400BString
401BHttpAuthentication::_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
450BString
451BHttpAuthentication::_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
482BString
483BHttpAuthentication::_KD(const BString& secret, const BString& data) const
484{
485	BString encode;
486	encode << secret << ':' << data;
487
488	return _H(encode);
489}
490