4
votes

I am trying to upload photo to my web-server via AFHTTPRequestOperation. Here is my AFHTTPSessionManager. All http-requests via this manager works perfect.

- (AFHTTPSessionManager *)manager
{
    if (_manager == nil) {
        _manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"https://my.domain.com/api/v2"]];
        [_manager.requestSerializer setAuthorizationHeaderFieldWithUsername:@"username" password:@"password"];

        AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
        policy.allowInvalidCertificates = YES;
        _manager.securityPolicy = policy;
    }
    return _manager;
}

But when I am trying to upload photo with progress handling:

- (void)photoSend:(UIImage *)photo
          toUsers:(NSArray *)users
       completion:(CompletionHandler)completion
         progress:(ProgressionHandler)progress
{
    NSDictionary *params = @{
                             @"token":self.token,
                             @"to_usernames":[users componentsJoinedByString:@","],
                             };

    NSString *urlString = [[NSURL URLWithString:@"photo/send" relativeToURL:self.manager.baseURL] absoluteString];
    NSMutableURLRequest *request = [self.manager.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:urlString parameters:params constructingBodyWithBlock:^(id <AFMultipartFormData> formData)
                                    {
                                        [formData appendPartWithFileData:UIImageJPEGRepresentation(photo,0.8)
                                                                    name:@"photo"
                                                                fileName:@"image.jpg"
                                                                mimeType:@"image/jpg"];
                                    } error:nil];

    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    operation.responseSerializer = [AFJSONResponseSerializer serializer];
    // Following 2 lines does not make sense. Http headers contains auth data without this code
    //NSURLCredential *credential = [NSURLCredential credentialWithUser:@"login" password:@"password" persistence:NSURLCredentialPersistenceNone];
    //[operation setCredential:credential];

    [operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
        progress(totalBytesWritten*1.0/totalBytesExpectedToWrite);
    }];

    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        completion(operation.response, responseObject, operation.error);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        completion(operation.response, nil, error);
    }];

    [self.manager.operationQueue addOperation:operation];
}

I get following error:

Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed. (NSURLErrorDomain error -1012.)" UserInfo=0x994ff20 {NSErrorFailingURLKey=https://my.domain.com/v2/photo/send, NSErrorFailingURLStringKey=https://my.domain.com/v2/photo/send}

2

2 Answers

8
votes

Here is the solution:

AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
policy.allowInvalidCertificates = YES;
operation.securityPolicy = policy;

So HTTP basic auth data is provided to operation by manager, but SSL policy - not.

1
votes

In AFNetworking you can use the AFSSLPinningMode to make changes in the security policies so if your server does not have SSL installed use the below code

let operation = AFHTTPRequestOperation(request: YourMutableRequestObject)

        let policy = AFSecurityPolicy(pinningMode: AFSSLPinningMode.None)
        policy.validatesDomainName = false
        policy.allowInvalidCertificates = true

       operation.securityPolicy = policy

And if your server is using a certificate you can use the below code

let operation = AFHTTPRequestOperation(request: YourMutableRequestObject)
let policy = AFSecurityPolicy(pinningMode: AFSSLPinningMode.Certificate)
        policy.validatesDomainName = false
        policy.allowInvalidCertificates = true
        operation.securityPolicy = policy

Before using the above code make sure that you have added the cer certificate in your application bundle, coz if not you will not be able to make the calls, you can get the cer file from your web developer or any authority who is providing you with the cer.

In case if you receive a crt file from the web developer you need to convert it to cer using the OpenSSL using the below code

openssl x509 -in apache.crt -out yourCertFileName.cer -outform der

That should be enough to make the calls

Update

As per apple's new ATS pattern in iOS 9 you have to provide some permissions inside your info.plist file.

if you want to make a insecure call give below is the XML that you need to integrate inside the info.plist (Please note you are not going to get any auto fillers here, you have to view your plist as a source and copy + paste the below)

<key>NSAppTransportSecurity</key>
<dict>
 <key>NSExceptionDomains</key>
 <dict>
<key>yourserver.com</key>
<dict>
  <!--Include to allow subdomains-->
  <key>NSIncludesSubdomains</key>
  <true/>
  <!--Include to allow HTTP requests-->
  <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
  <true/>
  <!--Include to specify minimum TLS version-->
  <key>NSTemporaryExceptionMinimumTLSVersion</key>
  <string>TLSv1.1</string>
 </dict>
 </dict>
 </dict>

Apple also provides a way to disable ATS given below is the XML for the same but its not recommended

<key>NSAppTransportSecurity</key>
<dict>
<!--Include to allow all connections -->
<key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>

In my case since my server was using a self signed certificate i just changed the AFSSLPinningMode to Certificate and it worked as expected.

Hope this helps someone