HTTP2TransportTLSEnabledTests.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  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. struct Config<Transport, Security> {
  216. var security: Security
  217. var transport: Transport
  218. }
  219. enum ClientConfig {
  220. typealias Posix = Config<
  221. HTTP2ClientTransport.Posix.Config,
  222. HTTP2ClientTransport.Posix.TransportSecurity
  223. >
  224. case posix(Posix)
  225. #if canImport(Network)
  226. typealias TransportServices = Config<
  227. HTTP2ClientTransport.TransportServices.Config,
  228. HTTP2ClientTransport.TransportServices.TransportSecurity
  229. >
  230. case transportServices(TransportServices)
  231. #endif
  232. }
  233. enum ServerConfig {
  234. typealias Posix = Config<
  235. HTTP2ServerTransport.Posix.Config,
  236. HTTP2ServerTransport.Posix.TransportSecurity
  237. >
  238. case posix(Posix)
  239. #if canImport(Network)
  240. typealias TransportServices = Config<
  241. HTTP2ServerTransport.TransportServices.Config,
  242. HTTP2ServerTransport.TransportServices.TransportSecurity
  243. >
  244. case transportServices(TransportServices)
  245. #endif
  246. }
  247. private func makeDefaultPlaintextPosixClientConfig() -> ClientConfig.Posix {
  248. ClientConfig.Posix(
  249. security: .plaintext,
  250. transport: .defaults { config in
  251. config.backoff.initial = .milliseconds(100)
  252. config.backoff.multiplier = 1
  253. config.backoff.jitter = 0
  254. }
  255. )
  256. }
  257. #if canImport(Network)
  258. private func makeDefaultPlaintextTSClientConfig() -> ClientConfig.TransportServices {
  259. ClientConfig.TransportServices(
  260. security: .plaintext,
  261. transport: .defaults { config in
  262. config.backoff.initial = .milliseconds(100)
  263. config.backoff.multiplier = 1
  264. config.backoff.jitter = 0
  265. }
  266. )
  267. }
  268. #endif
  269. private func makeDefaultTLSClientConfig(
  270. for transportSecurity: TransportKind,
  271. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  272. authority: String? = "localhost"
  273. ) -> ClientConfig {
  274. switch transportSecurity {
  275. case .posix:
  276. var config = self.makeDefaultPlaintextPosixClientConfig()
  277. config.security = .tls {
  278. $0.trustRoots = .certificates([
  279. .bytes(certificateKeyPairs.server.certificate, format: .der)
  280. ])
  281. }
  282. config.transport.http2.authority = authority
  283. return .posix(config)
  284. #if canImport(Network)
  285. case .transportServices:
  286. var config = self.makeDefaultPlaintextTSClientConfig()
  287. config.security = .tls {
  288. $0.trustRoots = .certificates([
  289. .bytes(certificateKeyPairs.server.certificate, format: .der)
  290. ])
  291. }
  292. config.transport.http2.authority = authority
  293. return .transportServices(config)
  294. #endif
  295. }
  296. }
  297. #if canImport(Network)
  298. private func makeSecIdentityProvider(
  299. certificateBytes: [UInt8],
  300. privateKeyBytes: [UInt8]
  301. ) throws -> SecIdentity {
  302. let password = "somepassword"
  303. let bundle = NIOSSLPKCS12Bundle(
  304. certificateChain: [try NIOSSLCertificate(bytes: certificateBytes, format: .der)],
  305. privateKey: try NIOSSLPrivateKey(bytes: privateKeyBytes, format: .der)
  306. )
  307. let pkcs12Bytes = try bundle.serialize(passphrase: password.utf8)
  308. let options =
  309. [
  310. kSecImportExportPassphrase as String: password,
  311. kSecImportToMemoryOnly: kCFBooleanTrue!,
  312. ] as [AnyHashable: Any]
  313. var rawItems: CFArray?
  314. let status = SecPKCS12Import(
  315. Data(pkcs12Bytes) as CFData,
  316. options as CFDictionary,
  317. &rawItems
  318. )
  319. guard status == errSecSuccess else {
  320. Issue.record("Failed to import PKCS12 bundle: status \(status).")
  321. throw TLSEnabledTestsError.failedToImportPKCS12
  322. }
  323. let items = rawItems! as! [[String: Any]]
  324. let firstItem = items[0]
  325. let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
  326. return identity
  327. }
  328. #endif
  329. private func makeMTLSClientConfig(
  330. for transportKind: TransportKind,
  331. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  332. serverHostname: String?
  333. ) -> ClientConfig {
  334. switch transportKind {
  335. case .posix:
  336. var config = self.makeDefaultPlaintextPosixClientConfig()
  337. config.security = .mTLS(
  338. certificateChain: [.bytes(certificateKeyPairs.client.certificate, format: .der)],
  339. privateKey: .bytes(certificateKeyPairs.client.key, format: .der)
  340. ) {
  341. $0.trustRoots = .certificates([
  342. .bytes(certificateKeyPairs.server.certificate, format: .der)
  343. ])
  344. }
  345. config.transport.http2.authority = serverHostname
  346. return .posix(config)
  347. #if canImport(Network)
  348. case .transportServices:
  349. var config = self.makeDefaultPlaintextTSClientConfig()
  350. config.security = .mTLS {
  351. try self.makeSecIdentityProvider(
  352. certificateBytes: certificateKeyPairs.client.certificate,
  353. privateKeyBytes: certificateKeyPairs.client.key
  354. )
  355. } configure: {
  356. $0.trustRoots = .certificates([
  357. .bytes(certificateKeyPairs.server.certificate, format: .der)
  358. ])
  359. }
  360. config.transport.http2.authority = serverHostname
  361. return .transportServices(config)
  362. #endif
  363. }
  364. }
  365. private func makeDefaultPlaintextPosixServerConfig() -> ServerConfig.Posix {
  366. ServerConfig.Posix(security: .plaintext, transport: .defaults)
  367. }
  368. #if canImport(Network)
  369. private func makeDefaultPlaintextTSServerConfig() -> ServerConfig.TransportServices {
  370. ServerConfig.TransportServices(security: .plaintext, transport: .defaults)
  371. }
  372. #endif
  373. private func makeDefaultTLSServerConfig(
  374. for transportKind: TransportKind,
  375. certificateKeyPairs: SelfSignedCertificateKeyPairs
  376. ) -> ServerConfig {
  377. switch transportKind {
  378. case .posix:
  379. var config = self.makeDefaultPlaintextPosixServerConfig()
  380. config.security = .tls(
  381. certificateChain: [.bytes(certificateKeyPairs.server.certificate, format: .der)],
  382. privateKey: .bytes(certificateKeyPairs.server.key, format: .der)
  383. )
  384. return .posix(config)
  385. #if canImport(Network)
  386. case .transportServices:
  387. var config = self.makeDefaultPlaintextTSServerConfig()
  388. config.security = .tls {
  389. try self.makeSecIdentityProvider(
  390. certificateBytes: certificateKeyPairs.server.certificate,
  391. privateKeyBytes: certificateKeyPairs.server.key
  392. )
  393. }
  394. return .transportServices(config)
  395. #endif
  396. }
  397. }
  398. private func makeMTLSServerConfig(
  399. for transportKind: TransportKind,
  400. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  401. includeClientCertificateInTrustRoots: Bool
  402. ) -> ServerConfig {
  403. switch transportKind {
  404. case .posix:
  405. var config = self.makeDefaultPlaintextPosixServerConfig()
  406. config.security = .mTLS(
  407. certificateChain: [.bytes(certificateKeyPairs.server.certificate, format: .der)],
  408. privateKey: .bytes(certificateKeyPairs.server.key, format: .der)
  409. ) {
  410. if includeClientCertificateInTrustRoots {
  411. $0.trustRoots = .certificates([
  412. .bytes(certificateKeyPairs.client.certificate, format: .der)
  413. ])
  414. }
  415. }
  416. return .posix(config)
  417. #if canImport(Network)
  418. case .transportServices:
  419. var config = self.makeDefaultPlaintextTSServerConfig()
  420. config.security = .mTLS {
  421. try self.makeSecIdentityProvider(
  422. certificateBytes: certificateKeyPairs.server.certificate,
  423. privateKeyBytes: certificateKeyPairs.server.key
  424. )
  425. } configure: {
  426. if includeClientCertificateInTrustRoots {
  427. $0.trustRoots = .certificates([
  428. .bytes(certificateKeyPairs.client.certificate, format: .der)
  429. ])
  430. }
  431. }
  432. return .transportServices(config)
  433. #endif
  434. }
  435. }
  436. func withClientAndServer(
  437. clientConfig: ClientConfig,
  438. serverConfig: ServerConfig,
  439. _ test: (ControlClient<NIOClientTransport>) async throws -> Void
  440. ) async throws {
  441. try await withThrowingDiscardingTaskGroup { group in
  442. let server = self.makeServer(config: serverConfig)
  443. group.addTask {
  444. do {
  445. try await server.serve()
  446. } catch {
  447. throw TLSEnabledTestsError.serverError(cause: error)
  448. }
  449. }
  450. guard let address = try await server.listeningAddress?.ipv4 else {
  451. throw TLSEnabledTestsError.unexpectedListeningAddress
  452. }
  453. let target: any ResolvableTarget = .ipv4(host: address.host, port: address.port)
  454. let client = try self.makeClient(config: clientConfig, target: target)
  455. group.addTask {
  456. do {
  457. try await client.runConnections()
  458. } catch {
  459. throw TLSEnabledTestsError.clientError(cause: error)
  460. }
  461. }
  462. let control = ControlClient(wrapping: client)
  463. try await test(control)
  464. client.beginGracefulShutdown()
  465. server.beginGracefulShutdown()
  466. }
  467. }
  468. private func makeServer(config: ServerConfig) -> GRPCServer<NIOServerTransport> {
  469. let services = [ControlService()]
  470. switch config {
  471. case .posix(let config):
  472. return GRPCServer(
  473. transport: NIOServerTransport(
  474. .http2NIOPosix(
  475. address: .ipv4(host: "127.0.0.1", port: 0),
  476. transportSecurity: config.security,
  477. config: config.transport
  478. )
  479. ),
  480. services: services
  481. )
  482. #if canImport(Network)
  483. case .transportServices(let config):
  484. return GRPCServer(
  485. transport: NIOServerTransport(
  486. .http2NIOTS(
  487. address: .ipv4(host: "127.0.0.1", port: 0),
  488. transportSecurity: config.security,
  489. config: config.transport
  490. )
  491. ),
  492. services: services
  493. )
  494. #endif
  495. }
  496. }
  497. private func makeClient(
  498. config: ClientConfig,
  499. target: any ResolvableTarget
  500. ) throws -> GRPCClient<NIOClientTransport> {
  501. switch config {
  502. case .posix(let config):
  503. let transport = try HTTP2ClientTransport.Posix(
  504. target: target,
  505. transportSecurity: config.security,
  506. config: config.transport,
  507. serviceConfig: ServiceConfig()
  508. )
  509. return GRPCClient(transport: NIOClientTransport(transport))
  510. #if canImport(Network)
  511. case .transportServices(let config):
  512. let transport = try HTTP2ClientTransport.TransportServices(
  513. target: target,
  514. transportSecurity: config.security,
  515. config: config.transport,
  516. serviceConfig: ServiceConfig()
  517. )
  518. return GRPCClient(transport: NIOClientTransport(transport))
  519. #endif
  520. }
  521. }
  522. private func executeUnaryRPC(control: ControlClient<NIOClientTransport>) async throws {
  523. let input = ControlInput.with { $0.numberOfMessages = 1 }
  524. let request = ClientRequest(message: input)
  525. try await control.unary(request: request) { response in
  526. #expect(throws: Never.self) { try response.message }
  527. }
  528. }
  529. }