#38 Added support for digest authentication
This commit is contained in:
@ -58,6 +58,7 @@ extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; //
|
||||
#endif
|
||||
|
||||
extern NSString* const GCDWebServerAuthenticationMethod_Basic;
|
||||
extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
||||
|
||||
@class GCDWebServer;
|
||||
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
NSString* _serverName;
|
||||
NSString* _authenticationRealm;
|
||||
NSString* _authenticationBasicAccount;
|
||||
NSString* _authenticationDigestAccount;
|
||||
Class _connectionClass;
|
||||
BOOL _mapHEADToGET;
|
||||
CFTimeInterval _disconnectDelay;
|
||||
@ -95,6 +96,7 @@ NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"Automati
|
||||
#endif
|
||||
|
||||
NSString* const GCDWebServerAuthenticationMethod_Basic = @"Basic";
|
||||
NSString* const GCDWebServerAuthenticationMethod_DigestAccess = @"DigestAccess";
|
||||
|
||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
||||
#ifdef NDEBUG
|
||||
@ -155,7 +157,8 @@ static void _SignalHandler(int signal) {
|
||||
@implementation GCDWebServer
|
||||
|
||||
@synthesize delegate=_delegate, handlers=_handlers, port=_port, serverName=_serverName, authenticationRealm=_authenticationRealm,
|
||||
authenticationBasicAccount=_authenticationBasicAccount, shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
|
||||
authenticationBasicAccount=_authenticationBasicAccount, authenticationDigestAccount=_authenticationDigestAccount,
|
||||
shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
|
||||
|
||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
||||
|
||||
@ -383,6 +386,11 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
NSString* user = _GetOption(_options, GCDWebServerOption_AuthenticationUser, @"");
|
||||
NSString* password = _GetOption(_options, GCDWebServerOption_AuthenticationPassword, @"");
|
||||
_authenticationBasicAccount = ARC_RETAIN(_EncodeBase64([NSString stringWithFormat:@"%@:%@", user, password]));
|
||||
} else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
|
||||
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||
NSString* user = _GetOption(_options, GCDWebServerOption_AuthenticationUser, @"");
|
||||
NSString* password = _GetOption(_options, GCDWebServerOption_AuthenticationPassword, @"");
|
||||
_authenticationDigestAccount = ARC_RETAIN(GCDWebServerComputeMD5Digest(@"%@:%@:%@", user, _authenticationRealm, password));
|
||||
}
|
||||
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
||||
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
||||
@ -502,6 +510,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
_authenticationRealm = nil;
|
||||
ARC_RELEASE(_authenticationBasicAccount);
|
||||
_authenticationBasicAccount = nil;
|
||||
ARC_RELEASE(_authenticationDigestAccount);
|
||||
_authenticationDigestAccount = nil;
|
||||
|
||||
LOG_INFO(@"%@ stopped", [self class]);
|
||||
if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
|
||||
|
||||
@ -48,6 +48,7 @@ static NSData* _CRLFData = nil;
|
||||
static NSData* _CRLFCRLFData = nil;
|
||||
static NSData* _continueData = nil;
|
||||
static NSData* _lastChunkData = nil;
|
||||
static NSString* _digestAuthenticationNonce = nil;
|
||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||
static int32_t _connectionCounter = 0;
|
||||
#endif
|
||||
@ -357,6 +358,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
if (_lastChunkData == nil) {
|
||||
_lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5];
|
||||
}
|
||||
if (_digestAuthenticationNonce == nil) {
|
||||
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
|
||||
_digestAuthenticationNonce = ARC_RETAIN(GCDWebServerComputeMD5Digest(@"%@", ARC_BRIDGE_RELEASE(CFUUIDCreateString(kCFAllocatorDefault, uuid))));
|
||||
CFRelease(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
|
||||
@ -707,15 +713,46 @@ static NSString* _StringFromAddressData(NSData* data) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc2617
|
||||
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request {
|
||||
LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
|
||||
GCDWebServerResponse* response = nil;
|
||||
if (_server.authenticationBasicAccount) {
|
||||
BOOL authenticated = NO;
|
||||
NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
|
||||
if (![authorizationHeader hasPrefix:@"Basic "] || ![[authorizationHeader substringFromIndex:6] isEqualToString:_server.authenticationBasicAccount]) {
|
||||
if ([authorizationHeader hasPrefix:@"Basic "]) {
|
||||
NSString* basicAccount = [authorizationHeader substringFromIndex:6];
|
||||
if ([basicAccount isEqualToString:_server.authenticationBasicAccount]) {
|
||||
authenticated = YES;
|
||||
}
|
||||
}
|
||||
if (!authenticated) {
|
||||
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
|
||||
[response setValue:[NSString stringWithFormat:@"Basic realm=\"%@\"", _server.authenticationRealm] forAdditionalHeader:@"WWW-Authenticate"];
|
||||
}
|
||||
} else if (_server.authenticationDigestAccount) {
|
||||
BOOL authenticated = NO;
|
||||
BOOL isStaled = NO;
|
||||
NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
|
||||
if ([authorizationHeader hasPrefix:@"Digest "]) {
|
||||
NSString* nonce = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce");
|
||||
if ([nonce isEqualToString:_digestAuthenticationNonce]) { // TODO: Also check "realm" and "username" provided by client
|
||||
NSString* uri = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri");
|
||||
NSString* actualResponse = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response");
|
||||
NSString* ha1 = _server.authenticationDigestAccount;
|
||||
NSString* ha2 = GCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri); // We cannot use "request.path" as the query string is required
|
||||
NSString* expectedResponse = GCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2);
|
||||
if ([actualResponse isEqualToString:expectedResponse]) {
|
||||
authenticated = YES;
|
||||
}
|
||||
} else if (nonce.length) {
|
||||
isStaled = YES;
|
||||
}
|
||||
}
|
||||
if (!authenticated) {
|
||||
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
|
||||
[response setValue:[NSString stringWithFormat:@"Digest realm=\"%@\", nonce=\"%@\"%@", _server.authenticationRealm, _digestAuthenticationNonce, isStaled ? @", stale=TRUE" : @""] forAdditionalHeader:@"WWW-Authenticate"]; // TODO: Support Quality of Protection ("qop")
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
#else
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
#endif
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
#import <ifaddrs.h>
|
||||
#import <net/if.h>
|
||||
@ -264,3 +265,22 @@ NSString* GCDWebServerGetPrimaryIPv4Address() {
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
const char* string = [ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]) UTF8String];
|
||||
va_end(arguments);
|
||||
unsigned char md5[CC_MD5_DIGEST_LENGTH];
|
||||
CC_MD5(string, (CC_LONG)strlen(string), md5);
|
||||
char buffer[2 * CC_MD5_DIGEST_LENGTH + 1];
|
||||
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
|
||||
unsigned char byte = md5[i];
|
||||
unsigned char byteHi = (byte & 0xF0) >> 4;
|
||||
buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi;
|
||||
unsigned char byteLo = byte & 0x0F;
|
||||
buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
|
||||
}
|
||||
buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
|
||||
return [NSString stringWithUTF8String:buffer];
|
||||
}
|
||||
|
||||
@ -121,6 +121,7 @@ extern NSString* GCDWebServerExtractHeaderValueParameter(NSString* header, NSStr
|
||||
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
|
||||
extern BOOL GCDWebServerIsTextContentType(NSString* type);
|
||||
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
|
||||
extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2);
|
||||
|
||||
@interface GCDWebServerConnection ()
|
||||
- (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
|
||||
@ -131,6 +132,7 @@ extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
|
||||
@property(nonatomic, readonly) NSString* serverName;
|
||||
@property(nonatomic, readonly) NSString* authenticationRealm;
|
||||
@property(nonatomic, readonly) NSString* authenticationBasicAccount;
|
||||
@property(nonatomic, readonly) NSString* authenticationDigestAccount;
|
||||
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
|
||||
- (void)willStartConnection:(GCDWebServerConnection*)connection;
|
||||
- (void)didEndConnection:(GCDWebServerConnection*)connection;
|
||||
|
||||
Reference in New Issue
Block a user