0

I'm writing iOS code to authenticate with an HTTPS proxy server, and need to use the Core Foundation APIs in order to construct the "Proxy-Authorization" header. I'm blocked trying to build a valid CFHTTPAuthenticationRef object, though.

Upon receiving the '407 Proxy authentication required' response with a 'Proxy-Authenticate: Basic realm="example.com"' header, I'm calling CFHTTPAuthenticationCreateFromResponse. This returns a CFHTTPAuthenticationRef object that is invalid; the error is -1000, or kCFStreamErrorHTTPAuthenticationTypeUnsupported.

From looking at the very outdated source code available at http://www.opensource.apple.com/source/CFNetwork/CFNetwork-129.20/HTTP/CFHTTPAuthentication.c, this should all be working fine. I've verified in the debugger that some of the internal structures, like _schemes, are in fact being parsed properly, so I know my header value is correct. Clearly the code inside CFHTTPAuthenticationCreateFromResponse has changed, but I'm at a loss as to what I need to do to make this work.

From stepping through the assembly code, it seems like it may be related to a lack of URL. There is no public API to add a URL to a response, and the fact it's not needed for Basic and Digest auth is odd.

I've observed this on OSX Mavericks, iOS 7 simulator, and iOS 8 simulator. I've boiled this down to a simple repro function, pasted below. I've also tried using a test proxy server and CFReadStream, creating the response from CFReadStreamCopyProperty(requestStream, kCFStreamPropertyHTTPResponseHeader), to no avail (which, presumably, would associate a URL with the response object).

void testCFAuthentication()
{
    Boolean result;
    CFStringRef str;
    const char *rawResponse = "HTTP/1.1 407 Proxy authentication required\r\nProxy-Authenticate: Basic realm=\"example.com\"\r\n\r\n";

    CFHTTPMessageRef responseMessage = NULL;
    responseMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false);

    // Optional hack, doesn't help
    /*
    CFURLRef url = NULL;
    url = CFURLCreateWithString(NULL, CFSTR("https://example.com/"), NULL);
    _CFHTTPMessageSetResponseURL(responseMessage, url);
    NSLog(@"_CFHTTPMessageSetResponseURL called\n");
    */

    result = CFHTTPMessageAppendBytes(responseMessage, (uint8_t *)rawResponse, strlen(rawResponse));
    NSLog(@"CFHTTPMessageAppendBytes result: %d\n", (int)result);
    NSLog(@"CFHTTPMessageIsHeaderComplete: %d\n", (int)CFHTTPMessageIsHeaderComplete(responseMessage));
    NSLog(@"CFHTTPMessageGetResponseStatusCode: %d\n", (int)CFHTTPMessageGetResponseStatusCode(responseMessage));
    NSLog(@"CFHTTPMessageCopyResponseStatusLine: %s\n", CFStringGetCStringPtr(CFHTTPMessageCopyResponseStatusLine(responseMessage), kCFStringEncodingUTF8));
    NSLog(@"Proxy-Authenticate header value: %s\n", CFStringGetCStringPtr(CFHTTPMessageCopyHeaderFieldValue(responseMessage, CFSTR("Proxy-Authenticate")), kCFStringEncodingUTF8));

    CFHTTPAuthenticationRef auth = NULL;
    auth = CFHTTPAuthenticationCreateFromResponse(NULL, responseMessage);
    NSLog(@"CFHTTPAuthenticationCreateFromResponse:\n"); CFShow(auth);
    if (auth) {
        CFStreamError error = { 0 };
        result = CFHTTPAuthenticationIsValid(auth, &error);
        if (!result) {
            NSLog(@"CFHTTPAuthenticationIsValid: false, error %d (0x%x), domain %ld (0x%lx)\n", error.error, error.error, error.domain, error.domain);
        } else {
            NSLog(@"CFHTTPAuthenticationCopyMethod=%s\n", CFStringGetCStringPtr(CFHTTPAuthenticationCopyMethod(auth), kCFStringEncodingUTF8));
            NSLog(@"authRealm=%s\n", CFStringGetCStringPtr(CFHTTPAuthenticationCopyRealm(auth), kCFStringEncodingUTF8));
        }
    };
}

Output:

2015-02-13 11:01:05.654 CFHTTPMessageAppendBytes result: 1
2015-02-13 11:01:05.655 CFHTTPMessageIsHeaderComplete: 1
2015-02-13 11:01:05.656 CFHTTPMessageGetResponseStatusCode: 407
2015-02-13 11:01:05.657 CFHTTPMessageCopyResponseStatusLine: HTTP/1.1 407 Proxy authentication required
2015-02-13 11:01:05.657 Proxy-Authenticate header value: Basic realm="example.com"
2015-02-13 11:01:05.658 CFHTTPAuthenticationCreateFromResponse:
<CFHTTPAuthentication 0x7fe0d36017f0>{state = Failed; scheme = <undecided>, forProxy = true}
2015-02-13 11:01:05.658 CFHTTPAuthenticationIsValid: false, error -1000 (0xfffffc18), domain 4 (0x4)

1 Answers1

0

You encounter same problem as I, but in a bit different flavor. Problem is that CFHTTPAuthenticationCreateFromResponse works only with response fetched from CFHTTPStream. I found a solution see my answer.

Expose private API (this will cause that you will fail Apple review).

// exposing private API for workaround
extern void _CFHTTPMessageSetResponseURL(CFHTTPMessageRef, CFURLRef);

Add add URL to response using this private API:

_CFHTTPMessageSetResponseURL(responseMessage, 
                             (__bridge CFURLRef)[NSURL URLWithString: "https://example.com/"]);
 auth = CFHTTPAuthenticationCreateFromResponse(NULL, responseMessage);
Community
  • 1
  • 1
Marek R
  • 32,568
  • 6
  • 55
  • 140