ios, alamofire5: need an equivalent of curl --key foo.key --cert foo,pem --location --request GET 'https://bar.baz/foo"

Viewed 884

I've got a private key in addition to a public key to pin certificate.

How would authenticate with the to the server?

$ file *
foo.der:         data
foo.private.der: data

derived with openssl from

foo.key: PEM RSA private key
foo.pem: PEM certificate

what i need is an alamofire equivalent of this:

curl --key foo.key --cert foo.pem --location --request GET 'https://somhostofmine/v1/welcome/'

$ curl --key ./client_key.pem --cert ./client.pem --location --request GET 'https://someurl' "Hello wold!"

works

$ curl --cert ./client.pem --location --request GET 'https://someurl/v1/welcome/' curl: (58) unable to set private key file: './client.pem' type PEM

does not

So suggestions to use certificate itself without private key do not work.

Are we dealing with certificate pinning here or given the presence of the private key this is something else going here???

2 Answers

You can create a ServerTrustManager passing as parameter your evaluators per host name. For public key pinning use PublicKeysTrustEvaluator like this:

let evaluators: [String : ServerTrustEvaluating] = [
    "your.host.com": PublicKeysTrustEvaluator(performDefaultValidation: false, validateHost: false)
]
let serverTrustManager = ServerTrustManager(evaluators: evaluators)

For certificate pinning use PinnedCertificatesTrustEvaluator like this:

let evaluators: [String : ServerTrustEvaluating] = [
    "your.host.com": PinnedCertificatesTrustEvaluator(
        acceptSelfSignedCertificates: true,
        performDefaultValidation: false,
        validateHost: false
    )
]
let serverTrustManager = ServerTrustManager(evaluators: evaluators)

Both methods require your certificate to be included in your bundle as .cer or .der file.

After creating your ServerTrustManager pass it to a Session instance and use this for your requests:

let session = Session(serverTrustManager: serverTrustManager)

If you want to have a more complex logic on how you verify your server or you have to use wildcards in your domain you have to subclass ServerTrustManager and override serverTrustEvaluator(forHost:) function:

class MyServerTrustManager: ServerTrustManager {
    init() {
        super.init(evaluators: [:])
    }
    
    override func serverTrustEvaluator(forHost host: String) throws -> ServerTrustEvaluating? {
        guard host.hasSuffix(".host.com") else {
            return try super.serverTrustEvaluator(forHost: host)
        }
        return PublicKeysTrustEvaluator(performDefaultValidation: false, validateHost: false)
    }
}
  1. packed private and public key into p12 container with openssl
  2. this was key Alamofire without evaluation and with sending client certificate

created PKCS512() instance and created URLCredential using that (code in the link above) in alamofire all you need from the link above is the PKCS12 class and URLCredential extension

public class PKCS12 {
    let label:String?
    let keyID:NSData?
    let trust:SecTrust?
    let certChain:[SecTrust]?
    let identity:SecIdentity?

    public init(PKCS12Data:NSData,password:String)
    {
        let importPasswordOption:NSDictionary = [kSecImportExportPassphrase as NSString:password]
        var items : CFArray?
        let secError:OSStatus = SecPKCS12Import(PKCS12Data, importPasswordOption, &items)

        guard secError == errSecSuccess else {
            if secError == errSecAuthFailed {
                NSLog("ERROR: SecPKCS12Import returned errSecAuthFailed. Incorrect password?")
            }
            fatalError("SecPKCS12Import returned an error trying to import PKCS12 data")
        }

        guard let theItemsCFArray = items else { fatalError()  }
        let theItemsNSArray:NSArray = theItemsCFArray as NSArray
        guard let dictArray = theItemsNSArray as? [[String:AnyObject]] else { fatalError() }

        func f<T>(key:CFString) -> T? {
            for d in dictArray {
                if let v = d[key as String] as? T {
                    return v
                }
            }
            return nil
        }

        self.label = f(key: kSecImportItemLabel)
        self.keyID = f(key: kSecImportItemKeyID)
        self.trust = f(key: kSecImportItemTrust)
        self.certChain = f(key: kSecImportItemCertChain)
        self.identity =  f(key: kSecImportItemIdentity)
    }
}

extension URLCredential {
    public convenience init?(PKCS12 thePKCS12:PKCS12) {
        if let identity = thePKCS12.identity {
            self.init(
                identity: identity,
                certificates: thePKCS12.certChain,
                persistence: URLCredential.Persistence.forSession)
        }
        else { return nil }
    }
}
  1. fed that credential to authenticate(with: urlCredential) per Missing sessionDidReceiveChallenge in Alamofire 5 delegate

and that was it.

Hardcoding password into p12 constructor was super ugly. Eww What's key I guess is base64 baking in p12 into code rather than having it as a file prone to ipa patcher attach.

Related