Skip to content
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

URLSession: Added support for client certificate authentication for non MacOS platforms #4937

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CoreFoundation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,10 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL Darwin)
add_compile_definitions($<$<COMPILE_LANGUAGE:C>:NS_CURL_MISSING_MAX_HOST_CONNECTIONS>)
endif()

if((NS_CURL_ASSUME_FEATURES_MISSING) OR (CURL_VERSION_STRING VERSION_LESS "7.71.0"))
add_compile_definitions($<$<COMPILE_LANGUAGE:C>:NS_CURL_MISSING_CURLINFO_SSLCERT_BLOB> $<$<COMPILE_LANGUAGE:C>:NS_CURL_MISSING_CURLINFO_SSLKEY_BLOB>)
endif()

if((NS_CURL_ASSUME_FEATURES_MISSING) OR (CURL_VERSION_STRING VERSION_LESS "7.84.0"))
add_compile_definitions($<$<COMPILE_LANGUAGE:C>:NS_CURL_MISSING_CURLINFO_CAINFO>)
endif()
Expand Down
14 changes: 14 additions & 0 deletions CoreFoundation/URL.subproj/CFURLSessionInterface.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ CFURLSessionEasyCode CFURLSession_easy_setopt_tc(CFURLSessionEasyHandle _Nonnull
return MakeEasyCode(curl_easy_setopt(curl, option.value, a));
}

CFURLSessionEasyCode CFURLSession_easy_setopt_blob(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, void *_Nonnull data, size_t len) {
struct curl_blob stblob;
oliviermartin marked this conversation as resolved.
Show resolved Hide resolved
stblob.data = data;
stblob.len = len;
stblob.flags = CURL_BLOB_COPY;
return MakeEasyCode(curl_easy_setopt(curl, option.value, &stblob));
}

CFURLSessionEasyCode CFURLSession_easy_getinfo_long(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionInfo info, long *_Nonnull a) {
return MakeEasyCode(curl_easy_getinfo(curl, info.value, a));
}
Expand Down Expand Up @@ -366,6 +374,9 @@ CFURLSessionOption const CFURLSessionOptionCOOKIE = { CURLOPT_COOKIE };
CFURLSessionOption const CFURLSessionOptionHTTPHEADER = { CURLOPT_HTTPHEADER };
CFURLSessionOption const CFURLSessionOptionHTTPPOST = { CURLOPT_HTTPPOST };
CFURLSessionOption const CFURLSessionOptionSSLCERT = { CURLOPT_SSLCERT };
#if !NS_CURL_MISSING_CURLINFO_SSLCERT_BLOB
CFURLSessionOption const CFURLSessionOptionSSLCERT_BLOB = { CURLOPT_SSLCERT_BLOB };
oliviermartin marked this conversation as resolved.
Show resolved Hide resolved
#endif
CFURLSessionOption const CFURLSessionOptionKEYPASSWD = { CURLOPT_KEYPASSWD };
CFURLSessionOption const CFURLSessionOptionCRLF = { CURLOPT_CRLF };
CFURLSessionOption const CFURLSessionOptionQUOTE = { CURLOPT_QUOTE };
Expand Down Expand Up @@ -419,6 +430,9 @@ CFURLSessionOption const CFURLSessionOptionHTTP_VERSION = { CURLOPT_HTTP_VERSION
CFURLSessionOption const CFURLSessionOptionFTP_USE_EPSV = { CURLOPT_FTP_USE_EPSV };
CFURLSessionOption const CFURLSessionOptionSSLCERTTYPE = { CURLOPT_SSLCERTTYPE };
CFURLSessionOption const CFURLSessionOptionSSLKEY = { CURLOPT_SSLKEY };
#if !NS_CURL_MISSING_CURLINFO_SSLKEY_BLOB
CFURLSessionOption const CFURLSessionOptionSSLKEY_BLOB = { CURLOPT_SSLKEY_BLOB };
#endif
CFURLSessionOption const CFURLSessionOptionSSLKEYTYPE = { CURLOPT_SSLKEYTYPE };
CFURLSessionOption const CFURLSessionOptionSSLENGINE = { CURLOPT_SSLENGINE };
CFURLSessionOption const CFURLSessionOptionSSLENGINE_DEFAULT = { CURLOPT_SSLENGINE_DEFAULT };
Expand Down
3 changes: 3 additions & 0 deletions CoreFoundation/URL.subproj/CFURLSessionInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ CF_EXPORT CFURLSessionOption const CFURLSessionOptionCOOKIE; // CURLOPT_COOKIE
CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTPHEADER; // CURLOPT_HTTPHEADER
CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTPPOST; // CURLOPT_HTTPPOST
CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLCERT; // CURLOPT_SSLCERT
CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLCERT_BLOB; // CURLOPT_SSLCERT_BLOB
CF_EXPORT CFURLSessionOption const CFURLSessionOptionKEYPASSWD; // CURLOPT_KEYPASSWD
CF_EXPORT CFURLSessionOption const CFURLSessionOptionCRLF; // CURLOPT_CRLF
CF_EXPORT CFURLSessionOption const CFURLSessionOptionQUOTE; // CURLOPT_QUOTE
Expand Down Expand Up @@ -253,6 +254,7 @@ CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTP_VERSION; // CURLOPT_HT
CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTP_USE_EPSV; // CURLOPT_FTP_USE_EPSV
CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLCERTTYPE; // CURLOPT_SSLCERTTYPE
CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLKEY; // CURLOPT_SSLKEY
CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLKEY_BLOB; // CURLOPT_SSLKEY_BLOB
CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLKEYTYPE; // CURLOPT_SSLKEYTYPE
CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLENGINE; // CURLOPT_SSLENGINE
CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLENGINE_DEFAULT; // CURLOPT_SSLENGINE_DEFAULT
Expand Down Expand Up @@ -626,6 +628,7 @@ typedef int (CFURLSessionSeekCallback)(void *_Nullable userp, long long offset,
CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_seek(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, CFURLSessionSeekCallback * _Nullable a);
typedef int (CFURLSessionTransferInfoCallback)(void *_Nullable userp, long long dltotal, long long dlnow, long long ultotal, long long ulnow);
CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_tc(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, CFURLSessionTransferInfoCallback * _Nullable a);
CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_blob(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, void *_Nonnull data, size_t len);

CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_getinfo_long(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionInfo info, long *_Nonnull a);
CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_getinfo_double(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionInfo info, double *_Nonnull a);
Expand Down
4 changes: 4 additions & 0 deletions Sources/FoundationNetworking/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL Darwin)
add_compile_definitions(NS_CURL_MISSING_MAX_HOST_CONNECTIONS)
endif()

if((NS_CURL_ASSUME_FEATURES_MISSING) OR (CURL_VERSION_STRING VERSION_LESS "7.71.0"))
add_compile_definitions(NS_CURL_MISSING_CURLINFO_SSLCERT_BLOB NS_CURL_MISSING_CURLINFO_SSLKEY_BLOB)
endif()

if((NS_CURL_ASSUME_FEATURES_MISSING) OR (CURL_VERSION_STRING VERSION_LESS "7.84.0"))
add_compile_definitions(NS_CURL_MISSING_CURLINFO_CAINFO)
endif()
Expand Down
77 changes: 62 additions & 15 deletions Sources/FoundationNetworking/URLCredential.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ extension URLCredential {
@discussion This class is an immutable object representing an authentication credential. The actual type of the credential is determined by the constructor called in the categories declared below.
*/
open class URLCredential : NSObject, NSSecureCoding, NSCopying {
private var _user : String
private var _password : String
private var _user : String?
private var _password : String?
// _privateClientKey contains the private client key in DER format
private var _privateClientKey: Data?
// _privateClientCertificate contains the private client certificate in DER format
private var _privateClientCertificate: Data?
private var _persistence : Persistence

/*!
Expand All @@ -55,6 +59,25 @@ open class URLCredential : NSObject, NSSecureCoding, NSCopying {
public init(user: String, password: String, persistence: Persistence) {
_user = user
_password = password
_privateClientKey = nil
_privateClientCertificate = nil
_persistence = persistence
super.init()
}

/*!
@method initWithUser:password:persistence:
@abstract Initialize a URLCredential with a user and password
@param user the username
@param password the password
@param persistence enum that says to store per session, permanently or not at all
@result The initialized URLCredential
*/
public init(clientKey: Data, clientCertificate: Data, persistence: Persistence) {
_user = nil
_password = nil
_privateClientKey = clientKey
_privateClientCertificate = clientCertificate
_persistence = persistence
super.init()
}
Expand All @@ -76,24 +99,34 @@ open class URLCredential : NSObject, NSSecureCoding, NSCopying {
func bridgeString(_ value: NSString) -> String? {
return String._unconditionallyBridgeFromObjectiveC(value)
}

let encodedUser = aDecoder.decodeObject(forKey: "NS._user") as! NSString
self._user = bridgeString(encodedUser)!

let encodedPassword = aDecoder.decodeObject(forKey: "NS._password") as! NSString
self._password = bridgeString(encodedPassword)!

let encodedPersistence = aDecoder.decodeObject(forKey: "NS._persistence") as! NSNumber
self._persistence = Persistence(rawValue: encodedPersistence.uintValue)!

if let encodedUser = aDecoder.decodeObject(forKey: "NS._user") as? NSString {
self._user = bridgeString(encodedUser)!
}

if let encodedPassword = aDecoder.decodeObject(forKey: "NS._password") as? NSString {
self._password = bridgeString(encodedPassword)!
}

if let encodedPersistence = aDecoder.decodeObject(forKey: "NS._persistence") as? NSNumber {
self._persistence = Persistence(rawValue: encodedPersistence.uintValue)!
} else {
self._persistence = Persistence.none
}
}

open func encode(with aCoder: NSCoder) {
guard aCoder.allowsKeyedCoding else {
preconditionFailure("Unkeyed coding is unsupported.")
}

aCoder.encode(self._user._bridgeToObjectiveC(), forKey: "NS._user")
aCoder.encode(self._password._bridgeToObjectiveC(), forKey: "NS._password")

if let user = self._user {
aCoder.encode(user._bridgeToObjectiveC(), forKey: "NS._user")
oliviermartin marked this conversation as resolved.
Show resolved Hide resolved
}
if let password = self._password {
aCoder.encode(password._bridgeToObjectiveC(), forKey: "NS._password")
}

aCoder.encode(self._persistence.rawValue._bridgeToObjectiveC(), forKey: "NS._persistence")
}

Expand Down Expand Up @@ -141,6 +174,20 @@ open class URLCredential : NSObject, NSSecureCoding, NSCopying {
*/
open var password: String? { return _password }

/*!
@method privateClientKey
@abstract Get the private client key
@result The private key binary blob
*/
open var privateClientKey: Data? { return _privateClientKey }

/*!
@method privateClientCertificate
@abstract Get the private client key
@result The private key binary blob
*/
open var privateClientCertificate: Data? { return _privateClientCertificate }

/*!
@method hasPassword
@abstract Find out if this credential has a password, without trying to get it
Expand All @@ -152,6 +199,6 @@ open class URLCredential : NSObject, NSSecureCoding, NSCopying {
*/
open var hasPassword: Bool {
// Currently no support for SecTrust/SecIdentity, always return true
return true
return _password != nil
}
}
4 changes: 4 additions & 0 deletions Sources/FoundationNetworking/URLSession/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ internal extension URLSession {
let shouldUseExtendedBackgroundIdleMode: Bool

let protocolClasses: [AnyClass]?

/// The credentials to use for connecting to servers
let clientCredential: URLCredential?
}
}
internal extension URLSession._Configuration {
Expand All @@ -100,6 +103,7 @@ internal extension URLSession._Configuration {
urlCache = config.urlCache
shouldUseExtendedBackgroundIdleMode = config.shouldUseExtendedBackgroundIdleMode
protocolClasses = config.protocolClasses
clientCredential = config.clientCredential
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,14 @@ internal class _HTTPURLProtocol: _NativeProtocol {
}
let session = task?.session as! URLSession
let _config = session._configuration
easyHandle.set(sessionConfig: _config)
do {
try easyHandle.set(sessionConfig: _config)
} catch {
self.internalState = .transferFailed
let nsError = error as? NSError ?? NSError(domain: NSURLErrorDomain, code: NSURLErrorUserAuthenticationRequired)
failWith(error: nsError, request: request)
return
}
easyHandle.setAllowedProtocolsToHTTPAndHTTPS()
easyHandle.set(preferredReceiveBufferSize: Int.max)
do {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ open class URLSessionConfiguration : NSObject, NSCopying {
urlCredentialStorage: .shared,
urlCache: .shared,
shouldUseExtendedBackgroundIdleMode: false,
protocolClasses: [_HTTPURLProtocol.self, _FTPURLProtocol.self, _WebSocketURLProtocol.self])
protocolClasses: [_HTTPURLProtocol.self, _FTPURLProtocol.self, _WebSocketURLProtocol.self],
clientCredential: nil)
}

private init(identifier: String?,
requestCachePolicy: URLRequest.CachePolicy,
timeoutIntervalForRequest: TimeInterval,
Expand All @@ -95,7 +96,8 @@ open class URLSessionConfiguration : NSObject, NSCopying {
urlCredentialStorage: URLCredentialStorage?,
urlCache: URLCache?,
shouldUseExtendedBackgroundIdleMode: Bool,
protocolClasses: [AnyClass]?)
protocolClasses: [AnyClass]?,
clientCredential: URLCredential?)
{
self.identifier = identifier
self.requestCachePolicy = requestCachePolicy
Expand All @@ -115,6 +117,7 @@ open class URLSessionConfiguration : NSObject, NSCopying {
self.urlCache = urlCache
self.shouldUseExtendedBackgroundIdleMode = shouldUseExtendedBackgroundIdleMode
self.protocolClasses = protocolClasses
self.clientCredential = clientCredential
}

open override func copy() -> Any {
Expand All @@ -140,7 +143,8 @@ open class URLSessionConfiguration : NSObject, NSCopying {
urlCredentialStorage: urlCredentialStorage,
urlCache: urlCache,
shouldUseExtendedBackgroundIdleMode: shouldUseExtendedBackgroundIdleMode,
protocolClasses: protocolClasses)
protocolClasses: protocolClasses,
clientCredential: clientCredential)
}

open class var `default`: URLSessionConfiguration {
Expand Down Expand Up @@ -258,6 +262,8 @@ open class URLSessionConfiguration : NSObject, NSCopying {
@available(*, unavailable, message: "Not available on non-Darwin platforms")
open var multipathServiceType: URLSessionConfiguration.MultipathServiceType { NSUnsupported() }

/* Optional client credential to be used when connecting to servers */
open var clientCredential: URLCredential?
}

@available(*, unavailable, message: "Not available on non-Darwin platforms")
Expand Down
49 changes: 48 additions & 1 deletion Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,55 @@ extension _EasyHandle {
}
}

func set(sessionConfig config: URLSession._Configuration) {
func set(sessionConfig config: URLSession._Configuration) throws {
_config = config
if let c = _config, let clientCredential = c.clientCredential {
// For TLS client certificate authentication
if var privateClientKey = clientCredential.privateClientKey,
var privateClientCertificate = clientCredential.privateClientCertificate {
// Key and certificate are expected to be in DER format
"DER".withCString {
let mutablePointer = UnsafeMutablePointer(mutating: $0)
try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionSSLKEYTYPE, mutablePointer).asError()
try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionSSLCERTTYPE, mutablePointer).asError()
}

#if !NS_CURL_MISSING_CURLINFO_SSLKEY_BLOB
privateClientKey.withUnsafeMutableBytes {
if let baseAddress = $0.baseAddress {
try! CFURLSession_easy_setopt_blob(rawHandle, CFURLSessionOptionSSLKEY_BLOB,
baseAddress, $0.count).asError()
}
}
#endif // !NS_CURL_MISSING_CURLINFO_SSLKEY_BLOB

#if !NS_CURL_MISSING_CURLINFO_SSLCERT_BLOB
privateClientCertificate.withUnsafeMutableBytes {
if let baseAddress = $0.baseAddress {
try! CFURLSession_easy_setopt_blob(rawHandle, CFURLSessionOptionSSLCERT_BLOB,
baseAddress, $0.count).asError()
}
}
#endif // !NS_CURL_MISSING_CURLINFO_SSLCERT_BLOB
} else if let tlsAuthUsername = clientCredential.user,
let tlsAuthPassword = clientCredential.password {
"SRP".withCString {
let mutablePointer = UnsafeMutablePointer(mutating: $0)
try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionTLSAUTH_TYPE, mutablePointer).asError()
}
tlsAuthUsername.withCString {
let mutablePointer = UnsafeMutablePointer(mutating: $0)
try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionTLSAUTH_USERNAME, mutablePointer).asError()
}
tlsAuthPassword.withCString {
let mutablePointer = UnsafeMutablePointer(mutating: $0)
try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionTLSAUTH_PASSWORD, mutablePointer).asError()
}
} else {
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorUserAuthenticationRequired,
userInfo: [NSLocalizedDescriptionKey: "Client credentials from URLSessionConfiguration is incomplete."])
}
}
}

/// Set the CA bundle path automatically if it isn't set
Expand Down