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