-
Notifications
You must be signed in to change notification settings - Fork 554
Expand file tree
/
Copy pathTlsSession.swift
More file actions
148 lines (129 loc) · 5.07 KB
/
TlsSession.swift
File metadata and controls
148 lines (129 loc) · 5.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//
// HttpRouter.swift
// Swifter
//
// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
//
import Foundation
#if !os(Linux)
private func ensureNoErr(_ status: OSStatus) throws {
guard status == noErr else {
throw Errno.sslError(from: status)
}
}
public enum TLS {
/// Imports .p12 certificate file constructing structure to be used in TLS session.
///
/// See [SecPKCS12Import](https://developer.apple.com/documentation/security/1396915-secpkcs12import).
/// Apple docs contain a misleading information that it does not import items to Keychain even though
/// it does.
///
/// - Parameter data: .p12 certificate file content
/// - Parameter password: password used when importing certificate
public static func loadP12Certificate(_ data: Data, _ password: String) throws -> CFArray {
let options = [kSecImportExportPassphrase as String: password]
var items: CFArray?
try ensureNoErr(SecPKCS12Import(data as CFData, options as NSDictionary, &items))
guard
let dictionary = (items as? [[String: Any]])?.first,
let chain = dictionary[kSecImportItemCertChain as String] as? [SecCertificate]
else {
throw SocketError.tlsSessionFailed("Could not retrieve p12 data from given certificate")
}
// must be force casted, will be fixed in swift 5 https://bugs.swift.org/browse/SR-7015
// swiftlint:disable force_cast
let secIdentity = dictionary[kSecImportItemIdentity as String] as! SecIdentity
// swiftlint:enable force_cast
let chainWithoutIdentity = chain.dropFirst()
let certs = [secIdentity] + chainWithoutIdentity.map { $0 as Any }
return certs as CFArray
}
}
open class TlsSession {
private let context: SSLContext
private var fdPtr = UnsafeMutablePointer<Int32>.allocate(capacity: 1)
init(fd: Int32, certificate: CFArray) throws {
guard let newContext = SSLCreateContext(nil, .serverSide, .streamType) else {
throw SocketError.tlsSessionFailed("Could not create new SSL context")
}
context = newContext
fdPtr.pointee = fd
try ensureNoErr(SSLSetIOFuncs(context, sslRead, sslWrite))
try ensureNoErr(SSLSetConnection(context, fdPtr))
try ensureNoErr(SSLSetCertificate(context, certificate))
}
open func close() {
SSLClose(context)
fdPtr.deallocate()
}
open func handshake() throws {
var status: OSStatus = -1
repeat {
status = SSLHandshake(context)
} while status == errSSLWouldBlock
try ensureNoErr(status)
}
/// Write up to `length` bytes to TLS session from a buffer `pointer` points to.
///
/// - Returns: The number of bytes written
/// - Throws: SocketError.tlsSessionFailed if unable to write to the session
open func writeBuffer(_ pointer: UnsafeRawPointer, length: Int) throws -> Int {
var written = 0
try ensureNoErr(SSLWrite(context, pointer, length, &written))
return written
}
/// Read a single byte off the TLS session.
///
/// - Throws: SocketError.tlsSessionFailed if unable to read from the session
open func readByte(_ byte: UnsafeMutablePointer<UInt8>) throws {
_ = try read(into: byte, length: 1)
}
/// Read up to `length` bytes from TLS session into an existing buffer
///
/// - Parameter into: The buffer to read into (must be at least length bytes in size)
/// - Returns: The number of bytes read
/// - Throws: SocketError.tlsSessionFailed if unable to read from the session
open func read(into buffer: UnsafeMutablePointer<UInt8>, length: Int) throws -> Int {
var received = 0
try ensureNoErr(SSLRead(context, buffer, length, &received))
return received
}
}
private func sslWrite(connection: SSLConnectionRef, data: UnsafeRawPointer, dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
let fd = connection.assumingMemoryBound(to: Int32.self).pointee
let bytesToWrite = dataLength.pointee
let written = Darwin.write(fd, data, bytesToWrite)
dataLength.pointee = written
if written > 0 {
return written < bytesToWrite ? errSSLWouldBlock : noErr
}
if written == 0 {
return errSSLClosedGraceful
}
dataLength.pointee = 0
return errno == EAGAIN ? errSSLWouldBlock : errSecIO
}
private func sslRead(connection: SSLConnectionRef, data: UnsafeMutableRawPointer, dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
let fd = connection.assumingMemoryBound(to: Int32.self).pointee
let bytesToRead = dataLength.pointee
let read = recv(fd, data, bytesToRead, 0)
dataLength.pointee = read
if read > 0 {
return read < bytesToRead ? errSSLWouldBlock : noErr
}
if read == 0 {
return errSSLClosedGraceful
}
dataLength.pointee = 0
switch errno {
case ENOENT:
return errSSLClosedGraceful
case EAGAIN:
return errSSLWouldBlock
case ECONNRESET:
return errSSLClosedAbort
default:
return errSecIO
}
}
#endif