microsoft / WinObjC

Objective-C for Windows
MIT License
6.24k stars 811 forks source link

Support NSURLAuthenticationChallenge with methods NSURLAuthenticationMethodClientCertificate and NSURLAuthenticationMethodServerTrust with NSURLSessionDelegate didReceiveChallenge: #585

Open ehren opened 8 years ago

ehren commented 8 years ago

It would be nice if the standard iOS APIs could be used for supporting client certificates and for finer grained control of server certificate chain validation.

Here's an incomplete NSURLSessionDelegate example showing providing of a client certificate:

- (SecIdentityRef)findClientCertificate {
    if (clientCertificate) {
        CFRelease(clientCertificate);
        clientCertificate = NULL;
    }

    NSString *pkcs12Path = [[NSBundle mainBundle] pathForResource:@"blah" ofType:@"p12"];
    NSData *pkcs12Data = [[NSData alloc] initWithContentsOfFile:pkcs12Path];

    CFDataRef inPKCS12Data = (__bridge CFDataRef)pkcs12Data;
    CFStringRef password = CFSTR("password");
    const void *keys[] = { kSecImportExportPassphrase };
    const void *values[] = { password };
    CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
    CFArrayRef items = NULL;

    OSStatus err = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);

    CFRelease(optionsDictionary);
    CFRelease(password);

    if (err == errSecSuccess && CFArrayGetCount(items) > 0) {
        CFDictionaryRef pkcsDict = CFArrayGetValueAtIndex(items, 0);

        SecTrustRef trust = (SecTrustRef)CFDictionaryGetValue(pkcsDict, kSecImportItemTrust);

        if (trust != NULL) {
            clientCertificate = (SecIdentityRef)CFDictionaryGetValue(pkcsDict, kSecImportItemIdentity);
            CFRetain(clientCertificate);
        }
    }

    if (items) {
        CFRelease(items);
    }

    return clientCertificate;
}

- (NSURLCredential *)provideClientCertificate {
    SecIdentityRef identity = [self findClientCertificate];

    if (!identity) {
        return nil;
    }

    SecCertificateRef certificate = NULL;
    SecIdentityCopyCertificate (identity, &certificate);
    const void *certs[] = {certificate};
    CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);
    NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray *)certArray persistence:NSURLCredentialPersistencePermanent];
    CFRelease(certArray);

    return credential;
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
        NSURLCredential *credential = [self provideClientCertificate];
        completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
    }
}

However, looking a little at

https://msdn.microsoft.com/en-us/library/windows/apps/windows.web.http.httpclient.aspx and https://msdn.microsoft.com/en-us/library/windows.web.http.filters.httpbaseprotocolfilter.aspx

plus their use in Foundation/NSURLProtocol_WinHTTP.mm

it appears there's some mismatch between the iOS and Windows APIs that would make supporting these iOS APIs difficult.


My usecases are to

1) provide a client cert

2) ignore invalid hostname chain validation errors of the server cert but otherwise perform a complete chain validation.

For example, an invalid hostname in the server cert can be ignored in iOS at challenge time by implementing the didReceiveChallenge: delegate method

On windows, one can ignore an InvalidName ChainValidationResult:

https://msdn.microsoft.com/en-us/library/windows/apps/windows.security.cryptography.certificates.chainvalidationresult.aspx

by specifying the HttpBaseProtocolFilter.IgnorableServerCertificateErrors at HttpClient creation time - prior to the request: https://msdn.microsoft.com/en-us/library/windows/apps/windows.web.http.filters.httpbaseprotocolfilter.ignorableservercertificateerrors

(While one can set a handler for: https://msdn.microsoft.com/en-us/library/windows/apps/windows.web.http.filters.httpbaseprotocolfilter.servercustomvalidationrequested this would not be called if OS validation fails - so the only approach is to use the IgnorableServerCertificateErrors property to allow an InvalidName but otherwise performing a complete chain validation)


So it's looking like the best/fastest approach for handling these usecases in my app may be to just use alternative networking code on windows making use of the projections in WindowsWebHttp.h and WindowsWebHttpFilters.h

However, I wonder if providing a windows specific extension to NSURLSession (and NSURLConnection if desired) in order to get access to the HttpBaseProtocolFilter created in NSURLProtocol_WinHTTP.mm would be a viable approach. If someone think so let me know (otherwise I'll likely go forward with just using the Windows APIs directly via the projections).

rajsesh commented 8 years ago

@ehren thanks for the detailed write up. If this is something you are blocked on then using the projected APIs (or WRL just like in foundation) may be your best bets. We do need to think through what is the right way to expose the windows extensions from NSURL*, will look into this discuss with the team on where we can place this in our road map.