Chris Simpson | a9b6c70 | 2018-04-08 07:11:37 -0400 | [diff] [blame] | 1 | /* |
| 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 | |
| 20 | import Foundation |
| 21 | import CoreFoundation |
| 22 | |
| 23 | #if os(Linux) |
| 24 | public class TSSLSocketTransport { |
| 25 | init(hostname: String, port: UInt16) { |
| 26 | // FIXME! |
| 27 | assert(false, "Security not available in Linux, TSSLSocketTransport Unavilable for now") |
| 28 | } |
| 29 | } |
| 30 | #else |
| 31 | let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian |
| 32 | let htons = isLittleEndian ? _OSSwapInt16 : { $0 } |
| 33 | let htonl = isLittleEndian ? _OSSwapInt32 : { $0 } |
| 34 | |
| 35 | public class TSSLSocketTransport: TStreamTransport { |
| 36 | var sslHostname: String |
| 37 | var sd: Int32 = 0 |
| 38 | |
| 39 | public init(hostname: String, port: UInt16) throws { |
| 40 | sslHostname = hostname |
| 41 | var readStream: Unmanaged<CFReadStream>? |
| 42 | var writeStream: Unmanaged<CFWriteStream>? |
| 43 | |
| 44 | /* create a socket structure */ |
| 45 | var pin: sockaddr_in = sockaddr_in() |
| 46 | var hp: UnsafeMutablePointer<hostent>? = nil |
| 47 | for i in 0..<10 { |
| 48 | |
| 49 | hp = gethostbyname(hostname.cString(using: String.Encoding.utf8)!) |
| 50 | if hp == nil { |
| 51 | print("failed to resolve hostname \(hostname)") |
| 52 | herror("resolv") |
| 53 | if i == 9 { |
| 54 | super.init(inputStream: nil, outputStream: nil) // have to init before throwing |
| 55 | throw TSSLSocketTransportError(error: .hostanameResolution(hostname: hostname)) |
| 56 | } |
| 57 | Thread.sleep(forTimeInterval: 0.2) |
| 58 | } else { |
| 59 | break |
| 60 | } |
| 61 | } |
| 62 | pin.sin_family = UInt8(AF_INET) |
| 63 | pin.sin_addr = in_addr(s_addr: UInt32((hp?.pointee.h_addr_list.pointee?.pointee)!)) // Is there a better way to get this??? |
| 64 | pin.sin_port = htons(port) |
| 65 | |
| 66 | /* create the socket */ |
| 67 | sd = socket(Int32(AF_INET), Int32(SOCK_STREAM), Int32(IPPROTO_TCP)) |
| 68 | if sd == -1 { |
| 69 | super.init(inputStream: nil, outputStream: nil) // have to init before throwing |
| 70 | throw TSSLSocketTransportError(error: .socketCreate(port: Int(port))) |
| 71 | } |
| 72 | |
| 73 | /* open a connection */ |
| 74 | // need a non-self ref to sd, otherwise the j complains |
| 75 | let sd_local = sd |
| 76 | let connectResult = withUnsafePointer(to: &pin) { |
| 77 | connect(sd_local, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size)) |
| 78 | } |
| 79 | if connectResult == -1 { |
| 80 | super.init(inputStream: nil, outputStream: nil) // have to init before throwing |
| 81 | throw TSSLSocketTransportError(error: .connect) |
| 82 | } |
| 83 | |
| 84 | CFStreamCreatePairWithSocket(kCFAllocatorDefault, sd, &readStream, &writeStream) |
| 85 | |
| 86 | CFReadStreamSetProperty(readStream?.takeRetainedValue(), .socketNativeHandle, kCFBooleanTrue) |
| 87 | CFWriteStreamSetProperty(writeStream?.takeRetainedValue(), .socketNativeHandle, kCFBooleanTrue) |
| 88 | |
| 89 | var inputStream: InputStream? = nil |
| 90 | var outputStream: OutputStream? = nil |
| 91 | if readStream != nil && writeStream != nil { |
| 92 | |
| 93 | CFReadStreamSetProperty(readStream?.takeRetainedValue(), |
| 94 | .socketSecurityLevel, |
| 95 | kCFStreamSocketSecurityLevelTLSv1) |
| 96 | |
| 97 | let settings: [String: Bool] = [kCFStreamSSLValidatesCertificateChain as String: true] |
| 98 | |
| 99 | CFReadStreamSetProperty(readStream?.takeRetainedValue(), |
| 100 | .SSLSettings, |
| 101 | settings as CFTypeRef!) |
| 102 | |
| 103 | CFWriteStreamSetProperty(writeStream?.takeRetainedValue(), |
| 104 | .SSLSettings, |
| 105 | settings as CFTypeRef!) |
| 106 | |
| 107 | inputStream = readStream!.takeRetainedValue() |
| 108 | inputStream?.schedule(in: .current, forMode: .defaultRunLoopMode) |
| 109 | inputStream?.open() |
| 110 | |
| 111 | outputStream = writeStream!.takeRetainedValue() |
| 112 | outputStream?.schedule(in: .current, forMode: .defaultRunLoopMode) |
| 113 | outputStream?.open() |
| 114 | |
| 115 | readStream?.release() |
| 116 | writeStream?.release() |
| 117 | } |
| 118 | |
| 119 | |
| 120 | super.init(inputStream: inputStream, outputStream: outputStream) |
| 121 | self.input?.delegate = self |
| 122 | self.output?.delegate = self |
| 123 | } |
| 124 | |
| 125 | func recoverFromTrustFailure(_ myTrust: SecTrust, lastTrustResult: SecTrustResultType) -> Bool { |
| 126 | let trustTime = SecTrustGetVerifyTime(myTrust) |
| 127 | let currentTime = CFAbsoluteTimeGetCurrent() |
| 128 | |
| 129 | let timeIncrement = 31536000 // from TSSLSocketTransport.m |
| 130 | let newTime = currentTime - Double(timeIncrement) |
| 131 | |
| 132 | if trustTime - newTime != 0 { |
| 133 | let newDate = CFDateCreate(nil, newTime) |
| 134 | SecTrustSetVerifyDate(myTrust, newDate!) |
| 135 | |
| 136 | var tr = lastTrustResult |
| 137 | let success = withUnsafeMutablePointer(to: &tr) { trPtr -> Bool in |
| 138 | if SecTrustEvaluate(myTrust, trPtr) != errSecSuccess { |
| 139 | return false |
| 140 | } |
| 141 | return true |
| 142 | } |
| 143 | if !success { return false } |
| 144 | } |
| 145 | if lastTrustResult == .proceed || lastTrustResult == .unspecified { |
| 146 | return false |
| 147 | } |
| 148 | |
| 149 | print("TSSLSocketTransport: Unable to recover certificate trust failure") |
| 150 | return true |
| 151 | } |
| 152 | |
| 153 | public func isOpen() -> Bool { |
| 154 | return sd > 0 |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | extension TSSLSocketTransport: StreamDelegate { |
| 159 | public func stream(_ aStream: Stream, handle eventCode: Stream.Event) { |
| 160 | |
| 161 | switch eventCode { |
| 162 | case Stream.Event(): break |
| 163 | case Stream.Event.hasBytesAvailable: break |
| 164 | case Stream.Event.openCompleted: break |
| 165 | case Stream.Event.hasSpaceAvailable: |
| 166 | var proceed = false |
| 167 | var trustResult: SecTrustResultType = .invalid |
| 168 | |
| 169 | var newPolicies: CFMutableArray? |
| 170 | |
| 171 | repeat { |
| 172 | let trust: SecTrust = aStream.property(forKey: .SSLPeerTrust) as! SecTrust |
| 173 | |
| 174 | // Add new policy to current list of policies |
| 175 | let policy = SecPolicyCreateSSL(false, sslHostname as CFString?) |
| 176 | var ppolicy = policy // mutable for pointer |
| 177 | let policies: UnsafeMutablePointer<CFArray?>? = nil |
| 178 | if SecTrustCopyPolicies(trust, policies!) != errSecSuccess { |
| 179 | break |
| 180 | } |
| 181 | withUnsafeMutablePointer(to: &ppolicy) { ptr in |
| 182 | newPolicies = CFArrayCreateMutableCopy(nil, 0, policies?.pointee) |
| 183 | CFArrayAppendValue(newPolicies, ptr) |
| 184 | } |
| 185 | |
| 186 | // update trust policies |
| 187 | if SecTrustSetPolicies(trust, newPolicies!) != errSecSuccess { |
| 188 | break |
| 189 | } |
| 190 | |
| 191 | // Evaluate the trust chain |
| 192 | let success = withUnsafeMutablePointer(to: &trustResult) { trustPtr -> Bool in |
| 193 | if SecTrustEvaluate(trust, trustPtr) != errSecSuccess { |
| 194 | return false |
| 195 | } |
| 196 | return true |
| 197 | } |
| 198 | |
| 199 | if !success { |
| 200 | break |
| 201 | } |
| 202 | |
| 203 | |
| 204 | switch trustResult { |
| 205 | case .proceed: proceed = true |
| 206 | case .unspecified: proceed = true |
| 207 | case .recoverableTrustFailure: |
| 208 | proceed = self.recoverFromTrustFailure(trust, lastTrustResult: trustResult) |
| 209 | |
| 210 | case .deny: break |
| 211 | case .fatalTrustFailure: break |
| 212 | case .otherError: break |
| 213 | case .invalid: break |
| 214 | default: break |
| 215 | } |
| 216 | } while false |
| 217 | |
| 218 | if !proceed { |
| 219 | print("TSSLSocketTransport: Cannot trust certificate. Result: \(trustResult)") |
| 220 | aStream.close() |
| 221 | } |
| 222 | |
| 223 | case Stream.Event.errorOccurred: break |
| 224 | case Stream.Event.endEncountered: break |
| 225 | default: break |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | #endif |