Post

Icon iOS HTTPS with Self-Signed Certificates

Understanding iOS platform requirements and implementation details for handling HTTPS connections with self-signed certificates in the Krill iOS app

iOS HTTPS with Self-Signed Certificates

Overview

The iOS implementation for handling HTTPS connections with self-signed certificates differs from Android and JVM due to iOS platform restrictions. While Android and JVM can programmatically trust certificates at runtime, iOS requires user interaction to trust self-signed certificates.

Implementation Details

Certificate Fetching (HttpClient.ios.kt)

The trustHttpClient actual implementation:

  • Creates an insecure HTTP client to download the certificate from the /trust endpoint
  • Stores the certificate data in NSUserDefaults with key prefix krill_trusted_cert_
  • Compares certificates to detect changes and triggers client rebuilding when updated
  • Logs detailed instructions for the user when a new certificate is stored

HTTP Client Configuration (HttpClientContainer.ios.kt)

The httpClient actual implementation:

  • Uses Ktor’s Darwin engine (native iOS networking)
  • Configures challenge handling to check for stored certificates
  • Attempts to use system trust when certificates are installed
  • Falls back to default handling for non-matching hosts

User Workflow

When connecting to a local server with a self-signed certificate, users must manually trust the certificate. There are three options:

  1. Export Certificate: Future versions will provide UI to export stored certificates. For now, certificates are stored in NSUserDefaults but need to be exported manually.

  2. Transfer to Device:
    • Email the .crt file to yourself
    • Or use AirDrop to send to your iOS device
  3. Install Profile:
    • Tap the certificate file attachment or AirDrop notification
    • Follow the prompts to install the configuration profile
    • Go to Settings > General > VPN & Device Management
    • Find and tap the installed profile
    • Tap “Install” and enter your passcode if prompted
  4. Enable Trust:
    • Go to Settings > General > About > Certificate Trust Settings
    • Find your certificate in the list
    • Toggle the switch to enable full trust
    • Confirm the security warning

Option 2: Trust via Safari (Quick Test)

  1. Open Safari on your iOS device
  2. Navigate to https://[your-server-hostname]
  3. Tap “Show Details” on the security warning
  4. Tap “Visit this website”
  5. The certificate will be temporarily trusted for the current browsing session

Note: This is temporary and the app may still need proper certificate installation.

Option 3: Development Mode (Xcode Only)

When running through Xcode with proper entitlements configured:

  • The app may accept self-signed certificates without manual installation
  • This only works for development builds
  • Not suitable for TestFlight or App Store builds

Technical Limitations

iOS Platform Restrictions

Unlike JVM/Android, iOS does not allow:

  • Programmatic installation of certificates into the system trust store
  • Runtime modification of TLS trust managers without user consent
  • Bypassing certificate validation except through user-approved methods

Why Different from Android/JVM

Android/JVM approach:

1
2
3
4
5
6
7
// Can create custom TrustManager with downloaded certificates
val ks = KeyStore.getInstance(KeyStore.getDefaultType())
ks.load(null)
// Load certificate files and add to keystore programmatically
val tmf = TrustManagerFactory.getInstance(...)
tmf.init(ks)
// Use custom trust manager in HTTP client

iOS approach:

1
2
3
4
5
6
// Must use system trust store
// Can only accept certificates that user has installed
handleChallenge { _, _, challenge, completionHandler ->
    // Check if certificate is in system trust store
    // Accept if trusted, reject otherwise
}

App Store Considerations

For Production Apps

  1. Use Valid Certificates: Self-signed certificates should only be used for development/testing
  2. Let’s Encrypt: For production local servers, consider Let’s Encrypt certificates
  3. User Documentation: Clearly document the certificate installation process

Entitlements

No special entitlements are required for this implementation. The app uses standard iOS networking APIs.

Future Enhancements

Planned Features

  • UI to view stored certificates
  • UI to export certificates as .crt files
  • Share certificate via AirDrop/Email directly from app
  • QR code generation for easy certificate transfer
  • In-app instructions with step-by-step guide

Troubleshooting

Certificate Not Trusted After Installation

  1. Verify the certificate is installed: Settings > General > VPN & Device Management
  2. Verify trust is enabled: Settings > General > About > Certificate Trust Settings
  3. Restart the app after enabling trust
  4. Check that the hostname in the certificate matches the server hostname

Connection Still Fails

  1. Check server logs to verify the /trust endpoint is accessible
  2. Verify the certificate is not expired
  3. Ensure the device and server are on the same network (for local servers)
  4. Check iOS logs in Console.app (Mac) for detailed error messages

Certificate Not Fetching

  1. The initial fetch may fail if the certificate isn’t trusted yet
  2. Use Safari to accept the certificate first, then retry in the app
  3. Verify network connectivity
  4. Check that the server is serving the certificate at /trust endpoint

Development Notes

Testing Certificate Trust

1
2
3
4
5
6
7
8
9
// In your app initialization code
val url = Url("https://your-local-server.local")
val success = trustHttpClient.fetchPeerCert(url)
if (success) {
    println("Certificate fetched and stored")
    // User still needs to manually trust it
} else {
    println("Failed to fetch certificate")
}

Checking Certificate Status

1
2
3
4
5
6
// Check if certificate is stored
val certKey = "krill_trusted_cert_your-local-server.local"
val cert = NSUserDefaults.standardUserDefaults.dataForKey(certKey)
if (cert != null) {
    println("Certificate is stored, but may not be trusted yet")
}

Comparison with Other Platforms

FeatureJVMAndroidiOSWASM
Programmatic Trust✅ Yes✅ Yes❌ NoN/A (Browser)
Certificate StorageFilesystemApp FilesUserDefaultsN/A
User Interaction Required❌ No❌ No✅ Yes✅ Yes (Browser)
Trust Store LocationCustom KeyStoreCustom KeyStoreSystem OnlyBrowser
Runtime Trust Update✅ Yes✅ Yes❌ NoN/A

References

This post is licensed under CC BY 4.0 by the author.