HTTP2TransportTLSEnabledTests.swift 13 KB

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