Install TLS certificates on web server iOS app

603 Views Asked by At

I'm trying to integrate Swish payment in one of the apps I develop. In order to be able to connect to the swish api I have to "set up TLS certificates from Swish Certificate Management and install it on "my" web server" according to the documentation.Here is the full technical documentation https://developer.getswish.se/merchants-api-manual/4-merchant-setup-process/.

The problem I don't understand is that I don't use a web server and I can't install those certificates there.

My app just offers some services for the client and after pressing the pay button should open the Swish app to finish the transaction in short.

What I tried is to make a post request to get the request token with which I can open the swish app loaded with the payment details. I'm sure the problems are the certificates but couldn't find a good source explaining how to import(integrate) them.

let strURL = "https://mss.cpc.getswish.net/swish-cpcapi/api/v1/paymentrequests/"

guard let postURL = URL(string: strURL ) else { print("Can't create url") return }

    var request = URLRequest(url: postURL)

    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue("application/json", forHTTPHeaderField: "Accept")

    let data: [String: Any] = [
        "callbackUrl": "https://example.com/api/swishcb/paymentrequests",
        "payeeAlias": "123xxxxxxx", // The Swish number of the payee. It needs to match with Merchant Swish number.
        "amount": "100",
        "currency": "SEK",
        "message": "Test request to get the token"
    ]

    do {
        let jsonParams = try JSONSerialization.data(withJSONObject: data, options: [])
        request.httpBody = jsonParams
    } catch {
        print("Error serializing the parameters of the post request")
        return
    }

    // response will contain a Token, unique for each payment request

    let config = URLSessionConfiguration.default
    config.timeoutIntervalForResource = 120
    config.timeoutIntervalForRequest = 120
    let session = URLSession(configuration: config) 

    session.dataTask(with: request) { (data, response, error) in
        print("Data \(data)")
        print("Response \(response)")
        if error != nil {
            print("Error post request \(error?.localizedDescription)")
        }
    }.resume()

The error I got is: Error post request Optional("An SSL error has occurred and a secure connection to the server cannot be made.")

018-12-21 12:24:55.549759+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_context_alert_callback_handler(3718) [C6.1:2][0x7fce4a77bf00] Alert level: fatal, description: handshake failure 2018-12-21 12:24:55.550047+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_session_errorlog(224) [C6.1:2][0x7fce4a77bf00] [boringssl_session_handshake_incomplete] SSL_ERROR_SSL(1): operation failed within the library 2018-12-21 12:24:55.550332+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_session_handshake_error_print(205) [C6.1:2][0x7fce4a77bf00] 140523985879704:error:10000410:SSL routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE:/BuildRoot/Library/Caches/com.apple.xbs/Sources/boringssl_Sim/boringssl-109.220.4/ssl/tls_record.cc:586:SSL alert number 40 2018-12-21 12:24:55.550585+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_context_get_error_code(3539) [C6.1:2][0x7fce4a77bf00] SSL_AD_HANDSHAKE_FAILURE 2018-12-21 12:24:55.552299+0200 tolk-24-7[7230:111102] TIC TCP Conn Failed [6:0x600002dd6c40]: 3:-9824 Err(-9824) 2018-12-21 12:24:55.555924+0200 tolk-24-7[7230:111102] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824) 2018-12-21 12:24:55.556052+0200 tolk-24-7[7230:111102] Task <7888D080-D175-4DBF-8F66-4183F0D653E6>.<1> HTTP load failed (error code: -1200 [3:-9824]) 2018-12-21 12:24:55.556234+0200 tolk-24-7[7230:111613] Task <7888D080-D175-4DBF-8F66-4183F0D653E6>.<1> finished with error - code: -1200

1

There are 1 best solutions below

0
On

I feel your frustrations, I haven't worked with the Swish API per se, but it looks like URLSession is failing to perform the client certificate request. The handshake is failing on that step.

There is an option to add a URLSessionDelegate to URLSession in order to handle authentication challenges such as ServerTrust and ClientCertificate. They are discussing it here: Swift 3 UrlSession with client authentication by certificate

If you're able to create a p12/pfx with the client certificate and private key, you can use SecPKCS12Import to import it and use it for the URLCredential trust in the NSURLAuthenticationMethodClientCertificate received in URLSessionDelegate. Here's an implementation I wrote:

func urlSession(
        _ session: URLSession,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
    {

        let authenticationMethod = challenge.protectionSpace.authenticationMethod
        if authenticationMethod == NSURLAuthenticationMethodServerTrust {
            //Handle server trust if necessary
        } else if authenticationMethod == NSURLAuthenticationMethodClientCertificate {
            if let clientCredential = try? getClientUrlCredential() {
                completionHandler(.useCredential, clientCredential)
            } else {
                completionHandler(.cancelAuthenticationChallenge, nil)
            }
        }
    }

getClientUrlCredential function:

private func getClientUrlCredential() throws -> URLCredential {

        let p12Data = getP12Data() //Get the data from the bundle if it's bundled in the app
        let p12Key = getP12Key() //you need the key set for creating the p12/pfx

        let certOptions: NSDictionary = [
            kSecImportExportPassphrase as NSString : p12Key as NSString
        ]

        // import certificate to read its entries
        var items: CFArray?
        let status = SecPKCS12Import(p12Data, certOptions, &items)

        if status == errSecSuccess,
            let items = items,
            let dict = (items as Array).first as? [String: AnyObject],
            let certChain = dict[kSecImportItemCertChain as String] as? [SecTrust] {

            // Check if SecIdentityGetTypeID is present
            guard let cfIdentity = dict[kSecImportItemIdentity as String] as CFTypeRef?,
                CFGetTypeID(cfIdentity) == SecIdentityGetTypeID() else {
                    throw URLSessionPinningDelegateError.localClientCertificateError
            }

            let identity = dict[kSecImportItemIdentity as String] as! SecIdentity
            return URLCredential(
                identity: identity,
                certificates: certChain,
                persistence: .forSession
            )
        }

        //Failed to read local certificate, throw error
        throw URLSessionPinningDelegateError.localClientCertificateError
    }

With a valid client certificate you should be able to fulfill the client hello and set up the TLS towards the server since this seems to be where you are failing right now. The SSL alert number 40 you're getting by BoringSSL suggests so to me at least.

Hope this points you in the right direction at least, happy to support further if needed.