diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index b892620ca9..8acf79f32a 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -17,7 +17,7 @@ Bugfixes: Features: -- kotlin/c++: add option to support platform provided certificates validation interfaces on Android. (:issue `#2144 <2144>`) +- api: add option to support platform provided certificates validation interfaces on iOS and Android. (:issue `#2144 <2144>`) - api: Add a ``setPerTryIdleTimeoutSeconds()`` method to C++ EngineBuilder. - kotlin: add a way to tell Envoy Mobile to respect system proxy settings by calling an ``enableProxying(true)`` method on the engine builder. (:issue:`#2416 <2416>`) - kotlin: add a ``enableSkipDNSLookupForProxiedRequests(true)`` knob for controlling whether Envoy waits on DNS response in the dynamic forward proxy filter for proxied requests. (:issue:`#2602 <2602>`) diff --git a/library/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator.cc b/library/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator.cc index fa27a747fb..b4e025445a 100644 --- a/library/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator.cc +++ b/library/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator.cc @@ -145,7 +145,7 @@ void PlatformBridgeCertValidator::PendingValidation::postVerifyResultAndCleanUp( if (weak_alive_indicator.expired()) { return; } - ENVOY_LOG(trace, "Get validation result for {} from platform", host_name_); + ENVOY_LOG(trace, "Got validation result for {} from platform", host_name_); parent_.validation_threads_[thread_id].join(); parent_.validation_threads_.erase(thread_id); if (error_counter.has_value()) { @@ -158,7 +158,9 @@ void PlatformBridgeCertValidator::PendingValidation::postVerifyResultAndCleanUp( "Finished platform cert validation for {}, post result callback to network thread", host_name_); - parent_.platform_validator_->release_validator(); + if (parent_.platform_validator_->release_validator) { + parent_.platform_validator_->release_validator(); + } } } // namespace Tls diff --git a/library/common/network/BUILD b/library/common/network/BUILD index c60ffff308..5e3179a399 100644 --- a/library/common/network/BUILD +++ b/library/common/network/BUILD @@ -76,3 +76,23 @@ envoy_cc_library( "@envoy//source/common/network:socket_interface_lib", ], ) + +cc_library( + name = "apple_platform_cert_verifier", + srcs = select({ + "@envoy//bazel:apple": ["apple_platform_cert_verifier.cc"], + "//conditions:default": [], + }), + hdrs = select({ + "@envoy//bazel:apple": ["apple_platform_cert_verifier.h"], + "//conditions:default": [], + }), + deps = select({ + "@envoy//bazel:apple": [ + "//library/common/extensions/cert_validator/platform_bridge:c_types_lib", + "//library/common:envoy_main_interface_lib", + "@envoy//bazel:boringssl", + ], + "//conditions:default": [], + }), +) diff --git a/library/common/network/apple_platform_cert_verifier.cc b/library/common/network/apple_platform_cert_verifier.cc new file mode 100644 index 0000000000..26fcb50e2a --- /dev/null +++ b/library/common/network/apple_platform_cert_verifier.cc @@ -0,0 +1,120 @@ +#include "library/common/network/apple_platform_cert_verifier.h" + +#include +#include +#include +#include +#include + +#include "library/common/extensions/cert_validator/platform_bridge/c_types.h" +#include "library/common/main_interface.h" +#include "openssl/ssl.h" + +// NOLINT(namespace-envoy) + +// Returns a new CFMutableArrayRef containing a series of SecPolicyRefs to be +// added to a SecTrustRef used to validate a certificate for an SSL server, +// or NULL on failure. +CFMutableArrayRef CreateTrustPolicies() { + CFMutableArrayRef policies = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + if (!policies) { + return NULL; + } + + SecPolicyRef ssl_policy = SecPolicyCreateBasicX509(); + CFArrayAppendValue(policies, ssl_policy); + CFRelease(ssl_policy); + + ssl_policy = SecPolicyCreateSSL(true, NULL); + CFArrayAppendValue(policies, ssl_policy); + CFRelease(ssl_policy); + + return policies; +} + +// Returns a new CFMutableArrayRef containing the specified certificates +// in the form expected by Security.framework and Keychain Services, or +// NULL on failure. +CFMutableArrayRef CreateSecCertificateArray(const envoy_data* certs, uint8_t num_certs) { + CFMutableArrayRef cert_array = + CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + + if (!cert_array) { + return NULL; + } + + for (uint8_t i = 0; i < num_certs; ++i) { + CFDataRef cert_data = CFDataCreate(kCFAllocatorDefault, certs[i].bytes, certs[i].length); + if (!cert_data) { + CFRelease(cert_array); + return NULL; + } + SecCertificateRef sec_cert = SecCertificateCreateWithData(NULL, cert_data); + if (!sec_cert) { + CFRelease(cert_array); + return NULL; + } + CFArrayAppendValue(cert_array, sec_cert); + CFRelease(cert_data); + } + return cert_array; +} + +// Helper to create a envoy_cert_validation_result. +envoy_cert_validation_result make_result(envoy_status_t status, uint8_t tls_alert, + const char* error_details) { + envoy_cert_validation_result result; + result.result = status; + result.tls_alert = tls_alert; + result.error_details = error_details; + return result; +} + +static envoy_cert_validation_result verify_cert(const envoy_data* certs, uint8_t num_certs, + const char* hostname) { + CFArrayRef trust_policies = CreateTrustPolicies(); + if (!trust_policies) { + return make_result(ENVOY_FAILURE, SSL_AD_CERTIFICATE_UNKNOWN, + "validation couldn't be conducted."); + } + + CFMutableArrayRef cert_array = CreateSecCertificateArray(certs, num_certs); + if (!cert_array) { + return make_result(ENVOY_FAILURE, SSL_AD_CERTIFICATE_UNKNOWN, + "validation couldn't be conducted."); + } + + SecTrustRef trust = NULL; + OSStatus status = SecTrustCreateWithCertificates(cert_array, trust_policies, &trust); + if (status) { + return make_result(ENVOY_FAILURE, SSL_AD_CERTIFICATE_UNKNOWN, + "validation couldn't be conducted."); + } + + CFErrorRef error; + bool verified = SecTrustEvaluateWithError(trust, &error); + + CFRelease(cert_array); + CFRelease(trust); + + if (!verified) { + return make_result(ENVOY_FAILURE, SSL_AD_CERTIFICATE_UNKNOWN, + "validation couldn't be conducted."); + } + return make_result(ENVOY_SUCCESS, 0, ""); +} + +#ifdef __cplusplus +extern "C" { +#endif + +void register_apple_platform_cert_verifier() { + envoy_cert_validator* api = (envoy_cert_validator*)safe_malloc(sizeof(envoy_cert_validator)); + api->validate_cert = verify_cert; + api->release_validator = NULL; + register_platform_api("platform_cert_validator", api); +} + +#ifdef __cplusplus +} +#endif diff --git a/library/common/network/apple_platform_cert_verifier.h b/library/common/network/apple_platform_cert_verifier.h new file mode 100644 index 0000000000..63ba9e39ca --- /dev/null +++ b/library/common/network/apple_platform_cert_verifier.h @@ -0,0 +1,16 @@ +#pragma once + +// NOLINT(namespace-envoy) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Registers the Apple platform cert verifier API. + */ +void register_apple_platform_cert_verifier(); + +#ifdef __cplusplus +} +#endif diff --git a/library/objective-c/BUILD b/library/objective-c/BUILD index 60eec3bb16..fb4f6ad927 100644 --- a/library/objective-c/BUILD +++ b/library/objective-c/BUILD @@ -40,6 +40,7 @@ objc_library( ":envoy_objc_bridge_lib", "//library/common:envoy_main_interface_lib", "//library/common/api:c_types", + "//library/common/network:apple_platform_cert_verifier", ], ) diff --git a/library/objective-c/EnvoyConfiguration.m b/library/objective-c/EnvoyConfiguration.m index bf243200f4..b42f857bcc 100644 --- a/library/objective-c/EnvoyConfiguration.m +++ b/library/objective-c/EnvoyConfiguration.m @@ -19,6 +19,7 @@ - (instancetype)initWithAdminInterfaceEnabled:(BOOL)adminInterfaceEnabled enableInterfaceBinding:(BOOL)enableInterfaceBinding enableDrainPostDnsRefresh:(BOOL)enableDrainPostDnsRefresh enforceTrustChainVerification:(BOOL)enforceTrustChainVerification + enablePlatformCertificateValidation:(BOOL)enablePlatformCertificateValidation forceIPv6:(BOOL)forceIPv6 h2ConnectionKeepaliveIdleIntervalMilliseconds: (UInt32)h2ConnectionKeepaliveIdleIntervalMilliseconds @@ -64,6 +65,7 @@ - (instancetype)initWithAdminInterfaceEnabled:(BOOL)adminInterfaceEnabled self.enableInterfaceBinding = enableInterfaceBinding; self.enableDrainPostDnsRefresh = enableDrainPostDnsRefresh; self.enforceTrustChainVerification = enforceTrustChainVerification; + self.enablePlatformCertificateValidation = enablePlatformCertificateValidation; self.forceIPv6 = forceIPv6; self.h2ConnectionKeepaliveIdleIntervalMilliseconds = h2ConnectionKeepaliveIdleIntervalMilliseconds; @@ -187,8 +189,10 @@ - (nullable NSString *)resolveTemplate:(NSString *)templateYAML { [definitions appendFormat:@"- &stats_flush_interval %lus\n", (unsigned long)self.statsFlushSeconds]; - NSString *cert_validator_template = - [[NSString alloc] initWithUTF8String:default_cert_validation_context_template]; + NSString *cert_validator_template = self.enablePlatformCertificateValidation + ? @(platform_cert_validation_context_template) + : @(default_cert_validation_context_template); + [definitions appendFormat:@"%@\n", cert_validator_template]; NSMutableArray *stat_sinks_config = [self.statsSinks mutableCopy]; diff --git a/library/objective-c/EnvoyEngine.h b/library/objective-c/EnvoyEngine.h index 2f59223449..80811c65ba 100644 --- a/library/objective-c/EnvoyEngine.h +++ b/library/objective-c/EnvoyEngine.h @@ -361,6 +361,7 @@ extern const int kEnvoyFilterResumeStatusResumeIteration; @property (nonatomic, assign) BOOL enableInterfaceBinding; @property (nonatomic, assign) BOOL enableDrainPostDnsRefresh; @property (nonatomic, assign) BOOL enforceTrustChainVerification; +@property (nonatomic, assign) BOOL enablePlatformCertificateValidation; @property (nonatomic, assign) BOOL forceIPv6; @property (nonatomic, assign) UInt32 h2ConnectionKeepaliveIdleIntervalMilliseconds; @property (nonatomic, assign) UInt32 h2ConnectionKeepaliveTimeoutSeconds; @@ -398,6 +399,7 @@ extern const int kEnvoyFilterResumeStatusResumeIteration; enableInterfaceBinding:(BOOL)enableInterfaceBinding enableDrainPostDnsRefresh:(BOOL)enableDrainPostDnsRefresh enforceTrustChainVerification:(BOOL)enforceTrustChainVerification + enablePlatformCertificateValidation:(BOOL)enablePlatformCertificateValidation forceIPv6:(BOOL)forceIPv6 h2ConnectionKeepaliveIdleIntervalMilliseconds: (UInt32)h2ConnectionKeepaliveIdleIntervalMilliseconds diff --git a/library/objective-c/EnvoyEngineImpl.m b/library/objective-c/EnvoyEngineImpl.m index b6413bf887..e2a7358003 100644 --- a/library/objective-c/EnvoyEngineImpl.m +++ b/library/objective-c/EnvoyEngineImpl.m @@ -5,6 +5,7 @@ #include "library/common/api/c_types.h" #import "library/common/main_interface.h" +#import "library/common/network/apple_platform_cert_verifier.h" #import "library/common/types/c_types.h" #import "library/common/extensions/key_value/platform/c_types.h" @@ -555,6 +556,10 @@ - (int)runWithTemplate:(NSString *)yaml [self registerKeyValueStore:name keyValueStore:config.keyValueStores[name]]; } + if (config.enablePlatformCertificateValidation) { + register_apple_platform_cert_verifier(); + } + return [self runWithConfigYAML:resolvedYAML logLevel:logLevel]; } diff --git a/library/swift/EngineBuilder.swift b/library/swift/EngineBuilder.swift index 5d97c5c2d1..a96b1d8a31 100644 --- a/library/swift/EngineBuilder.swift +++ b/library/swift/EngineBuilder.swift @@ -27,6 +27,7 @@ open class EngineBuilder: NSObject { private var enableBrotli: Bool = false private var enableInterfaceBinding: Bool = false private var enforceTrustChainVerification: Bool = true + private var enablePlatformCertificateValidation: Bool = true private var enableDrainPostDnsRefresh: Bool = false private var forceIPv6: Bool = false private var h2ConnectionKeepaliveIdleIntervalMilliseconds: UInt32 = 1 @@ -242,6 +243,18 @@ open class EngineBuilder: NSObject { return self } + /// Specify whether to use the platform certificate verifier. + /// + /// - parameter enablePlatformCertificateValidation: whether to use the platform verifier. + /// + /// - returns: This builder. + @discardableResult + public func enablePlatformCertificateValidation( + _ enablePlatformCertificateValidation: Bool) -> Self { + self.enablePlatformCertificateValidation = enablePlatformCertificateValidation + return self + } + /// Specify whether to remap IPv4 addresses to the IPv6 space and always force connections /// to use IPv6. Note this is an experimental option and should be enabled with caution. /// @@ -519,6 +532,7 @@ open class EngineBuilder: NSObject { enableInterfaceBinding: self.enableInterfaceBinding, enableDrainPostDnsRefresh: self.enableDrainPostDnsRefresh, enforceTrustChainVerification: self.enforceTrustChainVerification, + enablePlatformCertificateValidation: self.enablePlatformCertificateValidation, forceIPv6: self.forceIPv6, h2ConnectionKeepaliveIdleIntervalMilliseconds: self.h2ConnectionKeepaliveIdleIntervalMilliseconds, diff --git a/test/swift/EngineBuilderTests.swift b/test/swift/EngineBuilderTests.swift index 92fd652f38..65ae9c9240 100644 --- a/test/swift/EngineBuilderTests.swift +++ b/test/swift/EngineBuilderTests.swift @@ -471,6 +471,7 @@ final class EngineBuilderTests: XCTestCase { enableInterfaceBinding: true, enableDrainPostDnsRefresh: false, enforceTrustChainVerification: false, + enablePlatformCertificateValidation: false, forceIPv6: false, h2ConnectionKeepaliveIdleIntervalMilliseconds: 1, h2ConnectionKeepaliveTimeoutSeconds: 333, @@ -506,6 +507,12 @@ final class EngineBuilderTests: XCTestCase { XCTAssertTrue(resolvedYAML.contains("&dns_multiple_addresses true")) XCTAssertTrue(resolvedYAML.contains("&enable_interface_binding true")) XCTAssertTrue(resolvedYAML.contains("&trust_chain_verification ACCEPT_UNTRUSTED")) + XCTAssertTrue(resolvedYAML.contains(""" +&validation_context + trusted_ca: + inline_string: *tls_root_certs +""" + )) XCTAssertTrue(resolvedYAML.contains("&enable_drain_post_dns_refresh false")) // HTTP/2 @@ -558,6 +565,7 @@ final class EngineBuilderTests: XCTestCase { enableInterfaceBinding: false, enableDrainPostDnsRefresh: true, enforceTrustChainVerification: true, + enablePlatformCertificateValidation: true, forceIPv6: true, h2ConnectionKeepaliveIdleIntervalMilliseconds: 1, h2ConnectionKeepaliveTimeoutSeconds: 333, @@ -586,6 +594,13 @@ final class EngineBuilderTests: XCTestCase { XCTAssertTrue(resolvedYAML.contains("&dns_multiple_addresses false")) XCTAssertTrue(resolvedYAML.contains("&enable_interface_binding false")) XCTAssertTrue(resolvedYAML.contains("&trust_chain_verification VERIFY_TRUST_CHAIN")) + XCTAssertTrue(resolvedYAML.contains( +""" +&validation_context + custom_validator_config: + name: "envoy_mobile.cert_validator.platform_bridge_cert_validator" +""" + )) XCTAssertTrue(resolvedYAML.contains("&h2_delay_keepalive_timeout false")) XCTAssertTrue(resolvedYAML.contains("&enable_drain_post_dns_refresh true")) @@ -611,6 +626,7 @@ final class EngineBuilderTests: XCTestCase { enableInterfaceBinding: false, enableDrainPostDnsRefresh: false, enforceTrustChainVerification: true, + enablePlatformCertificateValidation: true, forceIPv6: true, h2ConnectionKeepaliveIdleIntervalMilliseconds: 222, h2ConnectionKeepaliveTimeoutSeconds: 333, diff --git a/test/swift/apps/experimental/ViewController.swift b/test/swift/apps/experimental/ViewController.swift index 95908f87db..d62df15ea4 100644 --- a/test/swift/apps/experimental/ViewController.swift +++ b/test/swift/apps/experimental/ViewController.swift @@ -25,6 +25,7 @@ final class ViewController: UITableViewController { .h2ExtendKeepaliveTimeout(true) .enableAdminInterface() .enableInterfaceBinding(true) + .enablePlatformCertificateValidation(true) .addNativeFilter( name: "envoy.filters.http.buffer", typedConfig: """