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
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
/trustendpoint - Stores the certificate data in
NSUserDefaultswith key prefixkrill_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:
Option 1: Install Configuration Profile (Recommended for Production)
Export Certificate: Future versions will provide UI to export stored certificates. For now, certificates are stored in NSUserDefaults but need to be exported manually.
- Transfer to Device:
- Email the
.crtfile to yourself - Or use AirDrop to send to your iOS device
- Email the
- 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
- 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)
- Open Safari on your iOS device
- Navigate to
https://[your-server-hostname] - Tap “Show Details” on the security warning
- Tap “Visit this website”
- 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
- Use Valid Certificates: Self-signed certificates should only be used for development/testing
- Let’s Encrypt: For production local servers, consider Let’s Encrypt certificates
- 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
.crtfiles - 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
- Verify the certificate is installed: Settings > General > VPN & Device Management
- Verify trust is enabled: Settings > General > About > Certificate Trust Settings
- Restart the app after enabling trust
- Check that the hostname in the certificate matches the server hostname
Connection Still Fails
- Check server logs to verify the
/trustendpoint is accessible - Verify the certificate is not expired
- Ensure the device and server are on the same network (for local servers)
- Check iOS logs in Console.app (Mac) for detailed error messages
Certificate Not Fetching
- The initial fetch may fail if the certificate isn’t trusted yet
- Use Safari to accept the certificate first, then retry in the app
- Verify network connectivity
- Check that the server is serving the certificate at
/trustendpoint
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
| Feature | JVM | Android | iOS | WASM |
|---|---|---|---|---|
| Programmatic Trust | ✅ Yes | ✅ Yes | ❌ No | N/A (Browser) |
| Certificate Storage | Filesystem | App Files | UserDefaults | N/A |
| User Interaction Required | ❌ No | ❌ No | ✅ Yes | ✅ Yes (Browser) |
| Trust Store Location | Custom KeyStore | Custom KeyStore | System Only | Browser |
| Runtime Trust Update | ✅ Yes | ✅ Yes | ❌ No | N/A |