#38 Added support for digest authentication

This commit is contained in:
Pierre-Olivier Latour
2014-04-18 12:32:55 -03:00
parent ce1eb3c29a
commit 1e17d5c455
7 changed files with 86 additions and 7 deletions
+1
View File
@@ -58,6 +58,7 @@ extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; //
#endif
extern NSString* const GCDWebServerAuthenticationMethod_Basic;
extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
@class GCDWebServer;
+11 -1
View File
@@ -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:)]) {
+38 -1
View File
@@ -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;
}
+20
View File
@@ -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];
}
+2
View File
@@ -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;
+13 -4
View File
@@ -130,12 +130,13 @@ int main(int argc, const char* argv[]) {
BOOL recording = NO;
NSString* rootDirectory = NSHomeDirectory();
NSString* testDirectory = nil;
NSString* authenticationMethod = nil;
NSString* authenticationRealm = nil;
NSString* authenticationUser = nil;
NSString* authenticationPassword = nil;
if (argc == 1) {
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | webDAV | webUploader | streamingResponse] [-record] [-root directory] [-tests directory] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password]\n\n", basename((char*)argv[0]));
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | webDAV | webUploader | streamingResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password]\n\n", basename((char*)argv[0]));
} else {
for (int i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
@@ -164,6 +165,9 @@ int main(int argc, const char* argv[]) {
} else if (!strcmp(argv[i], "-tests") && (i + 1 < argc)) {
++i;
testDirectory = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])] stringByStandardizingPath];
} else if (!strcmp(argv[i], "-authenticationMethod") && (i + 1 < argc)) {
++i;
authenticationMethod = [NSString stringWithUTF8String:argv[i]];
} else if (!strcmp(argv[i], "-authenticationRealm") && (i + 1 < argc)) {
++i;
authenticationRealm = [NSString stringWithUTF8String:argv[i]];
@@ -296,11 +300,16 @@ int main(int argc, const char* argv[]) {
NSMutableDictionary* options = [NSMutableDictionary dictionary];
[options setObject:@8080 forKey:GCDWebServerOption_Port];
[options setObject:@"" forKey:GCDWebServerOption_BonjourName];
if (authenticationUser && authenticationPassword) {
if ([authenticationMethod isEqualToString:@"Basic"]) {
[options setObject:GCDWebServerAuthenticationMethod_Basic forKey:GCDWebServerOption_AuthenticationMethod];
[options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm];
[options setObject:authenticationUser forKey:GCDWebServerOption_AuthenticationUser];
[options setObject:authenticationPassword forKey:GCDWebServerOption_AuthenticationPassword];
[options setValue:authenticationUser forKey:GCDWebServerOption_AuthenticationUser];
[options setValue:authenticationPassword forKey:GCDWebServerOption_AuthenticationPassword];
} else if ([authenticationMethod isEqualToString:@"Digest"]) {
[options setObject:GCDWebServerAuthenticationMethod_DigestAccess forKey:GCDWebServerOption_AuthenticationMethod];
[options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm];
[options setValue:authenticationUser forKey:GCDWebServerOption_AuthenticationUser];
[options setValue:authenticationPassword forKey:GCDWebServerOption_AuthenticationPassword];
}
if ([webServer runWithOptions:options]) {
result = 0;
+1 -1
View File
@@ -18,7 +18,7 @@ Extra built-in features:
* [HTTP compression](https://en.wikipedia.org/wiki/HTTP_compression) with gzip for request and response HTTP bodies
* [HTTP range](https://en.wikipedia.org/wiki/Byte_serving) support for requests of local files
* Automatically handle transitions between foreground, background and suspended modes in iOS apps
* [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) for simple password protection
* [Basic](https://en.wikipedia.org/wiki/Basic_access_authentication) and [Digest Access](https://en.wikipedia.org/wiki/Digest_access_authentication) Authentications for password protection
Included extensions:
* [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser