4

here is my alamofire manager, how I can add public key pinning on it ? please help me, I couldn't know the way to do it in my code, if possible I need explanation step by step on how do that with AFManager that has all the requests

class AFManager : NSObject{


///without headers (post)
//used this to registration
class func requestPOSTURL(_ strURL : String, params : [String : 
AnyObject]?, success:@escaping (JSON) -> Void, failure:@escaping (Error) -> Void){
URLCache.shared.removeAllCachedResponses()
Alamofire.request(strURL, method: .post, parameters: params, encoding: URLEncoding.httpBody).responseJSON { (responseObject) -> Void in

    //print(responseObject)

    if responseObject.result.isSuccess {
        let resJson = JSON(responseObject.result.value!)
        success(resJson)
    }
    if responseObject.result.isFailure {
        let error : Error = responseObject.result.error!
        failure(error)
    }
}
}


///// response string (post)
//used this in login // used in change password
class func strRequestPOSTURL(_ strURL : String, params : [String : String]?, headers : [String : String]?, success:@escaping (JSON) -> Void, failure:@escaping (Error) -> Void){
URLCache.shared.removeAllCachedResponses()
Alamofire.request(strURL, method: .post, parameters: params, encoding: URLEncoding.httpBody, headers: headers).responseJSON { (response) in
    //print(response)

    if response.result.isSuccess {
        let resJson = JSON(response.result.value!)
        success(resJson)
    }
    if response.result.isFailure {
        let error : Error = response.result.error!

        failure(error)
    }

}

  }

}

I saw this sample but didn't know how to do it and where I should put the code see the link below : https://infinum.co/the-capsized-eight/ssl-pinning-revisited

Rooh Al-mahaba
  • 594
  • 1
  • 14
  • 28

6 Answers6

7

Security

Using a secure HTTPS connection when communicating with servers and web services is an important step in securing sensitive data. By default, Alamofire will evaluate the certificate chain provided by the server using Apple's built in validation provided by the Security framework. While this guarantees the certificate chain is valid, it does not prevent man-in-the-middle (MITM) attacks or other potential vulnerabilities. In order to mitigate MITM attacks, applications dealing with sensitive customer data or financial information should use certificate or public key pinning provided by the ServerTrustPolicy.

ServerTrustPolicy

The ServerTrustPolicy enumeration evaluates the server trust generally provided by an URLAuthenticationChallenge when connecting to a server over a secure HTTPS connection.

let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
    certificates: ServerTrustPolicy.certificates(),
    validateCertificateChain: true,
    validateHost: true
)

There are many different cases of server trust evaluation giving you complete control over the validation process:

  • performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to validate the host provided by the challenge.
  • pinCertificates: Uses the pinned certificates to validate the server trust. The server trust is considered valid if one of the pinned certificates match one of the server certificates.
  • pinPublicKeys: Uses the pinned public keys to validate the server trust. The server trust is considered valid if one of the pinned public keys match one of the server certificate public keys.
  • disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid.
  • customEvaluation: Uses the associated closure to evaluate the validity of the server trust thus giving you complete control over the validation process. Use with caution.

Server Trust Policy Manager

The ServerTrustPolicyManager is responsible for storing an internal mapping of server trust policies to a particular host. This allows Alamofire to evaluate each host against a different server trust policy.

let serverTrustPolicies: [String: ServerTrustPolicy] = [
    "test.example.com": .pinCertificates(
        certificates: ServerTrustPolicy.certificates(),
        validateCertificateChain: true,
        validateHost: true
    ),
    "insecure.expired-apis.com": .disableEvaluation
]

let sessionManager = SessionManager(
    serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)

Make sure to keep a reference to the new SessionManager instance, otherwise your requests will all get cancelled when your sessionManager is deallocated. These server trust policies will result in the following behavior:

test.example.com will always use certificate pinning with certificate chain and host validation enabled thus requiring the following criteria to be met to allow the TLS handshake to succeed: Certificate chain MUST be valid. Certificate chain MUST include one of the pinned certificates. Challenge host MUST match the host in the certificate chain's leaf certificate. insecure.expired-apis.com will never evaluate the certificate chain and will always allow the TLS handshake to succeed. All other hosts will use the default evaluation provided by Apple. Subclassing Server Trust Policy Manager

If you find yourself needing more flexible server trust policy matching behavior (i.e. wildcarded domains), then subclass the ServerTrustPolicyManager and override the serverTrustPolicyForHost method with your own custom implementation.

class CustomServerTrustPolicyManager: ServerTrustPolicyManager {
    override func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
        var policy: ServerTrustPolicy?

        // Implement your custom domain matching behavior...

        return policy
    }
}

Validating the Host

The .performDefaultEvaluation, .pinCertificates and .pinPublicKeys server trust policies all take a validateHost parameter. Setting the value to true will cause the server trust evaluation to verify that hostname in the certificate matches the hostname of the challenge. If they do not match, evaluation will fail. A validateHost value of false will still evaluate the full certificate chain, but will not validate the hostname of the leaf certificate.

It is recommended that validateHost always be set to true in production environments. Validating the Certificate Chain

Pinning certificates and public keys both have the option of validating the certificate chain using the validateCertificateChain parameter. By setting this value to true, the full certificate chain will be evaluated in addition to performing a byte equality check against the pinned certificates or public keys. A value of false will skip the certificate chain validation, but will still perform the byte equality check.

There are several cases where it may make sense to disable certificate chain validation. The most common use cases for disabling validation are self-signed and expired certificates. The evaluation would always fail in both of these cases, but the byte equality check will still ensure you are receiving the certificate you expect from the server.

It is recommended that validateCertificateChain always be set to true in production environments. App Transport Security

With the addition of App Transport Security (ATS) in iOS 9, it is possible that using a custom ServerTrustPolicyManager with several ServerTrustPolicy objects will have no effect. If you continuously see CFNetwork SSLHandshake failed (-9806) errors, you have probably run into this problem. Apple's ATS system overrides the entire challenge system unless you configure the ATS settings in your app's plist to disable enough of it to allow your app to evaluate the server trust.

If you run into this problem (high probability with self-signed certificates), you can work around this issue by adding the following to your Info.plist.

<dict>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>example.com</key>
            <dict>
                <key>NSExceptionAllowsInsecureHTTPLoads</key>
                <true/>
                <key>NSExceptionRequiresForwardSecrecy</key>
                <false/>
                <key>NSIncludesSubdomains</key>
                <true/>
                <!-- Optional: Specify minimum TLS version -->
                <key>NSTemporaryExceptionMinimumTLSVersion</key>
                <string>TLSv1.2</string>
            </dict>
        </dict>
    </dict>
</dict>

Whether you need to set the NSExceptionRequiresForwardSecrecy to NO depends on whether your TLS connection is using an allowed cipher suite. In certain cases, it will need to be set to NO. The NSExceptionAllowsInsecureHTTPLoads MUST be set to YES in order to allow the SessionDelegate to receive challenge callbacks. Once the challenge callbacks are being called, the ServerTrustPolicyManager will take over the server trust evaluation. You may also need to specify the NSTemporaryExceptionMinimumTLSVersion if you're trying to connect to a host that only supports TLS versions less than 1.2.

It is recommended to always use valid certificates in production environments. Using Self-Signed Certificates with Local Networking

If you are attempting to connect to a server running on your localhost, and you are using self-signed certificates, you will need to add the following to your Info.plist.

<dict>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsLocalNetworking</key>
        <true/>
    </dict>
</dict>

According to Apple documentation, setting NSAllowsLocalNetworking to YES allows loading of local resources without disabling ATS for the rest of your app.

Reference:- https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md

For implementation details refer the tests. https://github.com/Alamofire/Alamofire/blob/master/Tests/TLSEvaluationTests.swift#L290-L450

arango_86
  • 4,236
  • 4
  • 40
  • 46
2

SSL pinning using TrustKit with Alamofire. Here I have included API Manager class. This will help you solve using Alamofire with TrustKit.

class ApiManager: SessionDelegate{

  var sessionManager: SessionManager? 

  override init(){
        super.init()
        initReachibility()
        sessionManager = SessionManager.init(configuration: URLSessionConfiguration.ephemeral, delegate: self)
    }

  override func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        // Call into TrustKit here to do pinning validation
        if TrustKit.sharedInstance().pinningValidator.handle(challenge, completionHandler: completionHandler) == false {
            // TrustKit did not handle this challenge: perhaps it was not for server trust
            // or the domain was not pinned. Fall back to the default behavior
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }

  func makeRequestAlamofire(route:URL, method:HTTPMethod, autherized:Bool, parameter:Parameters,header:[String:String], callback: @escaping (APIResult<Data>) -> Void){

        sessionManager?.request(route,method: method,parameters:parameter, encoding: JSONEncoding.default,headers:headers ).validate(statusCode: 200..<300)
            .validate(contentType: ["application/json"]).responseData { response in
                //Pin Validtion returner
                guard response.error == nil else {
                    // Display Error Alert
                    print("Result Pinning validation failed for \(route.absoluteString)\n\n\(response.error.debugDescription)")
                    return
                }
                switch response.result {
                  case .success(let val):
                    print("Success")
                  case .failure(let error):
                    print("Faild")
                }
        }
    }
}

For the full tutorial refer this link.

Pranavan SP
  • 1,785
  • 1
  • 17
  • 33
2

Alamofire has changed sll pinning code snipped with the new version(Alamofire 5.0).

You should use ServerTrustManager just like below,

let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = timeoutIntervalForRequest
        let trustManager = ServerTrustManager(evaluators: [
                     "dev.ehliyetcepte.com": PublicKeysTrustEvaluator(),
                     "uat.ehliyetcepte.com": DisabledEvaluator(),
                     "pilot.ehliyetcepte.com": DisabledEvaluator(),
                     "prod.ehliyetcepte.com": DisabledEvaluator()])


        self.session = Session(startRequestsImmediately: true,
                               configuration: configuration,
                               delegate: self,
                               serverTrustManager: trustManager)
Emre Gürses
  • 1,992
  • 1
  • 23
  • 28
1

I would recommend using TrustKit. It is a dedicated library that works with everything base on NSURLSession, including Alamofire. Depending on your use case it may be as simple as adding a few values to Info.plist.

Certificate pinning, same as any security measure, is not something you should implement yourself, but you should use a proven library.

Igor Kulman
  • 16,211
  • 10
  • 57
  • 118
  • It looks very useful library thank you very much, but can you explain what is valid pin and backup pin in the demo because I didn't get it, and I don't know much about that, I have only one public key is that the valid pin? and from where I get the backup key – Rooh Al-mahaba Mar 07 '19 at 07:16
  • The repo contains a script that generates a hash from the certificate. This is called a pin. This is probably what you have. The backup pin is a has of a certificate that could be used in case of some problems in the backend. You do not have to provide a real one, you have to can make it up. – Igor Kulman Mar 07 '19 at 10:21
  • 1
    can you tell me how to use this TrustKit library with alamofire please because I couldnt find how to do it with alamofire – Rooh Al-mahaba Mar 12 '19 at 09:40
  • @RoohAl-mahaba Check my answer for Trustkit with alamofire. – Pranavan SP Aug 14 '19 at 08:29
  • Here is the code example: https://github.com/Alamofire/Alamofire/issues/3324#issuecomment-745130622 And don't forget to initialize TrustKit with correct configuration – Raman Shyniauski Dec 14 '21 at 08:10
0
let serverTrustPolicies: [String: ServerTrustPolicy] = [
     // or `pinPublicKeys`
    "test.example.com": .pinCertificates(
        certificates: ServerTrustPolicy.certificates(),
        validateCertificateChain: true,
        validateHost: true
    ),
    "insecure.expired-apis.com": .disableEvaluation
]

let sessionManager = SessionManager(
    serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)

  • Alamofire use SessionManager to manage ServerTrust, you can use custom ServerTrustPolicyManager setting Server Trust, then you can use `SessionManager.request` method , send request – 李腾芳 Aug 15 '19 at 02:00
0

I found this solution

let session = Session(delegate:CustomSessionDelegate())
session.request.... 


class CustomSessionDelegate: SessionDelegate {
  private static let publicKeyHash = "your_public_key"
  let rsa2048Asn1Header:[UInt8] = [
    0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
    0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00
]

override func urlSession(_ session: URLSession,
                         task: URLSessionTask,
                         didReceive challenge: URLAuthenticationChallenge,
                         completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    guard let serverTrust = challenge.protectionSpace.serverTrust else {
        completionHandler(.cancelAuthenticationChallenge, nil);
        return
    }
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
        // Server public key
        guard let serverPublicKey = SecCertificateCopyKey(serverCertificate) else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        guard let serverPublicKeyData = SecKeyCopyExternalRepresentation(serverPublicKey, nil) else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        let data:Data = serverPublicKeyData as Data
        // Server Hash key
        let serverHashKey = sha256(data: data)
        // Local Hash Key
        let publickKeyLocal = type(of: self).publicKeyHash
        if (serverHashKey == publickKeyLocal) {
            // Success! This is our server
            print("Public key pinning is successfully completed")
            completionHandler(.useCredential, URLCredential(trust:serverTrust))
            return
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
    }
}

private func sha256(data : Data) -> String {
    var keyWithHeader = Data(rsa2048Asn1Header)
    keyWithHeader.append(data)
    var hash = [UInt8](repeating: 0,  count: Int(CC_SHA256_DIGEST_LENGTH))
    keyWithHeader.withUnsafeBytes {
        _ = CC_SHA256($0, CC_LONG(keyWithHeader.count), &hash)
    }
    
    
    return Data(hash).base64EncodedString()
}
Tolga İskender
  • 158
  • 2
  • 14