MutualTLSTests.swift 10 KB

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