I am trying to make https request in swift, with identity created from certificate and key. My code look like this
To create the identity. func loadIdentity(certificate: String, privateKey: String)-> SecIdentity? { guard let certData = Data(base64Encoded: certificate, options:NSData.Base64DecodingOptions.ignoreUnknownCharacters) else { print("Unable to decode certificate PEM") return nil }
guard let cert = SecCertificateCreateWithData(kCFAllocatorDefault, certData as CFData) else {
return nil
}
let addCertQuery: [String: Any] = [kSecClass as String: kSecClassCertificate,
kSecValueRef as String: cert,
kSecAttrLabel as String: "certificateLabel"]
let tag = "fedvfdvdf-tag".data(using: .utf8)!
_ = deleteCertificateAndKey(certLabel: "certificateLabel",keyTag: tag )
let certAddStatus = SecItemAdd(addCertQuery as CFDictionary, nil)
guard let pemKeyData = Data(base64Encoded: privateKey, options:NSData.Base64DecodingOptions.ignoreUnknownCharacters) else {
return nil
}
let sizeInBits = pemKeyData.count * 8
let keyDict: [CFString: Any] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits: NSNumber(value: sizeInBits),
kSecReturnPersistentRef: true
]
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateWithData(pemKeyData as CFData, keyDict as CFDictionary, &error) else {
return nil
}
let addKeyQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrIsPermanent as String: true,
kSecValueRef as String: key,
kSecAttrApplicationTag as String: tag
]
let privKeyAddStatus = SecItemAdd(addKeyQuery as CFDictionary, nil)
let getIdentityQuery = [
kSecClass : kSecClassIdentity,
kSecReturnData : true,
kSecReturnAttributes : true,
kSecReturnRef : true,
kSecMatchLimit : kSecMatchLimitAll
] as CFDictionary
var identityItem: CFTypeRef?
let status = SecItemCopyMatching(getIdentityQuery , &identityItem)
print("identityItem finished with status: \(String(describing: identityItem))")
print("status finished with status: \(status)")
guard status == errSecSuccess else {
print("Unable to create identity")
return nil
}
return (identityItem as! SecIdentity);
} o make api request. Code is breaking in this function, around this lines let task = session.dataTask(with: request) { (data, response, error) in and let session = URLSession(configuration: .default, delegate: URLSessionPinningDelegate(identity: identity), delegateQueue: nil) For testing I removed identity and just used default URLSession, and request start giving response (although it was 401 but it was not crashing), so my guess is error is because of URLSession.
func makeAzureRequest(scopeId:String, registrationId:String, key:String, certificate:String, provisionHost:String, fileNameWithFolder:String, modelId:String, completion: @escaping (Result<String, Error>) -> Void ) throws { guard let identity = loadIdentity(certificate: certificate, privateKey: key) else { throw NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unable to create identity"])
}
let session = URLSession(configuration: .default, delegate: URLSessionPinningDelegate(identity: identity), delegateQueue: nil)
print("session: \(session)")
guard let url = URL(string: "https://global.azure-devices-provisioning.net/\(scopeId)/registrations/\(registrationId)/register?api-version=2021-06-01") else {
throw NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])
}
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("utf-8", forHTTPHeaderField: "Content-Encoding")
let body = ["registrationId": registrationId]
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: [])
let task = session.dataTask(with: request) { (data, response, error) in
if let error = error {
completion(.failure(error))
} else if let data = data, let responseString = String(data: data, encoding: .utf8) {
completion(.success(responseString))
}else {
completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unknown error occurred"])))
}
}
task.resume()
} to call function where api function is triggered. @objc(AzureProvisionWithCertificate) class AzureProvisionWithCertificate: NSObject {
@objc(provisionAndUploadFile:withRegistrationId:withKey:withCertificate:withProvisionHost:withFileNameWithFolder:withModelId:withResolver:withRejecter:) func provisionAndUploadFile(scopeId:String, registrationId:String, key:String, certificate:String, provisionHost:String, fileNameWithFolder:String, modelId:String, resolve:@escaping RCTPromiseResolveBlock, reject:@escaping RCTPromiseRejectBlock) -> Void { print("starting swift code here") do { try makeAzureRequest(scopeId: scopeId, registrationId:registrationId, key: key, certificate: certificate, provisionHost: provisionHost, fileNameWithFolder: fileNameWithFolder, modelId: modelId) { result in switch result { case .success(let responseString): // Handle success, perhaps update the UI or process the response case .failure(let error): // Handle failure, perhaps show an error message to the user } } } catch { print("Failed to initiate request: (error.localizedDescription)") } } } And URLSessionPinningDelegate class look like this to SSL pinning.
import Foundation import Security
class URLSessionPinningDelegate: NSObject, URLSessionDelegate { var identity: SecIdentity
init(identity: SecIdentity) {
self.identity = identity
}
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
let credential = URLCredential(identity: self.identity,
certificates: nil,
persistence: .forSession)
completionHandler(.useCredential, credential)
} else {
completionHandler(.performDefaultHandling, nil)
}
}
}