HTTP2TransportTLSEnabledTests.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /*
  2. * Copyright 2024, 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 Foundation
  17. import GRPCCore
  18. import GRPCNIOTransportHTTP2Posix
  19. import GRPCNIOTransportHTTP2TransportServices
  20. import NIOSSL
  21. import Testing
  22. #if canImport(Network)
  23. import Network
  24. #endif
  25. @Suite("HTTP/2 transport E2E tests with TLS enabled")
  26. struct HTTP2TransportTLSEnabledTests {
  27. // - MARK: Tests
  28. @Test(
  29. "When using defaults, server does not perform client verification",
  30. arguments: TransportKind.supported,
  31. TransportKind.supported
  32. )
  33. func testRPC_Defaults_OK(
  34. clientTransport: TransportKind,
  35. serverTransport: TransportKind
  36. ) async throws {
  37. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  38. let clientConfig = self.makeDefaultTLSClientConfig(
  39. for: clientTransport,
  40. certificateKeyPairs: certificateKeyPairs
  41. )
  42. let serverConfig = self.makeDefaultTLSServerConfig(
  43. for: serverTransport,
  44. certificateKeyPairs: certificateKeyPairs
  45. )
  46. try await self.withClientAndServer(
  47. clientConfig: clientConfig,
  48. serverConfig: serverConfig
  49. ) { control in
  50. await #expect(throws: Never.self) {
  51. try await self.executeUnaryRPC(control: control)
  52. }
  53. }
  54. }
  55. @Test(
  56. "When using mTLS defaults, both client and server verify each others' certificates",
  57. arguments: TransportKind.supported,
  58. TransportKind.supported
  59. )
  60. func testRPC_mTLS_OK(
  61. clientTransport: TransportKind,
  62. serverTransport: TransportKind
  63. ) async throws {
  64. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  65. let clientConfig = self.makeMTLSClientConfig(
  66. for: clientTransport,
  67. certificateKeyPairs: certificateKeyPairs,
  68. serverHostname: "localhost"
  69. )
  70. let serverConfig = self.makeMTLSServerConfig(
  71. for: serverTransport,
  72. certificateKeyPairs: certificateKeyPairs,
  73. includeClientCertificateInTrustRoots: true
  74. )
  75. try await self.withClientAndServer(
  76. clientConfig: clientConfig,
  77. serverConfig: serverConfig
  78. ) { control in
  79. await #expect(throws: Never.self) {
  80. try await self.executeUnaryRPC(control: control)
  81. }
  82. }
  83. }
  84. @Test(
  85. "Error is surfaced when client fails server verification",
  86. arguments: TransportKind.supported,
  87. TransportKind.supported
  88. )
  89. // Verification should fail because the custom hostname is missing on the client.
  90. func testClientFailsServerValidation(
  91. clientTransport: TransportKind,
  92. serverTransport: TransportKind
  93. ) async throws {
  94. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  95. let clientTransportConfig = self.makeDefaultTLSClientConfig(
  96. for: clientTransport,
  97. certificateKeyPairs: certificateKeyPairs,
  98. authority: "wrong-hostname"
  99. )
  100. let serverTransportConfig = self.makeDefaultTLSServerConfig(
  101. for: serverTransport,
  102. certificateKeyPairs: certificateKeyPairs
  103. )
  104. await #expect {
  105. try await self.withClientAndServer(
  106. clientConfig: clientTransportConfig,
  107. serverConfig: serverTransportConfig
  108. ) { control in
  109. try await self.executeUnaryRPC(control: control)
  110. }
  111. } throws: { error in
  112. let rootError = try #require(error as? RPCError)
  113. #expect(rootError.code == .unavailable)
  114. switch clientTransport {
  115. case .posix:
  116. #expect(
  117. rootError.message
  118. == "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
  119. )
  120. let sslError = try #require(rootError.cause as? NIOSSLExtraError)
  121. guard sslError == .failedToValidateHostname else {
  122. Issue.record(
  123. "Should be a NIOSSLExtraError.failedToValidateHostname error, but was: \(String(describing: rootError.cause))"
  124. )
  125. return false
  126. }
  127. #if canImport(Network)
  128. case .transportServices:
  129. #expect(rootError.message.starts(with: "Could not establish a connection to"))
  130. let nwError = try #require(rootError.cause as? NWError)
  131. guard case .tls(Security.errSSLBadCert) = nwError else {
  132. Issue.record(
  133. "Should be a NWError.tls(-9808/errSSLBadCert) error, but was: \(String(describing: rootError.cause))"
  134. )
  135. return false
  136. }
  137. #endif
  138. }
  139. return true
  140. }
  141. }
  142. @Test(
  143. "Error is surfaced when server fails client verification",
  144. arguments: TransportKind.supported,
  145. TransportKind.supported
  146. )
  147. // Verification should fail because the client does not offer a cert that
  148. // the server can use for mutual verification.
  149. func testServerFailsClientValidation(
  150. clientTransport: TransportKind,
  151. serverTransport: TransportKind
  152. ) async throws {
  153. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  154. let clientTransportConfig = self.makeDefaultTLSClientConfig(
  155. for: clientTransport,
  156. certificateKeyPairs: certificateKeyPairs
  157. )
  158. let serverTransportConfig = self.makeMTLSServerConfig(
  159. for: serverTransport,
  160. certificateKeyPairs: certificateKeyPairs,
  161. includeClientCertificateInTrustRoots: true
  162. )
  163. await #expect {
  164. try await self.withClientAndServer(
  165. clientConfig: clientTransportConfig,
  166. serverConfig: serverTransportConfig
  167. ) { control in
  168. try await self.executeUnaryRPC(control: control)
  169. }
  170. } throws: { error in
  171. let rootError = try #require(error as? RPCError)
  172. #expect(rootError.code == .unavailable)
  173. #expect(
  174. rootError.message
  175. == "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
  176. )
  177. switch clientTransport {
  178. case .posix:
  179. let sslError = try #require(rootError.cause as? NIOSSL.BoringSSLError)
  180. guard case .sslError = sslError else {
  181. Issue.record(
  182. "Should be a NIOSSL.sslError error, but was: \(String(describing: rootError.cause))"
  183. )
  184. return false
  185. }
  186. #if canImport(Network)
  187. case .transportServices:
  188. let nwError = try #require(rootError.cause as? NWError)
  189. guard case .tls(Security.errSSLPeerCertUnknown) = nwError else {
  190. // When the TLS handshake fails, the connection will be closed from the client.
  191. // Network.framework will generally surface the right SSL error (in this case, an "unknown
  192. // certificate" from the server), but it will sometimes instead return the broken pipe
  193. // error caused by the underlying TLS handshake handler closing the connection:
  194. // we should tolerate this.
  195. if case .posix(POSIXErrorCode.EPIPE) = nwError {
  196. return true
  197. }
  198. Issue.record(
  199. "Should be a NWError.tls(-9829/errSSLPeerCertUnknown) error, but was: \(String(describing: rootError.cause))"
  200. )
  201. return false
  202. }
  203. #endif
  204. }
  205. return true
  206. }
  207. }
  208. // - MARK: Test Utilities
  209. enum TLSEnabledTestsError: Error {
  210. case failedToImportPKCS12
  211. case unexpectedListeningAddress
  212. case serverError(cause: any Error)
  213. case clientError(cause: any Error)
  214. }
  215. enum TransportKind: Sendable {
  216. case posix
  217. #if canImport(Network)
  218. case transportServices
  219. #endif
  220. static var supported: [TransportKind] {
  221. #if canImport(Network)
  222. return [.posix, .transportServices]
  223. #else
  224. return [.posix]
  225. #endif
  226. }
  227. }
  228. struct Config<Transport, Security> {
  229. var security: Security
  230. var transport: Transport
  231. }
  232. enum ClientConfig {
  233. typealias Posix = Config<
  234. HTTP2ClientTransport.Posix.Config,
  235. HTTP2ClientTransport.Posix.TransportSecurity
  236. >
  237. case posix(Posix)
  238. #if canImport(Network)
  239. typealias TransportServices = Config<
  240. HTTP2ClientTransport.TransportServices.Config,
  241. HTTP2ClientTransport.TransportServices.TransportSecurity
  242. >
  243. case transportServices(TransportServices)
  244. #endif
  245. }
  246. enum ServerConfig {
  247. typealias Posix = Config<
  248. HTTP2ServerTransport.Posix.Config,
  249. HTTP2ServerTransport.Posix.TransportSecurity
  250. >
  251. case posix(Posix)
  252. #if canImport(Network)
  253. typealias TransportServices = Config<
  254. HTTP2ServerTransport.TransportServices.Config,
  255. HTTP2ServerTransport.TransportServices.TransportSecurity
  256. >
  257. case transportServices(TransportServices)
  258. #endif
  259. }
  260. private func makeDefaultPlaintextPosixClientConfig() -> ClientConfig.Posix {
  261. ClientConfig.Posix(
  262. security: .plaintext,
  263. transport: .defaults { config in
  264. config.backoff.initial = .milliseconds(100)
  265. config.backoff.multiplier = 1
  266. config.backoff.jitter = 0
  267. }
  268. )
  269. }
  270. #if canImport(Network)
  271. private func makeDefaultPlaintextTSClientConfig() -> ClientConfig.TransportServices {
  272. ClientConfig.TransportServices(
  273. security: .plaintext,
  274. transport: .defaults { config in
  275. config.backoff.initial = .milliseconds(100)
  276. config.backoff.multiplier = 1
  277. config.backoff.jitter = 0
  278. }
  279. )
  280. }
  281. #endif
  282. private func makeDefaultTLSClientConfig(
  283. for transportSecurity: TransportKind,
  284. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  285. authority: String? = "localhost"
  286. ) -> ClientConfig {
  287. switch transportSecurity {
  288. case .posix:
  289. var config = self.makeDefaultPlaintextPosixClientConfig()
  290. config.security = .tls {
  291. $0.trustRoots = .certificates([
  292. .bytes(certificateKeyPairs.server.certificate, format: .der)
  293. ])
  294. }
  295. config.transport.http2.authority = authority
  296. return .posix(config)
  297. #if canImport(Network)
  298. case .transportServices:
  299. var config = self.makeDefaultPlaintextTSClientConfig()
  300. config.security = .tls {
  301. $0.trustRoots = .certificates([
  302. .bytes(certificateKeyPairs.server.certificate, format: .der)
  303. ])
  304. }
  305. config.transport.http2.authority = authority
  306. return .transportServices(config)
  307. #endif
  308. }
  309. }
  310. #if canImport(Network)
  311. private func makeSecIdentityProvider(
  312. certificateBytes: [UInt8],
  313. privateKeyBytes: [UInt8]
  314. ) throws -> SecIdentity {
  315. let password = "somepassword"
  316. let bundle = NIOSSLPKCS12Bundle(
  317. certificateChain: [try NIOSSLCertificate(bytes: certificateBytes, format: .der)],
  318. privateKey: try NIOSSLPrivateKey(bytes: privateKeyBytes, format: .der)
  319. )
  320. let pkcs12Bytes = try bundle.serialize(passphrase: password.utf8)
  321. let options =
  322. [
  323. kSecImportExportPassphrase as String: password,
  324. kSecImportToMemoryOnly: kCFBooleanTrue!,
  325. ] as [AnyHashable: Any]
  326. var rawItems: CFArray?
  327. let status = SecPKCS12Import(
  328. Data(pkcs12Bytes) as CFData,
  329. options as CFDictionary,
  330. &rawItems
  331. )
  332. guard status == errSecSuccess else {
  333. Issue.record("Failed to import PKCS12 bundle: status \(status).")
  334. throw TLSEnabledTestsError.failedToImportPKCS12
  335. }
  336. let items = rawItems! as! [[String: Any]]
  337. let firstItem = items[0]
  338. let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
  339. return identity
  340. }
  341. #endif
  342. private func makeMTLSClientConfig(
  343. for transportKind: TransportKind,
  344. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  345. serverHostname: String?
  346. ) -> ClientConfig {
  347. switch transportKind {
  348. case .posix:
  349. var config = self.makeDefaultPlaintextPosixClientConfig()
  350. config.security = .mTLS(
  351. certificateChain: [.bytes(certificateKeyPairs.client.certificate, format: .der)],
  352. privateKey: .bytes(certificateKeyPairs.client.key, format: .der)
  353. ) {
  354. $0.trustRoots = .certificates([
  355. .bytes(certificateKeyPairs.server.certificate, format: .der)
  356. ])
  357. }
  358. config.transport.http2.authority = serverHostname
  359. return .posix(config)
  360. #if canImport(Network)
  361. case .transportServices:
  362. var config = self.makeDefaultPlaintextTSClientConfig()
  363. config.security = .mTLS {
  364. try self.makeSecIdentityProvider(
  365. certificateBytes: certificateKeyPairs.client.certificate,
  366. privateKeyBytes: certificateKeyPairs.client.key
  367. )
  368. } configure: {
  369. $0.trustRoots = .certificates([
  370. .bytes(certificateKeyPairs.server.certificate, format: .der)
  371. ])
  372. }
  373. config.transport.http2.authority = serverHostname
  374. return .transportServices(config)
  375. #endif
  376. }
  377. }
  378. private func makeDefaultPlaintextPosixServerConfig() -> ServerConfig.Posix {
  379. ServerConfig.Posix(security: .plaintext, transport: .defaults)
  380. }
  381. #if canImport(Network)
  382. private func makeDefaultPlaintextTSServerConfig() -> ServerConfig.TransportServices {
  383. ServerConfig.TransportServices(security: .plaintext, transport: .defaults)
  384. }
  385. #endif
  386. private func makeDefaultTLSServerConfig(
  387. for transportKind: TransportKind,
  388. certificateKeyPairs: SelfSignedCertificateKeyPairs
  389. ) -> ServerConfig {
  390. switch transportKind {
  391. case .posix:
  392. var config = self.makeDefaultPlaintextPosixServerConfig()
  393. config.security = .tls(
  394. certificateChain: [.bytes(certificateKeyPairs.server.certificate, format: .der)],
  395. privateKey: .bytes(certificateKeyPairs.server.key, format: .der)
  396. )
  397. return .posix(config)
  398. #if canImport(Network)
  399. case .transportServices:
  400. var config = self.makeDefaultPlaintextTSServerConfig()
  401. config.security = .tls {
  402. try self.makeSecIdentityProvider(
  403. certificateBytes: certificateKeyPairs.server.certificate,
  404. privateKeyBytes: certificateKeyPairs.server.key
  405. )
  406. }
  407. return .transportServices(config)
  408. #endif
  409. }
  410. }
  411. private func makeMTLSServerConfig(
  412. for transportKind: TransportKind,
  413. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  414. includeClientCertificateInTrustRoots: Bool
  415. ) -> ServerConfig {
  416. switch transportKind {
  417. case .posix:
  418. var config = self.makeDefaultPlaintextPosixServerConfig()
  419. config.security = .mTLS(
  420. certificateChain: [.bytes(certificateKeyPairs.server.certificate, format: .der)],
  421. privateKey: .bytes(certificateKeyPairs.server.key, format: .der)
  422. ) {
  423. if includeClientCertificateInTrustRoots {
  424. $0.trustRoots = .certificates([
  425. .bytes(certificateKeyPairs.client.certificate, format: .der)
  426. ])
  427. }
  428. }
  429. return .posix(config)
  430. #if canImport(Network)
  431. case .transportServices:
  432. var config = self.makeDefaultPlaintextTSServerConfig()
  433. config.security = .mTLS {
  434. try self.makeSecIdentityProvider(
  435. certificateBytes: certificateKeyPairs.server.certificate,
  436. privateKeyBytes: certificateKeyPairs.server.key
  437. )
  438. } configure: {
  439. if includeClientCertificateInTrustRoots {
  440. $0.trustRoots = .certificates([
  441. .bytes(certificateKeyPairs.client.certificate, format: .der)
  442. ])
  443. }
  444. }
  445. return .transportServices(config)
  446. #endif
  447. }
  448. }
  449. func withClientAndServer(
  450. clientConfig: ClientConfig,
  451. serverConfig: ServerConfig,
  452. _ test: (ControlClient) async throws -> Void
  453. ) async throws {
  454. try await withThrowingDiscardingTaskGroup { group in
  455. let server = self.makeServer(config: serverConfig)
  456. group.addTask {
  457. do {
  458. try await server.serve()
  459. } catch {
  460. throw TLSEnabledTestsError.serverError(cause: error)
  461. }
  462. }
  463. guard let address = try await server.listeningAddress?.ipv4 else {
  464. throw TLSEnabledTestsError.unexpectedListeningAddress
  465. }
  466. let target: any ResolvableTarget = .ipv4(host: address.host, port: address.port)
  467. let client = try self.makeClient(config: clientConfig, target: target)
  468. group.addTask {
  469. do {
  470. try await client.run()
  471. } catch {
  472. throw TLSEnabledTestsError.clientError(cause: error)
  473. }
  474. }
  475. let control = ControlClient(wrapping: client)
  476. try await test(control)
  477. client.beginGracefulShutdown()
  478. server.beginGracefulShutdown()
  479. }
  480. }
  481. private func makeServer(config: ServerConfig) -> GRPCServer {
  482. let services = [ControlService()]
  483. switch config {
  484. case .posix(let config):
  485. return GRPCServer(
  486. transport: .http2NIOPosix(
  487. address: .ipv4(host: "127.0.0.1", port: 0),
  488. transportSecurity: config.security,
  489. config: config.transport
  490. ),
  491. services: services
  492. )
  493. #if canImport(Network)
  494. case .transportServices(let config):
  495. return GRPCServer(
  496. transport: .http2NIOTS(
  497. address: .ipv4(host: "127.0.0.1", port: 0),
  498. transportSecurity: config.security,
  499. config: config.transport
  500. ),
  501. services: services
  502. )
  503. #endif
  504. }
  505. }
  506. private func makeClient(
  507. config: ClientConfig,
  508. target: any ResolvableTarget
  509. ) throws -> GRPCClient {
  510. let transport: any ClientTransport
  511. switch config {
  512. case .posix(let config):
  513. transport = try HTTP2ClientTransport.Posix(
  514. target: target,
  515. transportSecurity: config.security,
  516. config: config.transport,
  517. serviceConfig: ServiceConfig()
  518. )
  519. #if canImport(Network)
  520. case .transportServices(let config):
  521. transport = try HTTP2ClientTransport.TransportServices(
  522. target: target,
  523. transportSecurity: config.security,
  524. config: config.transport,
  525. serviceConfig: ServiceConfig()
  526. )
  527. #endif
  528. }
  529. return GRPCClient(transport: transport)
  530. }
  531. private func executeUnaryRPC(control: ControlClient) async throws {
  532. let input = ControlInput.with { $0.numberOfMessages = 1 }
  533. let request = ClientRequest(message: input)
  534. try await control.unary(request: request) { response in
  535. #expect(throws: Never.self) { try response.message }
  536. }
  537. }
  538. }