فهرست منبع

#38 Added support for digest authentication

Pierre-Olivier Latour 11 سال پیش
والد
کامیت
1e17d5c455

+ 1 - 0
GCDWebServer/Core/GCDWebServer.h

@@ -58,6 +58,7 @@ extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground;  //
 #endif
 
 extern NSString* const GCDWebServerAuthenticationMethod_Basic;
+extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
 
 @class GCDWebServer;
 

+ 11 - 1
GCDWebServer/Core/GCDWebServer.m

@@ -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
GCDWebServer/Core/GCDWebServerConnection.m

@@ -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 - 0
GCDWebServer/Core/GCDWebServerFunctions.m

@@ -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 - 0
GCDWebServer/Core/GCDWebServerPrivate.h

@@ -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
Mac/main.m

@@ -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
README.md

@@ -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