Alamofire with a self-signed certificate / ServerTrustPolicy - ios

I want to use Alamofire to communicate with my server over a https connection with a self signed certificate. My environment runs on localhost. I've tried to connect, but the response all the time looks like this:
Success: false
Response String: nil
I've done it with the following code:
import Foundation
import UIKit
import Alamofire
class MessageView: UITableViewController {
let defaultManager: Alamofire.Manager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"localhost": .DisableEvaluation
]
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders
return Alamofire.Manager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}()
override func viewDidLoad() {
super.viewDidLoad()
defaultManager
.request(.GET, "https://localhost:3443/message")
.responseJSON { _, _, result in
print("Success: \(result.isSuccess)")
print("Response String: \(result.value)")
}
}
}
I've created the server side certificates with this line of bash:
openssl req -x509 -nodes -days 999 -newkey rsa:2048 -keyout server.key -out server.crt
I don't know what am I doing wrong. Help would be great.
### Update ###
Here is the cURL request. In my opinion, there is no problem, or am I wrong?
curl -X GET https://localhost:3443/message -k -v
* Trying ::1...
* Connected to localhost (::1) port 3443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
* Server certificate: teawithfruit
> GET /message HTTP/1.1
> Host: localhost:3443
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Content-Length: 1073
< Date: Tue, 15 Sep 2015 06:20:45 GMT
< Connection: keep-alive
<
* Connection #0 to host localhost left intact
[{"_id":"55f3ed2d81a334558241e2f4","email":"abc#def.com","password":"abc","name":"teawithfruit","language":"en","__v":0,"timestamp":1442049325159,"messages":[{"_id":"55f40553e568236589772c61","user":"55f3ed2d81a334558241e2f4","language":"en","message":"hello world","__v":0,"timestamp":1442055507301,"id":"55f40553e568236589772c61"},{"_id":"55f48b2b02e7b059b54e99f6","user":"55f3ed2d81a334558241e2f4","language":"en","message":"hello world","__v":0,"timestamp":1442089771312,"id":"55f48b2b02e7b059b54e99f6"}],"id":"55f3ed2d81a334558241e2f4"}]
### Update 2 ###
Sorry for the late answer.
Here are the two debugPrints:
Request debugPrint:
$ curl -i \
-H "Accept-Language: en-US;q=1.0" \
-H "Accept-Encoding: gzip;q=1.0,compress;q=0.5" \
-H "User-Agent: Message/com.teawithfruit.Message (1; OS Version 9.0 (Build 13A340))" \
"https://localhost:3443/message"
Result debugPrint:
FAILURE: Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=https://localhost:3443/message, NSLocalizedDescription=cancelled, NSErrorFailingURLStringKey=https://localhost:3443/message}
### Update 3 ###
Here is the complete error with maybe an ATS problem?
nil
$ curl -i \
-H "Accept-Language: en-US;q=1.0" \
-H "Accept-Encoding: gzip;q=1.0,compress;q=0.5" \
-H "User-Agent: Message/com.teawithfruit.Message (1; OS Version 9.0 (Build 13A340))" \
"https://localhost:3443/message"
2015-10-17 15:10:48.346 Message[25531:1001269] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
FAILURE: Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x7fdc3044b740>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=<CFArray 0x7fdc2a7ca300 [0x10f7037b0]>{type = immutable, count = 1, values = (
0 : <cert(0x7fdc31d31670) s: teawithfruit i: teawithfruit>
)}, NSUnderlyingError=0x7fdc30064bd0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x7fdc3044b740>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=<CFArray 0x7fdc2a7ca300 [0x10f7037b0]>{type = immutable, count = 1, values = (
0 : <cert(0x7fdc31d31670) s: teawithfruit i: teawithfruit>
)}}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://localhost:3443/message, NSErrorFailingURLStringKey=https://localhost:3443/message, NSErrorClientCertificateStateKey=0}
Success: false
Response String: nil

You need to add the port domain when you create your ServerTrustPolicy dictionary.
let defaultManager: Alamofire.Manager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"localhost:3443": .DisableEvaluation
]
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders
return Alamofire.Manager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}()

For swift 4:
private static var Manager : Alamofire.SessionManager = {
// Create the server trust policies
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"your domain goes here": .disableEvaluation
]
// Create custom manager
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
let man = Alamofire.SessionManager(
configuration: URLSessionConfiguration.default,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
return man
}()
Then you call it like this:
Manager.upload(body.data(using: .utf8)!, to: url, method: .post, headers: headers)
Credits to Cnoon

My approach for self-signed https. The ServerTrustPolicyManager is an open class, and it's serverTrustPolicy function is open too. So it can be override.
In my case, the server list will grow in future. If I hard-code the https list, I will need to maintain the list when adding new https server. So, I decide to override the ServerTrustPolicyManager class in order to meet my needs.
// For Swift 3 and Alamofire 4.0
open class MyServerTrustPolicyManager: ServerTrustPolicyManager {
// Override this function in order to trust any self-signed https
open override func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
return ServerTrustPolicy.disableEvaluation
}
}
Then,
let trustPolicies = MyServerTrustPolicyManager(policies: [:])
let manager = Alamofire.SessionManager(configuration: sessionConfig, delegate: SessionDelegate(), serverTrustPolicyManager: trustPolicies)

So I know some time has passed, but I had exactly the same problem. And I found a solution with above answers. I had to add 2 things to trustPolicies:
let defaultManager: Alamofire.Manager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
// Here host with port (trustPolicy is my var where I pin my certificates)
"localhost:3443": trustPolicy
//Here without port
"localhost": .disableEvaluation
]
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders
return Alamofire.Manager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}()
Also in Info.plist had to add:
<key>AppTransportSecurity</key>
<dict>
<key>AllowsArbitraryLoads</key>
<true/>
</dict>

Related

iOS: Intermittently slow network requests

I have an iOS application that has two requests that have large response objects and typically take ~100-200ms to complete when connectivity is good. When connectivity is average these requests seem to get hung-up somewhere ~20% of the time and I've had them take anywhere from 30 to 60s. I'll then restart the app and try the request again seconds later and it will complete in the expected time (100-200 ms)
I've tested the api that the endpoints are querying with other clients (cURL, Postman) and haven't had any issue so I'm fairly certain this has to do with my front-end configuration. I'm using the Alamofire library to handle the requests.
Here's the code for one of the requests:
func login(_ params: [String: String], completion: #escaping (Response<UserResponseSuccess, UserResponseFail>) -> Void) {
Alamofire.request(self.url!, method: .post, parameters: params).responseJSON {
response in
if response.result.isSuccess {
let respJSON: JSON = JSON(response.result.value!)
if response.response?.statusCode == 200 {
// do stuff with json
let resp = UserResponseSuccess()
completion(Response.success(resp))
} else {
logRequestError()
let resp = UserResponseFail()
completion(Response.failure(resp))
}
} else {
logServerError()
let resp = UserResponseFail()
completion(Response.failure(resp))
}
}
}
And here's the request log:
2019-08-17 14:03:16.832462-0600 Debug - foo[17418:3273774] CredStore - performQuery - Error copying matching creds. Error=-25300, query={
class = inet;
"m_Limit" = "m_LimitAll";
ptcl = htps;
"r_Attributes" = 1;
sdmn = "foo.bar.com";
srvr = "foo.bar.com";
sync = syna;
}
$ curl -v \
-X POST \
-H "User-Agent: Debug - foo/1.3 (com.foo.bar.debug; build:4; iOS 12.4.0) Alamofire/4.7.3" \
-H "Content-Type: application/x-www-form-urlencoded; charset=utf-8" \
-H "Accept-Language: en-US;q=1.0" \
-H "Accept-Encoding: gzip;q=1.0, compress;q=0.5" \
-d "email=foo#foo.com&password=bar123" \
"https://rtj.foo.com/users/login"
The requests either go through instantly or don't hit the server. I could probably have these timeout and then retry the request but I don't want to cover up a probable bug.
This also seems to happen more often on a cell network than wifi.
Update: I ran the same query with URLSession instead of Alamofire and received similar results. The error output from the query is:
error:
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out."
UserInfo={NSUnderlyingError=0x28228fe10 {Error
Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo= .
{_kCFStreamErrorCodeKey=60, _kCFStreamErrorDomainKey=1}},
NSErrorFailingURLStringKey=https://foo.bar.com/users/login,
NSErrorFailingURLKey=https://foo.bar.com/users/login,
_kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=60,
NSLocalizedDescription=The request timed out.}
Versioning: Alamofire: 4.7.3, Xcode version: 10.3, Swift version: 5

How to trust an endpoint iOS swift

I'm calling an endpoint that has a self-signed ssl certificate i have tried adding this in my info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
But i am still not able to access the endpoint i keep getting this
NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “endpoint” which could put your confidential information at risk.
You need to create a session manager and tell it to disable evaluation of the ssl in that server.
Something like this
static var manager: Alamofire.SessionManager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"https://example.com": .disableEvaluation
]
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
let manager = Alamofire.SessionManager(
configuration: URLSessionConfiguration.default,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
return manager
}()
An then, instead of calling request in Alamofire, like this
Alamofire.request("https://example.com", method: .get…
you call it in your manager
manager.request("https://example.com"…

Swift 3 How to validate server certificate using SSL Pinning and AlamoFire?

I'm writing an app in swift 3 that needs to talk to my server. I have the full certificate chain in der and crt format which I am the CA for(Not to be confused with self signed). How do I use this in my app to validate my server? Below is my rest call and response
Rest Call:
var request = URLRequest(url: URL(string: "https://myserver/login")!)
request.addValue("Content-Type", forHTTPHeaderField: "application/json")
request.httpMethod = "GET"
let session = URLSession.shared
session.dataTask(with: request) {data, response, err in
print("=========================DATA===============================")
if data != nil {
print(data!)
}
print("=========================RESPONSE===============================")
if response != nil {
print(response!)
}
print("=========================ERR===============================")
if err != nil {
print(err!)
}
}.resume()
Output:
=========================DATA===============================
=========================RESPONSE===============================
=========================ERR===============================
Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x60800011f020>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
"<cert(0x7fae4803d200) s: myserver i: MySubCA>",
"<cert(0x7fae48047000) s: MySubCA i: MyRootCA>",
"<cert(0x7fae48044600) s: MyRootCA i: MyRootCA>"
), NSUnderlyingError=0x60800005a040 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x60800011f020>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
"<cert(0x7fae4803d200) s: myserver i: MySubCA>",
"<cert(0x7fae48047000) s: MySubCA i: MyRootCA>",
"<cert(0x7fae48044600) s: MyRootCA i: MyRootCA>"
)}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://myserver/login, NSErrorFailingURLStringKey=https://myserver/login, NSErrorClientCertificateStateKey=0}
I solved it pretty simply leveraging an online blog, AlamoFire and openssl.
I used AlamoFire for the networking on iOS.
I used an article about SSL pinning on iOS to get on the right direction.
I used openssl to convert my cert to der format.
Der conversion through openssl.
openssl x509 -in cert.crt -out cert.der -outform DER
You will need to add the der formatted cert to your app bundle.
Swift 3 implementation
// Your hostname and endpoint
let hostname = "YOUR_HOST_NAME"
let endpoint = "YOUR_ENDPOINT"
let cert = "YOUR_CERT" // e.g. for cert.der, this should just be "cert"
// Set up certificates
let pathToCert = Bundle.main.path(forResource: cert, ofType: "der")
let localCertificate = NSData(contentsOfFile: pathToCert!)
let certificates = [SecCertificateCreateWithData(nil, localCertificate!)!]
// Configure the trust policy manager
let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
certificates: certificates,
validateCertificateChain: true,
validateHost: true
)
let serverTrustPolicies = [hostname: serverTrustPolicy]
let serverTrustPolicyManager = ServerTrustPolicyManager(policies: serverTrustPolicies)
// Configure session manager with trust policy
afManager = SessionManager(
configuration: URLSessionConfiguration.default,
serverTrustPolicyManager: serverTrustPolicyManager
)
afManager.request(endpoint, method: .get).responseJSON { response in
debugPrint("All Response Info: \(response)")
}

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?

Self-signed certificate iOS

I am trying to configure my iOS app to accept self-signed certificates.
I am trying to fetch the data on a button click. Below is the code that I am using currently:
private var manager : SessionManager?
func setManager(url: String) {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
url: .disableEvaluation
]
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
manager = Alamofire.SessionManager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}
#IBAction func nonCertifiedClick(_ sender: UIButton) {
outputText.text = ""
setManager(url: "sand.xxx.int:16443")
manager?.request("https://sand.xxx.int:16443/version").response { response in
debugPrint("R: \(response)")
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)")
self.outputText.text = utf8Text
}
}
}
My Info.plist file has the below configuration:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
But when the request is executed, I get the following response:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
"R: DefaultDataResponse(request: Optional(https://sand.xxx.int:16443/version), response: nil, data: Optional(0 bytes),
error: Optional(Error Domain=NSURLErrorDomain Code=-1202 \"The certificate for this server is invalid. You might be connecting to a server that is pretending to be “sand.xxx.int” which could put your confidential information at risk.\"
UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x600000105730>,
NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?,
_kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9813,
NSErrorPeerCertificateChainKey=(\n \"<cert(0x7fcf8b81e800) s: sand i: sand>\"\n),
NSUnderlyingError=0x60000005f440 {Error Domain=kCFErrorDomainCFNetwork Code=-1202 \"(null)\" UserInfo={_kCFStreamPropertySSLClientCertificateState=0,
kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x600000105730>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9813,
_kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9813,
kCFStreamPropertySSLPeerCertificates=(\n \"<cert(0x7fcf8b81e800) s: sand i: sand>\"\n)}},
NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “sand.xxx.int” which could put your confidential information at risk.,
NSErrorFailingURLKey=https://sand.xxx.int:16443/version,
NSErrorFailingURLStringKey=https://sand.xxx.int:16443/version, NSErrorClientCertificateStateKey=0}),
_metrics: Optional((Task Interval) <_NSConcreteDateInterval: 0x600000224e40> (Start Date) 2016-11-02 14:13:57 +0000 + (Duration) 0.381569 seconds = (End Date) 2016-11-02 14:13:58 +0000\n(Redirect Count) 0\n(Transaction Metrics) (Request) <NSURLRequest: 0x600000200120> { URL: https://sand.xxx.int:16443/version }\n(Response) (null)\n(Fetch Start) 2016-11-02 14:13:57 +0000\n(Domain Lookup Start) (null)\n(Domain Lookup End) (null)\n(Connect Start) (null)\n(Secure Connection Start) (null)\n(Secure Connection End) (null)\n(Connect End) (null)\n(Request Start) 2016-11-02 14:13:57 +0000\n(Request End) 2016-11-02 14:13:57 +0000\n(Response Start) 2016-11-02 14:13:57 +0000\n(Response End) (null)\n(Protocol Name) (null)\n(Proxy Connection) NO\n(Reused Connection) YES\n(Fetch Type) Unknown\n\n))"
Data:
I am testing this on Xcode 8.1 with Swift3 and Alamofire4.
What am I misisng here to make it work right?
Update (Answer)
If incase someone encounters the same issue, the problem was the server's SSL certificate. The certificate needs to be signed with at least SHA256 algorithm but mine was signed with SHA1.
I faced the same problem. But using your question, i found the solution. Tnx..
Here is my solution. it works with swift 3
create a class SecurityCertificateManager
import Foundation
import Alamofire
class SecurityCertificateManager {
static let sharedInstance = SecurityCertificateManager()
let defaultManager: Alamofire.SessionManager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"272.73.41.156": .disableEvaluation
]
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
return Alamofire.SessionManager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}()
}
call it like this in viewDIdLoad
let baseUrl ="https://272.73.41.156/cas/tickets?"+"username="+userEmail.text!+"&password="+userPassword.text!
print("Base url : \(baseUrl)")
let params2 = ["nostring": "nodata", "nostring": "nodata",]
SecurityCertificateManager.sharedInstance.defaultManager.request(baseUrl, method: .post, parameters: params2, encoding: JSONEncoding.default, headers: ["Content-Type":"application/x-www-form-urlencoded"]).responseJSON { (response:DataResponse<Any>) in
switch(response.result) {
case .success(_):
if response.result.value != nil{
print("response : \(response.result.value)")
}
break
case .failure(_):
print("Failure : \(response.result.error)")
break
}
}
It Works in Swift3

Resources