|
1 | 1 | # SSL Configuration |
2 | 2 |
|
3 | | -SSL (Secure Sockets Layer) configuration in Ensemble allows you to secure your API communications through certificate pinning and verification controls. This guide explains the available SSL configuration options and their proper usage. |
| 3 | +SSL (Secure Sockets Layer) configuration in Ensemble allows you to secure your API communications through certificate pinning and verification controls. This guide explains both global and per-API SSL configuration options and their proper usage. |
4 | 4 |
|
5 | | -## Available Configuration Options |
| 5 | +## Configuration Levels |
6 | 6 |
|
7 | | -### ssl_pinning_enabled |
| 7 | +Ensemble supports SSL configuration at two levels: |
8 | 8 |
|
| 9 | +1. **Global Configuration** - Applied to all APIs by default using environment variables and secrets |
| 10 | +2. **Per-API Configuration** - Overrides global settings for specific APIs using the `sslConfig` property |
| 11 | + |
| 12 | +## Global SSL Configuration |
| 13 | + |
| 14 | +### Environment Variables |
| 15 | + |
| 16 | +These settings apply to all APIs unless overridden by per-API configuration: |
| 17 | + |
| 18 | +#### ssl_pinning_enabled |
9 | 19 | - **Type:** Environment Variable |
10 | | -- **Purpose:** Controls whether SSL certificate pinning is active |
11 | | -- **Behavior:** When set to 'true', the app will only trust connections with certificates matching the provided certificate |
| 20 | +- **Purpose:** Controls whether SSL certificate pinning is active globally |
| 21 | +- **Values:** 'true' or 'false' |
| 22 | +- **Default:** false |
12 | 23 | - **Availability:** Only supported in native apps (not available for web apps) |
| 24 | + |
| 25 | +#### bypass_ssl_pinning |
| 26 | +- **Type:** Environment Variable |
| 27 | +- **Purpose:** Allows bypassing SSL certificate verification globally |
| 28 | +- **Values:** 'true' or 'false' |
| 29 | +- **Default:** false |
| 30 | +- **Warning:** Should only be used in development environments, never in production |
| 31 | + |
| 32 | +#### bypass_ssl_pinning_with_validation |
| 33 | +- **Type:** Environment Variable |
| 34 | +- **Purpose:** Bypass SSL pinning while validating against stored certificate fingerprints |
| 35 | +- **Values:** 'true' or 'false' |
13 | 36 | - **Default:** false |
| 37 | +- **Usage:** Compares current certificate fingerprint with stored fingerprint from secure storage |
14 | 38 |
|
15 | | -### ssl_pinning_certificate |
| 39 | +### Secrets |
16 | 40 |
|
| 41 | +#### ssl_pinning_certificate |
17 | 42 | - **Type:** Secret |
18 | 43 | - **Purpose:** Provides the certificate for SSL pinning verification |
19 | 44 | - **Format:** Must be Base64 encoded |
20 | 45 | - **Behavior:** The app will only trust servers presenting this certificate |
21 | 46 | - **Dependencies:** Requires `ssl_pinning_enabled` to be 'true' |
22 | 47 |
|
23 | | -### bypass_ssl_pinning |
| 48 | +## Per-API SSL Configuration |
24 | 49 |
|
25 | | -- **Type:** Environment Variable |
26 | | -- **Purpose:** Allows bypassing SSL certificate verification |
27 | | -- **Warning:** Should only be used in development environments, never in production |
28 | | -- **Behavior:** When 'true', bypasses all SSL certificate verification |
29 | | -- **Default:** false |
| 50 | +For more granular control, you can override global SSL settings for individual APIs using the `sslConfig` property in your API definition. |
| 51 | + |
| 52 | +### Basic Syntax |
| 53 | + |
| 54 | +```yaml |
| 55 | +API: |
| 56 | + mySecureAPI: |
| 57 | + uri: https://api.example.com/data |
| 58 | + method: GET |
| 59 | + sslConfig: |
| 60 | + pinningEnabled: true |
| 61 | + bypassPinning: false |
| 62 | + bypassPinningWithFingerprint: false |
| 63 | + fingerprintKey: "api_example_com_fingerprint" |
| 64 | + headers: |
| 65 | + Authorization: Bearer ${token} |
| 66 | +``` |
| 67 | +
|
| 68 | +### sslConfig Properties |
| 69 | +
|
| 70 | +#### pinningEnabled |
| 71 | +- **Type:** Boolean |
| 72 | +- **Purpose:** Enable/disable SSL certificate pinning for this specific API |
| 73 | +- **Values:** true or false |
| 74 | +- **Overrides:** Global `ssl_pinning_enabled` environment variable |
| 75 | +- **Example:** `pinningEnabled: true` |
| 76 | + |
| 77 | +#### bypassPinning |
| 78 | +- **Type:** Boolean |
| 79 | +- **Purpose:** Bypass SSL certificate verification for this specific API |
| 80 | +- **Values:** true or false |
| 81 | +- **Overrides:** Global `bypass_ssl_pinning` environment variable |
| 82 | +- **Warning:** Use only in development |
| 83 | +- **Example:** `bypassPinning: true` |
| 84 | + |
| 85 | +#### bypassPinningWithFingerprint |
| 86 | +- **Type:** Boolean |
| 87 | +- **Purpose:** Bypass SSL pinning while validating against stored certificate fingerprints |
| 88 | +- **Values:** true or false |
| 89 | +- **Overrides:** Global `bypass_ssl_pinning_with_validation` environment variable |
| 90 | +- **Example:** `bypassPinningWithFingerprint: true` |
| 91 | +- **Requirement:** `fingerprintKey` should be set with the same key given in API defination as secureStorage. |
| 92 | + |
| 93 | +#### fingerprintKey |
| 94 | +- **Type:** String |
| 95 | +- **Purpose:** Specifies the key in secure storage where the certificate fingerprint is stored |
| 96 | +- **Default:** "bypass_ssl_fingerprint" |
| 97 | +- **Usage:** Used with `bypassPinningWithFingerprint` to retrieve the stored certificate fingerprint for validation |
| 98 | +- **Example:** `fingerprintKey: "api_example_com_fingerprint"` |
| 99 | + |
| 100 | +## Certificate Fingerprint Management |
| 101 | + |
| 102 | +When using `bypassPinningWithFingerprint`, you need to store the certificate fingerprint in secure storage. There are two main approaches: |
| 103 | + |
| 104 | +### Method 1: Using Ensemble's setSecureStorage Action |
| 105 | + |
| 106 | +Store the certificate fingerprint manually using Ensemble's secure storage: |
| 107 | + |
| 108 | +```yaml |
| 109 | +Button: |
| 110 | + label: Store Certificate Fingerprint |
| 111 | + onTap: |
| 112 | + setSecureStorage: |
| 113 | + key: "api_example_com_fingerprint" |
| 114 | + value: "a1b2c3d4e5f6..." # SHA256 fingerprint of the certificate |
| 115 | + onComplete: |
| 116 | + showToast: |
| 117 | + message: Certificate fingerprint stored |
| 118 | +``` |
| 119 | + |
| 120 | +### Method 2: Using External Methods (Dynamic Certificate Capture) |
| 121 | + |
| 122 | +For dynamic certificate capture, you can expose external methods from your host application: |
| 123 | + |
| 124 | +#### Host Application Setup (Flutter/Dart Example) |
| 125 | + |
| 126 | +```dart |
| 127 | +// In your main.dart or wherever you initialize EnsembleApp |
| 128 | +Future<Map<String, dynamic>> captureCertificateForHost({ |
| 129 | + required String host, |
| 130 | + int port = 443 |
| 131 | +}) async { |
| 132 | + HttpClient httpClient = HttpClient(); |
| 133 | + httpClient.connectionTimeout = const Duration(seconds: 10); |
| 134 | + |
| 135 | + String sha256Certificate = ''; |
| 136 | + |
| 137 | + httpClient.badCertificateCallback = (X509Certificate cert, String certHost, int certPort) { |
| 138 | + if (certHost.toLowerCase() == host.toLowerCase()) { |
| 139 | + sha256Certificate = sha256.convert(cert.der).toString(); |
| 140 | + return true; |
| 141 | + } |
| 142 | + return false; |
| 143 | + }; |
| 144 | + |
| 145 | + try { |
| 146 | + HttpClientRequest request = await httpClient.getUrl(Uri.parse('https://$host:$port/')); |
| 147 | + HttpClientResponse response = await request.close(); |
| 148 | + await response.drain(); |
| 149 | + httpClient.close(); |
| 150 | + |
| 151 | + if (sha256Certificate != '') { |
| 152 | + await StorageManager().writeSecurely( |
| 153 | + key: 'bypass_ssl_certificate', |
| 154 | + value: sha256Certificate, |
| 155 | + ); |
| 156 | + return {'status': true, 'fingerprint': sha256Certificate}; |
| 157 | + } else { |
| 158 | + return {'success': false, 'error': 'Failed to capture certificate'}; |
| 159 | + } |
| 160 | + } catch (e) { |
| 161 | + return {'success': false, 'error': e.toString()}; |
| 162 | + } |
| 163 | +} |
| 164 | +
|
| 165 | +// Register the external method |
| 166 | +EnsembleApp( |
| 167 | + externalMethods: const { |
| 168 | + 'captureCertificateForHost': captureCertificateForHost, |
| 169 | + }, |
| 170 | + // ... other properties |
| 171 | +) |
| 172 | +``` |
| 173 | + |
| 174 | +#### Using External Method in Ensemble EDL |
| 175 | + |
| 176 | +```yaml |
| 177 | +View: |
| 178 | + onLoad: |
| 179 | + callExternalMethod: |
| 180 | + name: captureCertificateForHost |
| 181 | + payload: |
| 182 | + host: ${HOST} |
| 183 | + port: ${PORT_NUMBER} |
| 184 | + onComplete: |
| 185 | + invokeAPI: |
| 186 | + name: secureAPI |
| 187 | + onError: |
| 188 | + showToast: |
| 189 | + message: "Failed to capture certificate: ${response.error}" |
| 190 | + options: |
| 191 | + type: error |
| 192 | +
|
| 193 | +API: |
| 194 | + secureAPI: |
| 195 | + uri: ${HOST}/endpoint |
| 196 | + method: GET |
| 197 | + sslConfig: |
| 198 | + bypassPinningWithFingerprint: true |
| 199 | + fingerprintKey: "api_fingerprint" |
| 200 | + headers: |
| 201 | + Authorization: Bearer ${token} |
| 202 | +``` |
| 203 | + |
| 204 | + |
| 205 | + |
| 206 | +## Configuration Examples |
| 207 | + |
| 208 | +### Example 1: High-Security API with Certificate Pinning |
| 209 | + |
| 210 | +```yaml |
| 211 | +API: |
| 212 | + paymentAPI: |
| 213 | + uri: https://secure-payment.example.com/process |
| 214 | + method: POST |
| 215 | + sslConfig: |
| 216 | + pinningEnabled: true |
| 217 | + bypassPinning: false |
| 218 | + bypassPinningWithFingerprint: false |
| 219 | + headers: |
| 220 | + Authorization: Bearer ${paymentToken} |
| 221 | + Content-Type: application/json |
| 222 | + body: |
| 223 | + amount: ${amount} |
| 224 | + currency: USD |
| 225 | +``` |
| 226 | + |
| 227 | +### Example 2: Development API with SSL Bypass |
| 228 | + |
| 229 | +```yaml |
| 230 | +API: |
| 231 | + devTestAPI: |
| 232 | + uri: https://dev-server.example.com/test |
| 233 | + method: GET |
| 234 | + sslConfig: |
| 235 | + pinningEnabled: false |
| 236 | + bypassPinning: true # Only for development! |
| 237 | + bypassPinningWithFingerprint: false |
| 238 | + headers: |
| 239 | + Authorization: Bearer ${devToken} |
| 240 | +``` |
30 | 241 |
|
31 | | -## Security Considerations |
| 242 | +## Security Best Practices |
32 | 243 |
|
33 | | -1. Certificate pinning provides protection against man-in-the-middle attacks |
34 | | -2. SSL bypass shouldn't be used in production environments as it will create security concerns. |
35 | | -3. Web applications cannot use SSL pinning and rely on browser-based verification |
| 244 | +1. **Production Environment**: Always use certificate pinning (`pinningEnabled: true`) for production APIs |
| 245 | +2. **Development Environment**: Use `bypassPinning: true` only during development |
| 246 | +3. **Dynamic Environments**: Use `bypassPinningWithFingerprint: true` when dealing with dynamic certificates or multiple environments |
| 247 | +4. **Certificate Storage**: Store certificate fingerprints securely using `setSecureStorage` or external methods |
0 commit comments