GRPCTLSConfiguration.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. /*
  2. * Copyright 2021, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import NIOSSL
  17. #if canImport(Network)
  18. import Network
  19. import NIOTransportServices
  20. import Security
  21. #endif
  22. /// TLS configuration.
  23. ///
  24. /// This structure allow configuring TLS for a wide range of TLS implementations. Some
  25. /// options are removed from the user's control to ensure the configuration complies with
  26. /// the gRPC specification.
  27. public struct GRPCTLSConfiguration {
  28. fileprivate enum Backend {
  29. /// Configuration for NIOSSSL.
  30. case nio(NIOConfiguration)
  31. #if canImport(Network)
  32. /// Configuration for Network.framework.
  33. case network(NetworkConfiguration)
  34. #endif
  35. }
  36. /// The TLS backend.
  37. private var backend: Backend
  38. private init(backend: Backend) {
  39. self.backend = backend
  40. }
  41. /// Return the configuration for NIOSSL or `nil` if Network.framework is being used as the
  42. /// TLS backend.
  43. internal var nioConfiguration: NIOConfiguration? {
  44. switch self.backend {
  45. case let .nio(configuration):
  46. return configuration
  47. #if canImport(Network)
  48. case .network:
  49. return nil
  50. #endif
  51. }
  52. }
  53. internal var isNetworkFrameworkTLSBackend: Bool {
  54. switch self.backend {
  55. case .nio:
  56. return false
  57. #if canImport(Network)
  58. case .network:
  59. return true
  60. #endif
  61. }
  62. }
  63. /// The server hostname override as used by the TLS SNI extension.
  64. ///
  65. /// This value is ignored when the configuration is used for a server.
  66. ///
  67. /// - Note: when using the Network.framework backend, this value may not be set to `nil`.
  68. internal var hostnameOverride: String? {
  69. get {
  70. switch self.backend {
  71. case let .nio(config):
  72. return config.hostnameOverride
  73. #if canImport(Network)
  74. case let .network(config):
  75. return config.hostnameOverride
  76. #endif
  77. }
  78. }
  79. set {
  80. switch self.backend {
  81. case var .nio(config):
  82. config.hostnameOverride = newValue
  83. self.backend = .nio(config)
  84. #if canImport(Network)
  85. case var .network(config):
  86. if #available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) {
  87. if let hostnameOverride = newValue {
  88. config.updateHostnameOverride(to: hostnameOverride)
  89. } else {
  90. // We can't unset the value so error instead.
  91. fatalError("Can't unset hostname override when using Network.framework TLS backend.")
  92. // FIXME: lazily set the value on the backend when applying the options.
  93. }
  94. } else {
  95. // We can only make the `.network` backend if we meet the above availability checks so
  96. // this should be unreachable.
  97. preconditionFailure()
  98. }
  99. self.backend = .network(config)
  100. #endif
  101. }
  102. }
  103. }
  104. /// Whether the configuration requires ALPN to be used.
  105. ///
  106. /// The Network.framework backend does not support this option and always requires ALPN.
  107. internal var requireALPN: Bool {
  108. get {
  109. switch self.backend {
  110. case let .nio(config):
  111. return config.requireALPN
  112. #if canImport(Network)
  113. case .network:
  114. return true
  115. #endif
  116. }
  117. }
  118. set {
  119. switch self.backend {
  120. case var .nio(config):
  121. config.requireALPN = newValue
  122. self.backend = .nio(config)
  123. #if canImport(Network)
  124. case .network:
  125. ()
  126. #endif
  127. }
  128. }
  129. }
  130. // Marked to silence the deprecation warning
  131. @available(*, deprecated)
  132. internal init(transforming deprecated: ClientConnection.Configuration.TLS) {
  133. self.backend = .nio(
  134. .init(
  135. configuration: deprecated.configuration,
  136. customVerificationCallback: deprecated.customVerificationCallback,
  137. hostnameOverride: deprecated.hostnameOverride,
  138. requireALPN: false // Not currently supported.
  139. )
  140. )
  141. }
  142. // Marked to silence the deprecation warning
  143. @available(*, deprecated)
  144. internal init(transforming deprecated: Server.Configuration.TLS) {
  145. self.backend = .nio(
  146. .init(configuration: deprecated.configuration, requireALPN: deprecated.requireALPN)
  147. )
  148. }
  149. @available(*, deprecated)
  150. internal var asDeprecatedClientConfiguration: ClientConnection.Configuration.TLS? {
  151. if case let .nio(config) = self.backend {
  152. var tls = ClientConnection.Configuration.TLS(
  153. configuration: config.configuration,
  154. hostnameOverride: config.hostnameOverride
  155. )
  156. tls.customVerificationCallback = config.customVerificationCallback
  157. return tls
  158. }
  159. return nil
  160. }
  161. @available(*, deprecated)
  162. internal var asDeprecatedServerConfiguration: Server.Configuration.TLS? {
  163. if case let .nio(config) = self.backend {
  164. return Server.Configuration.TLS(configuration: config.configuration)
  165. }
  166. return nil
  167. }
  168. }
  169. // MARK: - NIO Backend
  170. extension GRPCTLSConfiguration {
  171. internal struct NIOConfiguration {
  172. var configuration: TLSConfiguration
  173. var customVerificationCallback: NIOSSLCustomVerificationCallback?
  174. var hostnameOverride: String?
  175. // The client doesn't support this yet (https://github.com/grpc/grpc-swift/issues/1042).
  176. var requireALPN: Bool
  177. }
  178. /// TLS Configuration with suitable defaults for clients, using `NIOSSL`.
  179. ///
  180. /// This is a wrapper around `NIOSSL.TLSConfiguration` to restrict input to values which comply
  181. /// with the gRPC protocol.
  182. ///
  183. /// - Parameter certificateChain: The certificate to offer during negotiation, defaults to an
  184. /// empty array.
  185. /// - Parameter privateKey: The private key associated with the leaf certificate. This defaults
  186. /// to `nil`.
  187. /// - Parameter trustRoots: The trust roots to validate certificates, this defaults to using a
  188. /// root provided by the platform.
  189. /// - Parameter certificateVerification: Whether to verify the remote certificate. Defaults to
  190. /// `.fullVerification`.
  191. /// - Parameter hostnameOverride: Value to use for TLS SNI extension; this must not be an IP
  192. /// address, defaults to `nil`.
  193. /// - Parameter customVerificationCallback: A callback to provide to override the certificate verification logic,
  194. /// defaults to `nil`.
  195. public static func makeClientConfigurationBackedByNIOSSL(
  196. certificateChain: [NIOSSLCertificateSource] = [],
  197. privateKey: NIOSSLPrivateKeySource? = nil,
  198. trustRoots: NIOSSLTrustRoots = .default,
  199. certificateVerification: CertificateVerification = .fullVerification,
  200. hostnameOverride: String? = nil,
  201. customVerificationCallback: NIOSSLCustomVerificationCallback? = nil
  202. ) -> GRPCTLSConfiguration {
  203. var configuration = TLSConfiguration.makeClientConfiguration()
  204. configuration.minimumTLSVersion = .tlsv12
  205. configuration.certificateVerification = certificateVerification
  206. configuration.trustRoots = trustRoots
  207. configuration.certificateChain = certificateChain
  208. configuration.privateKey = privateKey
  209. configuration.applicationProtocols = GRPCApplicationProtocolIdentifier.client
  210. return GRPCTLSConfiguration.makeClientConfigurationBackedByNIOSSL(
  211. configuration: configuration,
  212. hostnameOverride: hostnameOverride,
  213. customVerificationCallback: customVerificationCallback
  214. )
  215. }
  216. /// Creates a gRPC TLS Configuration using the given `NIOSSL.TLSConfiguration`.
  217. ///
  218. /// - Note: If no ALPN tokens are set in `configuration.applicationProtocols` then "grpc-exp"
  219. /// and "h2" will be used.
  220. /// - Parameters:
  221. /// - configuration: The `NIOSSL.TLSConfiguration` to base this configuration on.
  222. /// - hostnameOverride: The hostname override to use for the TLS SNI extension.
  223. public static func makeClientConfigurationBackedByNIOSSL(
  224. configuration: TLSConfiguration,
  225. hostnameOverride: String? = nil,
  226. customVerificationCallback: NIOSSLCustomVerificationCallback? = nil
  227. ) -> GRPCTLSConfiguration {
  228. var configuration = configuration
  229. // Set the ALPN tokens if none were set.
  230. if configuration.applicationProtocols.isEmpty {
  231. configuration.applicationProtocols = GRPCApplicationProtocolIdentifier.client
  232. }
  233. let nioConfiguration = NIOConfiguration(
  234. configuration: configuration,
  235. customVerificationCallback: customVerificationCallback,
  236. hostnameOverride: hostnameOverride,
  237. requireALPN: false // We don't currently support this.
  238. )
  239. return GRPCTLSConfiguration(backend: .nio(nioConfiguration))
  240. }
  241. /// TLS Configuration with suitable defaults for servers.
  242. ///
  243. /// This is a wrapper around `NIOSSL.TLSConfiguration` to restrict input to values which comply
  244. /// with the gRPC protocol.
  245. ///
  246. /// - Parameter certificateChain: The certificate to offer during negotiation.
  247. /// - Parameter privateKey: The private key associated with the leaf certificate.
  248. /// - Parameter trustRoots: The trust roots to validate certificates, this defaults to using a
  249. /// root provided by the platform.
  250. /// - Parameter certificateVerification: Whether to verify the remote certificate. Defaults to
  251. /// `.none`.
  252. /// - Parameter requireALPN: Whether ALPN is required or not.
  253. public static func makeServerConfigurationBackedByNIOSSL(
  254. certificateChain: [NIOSSLCertificateSource],
  255. privateKey: NIOSSLPrivateKeySource,
  256. trustRoots: NIOSSLTrustRoots = .default,
  257. certificateVerification: CertificateVerification = .none,
  258. requireALPN: Bool = true
  259. ) -> GRPCTLSConfiguration {
  260. var configuration = TLSConfiguration.makeServerConfiguration(
  261. certificateChain: certificateChain,
  262. privateKey: privateKey
  263. )
  264. configuration.minimumTLSVersion = .tlsv12
  265. configuration.certificateVerification = certificateVerification
  266. configuration.trustRoots = trustRoots
  267. configuration.applicationProtocols = GRPCApplicationProtocolIdentifier.server
  268. return GRPCTLSConfiguration.makeServerConfigurationBackedByNIOSSL(
  269. configuration: configuration,
  270. requireALPN: requireALPN
  271. )
  272. }
  273. /// Creates a gRPC TLS Configuration suitable for servers using the given
  274. /// `NIOSSL.TLSConfiguration`.
  275. ///
  276. /// - Note: If no ALPN tokens are set in `configuration.applicationProtocols` then "grpc-exp",
  277. /// "h2", and "http/1.1" will be used.
  278. /// - Parameters:
  279. /// - configuration: The `NIOSSL.TLSConfiguration` to base this configuration on.
  280. /// - requiresALPN: Whether the server enforces ALPN. Defaults to `true`.
  281. public static func makeServerConfigurationBackedByNIOSSL(
  282. configuration: TLSConfiguration,
  283. requireALPN: Bool = true
  284. ) -> GRPCTLSConfiguration {
  285. var configuration = configuration
  286. // Set the ALPN tokens if none were set.
  287. if configuration.applicationProtocols.isEmpty {
  288. configuration.applicationProtocols = GRPCApplicationProtocolIdentifier.server
  289. }
  290. let nioConfiguration = NIOConfiguration(
  291. configuration: configuration,
  292. customVerificationCallback: nil,
  293. hostnameOverride: nil,
  294. requireALPN: requireALPN
  295. )
  296. return GRPCTLSConfiguration(backend: .nio(nioConfiguration))
  297. }
  298. internal func makeNIOSSLContext() throws -> NIOSSLContext? {
  299. switch self.backend {
  300. case let .nio(configuration):
  301. return try NIOSSLContext(configuration: configuration.configuration)
  302. #if canImport(Network)
  303. case .network:
  304. return nil
  305. #endif
  306. }
  307. }
  308. internal var nioSSLCustomVerificationCallback: NIOSSLCustomVerificationCallback? {
  309. switch self.backend {
  310. case let .nio(configuration):
  311. return configuration.customVerificationCallback
  312. #if canImport(Network)
  313. case .network:
  314. return nil
  315. #endif
  316. }
  317. }
  318. internal mutating func updateNIOCertificateChain(to certificateChain: [NIOSSLCertificate]) {
  319. self.modifyingNIOConfiguration {
  320. $0.configuration.certificateChain = certificateChain.map { .certificate($0) }
  321. }
  322. }
  323. internal mutating func updateNIOPrivateKey(to privateKey: NIOSSLPrivateKey) {
  324. self.modifyingNIOConfiguration {
  325. $0.configuration.privateKey = .privateKey(privateKey)
  326. }
  327. }
  328. internal mutating func updateNIOTrustRoots(to trustRoots: NIOSSLTrustRoots) {
  329. self.modifyingNIOConfiguration {
  330. $0.configuration.trustRoots = trustRoots
  331. }
  332. }
  333. internal mutating func updateNIOCertificateVerification(
  334. to verification: CertificateVerification
  335. ) {
  336. self.modifyingNIOConfiguration {
  337. $0.configuration.certificateVerification = verification
  338. }
  339. }
  340. internal mutating func updateNIOCustomVerificationCallback(
  341. to callback: @escaping NIOSSLCustomVerificationCallback
  342. ) {
  343. self.modifyingNIOConfiguration {
  344. $0.customVerificationCallback = callback
  345. }
  346. }
  347. private mutating func modifyingNIOConfiguration(_ modify: (inout NIOConfiguration) -> Void) {
  348. switch self.backend {
  349. case var .nio(configuration):
  350. modify(&configuration)
  351. self.backend = .nio(configuration)
  352. #if canImport(Network)
  353. case .network:
  354. preconditionFailure()
  355. #endif
  356. }
  357. }
  358. }
  359. // MARK: - Network Backend
  360. #if canImport(Network)
  361. extension GRPCTLSConfiguration {
  362. internal struct NetworkConfiguration {
  363. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  364. internal var options: NWProtocolTLS.Options {
  365. get {
  366. return self._options as! NWProtocolTLS.Options
  367. }
  368. set {
  369. self._options = newValue
  370. }
  371. }
  372. /// Always a NWProtocolTLS.Options.
  373. ///
  374. /// This somewhat insane type-erasure is necessary because we need to availability-guard the NWProtocolTLS.Options
  375. /// (it isn't available in older SDKs), but we cannot have stored properties guarded by availability in this way, only
  376. /// computed ones. To that end, we have to erase the type and then un-erase it. This is fairly silly.
  377. private var _options: Any
  378. // This is set privately via `updateHostnameOverride(to:)` because we require availability
  379. // guards to update the value in the underlying `sec_protocol_options`.
  380. internal private(set) var hostnameOverride: String?
  381. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  382. init(options: NWProtocolTLS.Options, hostnameOverride: String?) {
  383. self._options = options
  384. self.hostnameOverride = hostnameOverride
  385. }
  386. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  387. internal mutating func updateHostnameOverride(to hostnameOverride: String) {
  388. self.hostnameOverride = hostnameOverride
  389. sec_protocol_options_set_tls_server_name(
  390. self.options.securityProtocolOptions,
  391. hostnameOverride
  392. )
  393. }
  394. }
  395. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  396. public static func makeClientConfigurationBackedByNetworkFramework(
  397. identity: SecIdentity? = nil,
  398. hostnameOverride: String? = nil,
  399. verifyCallbackWithQueue: (sec_protocol_verify_t, DispatchQueue)? = nil
  400. ) -> GRPCTLSConfiguration {
  401. let options = NWProtocolTLS.Options()
  402. if let identity = identity {
  403. sec_protocol_options_set_local_identity(
  404. options.securityProtocolOptions,
  405. sec_identity_create(identity)!
  406. )
  407. }
  408. if let hostnameOverride = hostnameOverride {
  409. sec_protocol_options_set_tls_server_name(
  410. options.securityProtocolOptions,
  411. hostnameOverride
  412. )
  413. }
  414. if let verifyCallbackWithQueue = verifyCallbackWithQueue {
  415. sec_protocol_options_set_verify_block(
  416. options.securityProtocolOptions,
  417. verifyCallbackWithQueue.0,
  418. verifyCallbackWithQueue.1
  419. )
  420. }
  421. if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) {
  422. sec_protocol_options_set_min_tls_protocol_version(options.securityProtocolOptions, .TLSv12)
  423. } else {
  424. sec_protocol_options_set_tls_min_version(options.securityProtocolOptions, .tlsProtocol12)
  425. }
  426. for `protocol` in GRPCApplicationProtocolIdentifier.client {
  427. sec_protocol_options_add_tls_application_protocol(
  428. options.securityProtocolOptions,
  429. `protocol`
  430. )
  431. }
  432. return .makeClientConfigurationBackedByNetworkFramework(
  433. options: options,
  434. hostnameOverride: hostnameOverride
  435. )
  436. }
  437. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  438. public static func makeClientConfigurationBackedByNetworkFramework(
  439. options: NWProtocolTLS.Options,
  440. hostnameOverride: String? = nil
  441. ) -> GRPCTLSConfiguration {
  442. let network = NetworkConfiguration(options: options, hostnameOverride: hostnameOverride)
  443. return GRPCTLSConfiguration(backend: .network(network))
  444. }
  445. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  446. public static func makeServerConfigurationBackedByNetworkFramework(
  447. identity: SecIdentity
  448. ) -> GRPCTLSConfiguration {
  449. let options = NWProtocolTLS.Options()
  450. sec_protocol_options_set_local_identity(
  451. options.securityProtocolOptions,
  452. sec_identity_create(identity)!
  453. )
  454. if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) {
  455. sec_protocol_options_set_min_tls_protocol_version(options.securityProtocolOptions, .TLSv12)
  456. } else {
  457. sec_protocol_options_set_tls_min_version(options.securityProtocolOptions, .tlsProtocol12)
  458. }
  459. for `protocol` in GRPCApplicationProtocolIdentifier.server {
  460. sec_protocol_options_add_tls_application_protocol(
  461. options.securityProtocolOptions,
  462. `protocol`
  463. )
  464. }
  465. return GRPCTLSConfiguration.makeServerConfigurationBackedByNetworkFramework(options: options)
  466. }
  467. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  468. public static func makeServerConfigurationBackedByNetworkFramework(
  469. options: NWProtocolTLS.Options
  470. ) -> GRPCTLSConfiguration {
  471. let network = NetworkConfiguration(options: options, hostnameOverride: nil)
  472. return GRPCTLSConfiguration(backend: .network(network))
  473. }
  474. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  475. internal mutating func updateNetworkLocalIdentity(to identity: SecIdentity) {
  476. self.modifyingNetworkConfiguration {
  477. sec_protocol_options_set_local_identity(
  478. $0.options.securityProtocolOptions,
  479. sec_identity_create(identity)!
  480. )
  481. }
  482. }
  483. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  484. internal mutating func updateNetworkVerifyCallbackWithQueue(
  485. callback: @escaping sec_protocol_verify_t,
  486. queue: DispatchQueue
  487. ) {
  488. self.modifyingNetworkConfiguration {
  489. sec_protocol_options_set_verify_block(
  490. $0.options.securityProtocolOptions,
  491. callback,
  492. queue
  493. )
  494. }
  495. }
  496. private mutating func modifyingNetworkConfiguration(
  497. _ modify: (inout NetworkConfiguration) -> Void
  498. ) {
  499. switch self.backend {
  500. case var .network(configuration):
  501. modify(&configuration)
  502. self.backend = .network(configuration)
  503. case .nio:
  504. preconditionFailure()
  505. }
  506. }
  507. }
  508. #endif
  509. #if canImport(Network)
  510. extension GRPCTLSConfiguration {
  511. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  512. internal func applyNetworkTLSOptions(
  513. to bootstrap: NIOTSConnectionBootstrap
  514. ) -> NIOTSConnectionBootstrap {
  515. switch self.backend {
  516. case let .network(configuration):
  517. return bootstrap.tlsOptions(configuration.options)
  518. case .nio:
  519. // We're using NIOSSL with Network.framework; that's okay and permitted for backwards
  520. // compatibility.
  521. return bootstrap
  522. }
  523. }
  524. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  525. internal func applyNetworkTLSOptions(
  526. to bootstrap: NIOTSListenerBootstrap
  527. ) -> NIOTSListenerBootstrap {
  528. switch self.backend {
  529. case let .network(configuration):
  530. return bootstrap.tlsOptions(configuration.options)
  531. case .nio:
  532. // We're using NIOSSL with Network.framework; that's okay and permitted for backwards
  533. // compatibility.
  534. return bootstrap
  535. }
  536. }
  537. }
  538. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  539. extension NIOTSConnectionBootstrap {
  540. internal func tlsOptions(
  541. from configuration: GRPCTLSConfiguration
  542. ) -> NIOTSConnectionBootstrap {
  543. return configuration.applyNetworkTLSOptions(to: self)
  544. }
  545. }
  546. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  547. extension NIOTSListenerBootstrap {
  548. internal func tlsOptions(
  549. from configuration: GRPCTLSConfiguration
  550. ) -> NIOTSListenerBootstrap {
  551. return configuration.applyNetworkTLSOptions(to: self)
  552. }
  553. }
  554. #endif