@@ -1829,39 +1829,251 @@ For example, after the initial API call named "getuser", the onResponse event tr
18291829
18301830# SSL Configuration
18311831
1832- 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.
1832+ 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.
18331833
1834- ## Available Configuration Options
1834+ ## Configuration Levels
18351835
1836- ### ssl_pinning_enabled
1836+ Ensemble supports SSL configuration at two levels:
18371837
1838+ 1. **Global Configuration** - Applied to all APIs by default using environment variables and secrets
1839+ 2. **Per-API Configuration** - Overrides global settings for specific APIs using the `sslConfig` property
1840+
1841+ ## Global SSL Configuration
1842+
1843+ ### Environment Variables
1844+
1845+ These settings apply to all APIs unless overridden by per-API configuration:
1846+
1847+ #### ssl_pinning_enabled
18381848- **Type:** Environment Variable
1839- - **Purpose:** Controls whether SSL certificate pinning is active
1840- - **Behavior:** When set to 'true', the app will only trust connections with certificates matching the provided certificate
1849+ - **Purpose:** Controls whether SSL certificate pinning is active globally
1850+ - **Values:** 'true' or 'false'
1851+ - **Default:** false
18411852- **Availability:** Only supported in native apps (not available for web apps)
1853+
1854+ #### bypass_ssl_pinning
1855+ - **Type:** Environment Variable
1856+ - **Purpose:** Allows bypassing SSL certificate verification globally
1857+ - **Values:** 'true' or 'false'
1858+ - **Default:** false
1859+ - **Warning:** Should only be used in development environments, never in production
1860+
1861+ #### bypass_ssl_pinning_with_validation
1862+ - **Type:** Environment Variable
1863+ - **Purpose:** Bypass SSL pinning while validating against stored certificate fingerprints
1864+ - **Values:** 'true' or 'false'
18421865- **Default:** false
1866+ - **Usage:** Compares current certificate fingerprint with stored fingerprint from secure storage
18431867
1844- ### ssl_pinning_certificate
1868+ ### Secrets
18451869
1870+ #### ssl_pinning_certificate
18461871- **Type:** Secret
18471872- **Purpose:** Provides the certificate for SSL pinning verification
18481873- **Format:** Must be Base64 encoded
18491874- **Behavior:** The app will only trust servers presenting this certificate
18501875- **Dependencies:** Requires `ssl_pinning_enabled` to be 'true'
18511876
1852- ### bypass_ssl_pinning
1877+ ## Per-API SSL Configuration
18531878
1854- - **Type:** Environment Variable
1855- - **Purpose:** Allows bypassing SSL certificate verification
1856- - **Warning:** Should only be used in development environments, never in production
1857- - **Behavior:** When 'true', bypasses all SSL certificate verification
1858- - **Default:** false
1879+ For more granular control, you can override global SSL settings for individual APIs using the `sslConfig` property in your API definition.
1880+
1881+ ### Basic Syntax
1882+
1883+ ```yaml
1884+ API:
1885+ mySecureAPI:
1886+ uri: https://api.example.com/data
1887+ method: GET
1888+ sslConfig:
1889+ pinningEnabled: true
1890+ bypassPinning: false
1891+ bypassPinningWithFingerprint: false
1892+ fingerprintKey: "api_example_com_fingerprint"
1893+ headers:
1894+ Authorization: Bearer ${token}
1895+ ```
1896+
1897+ ### sslConfig Properties
1898+
1899+ #### pinningEnabled
1900+ - **Type:** Boolean
1901+ - **Purpose:** Enable/disable SSL certificate pinning for this specific API
1902+ - **Values:** true or false
1903+ - **Overrides:** Global `ssl_pinning_enabled` environment variable
1904+ - **Example:** `pinningEnabled: true`
1905+
1906+ #### bypassPinning
1907+ - **Type:** Boolean
1908+ - **Purpose:** Bypass SSL certificate verification for this specific API
1909+ - **Values:** true or false
1910+ - **Overrides:** Global `bypass_ssl_pinning` environment variable
1911+ - **Warning:** Use only in development
1912+ - **Example:** `bypassPinning: true`
1913+
1914+ #### bypassPinningWithFingerprint
1915+ - **Type:** Boolean
1916+ - **Purpose:** Bypass SSL pinning while validating against stored certificate fingerprints
1917+ - **Values:** true or false
1918+ - **Overrides:** Global `bypass_ssl_pinning_with_validation` environment variable
1919+ - **Example:** `bypassPinningWithFingerprint: true`
1920+ - **Requirement:** `fingerprintKey` should be set with the same key given in API defination as secureStorage.
1921+
1922+ #### fingerprintKey
1923+ - **Type:** String
1924+ - **Purpose:** Specifies the key in secure storage where the certificate fingerprint is stored
1925+ - **Default:** "bypass_ssl_fingerprint"
1926+ - **Usage:** Used with `bypassPinningWithFingerprint` to retrieve the stored certificate fingerprint for validation
1927+ - **Example:** `fingerprintKey: "api_example_com_fingerprint"`
1928+
1929+ ## Certificate Fingerprint Management
1930+
1931+ When using `bypassPinningWithFingerprint`, you need to store the certificate fingerprint in secure storage. There are two main approaches:
1932+
1933+ ### Method 1: Using Ensemble's setSecureStorage Action
1934+
1935+ Store the certificate fingerprint manually using Ensemble's secure storage:
1936+
1937+ ```yaml
1938+ Button:
1939+ label: Store Certificate Fingerprint
1940+ onTap:
1941+ setSecureStorage:
1942+ key: "api_example_com_fingerprint"
1943+ value: "a1b2c3d4e5f6..." # SHA256 fingerprint of the certificate
1944+ onComplete:
1945+ showToast:
1946+ message: Certificate fingerprint stored
1947+ ```
1948+
1949+ ### Method 2: Using External Methods (Dynamic Certificate Capture)
1950+
1951+ For dynamic certificate capture, you can expose external methods from your host application:
1952+
1953+ #### Host Application Setup (Flutter/Dart Example)
1954+
1955+ ```dart
1956+ // In your main.dart or wherever you initialize EnsembleApp
1957+ Future<Map<String, dynamic>> captureCertificateForHost({
1958+ required String host,
1959+ int port = 443
1960+ }) async {
1961+ HttpClient httpClient = HttpClient();
1962+ httpClient.connectionTimeout = const Duration(seconds: 10);
1963+
1964+ String sha256Certificate = '';
1965+
1966+ httpClient.badCertificateCallback = (X509Certificate cert, String certHost, int certPort) {
1967+ if (certHost.toLowerCase() == host.toLowerCase()) {
1968+ sha256Certificate = sha256.convert(cert.der).toString();
1969+ return true;
1970+ }
1971+ return false;
1972+ };
1973+
1974+ try {
1975+ HttpClientRequest request = await httpClient.getUrl(Uri.parse('https://$host:$port/'));
1976+ HttpClientResponse response = await request.close();
1977+ await response.drain();
1978+ httpClient.close();
1979+
1980+ if (sha256Certificate != '') {
1981+ await StorageManager().writeSecurely(
1982+ key: 'bypass_ssl_certificate',
1983+ value: sha256Certificate,
1984+ );
1985+ return {'status': true, 'fingerprint': sha256Certificate};
1986+ } else {
1987+ return {'success': false, 'error': 'Failed to capture certificate'};
1988+ }
1989+ } catch (e) {
1990+ return {'success': false, 'error': e.toString()};
1991+ }
1992+ }
1993+
1994+ // Register the external method
1995+ EnsembleApp(
1996+ externalMethods: const {
1997+ 'captureCertificateForHost': captureCertificateForHost,
1998+ },
1999+ // ... other properties
2000+ )
2001+ ```
2002+
2003+ #### Using External Method in Ensemble EDL
2004+
2005+ ```yaml
2006+ View:
2007+ onLoad:
2008+ callExternalMethod:
2009+ name: captureCertificateForHost
2010+ payload:
2011+ host: ${HOST}
2012+ port: ${PORT_NUMBER}
2013+ onComplete:
2014+ invokeAPI:
2015+ name: secureAPI
2016+ onError:
2017+ showToast:
2018+ message: "Failed to capture certificate: ${response.error}"
2019+ options:
2020+ type: error
2021+
2022+ API:
2023+ secureAPI:
2024+ uri: ${HOST}/endpoint
2025+ method: GET
2026+ sslConfig:
2027+ bypassPinningWithFingerprint: true
2028+ fingerprintKey: "api_fingerprint"
2029+ headers:
2030+ Authorization: Bearer ${token}
2031+ ```
2032+
2033+
2034+
2035+ ## Configuration Examples
2036+
2037+ ### Example 1: High-Security API with Certificate Pinning
2038+
2039+ ```yaml
2040+ API:
2041+ paymentAPI:
2042+ uri: https://secure-payment.example.com/process
2043+ method: POST
2044+ sslConfig:
2045+ pinningEnabled: true
2046+ bypassPinning: false
2047+ bypassPinningWithFingerprint: false
2048+ headers:
2049+ Authorization: Bearer ${paymentToken}
2050+ Content-Type: application/json
2051+ body:
2052+ amount: ${amount}
2053+ currency: USD
2054+ ```
2055+
2056+ ### Example 2: Development API with SSL Bypass
2057+
2058+ ```yaml
2059+ API:
2060+ devTestAPI:
2061+ uri: https://dev-server.example.com/test
2062+ method: GET
2063+ sslConfig:
2064+ pinningEnabled: false
2065+ bypassPinning: true # Only for development!
2066+ bypassPinningWithFingerprint: false
2067+ headers:
2068+ Authorization: Bearer ${devToken}
2069+ ```
18592070
1860- ## Security Considerations
2071+ ## Security Best Practices
18612072
1862- 1. Certificate pinning provides protection against man-in-the-middle attacks
1863- 2. SSL bypass shouldn't be used in production environments as it will create security concerns.
1864- 3. Web applications cannot use SSL pinning and rely on browser-based verification
2073+ 1. **Production Environment**: Always use certificate pinning (`pinningEnabled: true`) for production APIs
2074+ 2. **Development Environment**: Use `bypassPinning: true` only during development
2075+ 3. **Dynamic Environments**: Use `bypassPinningWithFingerprint: true` when dealing with dynamic certificates or multiple environments
2076+ 4. **Certificate Storage**: Store certificate fingerprints securely using `setSecureStorage` or external methods
18652077
18662078---
18672079
0 commit comments