HTTP2TransportTLSEnabledTests.swift 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  1. /*
  2. * Copyright 2025, 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 NIOCore
  21. import NIOSSL
  22. import SwiftASN1
  23. import Testing
  24. import X509
  25. #if canImport(Network)
  26. import Network
  27. #endif
  28. @Suite("HTTP/2 transport E2E tests with TLS enabled")
  29. struct HTTP2TransportTLSEnabledTests {
  30. // - MARK: Tests
  31. @Test(
  32. "When using defaults, server does not perform client verification",
  33. arguments: TransportKind.clientsWithTLS,
  34. TransportKind.serversWithTLS
  35. )
  36. @available(gRPCSwiftNIOTransport 2.0, *)
  37. func testRPC_Defaults_OK(
  38. clientTransport: TransportKind,
  39. serverTransport: TransportKind
  40. ) async throws {
  41. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  42. let clientConfig = self.makeDefaultTLSClientConfig(
  43. for: clientTransport,
  44. certificateKeyPairs: certificateKeyPairs
  45. )
  46. let serverConfig = self.makeDefaultTLSServerConfig(
  47. for: serverTransport,
  48. certificateKeyPairs: certificateKeyPairs
  49. )
  50. try await self.withClientAndServer(
  51. clientConfig: clientConfig,
  52. serverConfig: serverConfig
  53. ) { control in
  54. await #expect(throws: Never.self) {
  55. try await self.executeUnaryRPC(control: control)
  56. }
  57. }
  58. }
  59. @available(gRPCSwiftNIOTransport 2.0, *)
  60. final class TransportSpecificInterceptor: ServerInterceptor {
  61. let clientCert: [UInt8]
  62. init(_ clientCert: [UInt8]) {
  63. self.clientCert = clientCert
  64. }
  65. func intercept<Input, Output>(
  66. request: GRPCCore.StreamingServerRequest<Input>,
  67. context: GRPCCore.ServerContext,
  68. next:
  69. @Sendable (GRPCCore.StreamingServerRequest<Input>, GRPCCore.ServerContext) async throws
  70. -> GRPCCore.StreamingServerResponse<Output>
  71. ) async throws -> GRPCCore.StreamingServerResponse<Output>
  72. where Input: Sendable, Output: Sendable {
  73. let transportSpecific = context.transportSpecific
  74. let transportSpecificAsPosixContext = try #require(
  75. transportSpecific as? HTTP2ServerTransport.Posix.Context
  76. )
  77. let peerCertificate = try #require(transportSpecificAsPosixContext.peerCertificate)
  78. var derSerializer = DER.Serializer()
  79. try peerCertificate.serialize(into: &derSerializer)
  80. #expect(derSerializer.serializedBytes == self.clientCert)
  81. return try await next(request, context)
  82. }
  83. }
  84. @Test(
  85. "Using the mTLS defaults, and with Posix transport, validate we get the peer cert on the server",
  86. arguments: [TransportKind.posix]
  87. )
  88. @available(gRPCSwiftNIOTransport 2.0, *)
  89. func testRPC_mTLS_TransportContext_OK(supportedTransport: TransportKind) async throws {
  90. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  91. let clientConfig = self.makeMTLSClientConfig(
  92. for: supportedTransport,
  93. certificateKeyPairs: certificateKeyPairs,
  94. serverHostname: "localhost"
  95. )
  96. let serverConfig = self.makeMTLSServerConfig(
  97. for: supportedTransport,
  98. certificateKeyPairs: certificateKeyPairs,
  99. includeClientCertificateInTrustRoots: true
  100. )
  101. try await self.withClientAndServer(
  102. clientConfig: clientConfig,
  103. serverConfig: serverConfig,
  104. interceptors: [TransportSpecificInterceptor(certificateKeyPairs.client.certificate)]
  105. ) { control in
  106. await #expect(throws: Never.self) {
  107. try await self.executeUnaryRPC(control: control)
  108. }
  109. }
  110. }
  111. @Test(
  112. "When using mTLS defaults, both client and server verify each others' certificates",
  113. arguments: TransportKind.clientsWithTLS,
  114. TransportKind.clientsWithTLS
  115. )
  116. @available(gRPCSwiftNIOTransport 2.0, *)
  117. func testRPC_mTLS_OK(
  118. clientTransport: TransportKind,
  119. serverTransport: TransportKind
  120. ) async throws {
  121. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  122. let clientConfig = self.makeMTLSClientConfig(
  123. for: clientTransport,
  124. certificateKeyPairs: certificateKeyPairs,
  125. serverHostname: "localhost"
  126. )
  127. let serverConfig = self.makeMTLSServerConfig(
  128. for: serverTransport,
  129. certificateKeyPairs: certificateKeyPairs,
  130. includeClientCertificateInTrustRoots: true
  131. )
  132. try await self.withClientAndServer(
  133. clientConfig: clientConfig,
  134. serverConfig: serverConfig
  135. ) { control in
  136. await #expect(throws: Never.self) {
  137. try await self.executeUnaryRPC(control: control)
  138. }
  139. }
  140. }
  141. @Test(
  142. "When using mTLS with PEM files, both client and server verify each others' certificates"
  143. )
  144. @available(gRPCSwiftNIOTransport 2.0, *)
  145. func testRPC_mTLS_posixFileBasedCertificates_OK() async throws {
  146. // Create a new certificate chain that has 4 certificate/key pairs: root, intermediate, client, server
  147. let certificateChain = try CertificateChain()
  148. // Tag our certificate files with the function name
  149. let filePaths = try certificateChain.writeToTemp()
  150. // Check that the files
  151. #expect(FileManager.default.fileExists(atPath: filePaths.clientCert))
  152. #expect(FileManager.default.fileExists(atPath: filePaths.clientKey))
  153. #expect(FileManager.default.fileExists(atPath: filePaths.serverCert))
  154. #expect(FileManager.default.fileExists(atPath: filePaths.serverKey))
  155. #expect(FileManager.default.fileExists(atPath: filePaths.trustRoots))
  156. // Create configurations
  157. let clientConfig = self.makeMTLSClientConfig(
  158. certificatePath: filePaths.clientCert,
  159. keyPath: filePaths.clientKey,
  160. trustRootsPath: filePaths.trustRoots,
  161. serverHostname: CertificateChain.serverName
  162. )
  163. let serverConfig = self.makeMTLSServerConfig(
  164. certificatePath: filePaths.serverCert,
  165. keyPath: filePaths.serverKey,
  166. trustRootsPath: filePaths.trustRoots
  167. )
  168. // Run the test
  169. try await self.withClientAndServer(
  170. clientConfig: clientConfig,
  171. serverConfig: serverConfig
  172. ) { control in
  173. await #expect(throws: Never.self) {
  174. try await self.executeUnaryRPC(control: control)
  175. }
  176. }
  177. }
  178. @Test("Custom certification callbacks are used for verification.")
  179. @available(gRPCSwiftNIOTransport 2.0, *)
  180. func testRPC_mTLS_customVerificationCallback_OK() async throws {
  181. // Create a new certificate chain that has 4 certificate/key pairs: root, intermediate, client, server
  182. let certificateChain = try CertificateChain()
  183. let certificatesExpectedInCallback = [certificateChain.client.certificate]
  184. let filePaths = try certificateChain.writeToTemp()
  185. let clientConfig = self.makeMTLSClientConfig(
  186. certificatePath: filePaths.clientCert,
  187. keyPath: filePaths.clientKey,
  188. trustRootsPath: filePaths.trustRoots,
  189. serverHostname: CertificateChain.serverName
  190. )
  191. // The confirmation lets us check that the callback is used.
  192. try await confirmation(expectedCount: 1) { confirmation in
  193. let serverConfig = self.makeMTLSServerConfigWithCallback(
  194. certificatePath: filePaths.serverCert,
  195. keyPath: filePaths.serverKey,
  196. trustRootsPath: filePaths.trustRoots
  197. ) { certificates, promise in
  198. let presentedCertificates = certificates.map {
  199. try! Certificate(derEncoded: $0.toDERBytes())
  200. }
  201. #expect(certificatesExpectedInCallback == presentedCertificates)
  202. // "Verify" the chain and set the certificate.
  203. promise.succeed(
  204. .certificateVerified(VerificationMetadata(ValidatedCertificateChain(certificates)))
  205. )
  206. // This should be called once.
  207. confirmation.confirm()
  208. }
  209. // Run the test
  210. try await self.withClientAndServer(
  211. clientConfig: clientConfig,
  212. serverConfig: serverConfig
  213. ) { control in
  214. await #expect(throws: Never.self) {
  215. try await self.executeUnaryRPC(control: control)
  216. }
  217. }
  218. }
  219. }
  220. @Test("Custom certification callbacks are not called when verification is disabled.")
  221. @available(gRPCSwiftNIOTransport 2.0, *)
  222. func testRPC_mTLS_customVerificationCallback_notCalledWhenNoVerificationIsConfigured()
  223. async throws
  224. {
  225. // Create a new certificate chain that has 4 certificate/key pairs: root, intermediate, client, server
  226. let certificateChain = try CertificateChain()
  227. let certificatesExpectedInCallback = [certificateChain.client.certificate]
  228. let filePaths = try certificateChain.writeToTemp()
  229. let clientConfig = self.makeMTLSClientConfig(
  230. certificatePath: filePaths.clientCert,
  231. keyPath: filePaths.clientKey,
  232. trustRootsPath: filePaths.trustRoots,
  233. serverHostname: CertificateChain.serverName
  234. )
  235. // The confirmation lets us check that the callback is not used.
  236. try await confirmation(expectedCount: 0) { confirmation in
  237. let serverConfig = self.makeMTLSServerConfigWithCallback(
  238. certificatePath: filePaths.serverCert,
  239. keyPath: filePaths.serverKey,
  240. trustRootsPath: filePaths.trustRoots,
  241. certificateVerification: TLSConfig.CertificateVerification.noVerification
  242. ) { certificates, promise in
  243. let presentedCertificates = certificates.map {
  244. try! Certificate(derEncoded: $0.toDERBytes())
  245. }
  246. #expect(certificatesExpectedInCallback == presentedCertificates)
  247. // "Verify" the chain and set the certificate.
  248. promise.succeed(
  249. .certificateVerified(VerificationMetadata(ValidatedCertificateChain(certificates)))
  250. )
  251. // We expect this never to be called.
  252. confirmation()
  253. }
  254. // Run the test
  255. try await self.withClientAndServer(
  256. clientConfig: clientConfig,
  257. serverConfig: serverConfig
  258. ) { control in
  259. await #expect(throws: Never.self) {
  260. try await self.executeUnaryRPC(control: control)
  261. }
  262. }
  263. }
  264. }
  265. @Test("mTLS custom callback verification failure leads to denied authentication")
  266. @available(gRPCSwiftNIOTransport 2.0, *)
  267. // Verification should fail because the custom hostname is missing on the client.
  268. func testRPC_mTLS_customVerificationCallback_Failure() async throws {
  269. // Create a new certificate chain that has 4 certificate/key pairs: root, intermediate, client, server
  270. let certificateChain = try CertificateChain()
  271. let certificatesExpectedInCallback = [certificateChain.client.certificate]
  272. let filePaths = try certificateChain.writeToTemp()
  273. let clientConfig = self.makeMTLSClientConfig(
  274. certificatePath: filePaths.clientCert,
  275. keyPath: filePaths.clientKey,
  276. trustRootsPath: filePaths.trustRoots,
  277. serverHostname: CertificateChain.serverName
  278. )
  279. // The confirmation lets us check that the callback is used.
  280. await confirmation { confirmation in
  281. let serverConfig = self.makeMTLSServerConfigWithCallback(
  282. certificatePath: filePaths.serverCert,
  283. keyPath: filePaths.serverKey,
  284. trustRootsPath: filePaths.trustRoots
  285. ) { certificates, promise in
  286. let presentedCertificates = certificates.map {
  287. try! Certificate(derEncoded: $0.toDERBytes())
  288. }
  289. #expect(certificatesExpectedInCallback == presentedCertificates)
  290. // We are failing the certificate check here by propagating ".failed"!
  291. promise.succeed(.failed)
  292. confirmation.confirm()
  293. }
  294. // Run the test
  295. await #expect {
  296. try await self.withClientAndServer(
  297. clientConfig: clientConfig,
  298. serverConfig: serverConfig
  299. ) { control in
  300. try await self.executeUnaryRPC(control: control)
  301. }
  302. } throws: { error in
  303. // Check root error ...
  304. let rootError = try #require(error as? RPCError)
  305. #expect(rootError.code == .unavailable)
  306. #expect(
  307. rootError.message
  308. == "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
  309. )
  310. // ... and the its cause.
  311. let sslError = try #require(rootError.cause as? BoringSSLError)
  312. switch sslError {
  313. case .sslError:
  314. break
  315. default:
  316. Issue.record(
  317. "Should be a BoringSSLError.sslError error, but was: \(String(describing: rootError.cause))"
  318. )
  319. }
  320. return true
  321. }
  322. }
  323. }
  324. @available(gRPCSwiftNIOTransport 2.2, *)
  325. final class ValidatedCertificateChainInterceptor: ServerInterceptor {
  326. let expectedCertificateChain: [Certificate]
  327. init(_ expectedCertificateChain: [Certificate]) {
  328. self.expectedCertificateChain = expectedCertificateChain
  329. }
  330. func intercept<Input, Output>(
  331. request: GRPCCore.StreamingServerRequest<Input>,
  332. context: GRPCCore.ServerContext,
  333. next:
  334. @Sendable (GRPCCore.StreamingServerRequest<Input>, GRPCCore.ServerContext) async throws
  335. -> GRPCCore.StreamingServerResponse<Output>
  336. ) async throws -> GRPCCore.StreamingServerResponse<Output>
  337. where Input: Sendable, Output: Sendable {
  338. let transportSpecific = context.transportSpecific
  339. let transportSpecificAsPosixContext = try #require(
  340. transportSpecific as? HTTP2ServerTransport.Posix.Context
  341. )
  342. let peerCertificateChain = try #require(
  343. transportSpecificAsPosixContext.peerCertificateChain
  344. )
  345. // The validated certifiacte chain always contains at least one element.
  346. #expect(!peerCertificateChain.isEmpty)
  347. // And these chains should have the same length.
  348. #expect(peerCertificateChain.count == self.expectedCertificateChain.count)
  349. for (lhs, rhs) in zip(peerCertificateChain, self.expectedCertificateChain) {
  350. #expect(lhs == rhs)
  351. }
  352. // leaf and root should match the first and last element of the expected chain.
  353. #expect(peerCertificateChain.leaf == self.expectedCertificateChain.first!)
  354. #expect(peerCertificateChain.root == self.expectedCertificateChain.last!)
  355. return try await next(request, context)
  356. }
  357. }
  358. @Test(
  359. "When using a custom certificate callback the validated certifiate chain of the peer is available."
  360. )
  361. @available(gRPCSwiftNIOTransport 2.2, *)
  362. func testRPC_mTLS_peerCertificateChain() async throws {
  363. // Create a new certificate chain that has 4 certificate/key pairs: root, intermediate, client, server
  364. let certificateChain = try CertificateChain()
  365. let expectedCertificateChain = [certificateChain.client.certificate]
  366. let filePaths = try certificateChain.writeToTemp()
  367. // Client and server configurations.
  368. let clientConfig = self.makeMTLSClientConfig(
  369. certificatePath: filePaths.clientCert,
  370. keyPath: filePaths.clientKey,
  371. trustRootsPath: filePaths.trustRoots,
  372. serverHostname: CertificateChain.serverName
  373. )
  374. let serverConfig = self.makeMTLSServerConfigWithCallback(
  375. certificatePath: filePaths.serverCert,
  376. keyPath: filePaths.serverKey,
  377. trustRootsPath: filePaths.trustRoots
  378. ) { certificates, promise in
  379. let presentedCertificates = certificates.map {
  380. try! Certificate(derEncoded: $0.toDERBytes())
  381. }
  382. #expect([certificateChain.client.certificate] == presentedCertificates)
  383. // "Verify" the chain and set the certificate.
  384. promise.succeed(
  385. .certificateVerified(VerificationMetadata(ValidatedCertificateChain(certificates)))
  386. )
  387. }
  388. // Run the test. The interceptor checks that we can query the expected certificate chain.
  389. try await self.withClientAndServer(
  390. clientConfig: clientConfig,
  391. serverConfig: serverConfig,
  392. interceptors: [ValidatedCertificateChainInterceptor(expectedCertificateChain)]
  393. ) { control in
  394. await #expect(throws: Never.self) {
  395. try await self.executeUnaryRPC(control: control)
  396. }
  397. }
  398. }
  399. @Test(
  400. "Error is surfaced when client fails server verification",
  401. arguments: TransportKind.clientsWithTLS,
  402. TransportKind.clientsWithTLS
  403. )
  404. @available(gRPCSwiftNIOTransport 2.0, *)
  405. // Verification should fail because the custom hostname is missing on the client.
  406. func testClientFailsServerValidation(
  407. clientTransport: TransportKind,
  408. serverTransport: TransportKind
  409. ) async throws {
  410. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  411. let clientTransportConfig = self.makeDefaultTLSClientConfig(
  412. for: clientTransport,
  413. certificateKeyPairs: certificateKeyPairs,
  414. authority: "wrong-hostname"
  415. )
  416. let serverTransportConfig = self.makeDefaultTLSServerConfig(
  417. for: serverTransport,
  418. certificateKeyPairs: certificateKeyPairs
  419. )
  420. await #expect {
  421. try await self.withClientAndServer(
  422. clientConfig: clientTransportConfig,
  423. serverConfig: serverTransportConfig
  424. ) { control in
  425. try await self.executeUnaryRPC(control: control)
  426. }
  427. } throws: { error in
  428. let rootError = try #require(error as? RPCError)
  429. #expect(rootError.code == .unavailable)
  430. switch clientTransport {
  431. case .posix:
  432. #expect(
  433. rootError.message
  434. == "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
  435. )
  436. let sslError = try #require(rootError.cause as? NIOSSLExtraError)
  437. guard sslError == .failedToValidateHostname else {
  438. Issue.record(
  439. "Should be a NIOSSLExtraError.failedToValidateHostname error, but was: \(String(describing: rootError.cause))"
  440. )
  441. return false
  442. }
  443. #if canImport(Network)
  444. case .transportServices:
  445. #expect(rootError.message.starts(with: "Could not establish a connection to"))
  446. let nwError = try #require(rootError.cause as? NWError)
  447. guard case .tls(Security.errSSLBadCert) = nwError else {
  448. Issue.record(
  449. "Should be a NWError.tls(-9808/errSSLBadCert) error, but was: \(String(describing: rootError.cause))"
  450. )
  451. return false
  452. }
  453. #endif
  454. case .wrappedChannel:
  455. fatalError("Unsupported")
  456. }
  457. return true
  458. }
  459. }
  460. @Test(
  461. "Error is surfaced when server fails client verification",
  462. arguments: TransportKind.clientsWithTLS,
  463. TransportKind.clientsWithTLS
  464. )
  465. @available(gRPCSwiftNIOTransport 2.0, *)
  466. // Verification should fail because the client does not offer a cert that
  467. // the server can use for mutual verification.
  468. func testServerFailsClientValidation(
  469. clientTransport: TransportKind,
  470. serverTransport: TransportKind
  471. ) async throws {
  472. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  473. let clientTransportConfig = self.makeDefaultTLSClientConfig(
  474. for: clientTransport,
  475. certificateKeyPairs: certificateKeyPairs
  476. )
  477. let serverTransportConfig = self.makeMTLSServerConfig(
  478. for: serverTransport,
  479. certificateKeyPairs: certificateKeyPairs,
  480. includeClientCertificateInTrustRoots: true
  481. )
  482. await #expect {
  483. try await self.withClientAndServer(
  484. clientConfig: clientTransportConfig,
  485. serverConfig: serverTransportConfig
  486. ) { control in
  487. try await self.executeUnaryRPC(control: control)
  488. }
  489. } throws: { error in
  490. let rootError = try #require(error as? RPCError)
  491. #expect(rootError.code == .unavailable)
  492. #expect(
  493. rootError.message
  494. == "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
  495. )
  496. switch clientTransport {
  497. case .posix:
  498. let sslError = try #require(rootError.cause as? NIOSSL.BoringSSLError)
  499. guard case .sslError = sslError else {
  500. Issue.record(
  501. "Should be a NIOSSL.sslError error, but was: \(String(describing: rootError.cause))"
  502. )
  503. return false
  504. }
  505. #if canImport(Network)
  506. case .transportServices:
  507. let nwError = try #require(rootError.cause as? NWError)
  508. guard case .tls(Security.errSSLPeerCertUnknown) = nwError else {
  509. // When the TLS handshake fails, the connection will be closed from the client.
  510. // Network.framework will generally surface the right SSL error (in this case, an "unknown
  511. // certificate" from the server), but it will sometimes instead return the broken pipe
  512. // error caused by the underlying TLS handshake handler closing the connection:
  513. // we should tolerate this.
  514. if case .posix(POSIXErrorCode.EPIPE) = nwError {
  515. return true
  516. }
  517. Issue.record(
  518. "Should be a NWError.tls(-9829/errSSLPeerCertUnknown) error, but was: \(String(describing: rootError.cause))"
  519. )
  520. return false
  521. }
  522. #endif
  523. case .wrappedChannel:
  524. fatalError("Unsupported")
  525. }
  526. return true
  527. }
  528. }
  529. // - MARK: Test Utilities
  530. enum TLSEnabledTestsError: Error {
  531. case failedToImportPKCS12
  532. case unexpectedListeningAddress
  533. }
  534. struct Config<Transport, Security> {
  535. var security: Security
  536. var transport: Transport
  537. }
  538. @available(gRPCSwiftNIOTransport 2.0, *)
  539. enum ClientConfig {
  540. typealias Posix = Config<
  541. HTTP2ClientTransport.Posix.Config,
  542. HTTP2ClientTransport.Posix.TransportSecurity
  543. >
  544. case posix(Posix)
  545. #if canImport(Network)
  546. typealias TransportServices = Config<
  547. HTTP2ClientTransport.TransportServices.Config,
  548. HTTP2ClientTransport.TransportServices.TransportSecurity
  549. >
  550. case transportServices(TransportServices)
  551. #endif
  552. }
  553. @available(gRPCSwiftNIOTransport 2.0, *)
  554. enum ServerConfig {
  555. typealias Posix = Config<
  556. HTTP2ServerTransport.Posix.Config,
  557. HTTP2ServerTransport.Posix.TransportSecurity
  558. >
  559. case posix(Posix)
  560. #if canImport(Network)
  561. typealias TransportServices = Config<
  562. HTTP2ServerTransport.TransportServices.Config,
  563. HTTP2ServerTransport.TransportServices.TransportSecurity
  564. >
  565. case transportServices(TransportServices)
  566. #endif
  567. }
  568. @available(gRPCSwiftNIOTransport 2.0, *)
  569. private func makeDefaultPlaintextPosixClientConfig() -> ClientConfig.Posix {
  570. ClientConfig.Posix(
  571. security: .plaintext,
  572. transport: .defaults { config in
  573. config.backoff.initial = .milliseconds(100)
  574. config.backoff.multiplier = 1
  575. config.backoff.jitter = 0
  576. }
  577. )
  578. }
  579. #if canImport(Network)
  580. @available(gRPCSwiftNIOTransport 2.0, *)
  581. private func makeDefaultPlaintextTSClientConfig() -> ClientConfig.TransportServices {
  582. ClientConfig.TransportServices(
  583. security: .plaintext,
  584. transport: .defaults { config in
  585. config.backoff.initial = .milliseconds(100)
  586. config.backoff.multiplier = 1
  587. config.backoff.jitter = 0
  588. }
  589. )
  590. }
  591. #endif
  592. @available(gRPCSwiftNIOTransport 2.0, *)
  593. private func makeDefaultTLSClientConfig(
  594. for transportSecurity: TransportKind,
  595. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  596. authority: String? = "localhost"
  597. ) -> ClientConfig {
  598. switch transportSecurity {
  599. case .posix:
  600. var config = self.makeDefaultPlaintextPosixClientConfig()
  601. config.security = .tls {
  602. $0.trustRoots = .certificates([
  603. .bytes(certificateKeyPairs.server.certificate, format: .der)
  604. ])
  605. }
  606. config.transport.http2.authority = authority
  607. return .posix(config)
  608. #if canImport(Network)
  609. case .transportServices:
  610. var config = self.makeDefaultPlaintextTSClientConfig()
  611. config.security = .tls {
  612. $0.trustRoots = .certificates([
  613. .bytes(certificateKeyPairs.server.certificate, format: .der)
  614. ])
  615. }
  616. config.transport.http2.authority = authority
  617. return .transportServices(config)
  618. #endif
  619. case .wrappedChannel:
  620. fatalError("Unsupported")
  621. }
  622. }
  623. #if canImport(Network)
  624. @available(gRPCSwiftNIOTransport 2.0, *)
  625. private func makeSecIdentityProvider(
  626. certificateBytes: [UInt8],
  627. privateKeyBytes: [UInt8]
  628. ) throws -> SecIdentity {
  629. let password = "somepassword"
  630. let bundle = NIOSSLPKCS12Bundle(
  631. certificateChain: [try NIOSSLCertificate(bytes: certificateBytes, format: .der)],
  632. privateKey: try NIOSSLPrivateKey(bytes: privateKeyBytes, format: .der)
  633. )
  634. let pkcs12Bytes = try bundle.serialize(passphrase: password.utf8)
  635. let options =
  636. [
  637. kSecImportExportPassphrase as String: password,
  638. kSecImportToMemoryOnly: kCFBooleanTrue!,
  639. ] as [AnyHashable: Any]
  640. var rawItems: CFArray?
  641. let status = SecPKCS12Import(
  642. Data(pkcs12Bytes) as CFData,
  643. options as CFDictionary,
  644. &rawItems
  645. )
  646. guard status == errSecSuccess else {
  647. Issue.record("Failed to import PKCS12 bundle: status \(status).")
  648. throw TLSEnabledTestsError.failedToImportPKCS12
  649. }
  650. let items = rawItems! as! [[String: Any]]
  651. let firstItem = items[0]
  652. let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
  653. return identity
  654. }
  655. #endif
  656. @available(gRPCSwiftNIOTransport 2.0, *)
  657. private func makeMTLSClientConfig(
  658. for transportKind: TransportKind,
  659. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  660. serverHostname: String?
  661. ) -> ClientConfig {
  662. switch transportKind {
  663. case .posix:
  664. var config = self.makeDefaultPlaintextPosixClientConfig()
  665. config.security = .mTLS(
  666. certificateChain: [.bytes(certificateKeyPairs.client.certificate, format: .der)],
  667. privateKey: .bytes(certificateKeyPairs.client.key, format: .der)
  668. ) {
  669. $0.trustRoots = .certificates([
  670. .bytes(certificateKeyPairs.server.certificate, format: .der)
  671. ])
  672. }
  673. config.transport.http2.authority = serverHostname
  674. return .posix(config)
  675. #if canImport(Network)
  676. case .transportServices:
  677. var config = self.makeDefaultPlaintextTSClientConfig()
  678. config.security = .mTLS {
  679. try self.makeSecIdentityProvider(
  680. certificateBytes: certificateKeyPairs.client.certificate,
  681. privateKeyBytes: certificateKeyPairs.client.key
  682. )
  683. } configure: {
  684. $0.trustRoots = .certificates([
  685. .bytes(certificateKeyPairs.server.certificate, format: .der)
  686. ])
  687. }
  688. config.transport.http2.authority = serverHostname
  689. return .transportServices(config)
  690. #endif
  691. case .wrappedChannel:
  692. fatalError("Unsupported")
  693. }
  694. }
  695. @available(gRPCSwiftNIOTransport 2.0, *)
  696. private func makeMTLSClientConfig(
  697. certificatePath: String,
  698. keyPath: String,
  699. trustRootsPath: String,
  700. serverHostname: String?
  701. ) -> ClientConfig {
  702. var config = self.makeDefaultPlaintextPosixClientConfig()
  703. config.security = .mTLS(
  704. certificateChain: [.file(path: certificatePath, format: .pem)],
  705. privateKey: .file(path: keyPath, format: .pem)
  706. ) {
  707. $0.trustRoots = .certificates([
  708. .file(path: trustRootsPath, format: .pem)
  709. ])
  710. }
  711. config.transport.http2.authority = serverHostname
  712. return .posix(config)
  713. }
  714. @available(gRPCSwiftNIOTransport 2.0, *)
  715. private func makeDefaultPlaintextPosixServerConfig() -> ServerConfig.Posix {
  716. ServerConfig.Posix(security: .plaintext, transport: .defaults)
  717. }
  718. #if canImport(Network)
  719. @available(gRPCSwiftNIOTransport 2.0, *)
  720. private func makeDefaultPlaintextTSServerConfig() -> ServerConfig.TransportServices {
  721. ServerConfig.TransportServices(security: .plaintext, transport: .defaults)
  722. }
  723. #endif
  724. @available(gRPCSwiftNIOTransport 2.0, *)
  725. private func makeDefaultTLSServerConfig(
  726. for transportKind: TransportKind,
  727. certificateKeyPairs: SelfSignedCertificateKeyPairs
  728. ) -> ServerConfig {
  729. switch transportKind {
  730. case .posix:
  731. var config = self.makeDefaultPlaintextPosixServerConfig()
  732. config.security = .tls(
  733. certificateChain: [.bytes(certificateKeyPairs.server.certificate, format: .der)],
  734. privateKey: .bytes(certificateKeyPairs.server.key, format: .der)
  735. )
  736. return .posix(config)
  737. #if canImport(Network)
  738. case .transportServices:
  739. var config = self.makeDefaultPlaintextTSServerConfig()
  740. config.security = .tls {
  741. try self.makeSecIdentityProvider(
  742. certificateBytes: certificateKeyPairs.server.certificate,
  743. privateKeyBytes: certificateKeyPairs.server.key
  744. )
  745. }
  746. return .transportServices(config)
  747. #endif
  748. case .wrappedChannel:
  749. fatalError("Unsupported")
  750. }
  751. }
  752. @available(gRPCSwiftNIOTransport 2.0, *)
  753. private func makeMTLSServerConfig(
  754. for transportKind: TransportKind,
  755. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  756. includeClientCertificateInTrustRoots: Bool
  757. ) -> ServerConfig {
  758. switch transportKind {
  759. case .posix:
  760. var config = self.makeDefaultPlaintextPosixServerConfig()
  761. config.security = .mTLS(
  762. certificateChain: [.bytes(certificateKeyPairs.server.certificate, format: .der)],
  763. privateKey: .bytes(certificateKeyPairs.server.key, format: .der)
  764. ) {
  765. if includeClientCertificateInTrustRoots {
  766. $0.trustRoots = .certificates([
  767. .bytes(certificateKeyPairs.client.certificate, format: .der)
  768. ])
  769. }
  770. }
  771. return .posix(config)
  772. #if canImport(Network)
  773. case .transportServices:
  774. var config = self.makeDefaultPlaintextTSServerConfig()
  775. config.security = .mTLS {
  776. try self.makeSecIdentityProvider(
  777. certificateBytes: certificateKeyPairs.server.certificate,
  778. privateKeyBytes: certificateKeyPairs.server.key
  779. )
  780. } configure: {
  781. if includeClientCertificateInTrustRoots {
  782. $0.trustRoots = .certificates([
  783. .bytes(certificateKeyPairs.client.certificate, format: .der)
  784. ])
  785. }
  786. }
  787. return .transportServices(config)
  788. #endif
  789. case .wrappedChannel:
  790. fatalError("Unsupported")
  791. }
  792. }
  793. @available(gRPCSwiftNIOTransport 2.0, *)
  794. private func makeMTLSServerConfig(
  795. certificatePath: String,
  796. keyPath: String,
  797. trustRootsPath: String
  798. ) -> ServerConfig {
  799. var config = self.makeDefaultPlaintextPosixServerConfig()
  800. config.security = .mTLS(
  801. certificateChain: [.file(path: certificatePath, format: .pem)],
  802. privateKey: .file(path: keyPath, format: .pem)
  803. ) {
  804. $0.trustRoots = .certificates([
  805. .file(path: trustRootsPath, format: .pem)
  806. ])
  807. }
  808. return .posix(config)
  809. }
  810. @available(gRPCSwiftNIOTransport 2.0, *)
  811. private func makeMTLSServerConfigWithCallback(
  812. certificatePath: String,
  813. keyPath: String,
  814. trustRootsPath: String,
  815. certificateVerification: TLSConfig.CertificateVerification = .noHostnameVerification,
  816. customVerificationCallback:
  817. @escaping (
  818. @Sendable ([NIOSSLCertificate], EventLoopPromise<NIOSSLVerificationResultWithMetadata>) ->
  819. Void
  820. )
  821. ) -> ServerConfig {
  822. var config = self.makeDefaultPlaintextPosixServerConfig()
  823. config.security = .mTLS(
  824. certificateChain: [.file(path: certificatePath, format: .pem)],
  825. privateKey: .file(path: keyPath, format: .pem)
  826. ) {
  827. $0.clientCertificateVerification = certificateVerification
  828. $0.trustRoots = .certificates([
  829. .file(path: trustRootsPath, format: .pem)
  830. ])
  831. $0.customVerificationCallback = customVerificationCallback
  832. }
  833. return .posix(config)
  834. }
  835. @available(gRPCSwiftNIOTransport 2.0, *)
  836. func withClientAndServer(
  837. clientConfig: ClientConfig,
  838. serverConfig: ServerConfig,
  839. interceptors: [any ServerInterceptor] = [],
  840. _ test: (ControlClient<NIOClientTransport>) async throws -> Void
  841. ) async throws {
  842. let serverTransport: NIOServerTransport
  843. switch serverConfig {
  844. case .posix(let posix):
  845. serverTransport = NIOServerTransport(
  846. .http2NIOPosix(
  847. address: .ipv4(host: "127.0.0.1", port: 0),
  848. transportSecurity: posix.security,
  849. config: posix.transport
  850. )
  851. )
  852. #if canImport(Network)
  853. case .transportServices(let config):
  854. serverTransport = NIOServerTransport(
  855. .http2NIOTS(
  856. address: .ipv4(host: "127.0.0.1", port: 0),
  857. transportSecurity: config.security,
  858. config: config.transport
  859. )
  860. )
  861. #endif
  862. }
  863. try await withGRPCServer(
  864. transport: serverTransport,
  865. services: [ControlService()],
  866. interceptors: interceptors
  867. ) { server in
  868. guard let address = try await server.listeningAddress?.ipv4 else {
  869. throw TLSEnabledTestsError.unexpectedListeningAddress
  870. }
  871. let target: any ResolvableTarget = .ipv4(address: address.host, port: address.port)
  872. let clientTransport: NIOClientTransport
  873. switch clientConfig {
  874. case .posix(let config):
  875. clientTransport = try NIOClientTransport(
  876. .http2NIOPosix(
  877. target: target,
  878. transportSecurity: config.security,
  879. config: config.transport
  880. )
  881. )
  882. #if canImport(Network)
  883. case .transportServices(let config):
  884. clientTransport = try NIOClientTransport(
  885. .http2NIOTS(target: target, transportSecurity: config.security, config: config.transport)
  886. )
  887. #endif
  888. }
  889. try await withGRPCClient(transport: clientTransport) { client in
  890. let control = ControlClient(wrapping: client)
  891. try await test(control)
  892. }
  893. }
  894. }
  895. @available(gRPCSwiftNIOTransport 2.0, *)
  896. private func executeUnaryRPC(control: ControlClient<NIOClientTransport>) async throws {
  897. let input = ControlInput.with { $0.numberOfMessages = 1 }
  898. let request = ClientRequest(message: input)
  899. try await control.unary(request: request) { response in
  900. _ = #expect(throws: Never.self) {
  901. try response.message
  902. }
  903. }
  904. }
  905. }