SSL/TLS Add-in framework for BlueSocket in Swift using the Swift Package Manager. Works on supported Apple platforms (using Secure Transport) and on Linux (using OpenSSL).
Requires Swift 5.1 or newer. You can download the Swift binaries by following this link.
Compatibility with other Swift versions is not guaranteed.
- macOS 10.14 or higher.
- Xcode Version 11.3 or higher using the included toolchain.
- Secure Transport is provided by macOS.
- Ubuntu tested on 18.04.
- One of the Swift Open Source toolchain listed above.
- OpenSSL is provided by the distribution. Note: 1.0.x, 1.1.x and later releases of OpenSSL are supported.
- The appropriate libssl-dev package is required to be installed when building.
- BlueSSLService is NOT supported on watchOS since POSIX/BSD/Darwin sockets are not supported on the actual device although they are supported in the simulator.
- BlueSSLService should work on tvOS but has NOT been tested.
Note: See Package.swift
for details.
To build SSLService
from the command line:
% cd <path-to-clone>
% swift build
To run the supplied unit tests for SSLService from the command line:
% cd <path-to-clone>
% swift build
% swift test
The first you need to do is import both the Socket
and SSLService
frameworks. This is done by the following:
import Socket
import SSLService
Both clients and server require at a minimum the following configuration items:
- CA Certficate (either
caCertificateFile
orcaCertificateDirPath
) - Application certificate (
certificateFilePath
) - Private Key file (
keyFilePath
)
or
- Certificate Chain File (
chainFilePath
)
or, if using self-signed
certificates:
- Application certificate (
certificateFilePath
) - Private Key file (
keyFilePath
)
or, if running on Linux (for now),
- A string containing a PEM formatted certificate
or, if running on macOS:
- Certificate Chain File (
chainFilePath
) in PKCS12 format
or,
- No certificate at all.
BlueSSLService provides five ways to create a Configuration
supporting the scenarios above. Only the last version is supported on Apple platforms. On Linux, ALL versions are supported. This is due to the limits imposed on the current implementation of Apple Secure Transport.
init()
- This API allows for the creation of default configuration. This is equivalent to calling the next initializer without changing any parameters.init(withCipherSuite cipherSuite: String? = nil, clientAllowsSelfSignedCertificates: Bool = true)
- This API allows for the creation of configuration that does not contain a backing certificate or certificate chain. You can optionally provide a cipherSuite and decide whether to allow, when in client mode, use of self-signed certificates by the server.init(withCACertificatePath caCertificateFilePath: String?, usingCertificateFile certificateFilePath: String?, withKeyFile keyFilePath: String? = nil, usingSelfSignedCerts selfSigned: Bool = true, cipherSuite: String? = nil)
- This API allows you to create a configuration using a self containedCertificate Authority (CA)
file. The second parameter is the path to theCertificate
file to be used by application to establish the connection. The next parameter is the path to thePrivate Key
file used by application corresponding to thePublic Key
in theCertificate
. If you're usingself-signed certificates
, set the last parameter to true.init(withCACertificateDirectory caCertificateDirPath: String?, usingCertificateFile certificateFilePath: String?, withKeyFile keyFilePath: String? = nil, usingSelfSignedCerts selfSigned: Bool = true, cipherSuite: String? = nil)
- This API allows you to create a configuration using a directory ofCertificate Authority (CA)
files. TheseCA
certificates must be hashed using theCertificate Tool
provided byOpenSSL
. The following parameters are identical to the previous API.init(withPEMCertificateString certificateString: String, usingSelfSignedCerts selfSigned: Bool = true, cipherSuite: String? = nil)
- This API used when supplying a PEM formatted certificate presented as a String. NOTE: At present, this API is only available on Linux.init(withChainFilePath chainFilePath: String? = nil, withPassword password: String? = nil, usingSelfSignedCerts selfSigned: Bool = true, clientAllowsSelfSignedCertificates: Bool = false, cipherSuite: String? = nil)
- This API allows you to create a configuration using a singleCertificate Chain File
(see note 2 below). Add an optional password (if required) using the third parameter. Set the third parameter to true if the certificates you are using areself-signed
, otherwise set it to false. If configuring a client and you want that client to be able to connect to servers usingself-signed
certificates, set the fourth parameter to true.
Note 1: All Certificate
and Private Key
files must be PEM
format. If supplying a certificate via a String
, it must be PEM formatted.
Note 2: If using a certificate chain file, the certificates must be in PEM
format and must be sorted starting with the subject's certificate (actual client or server certificate), followed by intermediate CA
certificates if applicable, and ending at the highest level (root) CA
.
Note 3: For the first two versions of the API, if your Private key
is included in your certificate file, you can omit this parameter and the API will use the same file name as specified for the certificate file.
Note 4: If you desire to customize the cipher suite used, you can do so by specifying the cipherSuite
parameter when using one of the above initializers. If not specified, the default value is set to DEFAULT
on Linux. On macOS, setting of this parameter is currently not supported and attempting to set it will result in unpredictable results. See the example below.
Note 5: If you're running on macOS, you must use the last form of init
for the Configuration
and provide a certificate chain file in PKCS12
format, supplying a password
if needed.
The following illustrates creating a configuration (on Linux) using the second form of the API above using a self-signed certificate file as the key file and not supplying a certificate chain file. It also illustrates setting the cipher suite to ALL
from the default:
import SSLService
...
let myCertPath = "/opt/myApp/config/myCertificate.pem"
let myKeyPath = "/opt/myApp/config/myKeyFile.pem"
let myConfig = SSLService.Configuration(withCACertificateDirectory: nil, usingCertificateFile: myCertPath, withKeyFile: myKeyFile)
myConfig.cipherSuite = "ALL"
...
Note: This example takes advantage of the default
parameters available on the SSLService.Configuration.init
function. Also, changing of the cipher suite
on macOS is currently not supported.
The following API is used to create the SSLService
:
init?(usingConfiguration config: Configuration) throws
- This will create an instance of theSSLService
using a previously createdConfiguration
.
Once the SSLService
is created, it can applied to a previously created Socket
instance that's just been created. This needs to be done before using the Socket
. The following code snippet illustrates how to do this (again using Linux). Note: Exception handling omitted for brevity.
import Socket
import SSLService
...
// Create the configuration...
let myCertPath = "/opt/myApp/config/myCertificate.pem"
let myKeyPath = "/opt/myApp/config/myKeyFile.pem"
let myConfig = SSLService.Configuration(withCACertificateDirectory: nil, usingCertificateFile: myCertPath, withKeyFile: myKeyFile)
// Create the socket...
var socket = try Socket.create()
guard let socket = socket else {
fatalError("Could not create socket.")
}
// Create and attach the SSLService to the socket...
// - Note: if you're going to be using the same
// configuration over and over, it'd be
// better to create it in the beginning
// as `let` constant.
socket.delegate = try SSLService(usingConfiguration: myConfig)
// Start listening...
try socket.listen(on: 1337)
The example above creates a SSL server
socket. Replacing the socket.listen
function with a socket.connect
would result in an SSL client
being created as illustrated below:
// Connect to the server...
try socket.connect(to: "someplace.org", port: 1337)
SSLService
handles all the negotiation and setup for the secure transfer of data. The determining factor for whether or not a Socket
is setup as a server or client Socket
is API which is used to initiate a connection. listen()
will cause the Socket
to be setup as a server socket. Calling connect()
results a client setup.
SSLService
provides a callback mechanism should you need to specify additional verification logic. After creating the instance of SSLService
, you can set the instance variable verifyCallback
. This instance variable has the following signature:
public var verifyCallback: ((_ service: SSLService) -> (Bool, String?))? = nil
Setting this callback is not required. It defaults to nil
unless set. The first parameter passed to your callback is the instance of SSLService
that has this callback. This will allow you to access the public members of the SSLService
instance in order to do additional verification. Upon completion, your callback should return a tuple. The first value is a Bool
indicating the sucess or failure of the routine. The second value is an optional String
value used to provide a description in the case where verification failed. In the event of callback failure, an exception
will be thrown by the internal verification function. Important Note: To effectively use this callback requires knowledge of the platforms underlying secure transport service, Apple Secure Transport
on supported Apple platforms
and OpenSSL
on Linux
.
If desired, SSLService
can skip the connection verification. To accomplish this, set the property skipVerification
to true
after creating the SSLService
instance. However, if the verifyCallback
property (described above) is set, that callback will be called regardless of this setting. The default for property is false. It is NOT recommended that you skip the connection verification in a production
environment unless you are providing verification via the verificationCallback
.
We love to talk server-side Swift and Kitura. Join our Slack to meet the team!
This library is licensed under Apache 2.0. Full license text is available in LICENSE.