Skip to content

Client certificate authentication#34

Merged
ianpartridge merged 17 commits intomasterfrom
clientCertificateAuth
Aug 8, 2018
Merged

Client certificate authentication#34
ianpartridge merged 17 commits intomasterfrom
clientCertificateAuth

Conversation

@ShihabMehboob
Copy link
Contributor

This PR aims to add client certificate authentication, in relation to this issue: #33 (comment)

.swift-version Outdated
@@ -1 +1 @@
4.1.2
4.1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

// Check if there exists a self-signed certificate and whether it's a secure connection
private let isSecure: Bool
private let isSelfSigned: Bool
private let certificateName: String
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this make more sense as an optional? Also, better named clientCertificateName? Also, comment needed.

Log.warning(warning)
fallthrough
}
if let _ = NSData(contentsOfFile: Bundle.main.path(forResource: self.certificateName, ofType: "der") ?? "") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why create an NSData then throw it away?

if let serverCertificate = SecTrustGetCertificateAtIndex(trust, 0) {
let certificate: SecCertificate = serverCertificate
var identity: SecIdentity? = nil
let _: OSStatus = SecIdentityCreateWithCertificate(nil, certificate, &identity)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check the result?

}
if let _ = NSData(contentsOfFile: Bundle.main.path(forResource: self.certificateName, ofType: "der") ?? "") {
if let serverCertificate = SecTrustGetCertificateAtIndex(trust, 0) {
let certificate: SecCertificate = serverCertificate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to do this?

#if !os(Linux)
if let certificatePath = self.certificatePath {
// Get the bundle path from the Certificates directory for a certificate that matches clientCertificateName's name
if let bundle = Bundle.path(forResource: self.clientCertificateName, ofType: "der", inDirectory: certificatePath) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please call this path.

// Convert the bundle path to NSData
if let key: NSData = NSData(base64Encoded: bundle, options: .ignoreUnknownCharacters) {
// Create a secure certificate from the NSData
if let certificate: SecCertificate = SecCertificateCreateWithData(kCFAllocatorDefault, key) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need the : SecCertificate or would the compiler infer it?

if let certificate: SecCertificate = SecCertificateCreateWithData(kCFAllocatorDefault, key) {
// Create a secure identity from the certificate
var identity: SecIdentity? = nil
let _: OSStatus = SecIdentityCreateWithCertificate(nil, certificate, &identity)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://developer.apple.com/documentation/security/1401160-secidentitycreatewithcertificate says "Call the CFRelease function to release this object when you are finished with it." so I guess we need to do that?

Copy link
Contributor Author

@ShihabMehboob ShihabMehboob Aug 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When attempting to use it, it returns the following error: 'CFRelease' is unavailable: Core Foundation objects are automatically memory managed, suggesting that this is handled by itself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK cool. Bad docs!

switch (method, host) {
case (NSURLAuthenticationMethodClientCertificate, baseHost):
#if !os(Linux)
if let certificatePath = self.certificatePath {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you guard let in here to reduce the nesting?

let host = challenge.protectionSpace.host

guard let url = URLComponents(string: self.url), let baseHost = url.host else {
guard let url = URLComponents(string: self.url), let baseHost = url.host, let certificatePath = self.certificatePath else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right, we'll never get to the switch if certificatePath is nil.

}
// Get the bundle path from the Certificates directory for a certificate that matches clientCertificateName's name
if let path = Bundle.path(forResource: self.clientCertificateName, ofType: "der", inDirectory: certificatePath) {
// Convert the bundle path to NSData
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment isn't correct, it's not the path that's being converted, it's the contents of the file. Would this be better?

// Read the certificate data from disk

// Get the bundle path from the Certificates directory for a certificate that matches clientCertificateName's name
if let path = Bundle.path(forResource: self.clientCertificateName, ofType: "der", inDirectory: certificatePath) {
// Convert the bundle path to NSData
if let key: NSData = NSData(base64Encoded: path, options: .ignoreUnknownCharacters) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the compiler can infer the type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to .ignoreUnknownCharacters? Would it be safer not to?

switch (method, host) {
case (NSURLAuthenticationMethodClientCertificate, baseHost):
#if !os(Linux)
guard let certificatePath = self.certificatePath else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should guard let clientCertificateName = self.clientCertificateName too, just in case someone passes us a path but no name?

/// - Parameters:
/// - url: URL string to use for network request
public init(method: HTTPMethod = .get, url: String, containsSelfSignedCert: Bool? = false) {
public init(method: HTTPMethod = .get, url: String, containsSelfSignedCert: Bool? = false, certificateName: String? = nil, certificatePath: String? = nil) {
Copy link
Contributor

@ianpartridge ianpartridge Aug 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it would be better to define a

public struct ClientCertificate {
    let name: String
    let path: String
}

and pass one of those in here?


self.isSecure = url.contains("https")
self.isSelfSigned = containsSelfSignedCert ?? false
self.clientCertificateName = clientCertificate?.name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just store the passed in ClientCertificate?

self.clientCertificate = clientCertificate

Then we can add things to ClientCertificate if we need to, without having to update RestRequest as well.


}

public struct ClientCertificate {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments needed in here.

gen.sh Outdated
@@ -0,0 +1,17 @@
podspec="Pod::Spec.new do |s|\ns.name = \"$projectName\"\ns.version = \"$TRAVIS_TAG\"\ns.summary = \"$TRAVIS_DESCRIPTION\"\ns.homepage = \"https://github.com/IBM-Swift/$projectName\"\ns.license = { :type => \"Apache License, Version 2.0\" }\ns.author = \"IBM\"\ns.module_name = '$projectName'\ns.requires_arc = true\ns.ios.deployment_target = \"10.0\"\ns.source = { :git => \"https://github.com/IBM-Swift/$projectName.git\", :tag => s.version }\ns.source_files = \"Sources/$projectName/*.swift\"\ns.pod_target_xcconfig = {\n'SWIFT_VERSION' => '4.0.3',\n}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this file should be here!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry! Fixed.

/// Struct to store client certificate name and path
public struct ClientCertificate {
/// The name for the client certificate
let name: String
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you mark these public please.

@ianpartridge ianpartridge merged commit 02c8e6a into master Aug 8, 2018
@ianpartridge ianpartridge deleted the clientCertificateAuth branch August 8, 2018 09:20
@neurosaurus
Copy link

neurosaurus commented Aug 8, 2018

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants