HTTP2TransportTLSEnabledTests.swift 20 KB

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