-
Notifications
You must be signed in to change notification settings - Fork 543
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SSL support #427
base: stable
Are you sure you want to change the base?
SSL support #427
Changes from 8 commits
8265d2c
c3923e3
2f394db
ff4257a
756716d
c5caed9
5d64270
d128cbf
88c4578
bfce850
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of Core Foundation types are toll-free bridged to their Cocoa Foundation counterparts and can be used interchangeably. But I agree, it's better to use actually expected type names here. Will fix that. /cc https://developer.apple.com/documentation/corefoundation/cfdata-rv9 |
||
let options = [kSecImportExportPassphrase: password] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the way Apple does it in the page you referenced they make a cast to let options = [ kSecImportExportPassphrase as String: password ] Do we know what difference could generate? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related to same discussion of |
||
var items: CFArray! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we please make this |
||
try ensureNoErr(SecPKCS12Import(data, options as NSDictionary, &items)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
try ensureNoErr(withUnsafeMutablePointer(to: &items) { SecPKCS12Import(data, options as CFDictionary, $0) }) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not fully sure what |
||
let dictionary = (items! as [AnyObject])[0] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The guard let dictionary = (items as? [[String: Any]]).first else { throw SomeError } |
||
let secIdentity = dictionary[kSecImportItemIdentity] as! SecIdentity | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here we can please avoid the force-unwrapping even when Apple does in his examples? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, this cannot quite be avoided, because when trying to make it optional compiler produces |
||
let chain = dictionary[kSecImportItemCertChain] as! [SecCertificate] | ||
let certs = [secIdentity] + chain.dropFirst().map { $0 as Any } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason we are dropping the first There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. from
so |
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SecCopyErrorMessageString
is only available for iOS 11.3+ and we're supporting iOS 8+, not sure which is the counterpart here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Very weird that Xcode didn't notify me.
Could not find any counterpart so formed custom message.