Notice: Due to size constraints and loading performance considerations, scripts referenced in blog posts are not attached directly. To request access, please complete the following form: Script Request Form Note: A Google account is required to access the form.
Disclaimer: I do not accept responsibility for any issues arising from scripts being run without adequate understanding. It is the user's responsibility to review and assess any code before execution. More information

iOS : Security Analysis Tool and the "battle" with Certificate Expirys


I set out to create HTTPInspector, a comprehensive iOS security analysis application that would provide users with detailed insights into website security. The app's core mission is to analyze SSL certificates, HTTP security headers, and provide an overall security grade for any website. Think of it as a pocket-sized security audit tool that helps users understand whether their favorite websites are properly secured.

Note : I started falling down rabbit holes of my own until I realised that the coding had already been done by someone else, and they had been though these issues before, so this application more an exercise in "combining" code and a lesson or myself in SwiftUI.

The application features three main components:

  • SSL Certificate Analysis: Deep inspection of certificate chains, validity periods, key sizes, and signature algorithms
  • HTTP Security Header Evaluation: Analysis of critical headers like HSTS, CSP, X-Frame-Options, and more
  • Security Scoring System: An overall grade (A+ to F) based on certificate strength and header configuration

What started as a straightforward iOS development project quickly became a months-long struggle against Apple's deliberately restrictive certificate APIs. This is the story of that journey.

XCode : Setting up the Application

You need to create a new application for Swift so lets create an iOS application:


Then we need to give the application a name, I will use HTTPInspector:


The we need to navigate to the project options and choose under Target the project then choose Signing and Capabilities, then from the + icon choose Network Extensions choose the following options:


Then from the Explorer, find the info.plist and open that file, that will open in the tabular view, right click the file and choose View then Code.....


Then we need to add this data into that window, this will set arbitrary code loads to "True" as you can see below:


We then need to navigate to the Project main option then Targets then the main project and choose "Build Settings" we then need to search for "CLANG_ALLOW_NON_MODULAR" and ensure this setting is "True" for the project as below:


We now need to import the OpenSSL framework to the project and depending on your testing you will need to import the correct framework, you will have a different version for the iOS device and the iOS simulator, below I have used the ios-amd64 which means this will fail to run on the simulator:


Open that folder and drop the top level folder as shown below into your project:


When you drop the folder ensure it is dropped into the area shown below in the green box, you can see bvelow that "OpenSSL" is already there as this has been done:



The Problem: Apple's Certificate API Limitations

When I began implementing certificate analysis, I assumed it would be straightforward. After all, certificate expiry dates are fundamental security information. Every browser displays them, security tools show them, and users need them to make informed decisions.

I started with what seemed like the obvious approach - Apple's Security framework:

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, 
               completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    
    if let serverTrust = challenge.protectionSpace.serverTrust {
        // This should give us certificate information, right?
        extractCertificateInfo(from: serverTrust)
    }
    
    completionHandler(.performDefaultHandling, nil)
}

The Security framework provides SecCertificateCopySubjectSummary() for basic certificate information, but when it comes to expiry dates, Apple offers virtually nothing reliable.

The Initial Approach: SecTrustCopyResult

private func getValidityDates(from serverTrust: SecTrust) -> (Date?, Date?) {
    var result: SecTrustResultType = .invalid
    let status = SecTrustEvaluate(serverTrust, &result)
    
    guard status == errSecSuccess,
          let trustResult = SecTrustCopyResult(serverTrust) as? [String: Any] else {
        return (nil, nil)
    }
    
    // These keys supposedly contain validity dates
    var validFrom: Date?
    var validTo: Date?
    
    if let notValidBefore = trustResult["NotValidBefore"] as? NSNumber {
        validFrom = Date(timeIntervalSince1970: notValidBefore.doubleValue)
    }
    
    if let notValidAfter = trustResult["NotValidAfter"] as? NSNumber {
        validTo = Date(timeIntervalSince1970: notValidAfter.doubleValue)
    }
    
    return (validFrom, validTo)
}

This approach failed consistently. The SecTrustCopyResult dictionary rarely contained the NotValidBefore and NotValidAfter keys, and when it did, the values were often incorrect or missing.

Why Does Apple Restricts Certificate Access?

After numerous failed attempts, I realized this wasn't a bug - it was intentional. Apple has deliberately limited certificate inspection capabilities on iOS for several reasons:

  1. Security Model: iOS is designed around trust evaluation, not certificate inspection
  2. User Protection: Detailed certificate data could be misused by malicious apps
  3. API Simplicity: Apple prefers simple "trusted/untrusted" decisions over complex certificate parsing

The iOS Security framework is built for answering "Should I trust this certificate?" rather than "What's in this certificate?"

The Working Solution: OpenSSL

While researching alternatives, I discovered I needed to not use Apple frameworks but just admit defeat and stop there.

I therefore decided to import the OpenSSL framework and use that as the "discovery"

The OpenSSL Integration Challenge

OpenSSL isn't included in iOS, so it must be compiled and integrated manually. I found Marcin Krzyzanowski's OpenSSL package, which provides pre-compiled frameworks for iOS.

The first challenge was framework architecture. iOS simulators run on Intel/ARM Macs, while iOS devices use ARM processors. This means you need different framework versions:

  • Simulator: x86_64 architecture for Intel Macs, arm64 for Apple Silicon Macs
  • Device: arm64 architecture for iPhone/iPad

You can download the pre-compiled frameworks from the OpenSSL releases page.

// During development, I had to manually swap frameworks:
// 1. Use simulator framework for Xcode simulator testing
// 2. Switch to ios-arm64 framework for device testing

The Implementation: OpenSSL Certificate Parsing

With OpenSSL integrated, I could finally extract certificate expiry dates reliably:

private func getValidityDates(from certificate: SecCertificate) -> (Date?, Date?) {
    // Get raw certificate data from Apple's Security framework
    let certData = SecCertificateCopyData(certificate)
    let certBytes = CFDataGetBytePtr(certData)
    let certLength = CFDataGetLength(certData)
    
    guard let certBytes = certBytes else { return (nil, nil) }
    
    // Parse certificate using OpenSSL - this is the key difference
    var mutableCertPointer: UnsafePointer<UInt8>? = certBytes
    guard let x509 = d2i_X509(nil, &mutableCertPointer, certLength) else { return (nil, nil) }
    defer { X509_free(x509) }
    
    // Extract validity dates using OpenSSL's proven APIs
    let notBefore = X509_getm_notBefore(x509)
    let notAfter = X509_getm_notAfter(x509)
    
    let validFrom = convertASN1TimeToDate(notBefore)
    let validTo = convertASN1TimeToDate(notAfter)
    
    return (validFrom, validTo)
}

Converting ASN.1 Time to Swift Date

OpenSSL stores certificate dates in ASN.1 format, which requires conversion to Swift's Date type:

Note : ASN.1 data for date and time uses the GeneralizedTime and UTC which return strings of digits formatted as YYYYMMDDHHMMSS[.fff] optionally followed by a 'Z' for UTC or a timezone offset, this need to be converted into human date and time.

private func convertASN1TimeToDate(_ asn1Time: UnsafeMutablePointer<ASN1_TIME>?) -> Date? {
    guard let asn1Time = asn1Time else { return nil }
    
    var day: Int32 = 0, sec: Int32 = 0
    
    // ASN1_TIME_diff calculates the difference from "now" to the certificate time
    if ASN1_TIME_diff(&day, &sec, nil, asn1Time) == 1 {
        let timeInterval = TimeInterval(day * 86400 + sec)
        return Date(timeIntervalSinceNow: timeInterval)
    }
    
    return nil
}

Handling Expired Certificates

One crucial challenge was analyzing expired certificates. By default, URLSession rejects connections to sites with expired certificates, preventing analysis:

// The error that blocked analysis
NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef>, 
NSLocalizedDescription=The certificate for this server is invalid.

The solution was to modify the SSL challenge handling to allow invalid certificates specifically for analysis:

class SSLCertificateDelegate: NSObject, URLSessionDelegate {
    var shouldAllowInvalidCerts = false
    
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, 
                   completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        
        // Always extract certificate info, regardless of validity
        if let serverTrust = challenge.protectionSpace.serverTrust {
            extractCertificateInfo(from: serverTrust)
        }
        
        // Allow connection to invalid certificates for analysis purposes
        if shouldAllowInvalidCerts {
            completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
        } else {
            completionHandler(.performDefaultHandling, nil)
        }
    }
}

The Architecture Juggling Act

During development, I constantly switched between framework versions:

  1. Development Phase: Use simulator framework for rapid testing
  2. Device Testing: Swap to ios-arm64 framework for real-world validation
  3. Production: Ideally use XCFramework containing both architectures

Valid and Reliable :  Certificate Analysis Tool

The iOS application named "HTTPInspector" successfully extracts certificate expiry dates, analyzes security headers, and provides comprehensive security scoring. The app can analyze both valid and expired certificates, providing users with detailed security insights.

The application now displays:

  • Certificate validity periods with day-accurate expiry countdown
  • Certificate chain analysis with proper issuer information
  • Security header evaluation with recommendations
  • Overall security grades from A+ to F
Conclusion

It seems that creating iOS applications is more today about learning from other peoples mistakes and not making the same mistakes all over again, which means it more about code combining that is is coding from scratch, unless that is you are doing something not done before - which is very unlikely, however you do need to get to grips with SwiftUI - this is the visual elements of the iOS/MacOS applications - you can start that journey here
Previous Post Next Post

نموذج الاتصال