blob: d350f821ee729f6b9e456f23d947b8e6e6c5e0ac [file] [log] [blame]
Chris Simpsona9b6c702018-04-08 07:11:37 -04001/*
2* Licensed to the Apache Software Foundation (ASF) under one
3* or more contributor license agreements. See the NOTICE file
4* distributed with this work for additional information
5* regarding copyright ownership. The ASF licenses this file
6* to you under the Apache License, Version 2.0 (the
7* "License"); you may not use this file except in compliance
8* with the License. You may obtain a copy of the License at
9*
10* http://www.apache.org/licenses/LICENSE-2.0
11*
12* Unless required by applicable law or agreed to in writing,
13* software distributed under the License is distributed on an
14* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15* KIND, either express or implied. See the License for the
16* specific language governing permissions and limitations
17* under the License.
18*/
19
20import Foundation
21import CoreFoundation
22
Antoine Cœur08a6eb62019-07-08 18:42:09 +080023#if !swift(>=4.2)
24// Swift 3/4 compatibility
25fileprivate extension RunLoopMode {
26 static let `default` = defaultRunLoopMode
27}
28#endif
29
Chris Simpsona9b6c702018-04-08 07:11:37 -040030#if os(Linux)
31public class TSSLSocketTransport {
32 init(hostname: String, port: UInt16) {
33 // FIXME!
34 assert(false, "Security not available in Linux, TSSLSocketTransport Unavilable for now")
35 }
36}
37#else
38let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian
39let htons = isLittleEndian ? _OSSwapInt16 : { $0 }
40let htonl = isLittleEndian ? _OSSwapInt32 : { $0 }
41
42public class TSSLSocketTransport: TStreamTransport {
43 var sslHostname: String
44 var sd: Int32 = 0
45
46 public init(hostname: String, port: UInt16) throws {
47 sslHostname = hostname
48 var readStream: Unmanaged<CFReadStream>?
49 var writeStream: Unmanaged<CFWriteStream>?
50
51 /* create a socket structure */
52 var pin: sockaddr_in = sockaddr_in()
53 var hp: UnsafeMutablePointer<hostent>? = nil
54 for i in 0..<10 {
55
56 hp = gethostbyname(hostname.cString(using: String.Encoding.utf8)!)
57 if hp == nil {
58 print("failed to resolve hostname \(hostname)")
59 herror("resolv")
60 if i == 9 {
61 super.init(inputStream: nil, outputStream: nil) // have to init before throwing
62 throw TSSLSocketTransportError(error: .hostanameResolution(hostname: hostname))
63 }
64 Thread.sleep(forTimeInterval: 0.2)
65 } else {
66 break
67 }
68 }
69 pin.sin_family = UInt8(AF_INET)
70 pin.sin_addr = in_addr(s_addr: UInt32((hp?.pointee.h_addr_list.pointee?.pointee)!)) // Is there a better way to get this???
71 pin.sin_port = htons(port)
72
73 /* create the socket */
74 sd = socket(Int32(AF_INET), Int32(SOCK_STREAM), Int32(IPPROTO_TCP))
75 if sd == -1 {
76 super.init(inputStream: nil, outputStream: nil) // have to init before throwing
77 throw TSSLSocketTransportError(error: .socketCreate(port: Int(port)))
78 }
79
80 /* open a connection */
81 // need a non-self ref to sd, otherwise the j complains
82 let sd_local = sd
83 let connectResult = withUnsafePointer(to: &pin) {
84 connect(sd_local, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size))
85 }
86 if connectResult == -1 {
87 super.init(inputStream: nil, outputStream: nil) // have to init before throwing
88 throw TSSLSocketTransportError(error: .connect)
89 }
90
91 CFStreamCreatePairWithSocket(kCFAllocatorDefault, sd, &readStream, &writeStream)
92
93 CFReadStreamSetProperty(readStream?.takeRetainedValue(), .socketNativeHandle, kCFBooleanTrue)
94 CFWriteStreamSetProperty(writeStream?.takeRetainedValue(), .socketNativeHandle, kCFBooleanTrue)
95
96 var inputStream: InputStream? = nil
97 var outputStream: OutputStream? = nil
98 if readStream != nil && writeStream != nil {
99
100 CFReadStreamSetProperty(readStream?.takeRetainedValue(),
101 .socketSecurityLevel,
102 kCFStreamSocketSecurityLevelTLSv1)
103
104 let settings: [String: Bool] = [kCFStreamSSLValidatesCertificateChain as String: true]
105
106 CFReadStreamSetProperty(readStream?.takeRetainedValue(),
107 .SSLSettings,
Antoine Cœur08a6eb62019-07-08 18:42:09 +0800108 settings as CFTypeRef)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400109
110 CFWriteStreamSetProperty(writeStream?.takeRetainedValue(),
111 .SSLSettings,
Antoine Cœur08a6eb62019-07-08 18:42:09 +0800112 settings as CFTypeRef)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400113
114 inputStream = readStream!.takeRetainedValue()
Antoine Cœur08a6eb62019-07-08 18:42:09 +0800115 inputStream?.schedule(in: .current, forMode: .default)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400116 inputStream?.open()
117
118 outputStream = writeStream!.takeRetainedValue()
Antoine Cœur08a6eb62019-07-08 18:42:09 +0800119 outputStream?.schedule(in: .current, forMode: .default)
Chris Simpsona9b6c702018-04-08 07:11:37 -0400120 outputStream?.open()
121
122 readStream?.release()
123 writeStream?.release()
124 }
125
126
127 super.init(inputStream: inputStream, outputStream: outputStream)
128 self.input?.delegate = self
129 self.output?.delegate = self
130 }
131
132 func recoverFromTrustFailure(_ myTrust: SecTrust, lastTrustResult: SecTrustResultType) -> Bool {
133 let trustTime = SecTrustGetVerifyTime(myTrust)
134 let currentTime = CFAbsoluteTimeGetCurrent()
135
136 let timeIncrement = 31536000 // from TSSLSocketTransport.m
137 let newTime = currentTime - Double(timeIncrement)
138
139 if trustTime - newTime != 0 {
140 let newDate = CFDateCreate(nil, newTime)
141 SecTrustSetVerifyDate(myTrust, newDate!)
142
143 var tr = lastTrustResult
144 let success = withUnsafeMutablePointer(to: &tr) { trPtr -> Bool in
145 if SecTrustEvaluate(myTrust, trPtr) != errSecSuccess {
146 return false
147 }
148 return true
149 }
150 if !success { return false }
151 }
152 if lastTrustResult == .proceed || lastTrustResult == .unspecified {
153 return false
154 }
155
156 print("TSSLSocketTransport: Unable to recover certificate trust failure")
157 return true
158 }
159
160 public func isOpen() -> Bool {
161 return sd > 0
162 }
163}
164
165extension TSSLSocketTransport: StreamDelegate {
166 public func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
167
168 switch eventCode {
169 case Stream.Event(): break
170 case Stream.Event.hasBytesAvailable: break
171 case Stream.Event.openCompleted: break
172 case Stream.Event.hasSpaceAvailable:
173 var proceed = false
174 var trustResult: SecTrustResultType = .invalid
175
176 var newPolicies: CFMutableArray?
177
178 repeat {
179 let trust: SecTrust = aStream.property(forKey: .SSLPeerTrust) as! SecTrust
180
181 // Add new policy to current list of policies
182 let policy = SecPolicyCreateSSL(false, sslHostname as CFString?)
183 var ppolicy = policy // mutable for pointer
184 let policies: UnsafeMutablePointer<CFArray?>? = nil
185 if SecTrustCopyPolicies(trust, policies!) != errSecSuccess {
186 break
187 }
188 withUnsafeMutablePointer(to: &ppolicy) { ptr in
189 newPolicies = CFArrayCreateMutableCopy(nil, 0, policies?.pointee)
190 CFArrayAppendValue(newPolicies, ptr)
191 }
192
193 // update trust policies
194 if SecTrustSetPolicies(trust, newPolicies!) != errSecSuccess {
195 break
196 }
197
198 // Evaluate the trust chain
199 let success = withUnsafeMutablePointer(to: &trustResult) { trustPtr -> Bool in
200 if SecTrustEvaluate(trust, trustPtr) != errSecSuccess {
201 return false
202 }
203 return true
204 }
205
206 if !success {
207 break
208 }
209
210
211 switch trustResult {
212 case .proceed: proceed = true
213 case .unspecified: proceed = true
214 case .recoverableTrustFailure:
215 proceed = self.recoverFromTrustFailure(trust, lastTrustResult: trustResult)
216
217 case .deny: break
218 case .fatalTrustFailure: break
219 case .otherError: break
220 case .invalid: break
221 default: break
222 }
223 } while false
224
225 if !proceed {
226 print("TSSLSocketTransport: Cannot trust certificate. Result: \(trustResult)")
227 aStream.close()
228 }
229
230 case Stream.Event.errorOccurred: break
231 case Stream.Event.endEncountered: break
232 default: break
233 }
234 }
235}
236#endif