* Copyright 2010-2023 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Christophe Huriaux, c.huriaux@gmail.com
*/
#include <HttpAuthentication.h>
#include <stdlib.h>
#include <stdio.h>
#include <AutoLocker.h>
using namespace BPrivate::Network;
#if DEBUG > 0
#define PRINT(x) printf x
#else
#define PRINT(x)
#endif
#ifdef OPENSSL_ENABLED
extern "C" {
#include <openssl/md5.h>
};
#else
#include "md5.h"
#endif
#ifndef MD5_DIGEST_LENGTH
#define MD5_DIGEST_LENGTH 16
#endif
static const char* kBase64Symbols
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
BHttpAuthentication::BHttpAuthentication()
:
fAuthenticationMethod(B_HTTP_AUTHENTICATION_NONE)
{
}
BHttpAuthentication::BHttpAuthentication(const BString& username, const BString& password)
:
fAuthenticationMethod(B_HTTP_AUTHENTICATION_NONE),
fUserName(username),
fPassword(password)
{
}
BHttpAuthentication::BHttpAuthentication(const BHttpAuthentication& other)
:
fAuthenticationMethod(other.fAuthenticationMethod),
fUserName(other.fUserName),
fPassword(other.fPassword),
fToken(other.fToken),
fRealm(other.fRealm),
fDigestNonce(other.fDigestNonce),
fDigestCnonce(other.fDigestCnonce),
fDigestNc(other.fDigestNc),
fDigestOpaque(other.fDigestOpaque),
fDigestStale(other.fDigestStale),
fDigestAlgorithm(other.fDigestAlgorithm),
fDigestQop(other.fDigestQop),
fAuthorizationString(other.fAuthorizationString)
{
}
BHttpAuthentication& BHttpAuthentication::operator=(
const BHttpAuthentication& other)
{
fAuthenticationMethod = other.fAuthenticationMethod;
fUserName = other.fUserName;
fPassword = other.fPassword;
fToken = other.fToken;
fRealm = other.fRealm;
fDigestNonce = other.fDigestNonce;
fDigestCnonce = other.fDigestCnonce;
fDigestNc = other.fDigestNc;
fDigestOpaque = other.fDigestOpaque;
fDigestStale = other.fDigestStale;
fDigestAlgorithm = other.fDigestAlgorithm;
fDigestQop = other.fDigestQop;
fAuthorizationString = other.fAuthorizationString;
return *this;
}
void
BHttpAuthentication::SetUserName(const BString& username)
{
fLock.Lock();
fUserName = username;
fLock.Unlock();
}
void
BHttpAuthentication::SetPassword(const BString& password)
{
fLock.Lock();
fPassword = password;
fLock.Unlock();
}
void
BHttpAuthentication::SetToken(const BString& token)
{
fLock.Lock();
fToken = token;
fLock.Unlock();
}
void
BHttpAuthentication::SetMethod(BHttpAuthenticationMethod method)
{
fLock.Lock();
fAuthenticationMethod = method;
fLock.Unlock();
}
status_t
BHttpAuthentication::Initialize(const BString& wwwAuthenticate)
{
BPrivate::AutoLocker<BLocker> lock(fLock);
fAuthenticationMethod = B_HTTP_AUTHENTICATION_NONE;
fDigestQop = B_HTTP_QOP_NONE;
if (wwwAuthenticate.Length() == 0)
return B_BAD_VALUE;
BString authRequired;
BString additionalData;
int32 firstSpace = wwwAuthenticate.FindFirst(' ');
if (firstSpace == -1)
wwwAuthenticate.CopyInto(authRequired, 0, wwwAuthenticate.Length());
else {
wwwAuthenticate.CopyInto(authRequired, 0, firstSpace);
wwwAuthenticate.CopyInto(additionalData, firstSpace + 1,
wwwAuthenticate.Length() - (firstSpace + 1));
}
authRequired.ToLower();
if (authRequired == "basic")
fAuthenticationMethod = B_HTTP_AUTHENTICATION_BASIC;
else if (authRequired == "digest") {
fAuthenticationMethod = B_HTTP_AUTHENTICATION_DIGEST;
fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5;
} else if (authRequired == "bearer")
fAuthenticationMethod = B_HTTP_AUTHENTICATION_BEARER;
else
return B_ERROR;
while (additionalData.Length()) {
int32 firstComma = additionalData.FindFirst(',');
if (firstComma == -1)
firstComma = additionalData.Length();
BString value;
additionalData.MoveInto(value, 0, firstComma);
additionalData.Remove(0, 1);
additionalData.Trim();
int32 equal = value.FindFirst('=');
if (equal <= 0)
continue;
BString name;
value.MoveInto(name, 0, equal);
value.Remove(0, 1);
name.ToLower();
if (value.Length() > 0 && value[0] == '"') {
value.Remove(0, 1);
value.Remove(value.Length() - 1, 1);
}
PRINT(("HttpAuth: name=%s, value=%s\n", name.String(),
value.String()));
if (name == "realm")
fRealm = value;
else if (name == "nonce")
fDigestNonce = value;
else if (name == "opaque")
fDigestOpaque = value;
else if (name == "stale") {
value.ToLower();
fDigestStale = (value == "true");
} else if (name == "algorithm") {
value.ToLower();
if (value == "md5")
fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5;
else if (value == "md5-sess")
fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS;
else
fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_NONE;
} else if (name == "qop")
fDigestQop = B_HTTP_QOP_AUTH;
}
if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_BASIC)
return B_OK;
else if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_BEARER)
return B_OK;
else if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_DIGEST
&& fDigestNonce.Length() > 0
&& fDigestAlgorithm != B_HTTP_AUTHENTICATION_ALGORITHM_NONE) {
return B_OK;
} else
return B_ERROR;
}
const BString&
BHttpAuthentication::UserName() const
{
BPrivate::AutoLocker<BLocker> lock(fLock);
return fUserName;
}
const BString&
BHttpAuthentication::Password() const
{
BPrivate::AutoLocker<BLocker> lock(fLock);
return fPassword;
}
BHttpAuthenticationMethod
BHttpAuthentication::Method() const
{
BPrivate::AutoLocker<BLocker> lock(fLock);
return fAuthenticationMethod;
}
BString
BHttpAuthentication::Authorization(const BUrl& url, const BString& method) const
{
BPrivate::AutoLocker<BLocker> lock(fLock);
BString authorizationString;
switch (fAuthenticationMethod) {
case B_HTTP_AUTHENTICATION_NONE:
break;
case B_HTTP_AUTHENTICATION_BASIC:
{
BString basicEncode;
basicEncode << fUserName << ':' << fPassword;
authorizationString << "Basic " << Base64Encode(basicEncode);
break;
}
case B_HTTP_AUTHENTICATION_BEARER:
authorizationString << "Bearer " << fToken;
break;
case B_HTTP_AUTHENTICATION_DIGEST:
case B_HTTP_AUTHENTICATION_IE_DIGEST:
authorizationString << "Digest " << "username=\"" << fUserName
<< "\", realm=\"" << fRealm << "\", nonce=\"" << fDigestNonce
<< "\", algorithm=";
if (fDigestAlgorithm == B_HTTP_AUTHENTICATION_ALGORITHM_MD5)
authorizationString << "MD5";
else
authorizationString << "MD5-sess";
if (fDigestOpaque.Length() > 0)
authorizationString << ", opaque=\"" << fDigestOpaque << "\"";
if (fDigestQop != B_HTTP_QOP_NONE) {
if (fDigestCnonce.Length() == 0) {
fDigestCnonce = _H(fDigestOpaque);
fDigestNc = 0;
}
authorizationString << ", uri=\"" << url.Path() << "\"";
authorizationString << ", qop=auth, cnonce=\"" << fDigestCnonce
<< "\"";
char strNc[9];
snprintf(strNc, 9, "%08x", ++fDigestNc);
authorizationString << ", nc=" << strNc;
}
authorizationString << ", response=\""
<< _DigestResponse(url.Path(), method) << "\"";
break;
}
return authorizationString;
}
BString
BHttpAuthentication::Base64Encode(const BString& string)
{
BString result;
BString tmpString = string;
while (tmpString.Length()) {
char in[3] = { 0, 0, 0 };
char out[4] = { 0, 0, 0, 0 };
int8 remaining = tmpString.Length();
tmpString.MoveInto(in, 0, 3);
out[0] = (in[0] & 0xFC) >> 2;
out[1] = ((in[0] & 0x03) << 4) | ((in[1] & 0xF0) >> 4);
out[2] = ((in[1] & 0x0F) << 2) | ((in[2] & 0xC0) >> 6);
out[3] = in[2] & 0x3F;
for (int i = 0; i < 4; i++)
out[i] = kBase64Symbols[(int)out[i]];
switch (remaining) {
case 1:
out[2] = '=';
case 2:
out[3] = '=';
break;
}
result.Append(out, 4);
}
return result;
}
BString
BHttpAuthentication::Base64Decode(const BString& string)
{
BString result;
if (string.Length() % 4 != 0)
return result;
BString base64Reverse(kBase64Symbols);
BString tmpString(string);
while (tmpString.Length()) {
char in[4] = { 0, 0, 0, 0 };
char out[3] = { 0, 0, 0 };
tmpString.MoveInto(in, 0, 4);
for (int i = 0; i < 4; i++) {
if (in[i] == '=')
in[i] = 0;
else
in[i] = base64Reverse.FindFirst(in[i], 0);
}
out[0] = (in[0] << 2) | ((in[1] & 0x30) >> 4);
out[1] = ((in[1] & 0x0F) << 4) | ((in[2] & 0x3C) >> 2);
out[2] = ((in[2] & 0x03) << 6) | in[3];
result.Append(out, 3);
}
return result;
}
BString
BHttpAuthentication::_DigestResponse(const BString& uri, const BString& method) const
{
PRINT(("HttpAuth: Computing digest response: \n"));
PRINT(("HttpAuth: > username = %s\n", fUserName.String()));
PRINT(("HttpAuth: > password = %s\n", fPassword.String()));
PRINT(("HttpAuth: > token = %s\n", fToken.String()));
PRINT(("HttpAuth: > realm = %s\n", fRealm.String()));
PRINT(("HttpAuth: > nonce = %s\n", fDigestNonce.String()));
PRINT(("HttpAuth: > cnonce = %s\n", fDigestCnonce.String()));
PRINT(("HttpAuth: > nc = %08x\n", fDigestNc));
PRINT(("HttpAuth: > uri = %s\n", uri.String()));
PRINT(("HttpAuth: > method = %s\n", method.String()));
PRINT(("HttpAuth: > algorithm = %d (MD5:%d, MD5-sess:%d)\n",
fDigestAlgorithm, B_HTTP_AUTHENTICATION_ALGORITHM_MD5,
B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS));
BString A1;
A1 << fUserName << ':' << fRealm << ':' << fPassword;
if (fDigestAlgorithm == B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS) {
A1 = _H(A1);
A1 << ':' << fDigestNonce << ':' << fDigestCnonce;
}
BString A2;
A2 << method << ':' << uri;
PRINT(("HttpAuth: > A1 = %s\n", A1.String()));
PRINT(("HttpAuth: > A2 = %s\n", A2.String()));
PRINT(("HttpAuth: > H(A1) = %s\n", _H(A1).String()));
PRINT(("HttpAuth: > H(A2) = %s\n", _H(A2).String()));
char strNc[9];
snprintf(strNc, 9, "%08x", fDigestNc);
BString secretResp;
secretResp << fDigestNonce << ':' << strNc << ':' << fDigestCnonce
<< ":auth:" << _H(A2);
PRINT(("HttpAuth: > R2 = %s\n", secretResp.String()));
BString response = _KD(_H(A1), secretResp);
PRINT(("HttpAuth: > response = %s\n", response.String()));
return response;
}
BString
BHttpAuthentication::_H(const BString& value) const
{
MD5_CTX context;
uchar hashResult[MD5_DIGEST_LENGTH];
MD5_Init(&context);
MD5_Update(&context, (void *)(value.String()), value.Length());
MD5_Final(hashResult, &context);
BString result;
char* resultChar = result.LockBuffer(MD5_DIGEST_LENGTH * 2);
if (resultChar == NULL)
return BString();
for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
char c = ((hashResult[i] & 0xF0) >> 4);
c += (c > 9) ? 'a' - 10 : '0';
resultChar[0] = c;
resultChar++;
c = hashResult[i] & 0x0F;
c += (c > 9) ? 'a' - 10 : '0';
resultChar[0] = c;
resultChar++;
}
result.UnlockBuffer(MD5_DIGEST_LENGTH * 2);
return result;
}
BString
BHttpAuthentication::_KD(const BString& secret, const BString& data) const
{
BString encode;
encode << secret << ':' << data;
return _H(encode);
}