-
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
Open
viktorasl
wants to merge
10
commits into
httpswift:stable
Choose a base branch
from
viktorasl:ssl-support
base: stable
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+259
−1
Open
SSL support #427
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
8265d2c
Provide initial API for socket TLS session
viktorasl c3923e3
Introduce SSL .p12 certificate import API
viktorasl 2f394db
Load demo SSL localhost certificate
viktorasl ff4257a
Setup TLS session connection
viktorasl 756716d
Perform SSL handshake
viktorasl c5caed9
Perform reading & writing using TLS session
viktorasl 5d64270
Add brief docs about SSL/TLS to Readme
viktorasl d128cbf
Document .p12 certificate loading method
viktorasl 88c4578
Make p12 SSL cert load more type safe
viktorasl bfce850
Form custom SSL error message if API not available
viktorasl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// | ||
// 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 | ||
let secIdentity = dictionary[kSecImportItemIdentity as String] as! SecIdentity | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.