Https call stopped working correctly after updated from iOS version 12.4 to 13.1

Hellow, I was using one iPhone with version 12.4 to run application for development. Suddely when I updated from version 12.4 to 13.1, one of my http rest api just stopped working and it returns me error:
"

019-09-25 16:56:56.578750+0200 SmaforetagarnasAPP[487:44045] task challenge NSURLAuthenticationMethodServerTrust

FAILURE: Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=https://appapi2.bankid.com/rp/v5/auth, NSErrorFailingURLKey=https://appapi2.bankid.com/rp/v5/auth, _NSURLErrorRelatedURLSessionTaskErrorKey=(

"LocalDataTask <1FDB0948-469A-4333-BA23-9CAB3452F59B>.<1>"

), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <1FDB0948-469A-4333-BA23-9CAB3452F59B>.<1>, NSLocalizedDescription=cancelled}

2019-09-25 16:56:56.590973+0200 SmaforetagarnasAPP[487:44045] Task <1FDB0948-469A-4333-BA23-9CAB3452F59B>.<1> HTTP load failed, 0/0 bytes (error code: -999 [1:89])

2019-09-25 16:56:56.592230+0200 SmaforetagarnasAPP[487:44045] Connection 3: unable to determine interface type without an established connection

2019-09-25 16:56:56.592313+0200 SmaforetagarnasAPP[487:44045] Connection 3: unable to determine fallback status without a connection

"

Im using Alamofire SessionManager to make the http call

The http call requires certificate and here is my code:


Calling function:

func authenticate(completion: @escaping ModelCompletion<Bool>) {
        
        let configuration = URLSessionConfiguration.default
        //        configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
        
        let sessionManager = Alamofire.SessionManager(
            configuration: configuration)
        
        let endUserIp: String = self.getIPAddress() ?? "192.168.8.103"
        let parameters: Parameters = ["endUserIp": endUserIp]
        
        //set condition
        //        certificatePolicies to “1.2.752.78.1.5” to restrict the order to Mobile BankID only.
        
        var header = HTTPHeaders()
        header["Content-Type"] = "application/json"
        
        sessionManager.request("https://appapi2.bankid.com/rp/v5/auth", method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: header)
            .validate()
            .responseJSON { [weak self] data in
                
                print(data)
                
                guard let strongSelf = self else { return }
                
                switch data.result {
                case .success(let value):
                    let bankIDJson = JSON(value)
                    strongSelf.autoStartToken = bankIDJson[RequestJsonTag.autoStartToken.rawValue].string
                    strongSelf.orderRef = bankIDJson[RequestJsonTag.orderRef.rawValue].string
                    SessionManager.sharedInstance.orderRef = strongSelf.orderRef!
                    completion(ModelResult.success(true))
                    
                case .failure(let error):
                    
                    completion(ModelResult.failure(error: error.localizedDescription))
                }
            }.session.finishTasksAndInvalidate()
        
        // Seeion delegate
        let delegate = sessionManager.delegate
        self.didReceiveSessionManagerChallenge(delegate: delegate)
    }

Once I receive Challenge this function will get called:

private func didReceiveSessionManagerChallenge(delegate: SessionDelegate) {
        
        // Did Receive Challenge
        delegate.sessionDidReceiveChallenge = { urlSession, challenge in
            
            let protectionSpace = challenge.protectionSpace
            NSLog("task challenge %@", protectionSpace.authenticationMethod)
            
            switch (protectionSpace.authenticationMethod, protectionSpace.host) {
            case (NSURLAuthenticationMethodServerTrust, "appapi2.bankid.com"):
                if self.shouldTrustBankID(protectionSpace: protectionSpace) {
                    let credential = URLCredential(trust: protectionSpace.serverTrust!)
                    return (.useCredential, credential)
                } else {
                    return (.cancelAuthenticationChallenge, nil)
                }
            case (NSURLAuthenticationMethodClientCertificate, "appapi2.bankid.com"):
                let identity = Bundle.main.identityForBankIDPayment(named: "smaforetagarnasrpca", password: "Helsingborg#1977")
                let credential = URLCredential(identity: identity, certificates: nil, persistence: .forSession)
                return (.useCredential, credential)
                
            default:
                return (.performDefaultHandling, nil)
            }
        }
    }


Here is extension once it requires the certificate:

extension Bundle {
    
    func certificateForBankIDPayment(named: String) -> SecCertificate {
        
        let cerURL = self.url(forResource: named, withExtension: "der")!
        let cerData = try! Data(contentsOf: cerURL)
        return SecCertificateCreateWithData(nil, cerData as NSData)!
    }
    
    func identityForBankIDPayment(named: String, password: String) -> SecIdentity {
        
        let p12URL = Bundle.main.url(forResource: named, withExtension: "p12")!
        let p12Data = try! Data(contentsOf: p12URL)
        var importResult: CFArray? = nil
        let err = SecPKCS12Import(
            p12Data as NSData,
            [kSecImportExportPassphrase: password] as NSDictionary,
            &importResult)
        
        assert(err == errSecSuccess)
        let identityDicts = importResult! as! [[String:Any]]
        return identityDicts.first![kSecImportItemIdentity as String]! as! SecIdentity
    }
}

My Info.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleDevelopmentRegion</key>
  <string>$(DEVELOPMENT_LANGUAGE)</string>
  <key>CFBundleDisplayName</key>
  <string>Småföretagarnas</string>
  <key>CFBundleExecutable</key>
  <string>$(EXECUTABLE_NAME)</string>
  <key>CFBundleIdentifier</key>
  <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundleName</key>
  <string>$(PRODUCT_NAME)</string>
  <key>CFBundlePackageType</key>
  <string>APPL</string>
  <key>CFBundleShortVersionString</key>
  <string>1.0.0</string>
  <key>CFBundleURLTypes</key>
  <array>
  <dict>
  <key>CFBundleTypeRole</key>
  <string>Editor</string>
  <key>CFBundleURLName</key>
  <string>com.curamet.SmaforetagarnasAPP</string>
  <key>CFBundleURLSchemes</key>
  <array>
  <string>sfrapp</string>
  </array>
  </dict>
  </array>
  <key>CFBundleVersion</key>
  <string>1</string>
  <key>LSRequiresIPhoneOS</key>
  <true/>
  <key>NSAppTransportSecurity</key>
  <dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
  <key>NSExceptionDomains</key>
  <dict>
  <key>appapi2.bankid.com</key>
  <dict>
  <key>NSExceptionAllowsInsecureHTTPLoads</key>
  <true/>
  <key>NSIncludesSubdomains</key>
  <true/>
  </dict>
  </dict>
  </dict>
  <key>UILaunchStoryboardName</key>
  <string>LaunchScreen</string>
  <key>UIMainStoryboardFile</key>
  <string>MainTapBarView</string>
  <key>UIRequiredDeviceCapabilities</key>
  <array>
  <string>armv7</string>
  </array>
  <key>UISupportedInterfaceOrientations</key>
  <array>
  <string>UIInterfaceOrientationPortrait</string>
  </array>
  <key>UISupportedInterfaceOrientations~ipad</key>
  <array>
  <string>UIInterfaceOrientationPortrait</string>
  <string>UIInterfaceOrientationPortraitUpsideDown</string>
  <string>UIInterfaceOrientationLandscapeLeft</string>
  <string>UIInterfaceOrientationLandscapeRight</string>
  </array>
</dict>
</plist>



P.s. I updated my Xcode till support the latest iOS 13.1 version as well.

Replies

Are there any Alamofire release notes that could point at compatibility issues ?


Have you the latest framework ?

Sadly I do not receive any release note from Alamofire and I do not have the latest framework.
Actually, I tested with another project which is also using the Alamofire (4.8.2), all API requests are working fine with software version 13.1.
Is this issue not related to the new iPhone version release?
I checked the changelog and I do not understand how the change gonna affect my application.
https://developer.apple.com/documentation/ios_ipados_release_notes/ios_13_release_notes

I don't know how Alamofire API works, but read this :

Starting with iOS 13 beta 4, the copy attribute of the

httpBodyStream
property of
NSMutableURLRequest
is enforced. If the body data is mutated after the property setter has been called, data sent in the HTTP request won't include that mutation. Invoking the property getter no longer returns a
NSMutableData
reference, even when the setter was invoked with data of that type. As of iOS 13 beta 5, apps built using the iOS 12 SDK or previous SDKs use the legacy behavior. (53427882)

I tested to rewrite code from Alamofire to URLSession Instead and I got almost the same issue:

Fatal error: cancelled: file /Users/menjiemao/Desktop/All The projects/IOS Projects/BankIDFinalTestingIOS/BankIDFinalTestingIOS/ViewController.swift, line 26

2019-09-26 14:02:22.240427+0200 BankIDFinalTestingIOS[935:222495] Fatal error: cancelled: file /Users/menjiemao/Desktop/All The projects/IOS Projects/BankIDFinalTestingIOS/BankIDFinalTestingIOS/ViewController.swift, line 26

Is there any chance you can show me what changes I need to make with my Current URLSession code below:

  func submitPost(post: TokenToServer, completion:((Error?) -> Void)?) {
        var request = URLRequest(url: URL(string: "https://appapi2.bankid.com/rp/v5/auth")!)
        request.cachePolicy = .reloadIgnoringLocalCacheData
        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        
        // Now let's encode out Post struct into JSON data...
        let encoder = JSONEncoder()
        do {
            let jsonData = try encoder.encode(post)
            // ... and set our request's HTTP body
            request.httpBody = jsonData
        } catch {
            completion?(error)
        }
        // Create and run a URLSession data task with our JSON encoded POST request
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.current)
        let task = session.dataTask(with: request) { (responseData, response, responseError) in
            guard responseError == nil else {
                completion?(responseError!)
                return
            }
            // APIs usually respond with the data you just sent in your POST request
            if let data = responseData, let utf8Representation = String(data: data, encoding: .utf8) {
                print("response: ", utf8Representation)
            } else {
                print("no readable data received in response")
            }
        }
        task.resume()
    }

Any Update?

Any Update?

DevForums is not an official support channel. The folks here, and that includes me, do what they can to help out in the time they have available. If you need official support, I encourage to open a DTS tech support incident.

As to what’s going on with your specific setup, it’s hard to say without actually trying it for myself. I can’t run the code you post on 26 Sep because it’s missing key information about how the body of your request is structured. Can you distill that down into a standalone code snippet that reproduces the problem?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

After my research, I found out is becuase of the requirements for trusted certificates in iOS 13. Thanks for the advice Eskimo, I will send one DTS tech support ticket instead.

Just in case someone else is researching a similar topic, did you find that this error was due to one of the minimum requirements not being met for trusted certificates in iOS 13? For example:


All TLS server certificates must comply with these new security requirements in iOS 13 and macOS 10.15:

  • TLS server certificates and issuing CAs using RSA keys must use key sizes greater than or equal to 2048 bits. Certificates using RSA key sizes smaller than 2048 bits are no longer trusted for TLS.
  • TLS server certificates and issuing CAs must use a hash algorithm from the SHA-2 family in the signature algorithm. SHA-1 signed certificates are no longer trusted for TLS.
  • TLS server certificates must present the DNS name of the server in the Subject Alternative Name extension of the certificate. DNS names in the CommonName of a certificate are no longer trusted.


https://support.apple.com/en-us/HT210176

I Found Daniel Nashed's blog and he actually explained all of these TLS server certificates requirements in iOS 13.


http://blog.nashcom.de/nashcomblog.nsf/dx/more-strict-server-certificate-handling-in-ios-13-macos-10.15.htm?opendocument&comments
For you who do not understand these requirements for the TLS server certificate, I did some researches and hope it will help you to understand.

TLS server certificates and issuing CAs using RSA keys must use key sizes greater than or equal to 2048 bits. Certificates using RSA key sizes smaller than 2048 bits are no longer trusted for TLS.

You can check the RSA key size by just simply open it with KeyChains Access. Almost all server certificates are using more than 2048 bits RSA keys, so you do not need to worry about that. In case is your server certificate is lower than 2048, you have to contact your server certificate provider to change the key size to 2048 or more.

TLS server certificates and issuing CAs must use a hash algorithm from the SHA-2 family in the signature algorithm. SHA-1 signed certificates are no longer trusted for TLS.

You can also check the information in KeyChains Access.
Here is some reference about Sha-2 family:
https://en.wikipedia.org/wiki/SHA-2
The link is saying if your server certificate is using "SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256" as the signature algorithm, your server certificate is passed for this requirement.

TLS server certificates must present the DNS name of the server in the Subject Alternative Name extension of the certificate. DNS names in the CommonName of a certificate are no longer trusted.


DNS stands for "Domain Name System" and you can check your server DNS in the KeyChain Access, The Common Name (CN) is actually the server certificate's DNS.
If I understand correctly, the DNS has to be in one of the extensions of the certificate and you can find these extensions if you scroll down your certificate with KeyChain Access. If you cannot find the DNS of the server certificate, the server certificate will not be trusted.


I have no idea how to add the extension to the server certificate, I guess you have to contact the certificate provider to tell them to add it into the server certificate.


Let me know if there is a way to add it manually!

Additionally, all TLS server certificates issued after July 1, 2019 (as indicated in the NotBefore field of the certificate) must follow these guidelines:


TLS server certificates must contain an ExtendedKeyUsage (EKU) extension containing the id-kp-serverAuth OID.


After 2019 July 1, Your server certificate needs to add another extension for the id-kp-serverAuth OID(have no idea what this is), but the most important thins is if you do not see there is one extension with name "ExtendedKeyUsage", you are out of the game.


TLS server certificates must have a validity period of 825 days or fewer (as expressed in the NotBefore and NotAfter fields of the certificate).


Your server certificate can max have 825 days(Around 2 years 2 months) valid period and you can get the information by open the certificate with KeyChain Access.


Here is some more information about why it needs to reduce to 2 years
https://www.globalsign.com/en/blog/ssl-certificate-validity-capped-at-maximum-two-years/




If there is any information which is incorrect, let me know and I will update it.

If I understand correctly, the DNS has to be in one of the extensions of the certificate and you can find these extensions if you scroll down your certificate with Keychain Access.

Correct. Specifically, Keychain Access labels this as Subject Alternative Name extension (OID 2.5.29.17).

I have no idea how to add the extension to the server certificate, I guess you have to contact the certificate provider to tell them to add it into the server certificate.

If your certificate was issued by a standard certificate authority, they will almost certainly have put the server’s DNS name in the Subject Alternative Name extension. While Apple’s insistence on it is new, that extension has been around for decades. Indeed, the use of the Common Name field has been deprecated for decades. RFC 2818, from May 2000, has this to say on the subject:

Although the use of the Common Name is existing practice, it is deprecated and Certification Authorities are encouraged to use the

dNSName
[subelement of the Subject Alternative Name extension.] instead.

With regards this:

TLS server certificates must have a validity period of 825 days or fewer (as expressed in the NotBefore and NotAfter fields of the certificate).

I’m now grumpy because I just had to re-issue my test certificates and I forgot about this new requirements and set the validity period for way more than that. I guess I’ll have to re-re-issue them )-:

Finally, I wanted to post a direct link to the Apple support article, Requirements for trusted certificates in iOS 13 and macOS 10.15, that documents all this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for the clarification Quinn and I'm pretty sure now my issue is related to the new TLS server certificate change from Apple. Since I cannot do anything about it from my side, I will try to make contact with the server certificate provider and see if they can give me the valid server certificate for iOS 13.

Hi


I have the same issue calling a simple rest service on my production environment from my app. It is hosted on aws ec2 and protected by a certificate released by aws certificate manager. I think the certificate is ok and I don't understand where the problem is.


Have you got any idea to help me? Is there a site where we can test if the certificate is compliant with the new apple's policies?



Thanks