-
Notifications
You must be signed in to change notification settings - Fork 554
Expand file tree
/
Copy pathTlsSession.swift
More file actions
138 lines (119 loc) · 4.55 KB
/
TlsSession.swift
File metadata and controls
138 lines (119 loc) · 4.55 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
//
// 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 data = _data as NSData
let options = [kSecImportExportPassphrase: password]
var items: CFArray!
try ensureNoErr(SecPKCS12Import(data, options as NSDictionary, &items))
let dictionary = (items! as [AnyObject])[0]
let secIdentity = dictionary[kSecImportItemIdentity] as! SecIdentity
let chain = dictionary[kSecImportItemCertChain] as! [SecCertificate]
let certs = [secIdentity] + chain.dropFirst().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 {
context = SSLCreateContext(nil, .serverSide, .streamType)!
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