/* * Copyright 2010-2023 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Christophe Huriaux, c.huriaux@gmail.com */ #include #include #include #include using namespace BPrivate::Network; #if DEBUG > 0 #define PRINT(x) printf x #else #define PRINT(x) #endif #ifdef OPENSSL_ENABLED extern "C" { #include }; #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; } // #pragma mark Field modification 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 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; } // #pragma mark Field access const BString& BHttpAuthentication::UserName() const { BPrivate::AutoLocker lock(fLock); return fUserName; } const BString& BHttpAuthentication::Password() const { BPrivate::AutoLocker lock(fLock); return fPassword; } BHttpAuthenticationMethod BHttpAuthentication::Method() const { BPrivate::AutoLocker lock(fLock); return fAuthenticationMethod; } BString BHttpAuthentication::Authorization(const BUrl& url, const BString& method) const { BPrivate::AutoLocker 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); //fDigestCnonce = "03c6790a055cbbac"; 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; } // #pragma mark Base64 encoding /*static*/ 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]]; // Add padding if the input length is not a multiple // of 3 switch (remaining) { case 1: out[2] = '='; // Fall through case 2: out[3] = '='; break; } result.Append(out, 4); } return result; } /*static*/ BString BHttpAuthentication::Base64Decode(const BString& string) { BString result; // Check for invalid input 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; // Preallocate the string 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); }