HTTP2TransportTLSEnabledTests.swift 20 KB

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