From bf338c82b81d2f0216e4ff33f1a10ff9b39b42d3 Mon Sep 17 00:00:00 2001 From: Olivier Martin Date: Mon, 22 Apr 2024 21:52:44 +0000 Subject: [PATCH] FoundationNetworking/URLSessionConfiguration: Add URLCredential --- .../URLSession/Configuration.swift | 4 ++ .../URLSession/HTTP/HTTPURLProtocol.swift | 9 +++- .../URLSession/URLSessionConfiguration.swift | 14 ++++-- .../URLSession/libcurl/EasyHandle.swift | 49 ++++++++++++++++++- 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/Sources/FoundationNetworking/URLSession/Configuration.swift b/Sources/FoundationNetworking/URLSession/Configuration.swift index 7b3996e2e80..5260cbd147e 100644 --- a/Sources/FoundationNetworking/URLSession/Configuration.swift +++ b/Sources/FoundationNetworking/URLSession/Configuration.swift @@ -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 { @@ -100,6 +103,7 @@ internal extension URLSession._Configuration { urlCache = config.urlCache shouldUseExtendedBackgroundIdleMode = config.shouldUseExtendedBackgroundIdleMode protocolClasses = config.protocolClasses + clientCredential = config.clientCredential } } diff --git a/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift b/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift index abf6623435e..6dfc0a9ba54 100644 --- a/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift +++ b/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift @@ -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 { diff --git a/Sources/FoundationNetworking/URLSession/URLSessionConfiguration.swift b/Sources/FoundationNetworking/URLSession/URLSessionConfiguration.swift index 1282b70135c..348847f1d31 100644 --- a/Sources/FoundationNetworking/URLSession/URLSessionConfiguration.swift +++ b/Sources/FoundationNetworking/URLSession/URLSessionConfiguration.swift @@ -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, @@ -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 @@ -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 { @@ -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 { @@ -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") diff --git a/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift b/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift index 45cd8cae7e7..b805895b8c9 100644 --- a/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift +++ b/Sources/FoundationNetworking/URLSession/libcurl/EasyHandle.swift @@ -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