HTTP2TransportTLSEnabledTests.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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 Crypto
  17. import Foundation
  18. import GRPCNIOTransportHTTP2Posix
  19. import NIOSSL
  20. import SwiftASN1
  21. import Testing
  22. import X509
  23. @Suite("HTTP/2 transport E2E tests with TLS enabled")
  24. struct HTTP2TransportTLSEnabledTests {
  25. // - MARK: Tests
  26. @Test(
  27. "When using defaults, server does not perform client verification",
  28. arguments: [TransportKind.posix],
  29. [TransportKind.posix]
  30. )
  31. func testRPC_Defaults_OK(
  32. clientTransport: TransportKind,
  33. serverTransport: TransportKind
  34. ) async throws {
  35. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  36. let clientTransportConfig = self.makeDefaultClientTLSConfig(
  37. for: clientTransport,
  38. certificateKeyPairs: certificateKeyPairs
  39. )
  40. let serverTransportConfig = self.makeDefaultServerTLSConfig(
  41. for: serverTransport,
  42. certificateKeyPairs: certificateKeyPairs
  43. )
  44. try await self.withClientAndServer(
  45. clientTLSConfig: clientTransportConfig,
  46. serverTLSConfig: serverTransportConfig
  47. ) { control in
  48. await #expect(throws: Never.self) {
  49. try await self.executeUnaryRPC(control: control)
  50. }
  51. }
  52. }
  53. @Test(
  54. "When using mTLS defaults, both client and server verify each others' certificates",
  55. arguments: [TransportKind.posix],
  56. [TransportKind.posix]
  57. )
  58. func testRPC_mTLS_OK(
  59. clientTransport: TransportKind,
  60. serverTransport: TransportKind
  61. ) async throws {
  62. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  63. let clientTransportConfig = self.makeMTLSClientTLSConfig(
  64. for: clientTransport,
  65. certificateKeyPairs: certificateKeyPairs,
  66. serverHostname: "localhost"
  67. )
  68. let serverTransportConfig = self.makeMTLSServerTLSConfig(
  69. for: serverTransport,
  70. certificateKeyPairs: certificateKeyPairs,
  71. includeClientCertificateInTrustRoots: true
  72. )
  73. try await self.withClientAndServer(
  74. clientTLSConfig: clientTransportConfig,
  75. serverTLSConfig: serverTransportConfig
  76. ) { control in
  77. await #expect(throws: Never.self) {
  78. try await self.executeUnaryRPC(control: control)
  79. }
  80. }
  81. }
  82. @Test(
  83. "Error is surfaced when client fails server verification",
  84. arguments: [TransportKind.posix],
  85. [TransportKind.posix]
  86. )
  87. // Verification should fail because the custom hostname is missing on the client.
  88. func testClientFailsServerValidation(
  89. clientTransport: TransportKind,
  90. serverTransport: TransportKind
  91. ) async throws {
  92. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  93. let clientTransportConfig = self.makeMTLSClientTLSConfig(
  94. for: clientTransport,
  95. certificateKeyPairs: certificateKeyPairs,
  96. serverHostname: nil
  97. )
  98. let serverTransportConfig = self.makeMTLSServerTLSConfig(
  99. for: serverTransport,
  100. certificateKeyPairs: certificateKeyPairs,
  101. includeClientCertificateInTrustRoots: true
  102. )
  103. try await self.withClientAndServer(
  104. clientTLSConfig: clientTransportConfig,
  105. serverTLSConfig: serverTransportConfig
  106. ) { control in
  107. await #expect {
  108. try await self.executeUnaryRPC(control: control)
  109. } throws: { error in
  110. guard let rootError = error as? RPCError else {
  111. Issue.record("Should be an RPC error")
  112. return false
  113. }
  114. #expect(rootError.code == .unavailable)
  115. #expect(
  116. rootError.message
  117. == "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
  118. )
  119. guard
  120. let sslError = rootError.cause as? NIOSSLExtraError,
  121. case .failedToValidateHostname = sslError
  122. else {
  123. Issue.record(
  124. "Should be a NIOSSLExtraError.failedToValidateHostname error, but was: \(String(describing: rootError.cause))"
  125. )
  126. return false
  127. }
  128. return true
  129. }
  130. }
  131. }
  132. @Test(
  133. "Error is surfaced when server fails client verification",
  134. arguments: [TransportKind.posix],
  135. [TransportKind.posix]
  136. )
  137. // Verification should fail because the server does not have trust roots containing the client cert.
  138. func testServerFailsClientValidation(
  139. clientTransport: TransportKind,
  140. serverTransport: TransportKind
  141. ) async throws {
  142. let certificateKeyPairs = try SelfSignedCertificateKeyPairs()
  143. let clientTransportConfig = self.makeMTLSClientTLSConfig(
  144. for: clientTransport,
  145. certificateKeyPairs: certificateKeyPairs,
  146. serverHostname: "localhost"
  147. )
  148. let serverTransportConfig = self.makeMTLSServerTLSConfig(
  149. for: serverTransport,
  150. certificateKeyPairs: certificateKeyPairs,
  151. includeClientCertificateInTrustRoots: false
  152. )
  153. try await self.withClientAndServer(
  154. clientTLSConfig: clientTransportConfig,
  155. serverTLSConfig: serverTransportConfig
  156. ) { control in
  157. await #expect {
  158. try await self.executeUnaryRPC(control: control)
  159. } throws: { error in
  160. guard let rootError = error as? RPCError else {
  161. Issue.record("Should be an RPC error")
  162. return false
  163. }
  164. #expect(rootError.code == .unavailable)
  165. #expect(
  166. rootError.message
  167. == "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
  168. )
  169. guard
  170. let sslError = rootError.cause as? NIOSSL.BoringSSLError,
  171. case .sslError = sslError
  172. else {
  173. Issue.record(
  174. "Should be a NIOSSL.sslError error, but was: \(String(describing: rootError.cause))"
  175. )
  176. return false
  177. }
  178. return true
  179. }
  180. }
  181. }
  182. // - MARK: Test Utilities
  183. enum TransportKind: Sendable {
  184. case posix
  185. }
  186. enum TLSConfig {
  187. enum Client {
  188. case posix(HTTP2ClientTransport.Posix.Config.TransportSecurity)
  189. }
  190. enum Server {
  191. case posix(HTTP2ServerTransport.Posix.Config.TransportSecurity)
  192. }
  193. }
  194. func makeDefaultClientTLSConfig(
  195. for transportSecurity: TransportKind,
  196. certificateKeyPairs: SelfSignedCertificateKeyPairs
  197. ) -> TLSConfig.Client {
  198. switch transportSecurity {
  199. case .posix:
  200. return .posix(
  201. .tls(
  202. .defaults {
  203. $0.trustRoots = .certificates([
  204. .bytes(certificateKeyPairs.server.certificate, format: .der)
  205. ])
  206. $0.serverHostname = "localhost"
  207. }
  208. )
  209. )
  210. }
  211. }
  212. func makeMTLSClientTLSConfig(
  213. for transportKind: TransportKind,
  214. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  215. serverHostname: String?
  216. ) -> TLSConfig.Client {
  217. switch transportKind {
  218. case .posix:
  219. return .posix(
  220. .tls(
  221. .mTLS(
  222. certificateChain: [.bytes(certificateKeyPairs.client.certificate, format: .der)],
  223. privateKey: .bytes(certificateKeyPairs.client.key, format: .der)
  224. ) {
  225. $0.trustRoots = .certificates([
  226. .bytes(certificateKeyPairs.server.certificate, format: .der)
  227. ])
  228. $0.serverHostname = serverHostname
  229. }
  230. )
  231. )
  232. }
  233. }
  234. func makeDefaultServerTLSConfig(
  235. for transportKind: TransportKind,
  236. certificateKeyPairs: SelfSignedCertificateKeyPairs
  237. ) -> TLSConfig.Server {
  238. switch transportKind {
  239. case .posix:
  240. return .posix(
  241. .tls(
  242. .defaults(
  243. certificateChain: [.bytes(certificateKeyPairs.server.certificate, format: .der)],
  244. privateKey: .bytes(certificateKeyPairs.server.key, format: .der)
  245. )
  246. )
  247. )
  248. }
  249. }
  250. func makeMTLSServerTLSConfig(
  251. for transportKind: TransportKind,
  252. certificateKeyPairs: SelfSignedCertificateKeyPairs,
  253. includeClientCertificateInTrustRoots: Bool
  254. ) -> TLSConfig.Server {
  255. switch transportKind {
  256. case .posix:
  257. return .posix(
  258. .tls(
  259. .mTLS(
  260. certificateChain: [.bytes(certificateKeyPairs.server.certificate, format: .der)],
  261. privateKey: .bytes(certificateKeyPairs.server.key, format: .der)
  262. ) {
  263. if includeClientCertificateInTrustRoots {
  264. $0.trustRoots = .certificates([
  265. .bytes(certificateKeyPairs.client.certificate, format: .der)
  266. ])
  267. }
  268. }
  269. )
  270. )
  271. }
  272. }
  273. func withClientAndServer(
  274. clientTLSConfig: TLSConfig.Client,
  275. serverTLSConfig: TLSConfig.Server,
  276. _ test: (ControlClient) async throws -> Void
  277. ) async throws {
  278. try await withThrowingDiscardingTaskGroup { group in
  279. let server = self.makeServer(tlsConfig: serverTLSConfig)
  280. group.addTask {
  281. try await server.serve()
  282. }
  283. guard let address = try await server.listeningAddress?.ipv4 else {
  284. Issue.record("Unexpected address to connect to")
  285. return
  286. }
  287. let target: any ResolvableTarget = .ipv4(host: address.host, port: address.port)
  288. let client = try self.makeClient(tlsConfig: clientTLSConfig, target: target)
  289. group.addTask {
  290. try await client.run()
  291. }
  292. let control = ControlClient(wrapping: client)
  293. try await test(control)
  294. server.beginGracefulShutdown()
  295. client.beginGracefulShutdown()
  296. }
  297. }
  298. private func makeServer(tlsConfig: TLSConfig.Server) -> GRPCServer {
  299. let services = [ControlService()]
  300. switch tlsConfig {
  301. case .posix(let transportSecurity):
  302. let server = GRPCServer(
  303. transport: .http2NIOPosix(
  304. address: .ipv4(host: "127.0.0.1", port: 0),
  305. config: .defaults(transportSecurity: transportSecurity)
  306. ),
  307. services: services
  308. )
  309. return server
  310. }
  311. }
  312. private func makeClient(
  313. tlsConfig: TLSConfig.Client,
  314. target: any ResolvableTarget
  315. ) throws -> GRPCClient {
  316. let transport: any ClientTransport
  317. switch tlsConfig {
  318. case .posix(let transportSecurity):
  319. transport = try HTTP2ClientTransport.Posix(
  320. target: target,
  321. config: .defaults(transportSecurity: transportSecurity) { config in
  322. config.backoff.initial = .milliseconds(100)
  323. config.backoff.multiplier = 1
  324. config.backoff.jitter = 0
  325. },
  326. serviceConfig: ServiceConfig()
  327. )
  328. }
  329. return GRPCClient(transport: transport)
  330. }
  331. private func executeUnaryRPC(control: ControlClient) async throws {
  332. let input = ControlInput.with { $0.numberOfMessages = 1 }
  333. let request = ClientRequest(message: input)
  334. try await control.unary(request: request) { response in
  335. #expect(throws: Never.self) { try response.message }
  336. }
  337. }
  338. }
  339. struct SelfSignedCertificateKeyPairs {
  340. struct CertificateKeyPair {
  341. let certificate: [UInt8]
  342. let key: [UInt8]
  343. }
  344. let server: CertificateKeyPair
  345. let client: CertificateKeyPair
  346. init() throws {
  347. let server = try Self.makeSelfSignedDERCertificateAndPrivateKey(name: "Server Certificate")
  348. let client = try Self.makeSelfSignedDERCertificateAndPrivateKey(name: "Client Certificate")
  349. self.server = CertificateKeyPair(certificate: server.cert, key: server.key)
  350. self.client = CertificateKeyPair(certificate: client.cert, key: client.key)
  351. }
  352. private static func makeSelfSignedDERCertificateAndPrivateKey(
  353. name: String
  354. ) throws -> (cert: [UInt8], key: [UInt8]) {
  355. let swiftCryptoKey = P256.Signing.PrivateKey()
  356. let key = Certificate.PrivateKey(swiftCryptoKey)
  357. let subjectName = try DistinguishedName { CommonName(name) }
  358. let issuerName = subjectName
  359. let now = Date()
  360. let extensions = try Certificate.Extensions {
  361. Critical(
  362. BasicConstraints.isCertificateAuthority(maxPathLength: nil)
  363. )
  364. Critical(
  365. KeyUsage(digitalSignature: true, keyCertSign: true)
  366. )
  367. Critical(
  368. try ExtendedKeyUsage([.serverAuth, .clientAuth])
  369. )
  370. SubjectAlternativeNames([.dnsName("localhost")])
  371. }
  372. let certificate = try Certificate(
  373. version: .v3,
  374. serialNumber: Certificate.SerialNumber(),
  375. publicKey: key.publicKey,
  376. notValidBefore: now.addingTimeInterval(-60 * 60),
  377. notValidAfter: now.addingTimeInterval(60 * 60 * 24 * 365),
  378. issuer: issuerName,
  379. subject: subjectName,
  380. signatureAlgorithm: .ecdsaWithSHA256,
  381. extensions: extensions,
  382. issuerPrivateKey: key
  383. )
  384. var serializer = DER.Serializer()
  385. try serializer.serialize(certificate)
  386. let certBytes = serializer.serializedBytes
  387. let keyBytes = try key.serializeAsPEM().derBytes
  388. return (certBytes, keyBytes)
  389. }
  390. }