MutualTLSTests.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. /*
  2. * Copyright 2021, 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. #if canImport(NIOSSL)
  17. import EchoImplementation
  18. import EchoModel
  19. @testable import GRPC
  20. import GRPCSampleData
  21. import NIOCore
  22. import NIOPosix
  23. import NIOSSL
  24. import XCTest
  25. class MutualTLSTests: GRPCTestCase {
  26. enum ExpectedClientError {
  27. case handshakeError
  28. case alertCertRequired
  29. case dropped
  30. }
  31. var clientEventLoopGroup: EventLoopGroup!
  32. var serverEventLoopGroup: EventLoopGroup!
  33. var channel: GRPCChannel?
  34. var server: Server?
  35. override func setUp() {
  36. super.setUp()
  37. self.serverEventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  38. self.clientEventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  39. }
  40. override func tearDown() {
  41. XCTAssertNoThrow(try self.channel?.close().wait())
  42. XCTAssertNoThrow(try self.server?.close().wait())
  43. XCTAssertNoThrow(try self.clientEventLoopGroup.syncShutdownGracefully())
  44. XCTAssertNoThrow(try self.serverEventLoopGroup.syncShutdownGracefully())
  45. super.tearDown()
  46. }
  47. func performTestWith(
  48. _ serverTLSConfiguration: GRPCTLSConfiguration?,
  49. _ clientTLSConfiguration: GRPCTLSConfiguration?,
  50. expectServerHandshakeError: Bool,
  51. expectedClientError: ExpectedClientError?
  52. ) throws {
  53. // Setup the server.
  54. var serverConfiguration = Server.Configuration.default(
  55. target: .hostAndPort("localhost", 0),
  56. eventLoopGroup: self.serverEventLoopGroup,
  57. serviceProviders: [EchoProvider()]
  58. )
  59. serverConfiguration.tlsConfiguration = serverTLSConfiguration
  60. serverConfiguration.logger = self.serverLogger
  61. let serverErrorExpectation = self.expectation(description: "server error")
  62. serverErrorExpectation.isInverted = !expectServerHandshakeError
  63. serverErrorExpectation.assertForOverFulfill = false
  64. let serverErrorDelegate = ServerErrorRecordingDelegate(expectation: serverErrorExpectation)
  65. serverConfiguration.errorDelegate = serverErrorDelegate
  66. self.server = try! Server.start(configuration: serverConfiguration).wait()
  67. let port = self.server!.channel.localAddress!.port!
  68. // Setup the client.
  69. var clientConfiguration = ClientConnection.Configuration.default(
  70. target: .hostAndPort("localhost", port),
  71. eventLoopGroup: self.clientEventLoopGroup
  72. )
  73. clientConfiguration.tlsConfiguration = clientTLSConfiguration
  74. clientConfiguration.connectionBackoff = nil
  75. clientConfiguration.backgroundActivityLogger = self.clientLogger
  76. let clientErrorExpectation = self.expectation(description: "client error")
  77. switch expectedClientError {
  78. case .none:
  79. clientErrorExpectation.isInverted = true
  80. case .handshakeError, .alertCertRequired:
  81. // After the SSL error, the connection being closed also presents as an error.
  82. clientErrorExpectation.expectedFulfillmentCount = 2
  83. case .dropped:
  84. clientErrorExpectation.expectedFulfillmentCount = 1
  85. }
  86. let clientErrorDelegate = ErrorRecordingDelegate(expectation: clientErrorExpectation)
  87. clientConfiguration.errorDelegate = clientErrorDelegate
  88. self.channel = ClientConnection(configuration: clientConfiguration)
  89. let client = Echo_EchoNIOClient(channel: channel!)
  90. // Make the call.
  91. let call = client.get(.with { $0.text = "mumble" })
  92. // Wait for side effects.
  93. self.wait(for: [clientErrorExpectation, serverErrorExpectation], timeout: 10)
  94. if !expectServerHandshakeError {
  95. XCTAssert(
  96. serverErrorDelegate.errors.isEmpty,
  97. "Unexpected server errors: \(serverErrorDelegate.errors)"
  98. )
  99. } else if case .handshakeFailed = serverErrorDelegate.errors.first as? NIOSSLError {
  100. // This is the expected error.
  101. } else {
  102. XCTFail(
  103. "Expected NIOSSLError.handshakeFailed, actual error(s): \(serverErrorDelegate.errors)"
  104. )
  105. }
  106. switch expectedClientError {
  107. case .none:
  108. XCTAssert(
  109. clientErrorDelegate.errors.isEmpty,
  110. "Unexpected client errors: \(clientErrorDelegate.errors)"
  111. )
  112. case .some(.handshakeError):
  113. if case .handshakeFailed = clientErrorDelegate.errors.first as? NIOSSLError {
  114. // This is the expected error.
  115. } else {
  116. XCTFail(
  117. "Expected NIOSSLError.handshakeFailed, actual error(s): \(clientErrorDelegate.errors)"
  118. )
  119. }
  120. case .some(.alertCertRequired):
  121. if let error = clientErrorDelegate.errors.first, error is BoringSSLError {
  122. // This is the expected error when client receives TLSV1_ALERT_CERTIFICATE_REQUIRED.
  123. } else {
  124. XCTFail("Expected BoringSSLError, actual error(s): \(clientErrorDelegate.errors)")
  125. }
  126. case .some(.dropped):
  127. if let error = clientErrorDelegate.errors.first as? GRPCStatus, error.code == .unavailable {
  128. // This is the expected error when client closes the connection.
  129. } else {
  130. XCTFail("Expected BoringSSLError, actual error(s): \(clientErrorDelegate.errors)")
  131. }
  132. }
  133. if !expectServerHandshakeError, expectedClientError == nil {
  134. // Verify response.
  135. let response = try call.response.wait()
  136. XCTAssertEqual(response.text, "Swift echo get: mumble")
  137. let status = try call.status.wait()
  138. XCTAssertEqual(status.code, .ok)
  139. }
  140. }
  141. func test_trustedClientAndServerCerts_success() throws {
  142. let serverTLSConfiguration = GRPCTLSConfiguration.makeServerConfigurationBackedByNIOSSL(
  143. certificateChain: [.certificate(SampleCertificate.server.certificate)],
  144. privateKey: .privateKey(SamplePrivateKey.server),
  145. trustRoots: .certificates([
  146. SampleCertificate.ca.certificate,
  147. SampleCertificate.otherCA.certificate,
  148. ]),
  149. certificateVerification: .noHostnameVerification
  150. )
  151. let clientTLSConfiguration = GRPCTLSConfiguration.makeClientConfigurationBackedByNIOSSL(
  152. certificateChain: [.certificate(SampleCertificate.clientSignedByOtherCA.certificate)],
  153. privateKey: .privateKey(SamplePrivateKey.client),
  154. trustRoots: .certificates([
  155. SampleCertificate.ca.certificate,
  156. SampleCertificate.otherCA.certificate,
  157. ]),
  158. certificateVerification: .fullVerification
  159. )
  160. try self.performTestWith(
  161. serverTLSConfiguration,
  162. clientTLSConfiguration,
  163. expectServerHandshakeError: false,
  164. expectedClientError: nil
  165. )
  166. }
  167. func test_untrustedServerCert_clientError() throws {
  168. let serverTLSConfiguration = GRPCTLSConfiguration.makeServerConfigurationBackedByNIOSSL(
  169. certificateChain: [.certificate(SampleCertificate.server.certificate)],
  170. privateKey: .privateKey(SamplePrivateKey.server),
  171. trustRoots: .certificates([
  172. SampleCertificate.ca.certificate,
  173. SampleCertificate.otherCA.certificate,
  174. ]),
  175. certificateVerification: .noHostnameVerification
  176. )
  177. let clientTLSConfiguration = GRPCTLSConfiguration.makeClientConfigurationBackedByNIOSSL(
  178. certificateChain: [.certificate(SampleCertificate.clientSignedByOtherCA.certificate)],
  179. privateKey: .privateKey(SamplePrivateKey.client),
  180. trustRoots: .certificates([
  181. SampleCertificate.otherCA.certificate
  182. ]),
  183. certificateVerification: .fullVerification
  184. )
  185. try self.performTestWith(
  186. serverTLSConfiguration,
  187. clientTLSConfiguration,
  188. expectServerHandshakeError: true,
  189. expectedClientError: .handshakeError
  190. )
  191. }
  192. func test_untrustedClientCert_serverError() throws {
  193. let serverTLSConfiguration = GRPCTLSConfiguration.makeServerConfigurationBackedByNIOSSL(
  194. certificateChain: [.certificate(SampleCertificate.server.certificate)],
  195. privateKey: .privateKey(SamplePrivateKey.server),
  196. trustRoots: .certificates([
  197. SampleCertificate.ca.certificate
  198. ]),
  199. certificateVerification: .noHostnameVerification
  200. )
  201. let clientTLSConfiguration = GRPCTLSConfiguration.makeClientConfigurationBackedByNIOSSL(
  202. certificateChain: [.certificate(SampleCertificate.clientSignedByOtherCA.certificate)],
  203. privateKey: .privateKey(SamplePrivateKey.client),
  204. trustRoots: .certificates([
  205. SampleCertificate.ca.certificate,
  206. SampleCertificate.otherCA.certificate,
  207. ]),
  208. certificateVerification: .fullVerification
  209. )
  210. try self.performTestWith(
  211. serverTLSConfiguration,
  212. clientTLSConfiguration,
  213. expectServerHandshakeError: true,
  214. expectedClientError: .alertCertRequired
  215. )
  216. }
  217. func test_plaintextServer_clientError() throws {
  218. let clientTLSConfiguration = GRPCTLSConfiguration.makeClientConfigurationBackedByNIOSSL(
  219. certificateChain: [.certificate(SampleCertificate.clientSignedByOtherCA.certificate)],
  220. privateKey: .privateKey(SamplePrivateKey.client),
  221. trustRoots: .certificates([
  222. SampleCertificate.ca.certificate,
  223. SampleCertificate.otherCA.certificate,
  224. ]),
  225. certificateVerification: .fullVerification
  226. )
  227. try self.performTestWith(
  228. nil,
  229. clientTLSConfiguration,
  230. expectServerHandshakeError: false,
  231. expectedClientError: .handshakeError
  232. )
  233. }
  234. func test_plaintextClient_serverError() throws {
  235. let serverTLSConfiguration = GRPCTLSConfiguration.makeServerConfigurationBackedByNIOSSL(
  236. certificateChain: [.certificate(SampleCertificate.server.certificate)],
  237. privateKey: .privateKey(SamplePrivateKey.server),
  238. trustRoots: .certificates([
  239. SampleCertificate.ca.certificate,
  240. SampleCertificate.otherCA.certificate,
  241. ]),
  242. certificateVerification: .noHostnameVerification
  243. )
  244. try self.performTestWith(
  245. serverTLSConfiguration,
  246. nil,
  247. expectServerHandshakeError: true,
  248. expectedClientError: .dropped
  249. )
  250. }
  251. }
  252. #endif // canImport(NIOSSL)