certificatesInBundle doesn't append self signed certificates - ios

Using the convenience method ServerTrustPolicy.certificatesInBundle() doesn't appear to work correctly in my case
// MARK: - Bundle Location
/**
Returns all certificates within the given bundle with a `.cer` file extension.
- parameter bundle: The bundle to search for all `.cer` files.
- returns: All certificates within the given bundle.
*/
public static func certificatesInBundle(bundle: NSBundle = NSBundle.mainBundle()) -> [SecCertificate] {
var certificates: [SecCertificate] = []
let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
bundle.pathsForResourcesOfType(fileExtension, inDirectory: nil)
}.flatten())
for path in paths {
if let
certificateData = NSData(contentsOfFile: path), // <-- we get the data of the certificate in bundle
certificate = SecCertificateCreateWithData(nil, certificateData) // <-- The problem is here, the certificate is not set neither errors.
{
certificates.append(certificate) // <-- this doesn't run
}
}
return certificates
}
Probably has to do something with the format of the self-signed certificate. I used exactly the #tip 5 from this blog post. Five Tips for Using Self Signed SSL Certificates with iOS
The question is what is the limitations of the SecCertificateCreateWithData method and which certificate formats are acceptable? Even better where can I read more about this particular issue.
my code appears to be correct it's nothing special, probably one of the most used snippets :P
let defaultManager:Alamofire.Manager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"localhost": .PinCertificates(
certificates: ServerTrustPolicy.certificatesInBundle(),
validateCertificateChain: true,
validateHost: true
)
]
let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration()
configuration.HTTPAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders
return Alamofire.Manager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}()

The most likely reason that SecCertificateCreateWithData would return nil, is that the file is in PEM not DER format.
As per the documentation, data should contain
A DER (Distinguished Encoding Rules) representation of an X.509
certificate
If your data begins with "-----BEGIN...", then it is the wrong format. PEM can be converted to DER (and vice versa) with OpenSSL - here is a handy reference https://www.sslshopper.com/article-most-common-openssl-commands.html.
Also, in case of a self-signed certificate (judging by "localhost"), the validateCertificateChain property should be false. Otherwise the request will fail with a "cancelled" NSError.
Additionally, starting from iOS9, App Transport Security settings should be set to allow arbitrary loads (in Info.plist). That is the only setting that will permit self-signed certificates to be evaluated by your app. Without it, the Alamofire trust policy mechanism will not get a chance to kick in.

I had a similar problem. Alamofire couldn't find my certificate, the ServerTrustPolicy.certificatesInBundle() method did not return anything.
The problem was that when dragging my certificate into my Xcode project I didn't select "Add to targets: MyProjectName".

Make sure that you downloaded the certificate in der format and added correctly to your project.
after that define a static SessionManager as mentioned below
public static let sharedManager: SessionManager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"subdomain.domain.com": .pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: false,
validateHost: true
),
"insecure.expired-apis.com": .disableEvaluation
]
let manager = Alamofire.SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
return manager
}()
then you can call the above sharedManager:
YourHttpClassName.sharedManager.request(url, method: .get, headers: headers).response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.error)")
debugPrint(response)
}
it should work fine with your self-signed certificate.

Related

How to find the certificate added with SecItemAdd in Keychain

Following apple's doc, I wrote this code to add a certificate to my login keychain on macOS
let certUrl = Bundle.main.url(forResource: "cert", withExtension: "cer")!
let certData = try! Data(contentsOf: certUrl)
let cert = SecCertificateCreateWithData(nil, certData as CFData)!
let addQuery: [String: Any] = [
kSecClass as String: kSecClassCertificate,
kSecValueRef as String: cert,
kSecAttrLabel as String: "My Certificate"
]
let status = SecItemAdd(addQuery as CFDictionary, nil)
guard status == errSecSuccess else {
print(status)
return true
}
print("cert added")
return true
It works, but the problem is that when I opened the Keychain Access app, I couldn't find the added certificate in login keychain's certificate category.
I tried running the code again, and it failed with duplicate certificate error. So the certificate must have been added. I wonder how to find it in Keychain Access? Or certificates added using SecItemAdd are always hidden from the Keychain Access app?
macOS version: 10.15.1
I was using Mac Catalyst. Switching to a cocoa app made it work.

How to use ssl certificate with Swift Alamofire?

I am upgrading my iOS apps with HTTPS web services.
I have upgrade my web server with SSL certificate. But don't know what to do from iOS code side?
Do I need to pass any certificate along with the web request?
I am using Alamofire for making web request.
Thanks
Simple googling would have given you so many results.
For example, https://infinum.co/the-capsized-eight/how-to-make-your-ios-apps-more-secure-with-ssl-pinning
func configureAlamoFireSSLPinning {
let pathToCert = NSBundle.mainBundle().pathForResource(githubCert, ofType: "cer")
let localCertificate:NSData = NSData(contentsOfFile: pathToCert!)!
self.serverTrustPolicy = ServerTrustPolicy.PinCertificates(
certificates: [SecCertificateCreateWithData(nil, localCertificate)!],
validateCertificateChain: true,
validateHost: true
)
self.serverTrustPolicies = [
"your-api.com": self.serverTrustPolicy!
]
self.afManager = Manager(
configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
serverTrustPolicyManager: ServerTrustPolicyManager(policies: self.serverTrustPolicies)
)
}
func alamoFireRequestHandler {
self.afManager.request(.GET, self.urlTextField.text!)
.response { request, response, data, error in
// response management code
}
}

Pinning PublicKey with AlamoFire

I've been trying to perform a public key pinning for my application.
And I did the following steps:
1) First I used openssl to extract the server cert in der format
openssl s_client -showcerts -connect my.server.com:443 < /dev/null | openssl x509 -outform DER > serverCert.der
2) Loaded publicKey from cert:
func getPubKey() -> SecKey? {
let certificateData = NSData(contentsOfURL:NSBundle.mainBundle().URLForResource("serverCert", withExtension: "der")!)
let certificate = SecCertificateCreateWithData(nil, certificateData!)
var trust: SecTrustRef?
let policy = SecPolicyCreateBasicX509()
let status = SecTrustCreateWithCertificates(certificate!, policy, &trust)
var key: SecKey?
if status == errSecSuccess {
key = SecTrustCopyPublicKey(trust!)!;
print("NetworkImplementation :: getPubKey :: success")
}
return key
}
3) added my serverTrustPolicy to Alamofire’s Manager:
let key: SecKey? = self.getPubKey()
self.serverTrustPolicy = ServerTrustPolicy.PinPublicKeys(publicKeys: [key!],
validateCertificateChain: true,
validateHost: true)
self.serverTrustPolicies = [
"*.my.server.com": self.serverTrustPolicy!
]
self.afManager = Manager(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
serverTrustPolicyManager: ServerTrustPolicyManager(policies: self.serverTrustPolicies))
print("NetworkImplementation :: init :: success")
4) When I want to perform a request I do this:
self.afManager
.request(almethod, url, parameters: alparams, encoding: encoding, headers: alheaders)
.validate()
.response { //... }
Then I tried to check if it was not trusting a self signed certificate, to do that I used Charles and the iOS Simulator.
After installing Charles, added my server domain to the SSL Proxy configuration on port 443 and clicked Help -> SSL Proxying -> Install on simulators.
I was able to see the request and response content of my application inside the Charles UI and the request connection status was not an error.
What am I missing?

Generate a P12 file Xcode?

I know there is a function called SecPKCS12Import that allows you to import data from a p12 file. However, I want to go the reverse route. I have a SecCertificateRef and a public/private SecKeyRef, which I want to use to create a P12 file. Does anyone know how to do this on iPhone?
Thanks
Unfortunately, there CommonCrypto does not provide any means to export PKCS12 containers let alone any other export functionality (even though its OSX counterpart can do that). There are ways to extract the SecKeyRef raw data from the key chain but then you still need to write all the PKCS12 wrapping yourself.
We were facing a similar issue and went with OpenSSL.
Compiling OpenSSL for iOS
Integrating OpenSSL requires a bit of work as you need to compile and link the OpenSSL sources yourself. Fortunately, there are some build scripts available so you do not have to do that yourself, e.g, https://github.com/x2on/OpenSSL-for-iPhone . I suggest you use them as you need to patch some of the Makefiles which is a bit of a hazel. Those build scripts generate static linked libraries for both iOS and tvOS. You just need to link them against your project and set the Header and Library Search Path accordingly.
CocoaPods
You can also use the official OpenSSL CocoaPod . That saves you the trouble of configuring your project.
Exporting PKCS12
As you might know, OpenSSL is a C library. That means you might want to encapsulate all the C functions into a Objective-C or Swift wrapper. There are some open source wrappers that support im- and exporting PKCS12 containers but I have not found a single one with good documentation. You should be able to derive the relevant snippets from some of the sources though.
https://github.com/microsec/MscX509Common/blob/master/src/MscPKCS12.m
You can have a look at this example as well http://fm4dd.com/openssl/pkcs12test.htm .
Hope that helps!
I agree that this task can only be performed using OpenSSL. It is a bit tricky to compile it for iOS but with OpenSSL-for-iPhone it is quite possible.
To solve the given task of creating a PKCS12 keystore from a SecCertificate and a SecKey with Swift 3 just add the static libraries libssl.aand libcrypto.a to your project and create the following bridging header:
#import <openssl/err.h>
#import <openssl/pem.h>
#import <openssl/pkcs12.h>
#import <openssl/x509.h>
To create the keystore the input data have to be converted to OpenSSL data structures, which requires some creativity. The SecCertificate can be converted directly to DER format and then read into a X509 structure. The SecKey is even worse to handle. The only possible solution to get the data of the key is to write it to the keychain and get the reference. From the reference we can get the base 64 encoded string, which then can be read into a EVP_PKEY structure. Now, we can create the keystore and save it to a file. To access the data in the keystore via iOS functions we must read the file via let data = FileManager.default.contents(atPath: path)! as NSData
The full solution is shown in the following:
func createP12(secCertificate: SecCertificate, secPrivateKey: SecKey) {
// Read certificate
// Convert sec certificate to DER certificate
let derCertificate = SecCertificateCopyData(secCertificate)
// Create strange pointer to read DER certificate with OpenSSL
// data must be a two-dimensional array containing the pointer to the DER certificate as single element at position [0][0]
let certificatePointer = CFDataGetBytePtr(derCertificate)
let certificateLength = CFDataGetLength(derCertificate)
let certificateData = UnsafeMutablePointer<UnsafePointer<UInt8>?>.allocate(capacity: 1)
certificateData.pointee = certificatePointer
// Read DER certificate
let certificate = d2i_X509(nil, certificateData, certificateLength)
// Print certificate
X509_print_fp(stdout, certificate)
// Read private key
// Convert sec key to PEM key
let tempTag = "bundle.temp"
let tempAttributes = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: tempTag,
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecValueRef: secPrivateKey,
kSecReturnData: kCFBooleanTrue
] as NSDictionary
var privateKeyRef: AnyObject?
// Store private key in keychain
SecItemDelete(tempAttributes)
guard SecItemAdd(tempAttributes, &privateKeyRef) == noErr else {
NSLog("Cannot store private key")
return
}
// Get private key data
guard let privateKeyData = privateKeyRef as? Data else {
NSLog("Cannot get private key data")
return
}
let pemPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n\(privateKeyData.base64EncodedString())\n-----END RSA PRIVATE KEY-----\n"
// Delete private key in keychain
SecItemDelete(tempAttributes)
let privateKeyBuffer = BIO_new(BIO_s_mem())
pemPrivateKey.data(using: .utf8)!.withUnsafeBytes({ (bytes: UnsafePointer<Int8>) -> Void in
BIO_puts(privateKeyBuffer, bytes)
})
let privateKey = PEM_read_bio_PrivateKey(privateKeyBuffer, nil, nil, nil)
// !!! Remove in production: Print private key
PEM_write_PrivateKey(stdout, privateKey, nil, nil, 0, nil, nil)
// Check if private key matches certificate
guard X509_check_private_key(certificate, privateKey) == 1 else {
NSLog("Private key does not match certificate")
return
}
// Set OpenSSL parameters
OPENSSL_add_all_algorithms_noconf()
ERR_load_crypto_strings()
// Create P12 keystore
let passPhrase = UnsafeMutablePointer(mutating: ("" as NSString).utf8String)
let name = UnsafeMutablePointer(mutating: ("SSL Certificate" as NSString).utf8String)
guard let p12 = PKCS12_create(passPhrase, name, privateKey, certificate, nil, 0, 0, 0, 0, 0) else {
NSLog("Cannot create P12 keystore:")
ERR_print_errors_fp(stderr)
return
}
// Save P12 keystore
let fileManager = FileManager.default
let tempDirectory = NSTemporaryDirectory() as NSString
let path = tempDirectory.appendingPathComponent("ssl.p12")
fileManager.createFile(atPath: path, contents: nil, attributes: nil)
guard let fileHandle = FileHandle(forWritingAtPath: path) else {
NSLog("Cannot open file handle: \(path)")
return
}
let p12File = fdopen(fileHandle.fileDescriptor, "w")
i2d_PKCS12_fp(p12File, p12)
fclose(p12File)
fileHandle.closeFile()
}

Certificate pinning in Alamofire not working

I've added a .der certificate to my project, which is recognised by ServerTrustPolicy.certificatesInBundle(). I'm now creating a Manager which is then used to make API calls:
private class func manager() -> Alamofire.Manager {
let certificates = ServerTrustPolicy.PinCertificates(
certificates: ServerTrustPolicy.certificatesInBundle(),
validateCertificateChain: true,
validateHost: true
)
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"api.oursite.com": certificates
]
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let manager = Alamofire.Manager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
return manager
}
Before this, it was just using Alamofire.request(..., and those requests were working perfectly fine, but now it's giving me an error on every call:
Error Domain=NSURLErrorDomain Code=-999 "cancelled"
Am I setting up the manager incorrectly? Perhaps my certificate was created incorrectly, and that's causing this to not work?
Be sure that your manager not deallocated in short time after request. Make it as stored property in custom Manager class for example.

Resources