p2 / OAuth2

OAuth2 framework for macOS and iOS, written in Swift.
Other
1.14k stars 275 forks source link

pfx certificate use for sending requests with oauth2.session #386

Closed edimoldovan closed 2 years ago

edimoldovan commented 2 years ago

Hi,

Great work on the library, it got me really quickly through most of my implementation. I do have one question though: I need to send requests signed with a certificate to my api endpoint. What would be the recommended way of doing this?

So far what I have is this, no certificate use here yet.

let req = oauth2.request(forURL:  URL(string: APISettings.accountsURL)!)
// set up your request, e.g. `req.HTTPMethod = "POST"`
let task = oauth2.session.dataTask(with: req) { data, response, error in
    if let error = error {
        print("error")
        log(logItem: error)
    }
    else {
        print("data")
        log(logItem: data as Any)
        print("response")
        log(logItem: response as Any)
    }
}
task.resume()

I'll be trying soon to define a session delegate and perhaps I could reuse that with the above?

edimoldovan commented 2 years ago

I did implement the session delegate like this, which seems to work. Feedback welcome in case anyone notices something that is still not ok. Code strongly inspired by this post https://stackoverflow.com/questions/48536925/how-do-i-make-a-request-to-a-rest-api-with-a-certificate-in-swift#48537659

class SessionDelegate: NSObject, URLSessionDelegate {

    public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
                if let localCertPath = Bundle.main.url(forResource: "cert", withExtension: "pfx"),
                    let localCertData = try?  Data(contentsOf: localCertPath)
                {

                    let identityAndTrust: IdentityAndTrust = extractIdentity(certData: localCertData as NSData, certPassword: "pwd")

                    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {

                        let urlCredential:URLCredential = URLCredential(
                            identity: identityAndTrust.identityRef,
                            certificates: identityAndTrust.certArray as [AnyObject],
                            persistence: URLCredential.Persistence.forSession);

                        completionHandler(URLSession.AuthChallengeDisposition.useCredential, urlCredential);

                        return
                    }
                }

                challenge.sender?.cancel(challenge)
                completionHandler(URLSession.AuthChallengeDisposition.rejectProtectionSpace, nil)
            }

    public struct IdentityAndTrust {
        public var identityRef: SecIdentity
        public var trust: SecTrust
        public var certArray: NSArray
    }

    public func extractIdentity(certData:NSData, certPassword:String) -> IdentityAndTrust {

        var identityAndTrust:IdentityAndTrust!
        var securityError:OSStatus = errSecSuccess

        var items: CFArray?
        let certOptions: Dictionary = [ kSecImportExportPassphrase as String : certPassword ];
        // import certificate to read its entries
        securityError = SecPKCS12Import(certData, certOptions as CFDictionary, &items);
        if securityError == errSecSuccess {

            let certItems:CFArray = items as! CFArray;
            let certItemsArray:Array = certItems as Array
            let dict:AnyObject? = certItemsArray.first;

            if let certEntry:Dictionary = dict as? Dictionary<String, AnyObject> {

                // grab the identity
                let identityPointer:AnyObject? = certEntry["identity"];
                let secIdentityRef:SecIdentity = identityPointer as! SecIdentity;

                // grab the trust
                let trustPointer:AnyObject? = certEntry["trust"];
                let trustRef:SecTrust = trustPointer as! SecTrust;

                // grab the certificate chain
                var certRef: SecCertificate?
                SecIdentityCopyCertificate(secIdentityRef, &certRef);
                let certArray:NSMutableArray = NSMutableArray();
                certArray.add(certRef as! SecCertificate);

                identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray: certArray);
            }
        }

        return identityAndTrust;
    }
}

Then, called the code like this:

oauth2.sessionDelegate = SessionDelegate()
var req = oauth2.request(forURL:  URL(string: APISettings.URL)!)
req.setValue(APISettings.clientID, forHTTPHeaderField: "client_id")
req.setValue(APISettings.clientSecret, forHTTPHeaderField: "client_secret")
req.setValue("application/json", forHTTPHeaderField: "Accept")

let task = oauth2.session.dataTask(with: req) { data, response, error in
   ///
}
task.resume()