HTTP2TransportTLSEnabledTests.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  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.supported,
  32. TransportKind.supported
  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.supported,
  108. TransportKind.supported
  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.supported,
  137. TransportKind.supported
  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. }
  189. return true
  190. }
  191. }
  192. @Test(
  193. "Error is surfaced when server fails client verification",
  194. arguments: TransportKind.supported,
  195. TransportKind.supported
  196. )
  197. // Verification should fail because the client does not offer a cert that
  198. // the server can use for mutual verification.
  199. func testServerFailsClientValidation(
  200. clientTransport: TransportKind,
  201. serverTransport: TransportKind
  202. ) async throws {
  203. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  204. let clientTransportConfig = self.makeDefaultTLSClientConfig(
  205. for: clientTransport,
  206. certificateKeyPairs: certificateKeyPairs
  207. )
  208. let serverTransportConfig = self.makeMTLSServerConfig(
  209. for: serverTransport,
  210. certificateKeyPairs: certificateKeyPairs,
  211. includeClientCertificateInTrustRoots: true
  212. )
  213. await #expect {
  214. try await self.withClientAndServer(
  215. clientConfig: clientTransportConfig,
  216. serverConfig: serverTransportConfig
  217. ) { control in
  218. try await self.executeUnaryRPC(control: control)
  219. }
  220. } throws: { error in
  221. let rootError = try #require(error as? RPCError)
  222. #expect(rootError.code == .unavailable)
  223. #expect(
  224. rootError.message
  225. == "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
  226. )
  227. switch clientTransport {
  228. case .posix:
  229. let sslError = try #require(rootError.cause as? NIOSSL.BoringSSLError)
  230. guard case .sslError = sslError else {
  231. Issue.record(
  232. "Should be a NIOSSL.sslError error, but was: \(String(describing: rootError.cause))"
  233. )
  234. return false
  235. }
  236. #if canImport(Network)
  237. case .transportServices:
  238. let nwError = try #require(rootError.cause as? NWError)
  239. guard case .tls(Security.errSSLPeerCertUnknown) = nwError else {
  240. // When the TLS handshake fails, the connection will be closed from the client.
  241. // Network.framework will generally surface the right SSL error (in this case, an "unknown
  242. // certificate" from the server), but it will sometimes instead return the broken pipe
  243. // error caused by the underlying TLS handshake handler closing the connection:
  244. // we should tolerate this.
  245. if case .posix(POSIXErrorCode.EPIPE) = nwError {
  246. return true
  247. }
  248. Issue.record(
  249. "Should be a NWError.tls(-9829/errSSLPeerCertUnknown) error, but was: \(String(describing: rootError.cause))"
  250. )
  251. return false
  252. }
  253. #endif
  254. }
  255. return true
  256. }
  257. }
  258. // - MARK: Test Utilities
  259. enum TLSEnabledTestsError: Error {
  260. case failedToImportPKCS12
  261. case unexpectedListeningAddress
  262. }
  263. struct Config<Transport, Security> {
  264. var security: Security
  265. var transport: Transport
  266. }
  267. enum ClientConfig {
  268. typealias Posix = Config<
  269. HTTP2ClientTransport.Posix.Config,
  270. HTTP2ClientTransport.Posix.TransportSecurity
  271. >
  272. case posix(Posix)
  273. #if canImport(Network)
  274. typealias TransportServices = Config<
  275. HTTP2ClientTransport.TransportServices.Config,
  276. HTTP2ClientTransport.TransportServices.TransportSecurity
  277. >
  278. case transportServices(TransportServices)
  279. #endif
  280. }
  281. enum ServerConfig {
  282. typealias Posix = Config<
  283. HTTP2ServerTransport.Posix.Config,
  284. HTTP2ServerTransport.Posix.TransportSecurity
  285. >
  286. case posix(Posix)
  287. #if canImport(Network)
  288. typealias TransportServices = Config<
  289. HTTP2ServerTransport.TransportServices.Config,
  290. HTTP2ServerTransport.TransportServices.TransportSecurity
  291. >
  292. case transportServices(TransportServices)
  293. #endif
  294. }
  295. private func makeDefaultPlaintextPosixClientConfig() -> ClientConfig.Posix {
  296. ClientConfig.Posix(
  297. security: .plaintext,
  298. transport: .defaults { config in
  299. config.backoff.initial = .milliseconds(100)
  300. config.backoff.multiplier = 1
  301. config.backoff.jitter = 0
  302. }
  303. )
  304. }
  305. #if canImport(Network)
  306. private func makeDefaultPlaintextTSClientConfig() -> ClientConfig.TransportServices {
  307. ClientConfig.TransportServices(
  308. security: .plaintext,
  309. transport: .defaults { config in
  310. config.backoff.initial = .milliseconds(100)
  311. config.backoff.multiplier = 1
  312. config.backoff.jitter = 0
  313. }
  314. )
  315. }
  316. #endif
  317. private func makeDefaultTLSClientConfig(
  318. for transportSecurity: TransportKind,
  319. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  320. authority: String? = "localhost"
  321. ) -> ClientConfig {
  322. switch transportSecurity {
  323. case .posix:
  324. var config = self.makeDefaultPlaintextPosixClientConfig()
  325. config.security = .tls {
  326. $0.trustRoots = .certificates([
  327. .bytes(certificateKeyPairs.server.certificate, format: .der)
  328. ])
  329. }
  330. config.transport.http2.authority = authority
  331. return .posix(config)
  332. #if canImport(Network)
  333. case .transportServices:
  334. var config = self.makeDefaultPlaintextTSClientConfig()
  335. config.security = .tls {
  336. $0.trustRoots = .certificates([
  337. .bytes(certificateKeyPairs.server.certificate, format: .der)
  338. ])
  339. }
  340. config.transport.http2.authority = authority
  341. return .transportServices(config)
  342. #endif
  343. }
  344. }
  345. #if canImport(Network)
  346. private func makeSecIdentityProvider(
  347. certificateBytes: [UInt8],
  348. privateKeyBytes: [UInt8]
  349. ) throws -> SecIdentity {
  350. let password = "somepassword"
  351. let bundle = NIOSSLPKCS12Bundle(
  352. certificateChain: [try NIOSSLCertificate(bytes: certificateBytes, format: .der)],
  353. privateKey: try NIOSSLPrivateKey(bytes: privateKeyBytes, format: .der)
  354. )
  355. let pkcs12Bytes = try bundle.serialize(passphrase: password.utf8)
  356. let options =
  357. [
  358. kSecImportExportPassphrase as String: password,
  359. kSecImportToMemoryOnly: kCFBooleanTrue!,
  360. ] as [AnyHashable: Any]
  361. var rawItems: CFArray?
  362. let status = SecPKCS12Import(
  363. Data(pkcs12Bytes) as CFData,
  364. options as CFDictionary,
  365. &rawItems
  366. )
  367. guard status == errSecSuccess else {
  368. Issue.record("Failed to import PKCS12 bundle: status \(status).")
  369. throw TLSEnabledTestsError.failedToImportPKCS12
  370. }
  371. let items = rawItems! as! [[String: Any]]
  372. let firstItem = items[0]
  373. let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
  374. return identity
  375. }
  376. #endif
  377. private func makeMTLSClientConfig(
  378. for transportKind: TransportKind,
  379. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  380. serverHostname: String?
  381. ) -> ClientConfig {
  382. switch transportKind {
  383. case .posix:
  384. var config = self.makeDefaultPlaintextPosixClientConfig()
  385. config.security = .mTLS(
  386. certificateChain: [.bytes(certificateKeyPairs.client.certificate, format: .der)],
  387. privateKey: .bytes(certificateKeyPairs.client.key, format: .der)
  388. ) {
  389. $0.trustRoots = .certificates([
  390. .bytes(certificateKeyPairs.server.certificate, format: .der)
  391. ])
  392. }
  393. config.transport.http2.authority = serverHostname
  394. return .posix(config)
  395. #if canImport(Network)
  396. case .transportServices:
  397. var config = self.makeDefaultPlaintextTSClientConfig()
  398. config.security = .mTLS {
  399. try self.makeSecIdentityProvider(
  400. certificateBytes: certificateKeyPairs.client.certificate,
  401. privateKeyBytes: certificateKeyPairs.client.key
  402. )
  403. } configure: {
  404. $0.trustRoots = .certificates([
  405. .bytes(certificateKeyPairs.server.certificate, format: .der)
  406. ])
  407. }
  408. config.transport.http2.authority = serverHostname
  409. return .transportServices(config)
  410. #endif
  411. }
  412. }
  413. private func makeDefaultPlaintextPosixServerConfig() -> ServerConfig.Posix {
  414. ServerConfig.Posix(security: .plaintext, transport: .defaults)
  415. }
  416. #if canImport(Network)
  417. private func makeDefaultPlaintextTSServerConfig() -> ServerConfig.TransportServices {
  418. ServerConfig.TransportServices(security: .plaintext, transport: .defaults)
  419. }
  420. #endif
  421. private func makeDefaultTLSServerConfig(
  422. for transportKind: TransportKind,
  423. certificateKeyPairs: SelfSignedCertificateKeyPairs
  424. ) -> ServerConfig {
  425. switch transportKind {
  426. case .posix:
  427. var config = self.makeDefaultPlaintextPosixServerConfig()
  428. config.security = .tls(
  429. certificateChain: [.bytes(certificateKeyPairs.server.certificate, format: .der)],
  430. privateKey: .bytes(certificateKeyPairs.server.key, format: .der)
  431. )
  432. return .posix(config)
  433. #if canImport(Network)
  434. case .transportServices:
  435. var config = self.makeDefaultPlaintextTSServerConfig()
  436. config.security = .tls {
  437. try self.makeSecIdentityProvider(
  438. certificateBytes: certificateKeyPairs.server.certificate,
  439. privateKeyBytes: certificateKeyPairs.server.key
  440. )
  441. }
  442. return .transportServices(config)
  443. #endif
  444. }
  445. }
  446. private func makeMTLSServerConfig(
  447. for transportKind: TransportKind,
  448. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  449. includeClientCertificateInTrustRoots: Bool
  450. ) -> ServerConfig {
  451. switch transportKind {
  452. case .posix:
  453. var config = self.makeDefaultPlaintextPosixServerConfig()
  454. config.security = .mTLS(
  455. certificateChain: [.bytes(certificateKeyPairs.server.certificate, format: .der)],
  456. privateKey: .bytes(certificateKeyPairs.server.key, format: .der)
  457. ) {
  458. if includeClientCertificateInTrustRoots {
  459. $0.trustRoots = .certificates([
  460. .bytes(certificateKeyPairs.client.certificate, format: .der)
  461. ])
  462. }
  463. }
  464. return .posix(config)
  465. #if canImport(Network)
  466. case .transportServices:
  467. var config = self.makeDefaultPlaintextTSServerConfig()
  468. config.security = .mTLS {
  469. try self.makeSecIdentityProvider(
  470. certificateBytes: certificateKeyPairs.server.certificate,
  471. privateKeyBytes: certificateKeyPairs.server.key
  472. )
  473. } configure: {
  474. if includeClientCertificateInTrustRoots {
  475. $0.trustRoots = .certificates([
  476. .bytes(certificateKeyPairs.client.certificate, format: .der)
  477. ])
  478. }
  479. }
  480. return .transportServices(config)
  481. #endif
  482. }
  483. }
  484. func withClientAndServer(
  485. clientConfig: ClientConfig,
  486. serverConfig: ServerConfig,
  487. interceptors: [any ServerInterceptor] = [],
  488. _ test: (ControlClient<NIOClientTransport>) async throws -> Void
  489. ) async throws {
  490. let serverTransport: NIOServerTransport
  491. switch serverConfig {
  492. case .posix(let posix):
  493. serverTransport = NIOServerTransport(
  494. .http2NIOPosix(
  495. address: .ipv4(host: "127.0.0.1", port: 0),
  496. transportSecurity: posix.security,
  497. config: posix.transport
  498. )
  499. )
  500. #if canImport(Network)
  501. case .transportServices(let config):
  502. serverTransport = NIOServerTransport(
  503. .http2NIOTS(
  504. address: .ipv4(host: "127.0.0.1", port: 0),
  505. transportSecurity: config.security,
  506. config: config.transport
  507. )
  508. )
  509. #endif
  510. }
  511. try await withGRPCServer(
  512. transport: serverTransport,
  513. services: [ControlService()],
  514. interceptors: interceptors
  515. ) { server in
  516. guard let address = try await server.listeningAddress?.ipv4 else {
  517. throw TLSEnabledTestsError.unexpectedListeningAddress
  518. }
  519. let target: any ResolvableTarget = .ipv4(host: address.host, port: address.port)
  520. let clientTransport: NIOClientTransport
  521. switch clientConfig {
  522. case .posix(let config):
  523. clientTransport = try NIOClientTransport(
  524. .http2NIOPosix(
  525. target: target,
  526. transportSecurity: config.security,
  527. config: config.transport
  528. )
  529. )
  530. #if canImport(Network)
  531. case .transportServices(let config):
  532. clientTransport = try NIOClientTransport(
  533. .http2NIOTS(target: target, transportSecurity: config.security, config: config.transport)
  534. )
  535. #endif
  536. }
  537. try await withGRPCClient(transport: clientTransport) { client in
  538. let control = ControlClient(wrapping: client)
  539. try await test(control)
  540. }
  541. }
  542. }
  543. private func executeUnaryRPC(control: ControlClient<NIOClientTransport>) async throws {
  544. let input = ControlInput.with { $0.numberOfMessages = 1 }
  545. let request = ClientRequest(message: input)
  546. try await control.unary(request: request) { response in
  547. #expect(throws: Never.self) { try response.message }
  548. }
  549. }
  550. }