In this guide, we will show you how to secure an intranet API with your own Certificate Authority (CA) and make it trusted on an iOS app with no user intervention.
Warning: Operating a CA requires secure key management, infrastructure protection, and in case of certificate compromisation, you will not be able to revoke a certificate.
First of all, you need to create your own Certificate Authority.
To do this, follow the step-by-step guide from
In your iOS app, you'll need to add the self-signed CA as trusted when making HTTPS requests.
In order to do that, we need to handle URLAuthenticationChallenge
in URLSessionDelegate
.
import Foundation
class CustomURLSessionDelegate: NSObject, URLSessionDelegate {
let cert = """
-----BEGIN CERTIFICATE-----
Paste your CA .pem certificate here (ca/certs/ca.cert.pem)
-----END CERTIFICATE-----
"""
private func createSecCertificateFromPEMString(pemString: String) -> SecCertificate? {
let base64Encoded = pemString
.replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "")
.replacingOccurrences(of: "-----END CERTIFICATE-----", with: "")
.replacingOccurrences(of: "\n", with: "")
.replacingOccurrences(of: "\r", with: "")
.replacingOccurrences(of: " ", with: "")
guard let decodedData = Data(base64Encoded: base64Encoded) else {
print("Failed to decode base64 string")
return nil
}
// Create a SecCertificate from the Data
guard let certificate = SecCertificateCreateWithData(nil, decodedData as CFData) else {
print("Failed to create SecCertificate from data")
return nil
}
return certificate
}
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust else {
return completionHandler(.performDefaultHandling, nil)
}
let serverTrust = challenge.protectionSpace.serverTrust else {
return completionHandler(.performDefaultHandling, nil)
}
guard let customCARootCert = createSecCertificateFromPEMString(pemString: cert) else {
return completionHandler(.performDefaultHandling, nil)
}
SecTrustSetAnchorCertificates(serverTrust, [customCARootCert] as CFArray)
SecTrustSetAnchorCertificatesOnly(serverTrust, false)
completionHandler(.performDefaultHandling, nil)
}
}
// Usage
let delegate = CustomURLSessionDelegate()
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
The final step is to disable App Transport Security (ATS). ATS is a security feature introduced in iOS 9, which enforces best practices for secure network communication by default.
ATS requires apps to communicate using HTTPS and secure TLS protocols. By default, ATS does not trust self-signed certificates.
To work with self-signed certificates in your app, you'll need to update your app's Info.plist
.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
This configuration allows you to work with self-signed certificates. Now your app can handle https requests signed with our custom CA without installing CA’s root certificate on your client devices.
Handling self-signed certificates in Swift can be a bit tricky, but it's a valuable technique for securing your intranet API environment.
By following this step-by-step guide, you can efficiently work with self-signed certificates in your iOS app and ensure a seamless transition to production with trusted CA-issued certificates.
The lead image for this article was generated by HackerNoon's AI Image Generator via the prompt "ios app".