| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- /*
- * Copyright 2021, gRPC Authors All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #if canImport(NIOSSL)
- #if canImport(Network)
- import Dispatch
- import EchoImplementation
- import EchoModel
- import GRPC
- import Network
- import NIOCore
- import NIOPosix
- import NIOSSL
- import NIOTransportServices
- import Security
- import XCTest
- @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
- final class GRPCNetworkFrameworkTests: GRPCTestCase {
- private var server: Server!
- private var client: ClientConnection!
- private var identity: SecIdentity!
- private var pkcs12Bundle: NIOSSLPKCS12Bundle!
- private var tsGroup: NIOTSEventLoopGroup!
- private var group: MultiThreadedEventLoopGroup!
- private let queue = DispatchQueue(label: "io.grpc.verify-handshake")
- private static let p12bundleURL = URL(fileURLWithPath: #filePath)
- .deletingLastPathComponent() // (this file)
- .deletingLastPathComponent() // GRPCTests
- .deletingLastPathComponent() // Tests
- .appendingPathComponent("Sources")
- .appendingPathComponent("GRPCSampleData")
- .appendingPathComponent("bundle")
- .appendingPathExtension("p12")
- // Not really 'async' but there is no 'func setUp() throws' to override.
- override func setUp() async throws {
- try await super.setUp()
- self.tsGroup = NIOTSEventLoopGroup(loopCount: 1)
- self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
- self.identity = try self.loadIdentity()
- XCTAssertNotNil(
- self.identity,
- "Unable to load identity from '\(GRPCNetworkFrameworkTests.p12bundleURL)'"
- )
- self.pkcs12Bundle = try NIOSSLPKCS12Bundle(
- file: GRPCNetworkFrameworkTests.p12bundleURL.path,
- passphrase: "password".utf8
- )
- XCTAssertNotNil(
- self.pkcs12Bundle,
- "Unable to load PCKS12 bundle from '\(GRPCNetworkFrameworkTests.p12bundleURL)'"
- )
- }
- override func tearDown() {
- XCTAssertNoThrow(try self.client?.close().wait())
- XCTAssertNoThrow(try self.server?.close().wait())
- XCTAssertNoThrow(try self.group?.syncShutdownGracefully())
- XCTAssertNoThrow(try self.tsGroup?.syncShutdownGracefully())
- super.tearDown()
- }
- private func loadIdentity() throws -> SecIdentity? {
- let data = try Data(contentsOf: GRPCNetworkFrameworkTests.p12bundleURL)
- let options = [kSecImportExportPassphrase as String: "password"]
- var rawItems: CFArray?
- let status = SecPKCS12Import(data as CFData, options as CFDictionary, &rawItems)
- switch status {
- case errSecSuccess:
- ()
- case errSecInteractionNotAllowed:
- throw XCTSkip("Unable to import PKCS12 bundle: no interaction allowed")
- default:
- XCTFail("SecPKCS12Import: failed with status \(status)")
- return nil
- }
- let items = rawItems! as! [[String: Any]]
- return items.first?[kSecImportItemIdentity as String] as! SecIdentity?
- }
- private func doEchoGet() throws {
- let echo = Echo_EchoNIOClient(channel: self.client)
- let get = echo.get(.with { $0.text = "hello" })
- XCTAssertNoThrow(try get.response.wait())
- }
- private func startServer(_ builder: Server.Builder) throws {
- self.server =
- try builder
- .withServiceProviders([EchoProvider()])
- .withLogger(self.serverLogger)
- .bind(host: "127.0.0.1", port: 0)
- .wait()
- }
- private func startClient(_ builder: ClientConnection.Builder) {
- self.client =
- builder
- .withBackgroundActivityLogger(self.clientLogger)
- .withConnectionReestablishment(enabled: false)
- .connect(host: "127.0.0.1", port: self.server.channel.localAddress!.port!)
- }
- func testNetworkFrameworkServerWithNIOSSLClient() throws {
- let serverBuilder = Server.usingTLSBackedByNetworkFramework(
- on: self.tsGroup,
- with: self.identity
- )
- XCTAssertNoThrow(try self.startServer(serverBuilder))
- let clientBuilder = ClientConnection.usingTLSBackedByNIOSSL(on: self.group)
- .withTLS(serverHostnameOverride: "localhost")
- .withTLS(trustRoots: .certificates(self.pkcs12Bundle.certificateChain))
- self.startClient(clientBuilder)
- XCTAssertNoThrow(try self.doEchoGet())
- }
- func testNIOSSLServerOnMTELGWithNetworkFrameworkClient() throws {
- try self.doTestNIOSSLServerWithNetworkFrameworkClient(serverGroup: self.group)
- }
- func testNIOSSLServerOnNIOTSGroupWithNetworkFrameworkClient() throws {
- try self.doTestNIOSSLServerWithNetworkFrameworkClient(serverGroup: self.tsGroup)
- }
- func doTestNIOSSLServerWithNetworkFrameworkClient(serverGroup: EventLoopGroup) throws {
- let serverBuilder = Server.usingTLSBackedByNIOSSL(
- on: serverGroup,
- certificateChain: self.pkcs12Bundle.certificateChain,
- privateKey: self.pkcs12Bundle.privateKey
- )
- XCTAssertNoThrow(try self.startServer(serverBuilder))
- var certificate: SecCertificate?
- guard SecIdentityCopyCertificate(self.identity, &certificate) == errSecSuccess else {
- XCTFail("Unable to extract certificate from identity")
- return
- }
- let clientBuilder = ClientConnection.usingTLSBackedByNetworkFramework(on: self.tsGroup)
- .withTLS(serverHostnameOverride: "localhost")
- .withTLSHandshakeVerificationCallback(on: self.queue) { _, trust, verify in
- let actualTrust = sec_trust_copy_ref(trust).takeRetainedValue()
- SecTrustSetAnchorCertificates(actualTrust, [certificate!] as CFArray)
- SecTrustEvaluateAsyncWithError(actualTrust, self.queue) { _, valid, error in
- if let error = error {
- XCTFail("Trust evaluation error: \(error)")
- }
- verify(valid)
- }
- }
- self.startClient(clientBuilder)
- XCTAssertNoThrow(try self.doEchoGet())
- }
- func testNetworkFrameworkTLServerAndClient() throws {
- let serverBuilder = Server.usingTLSBackedByNetworkFramework(
- on: self.tsGroup,
- with: self.identity
- )
- XCTAssertNoThrow(try self.startServer(serverBuilder))
- var certificate: SecCertificate?
- guard SecIdentityCopyCertificate(self.identity, &certificate) == errSecSuccess else {
- XCTFail("Unable to extract certificate from identity")
- return
- }
- let clientBuilder = ClientConnection.usingTLSBackedByNetworkFramework(on: self.tsGroup)
- .withTLS(serverHostnameOverride: "localhost")
- .withTLSHandshakeVerificationCallback(on: self.queue) { _, trust, verify in
- let actualTrust = sec_trust_copy_ref(trust).takeRetainedValue()
- SecTrustSetAnchorCertificates(actualTrust, [certificate!] as CFArray)
- SecTrustEvaluateAsyncWithError(actualTrust, self.queue) { _, valid, error in
- if let error = error {
- XCTFail("Trust evaluation error: \(error)")
- }
- verify(valid)
- }
- }
- self.startClient(clientBuilder)
- XCTAssertNoThrow(try self.doEchoGet())
- }
- }
- #endif // canImport(Network)
- #endif // canImport(NIOSSL)
|