I am trying to consume web services for my iOS app over https. The web server uses a self signed certificate.
When consuming the web service, I get the error “certificate is Invalid”.
FAILURE: Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “portal” which could put your confidential information at risk."
I know the best practise is to fix this at the server side to enable a trusted root CA. But as this is a temporary development environment, we are using a self signed certificate.
Since this is ATS issue, I have edited ATS in my info.plist as below.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>devportal</key>
<dict>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
</plist>
As the NSException domains doesn’t work with IP and port number, I have created a host entry in my etc/hosts file for the web server IP and consuming it like https://devportal:8443/rest/login instead of consuming it as https://192.22.xx.xxx:8443/rest/login
I have followed alamofire documentation on server trust policies, edited ATS to allow exception domains but nothing worked out for me. I have spent over 3 days on this issue. Am I missing something? Does anybody faced a similar issue? Is there any solution for this? Thanks in advance
I am using almofire 4.0, Xcode 8.0. Below is my code.
class LoginService{
private static var Manager: Alamofire.SessionManager = {
let pathToCert = Bundle.main.path(forResource: "192.22.xx.xxx", ofType: "crt") // Downloaded this certificate and have added to my bundle
let localCertificate:NSData = NSData(contentsOfFile: pathToCert!)!
// Create the server trust policies
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"192.22.xx.xxx": .pinCertificates(
certificates: [SecCertificateCreateWithData(nil, localCertificate)!],
validateCertificateChain: true,
validateHost: true
),
"devportal:8443": .disableEvaluation
]
// Create custom manager
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
let manager = Alamofire.SessionManager(
configuration: URLSessionConfiguration.default,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
return manager
}()
/**
Calls the Login Web Service to authenticate the user
*/
public func login(username:String, password: String){
let parameters = [
"username": "TEST",
"password": "PASSWORD",
]
let header: HTTPHeaders = ["Accept": "application/json"]
LoginService.Manager.request("https://devportal:8443/rest/login", method: .post, parameters: parameters, encoding: JSONEncoding(options: []),headers :header).responseJSON { response in
debugPrint(response)
if let json = response.result.value {
print("JSON: \(json)")
}
}
}
}
I modified my code like below and it worked. I referred Swift: How to Make Https Request Using Server SSL Certificate for fixing this issue.
class LoginService{
private static var Manager: Alamofire.SessionManager = {
// Create the server trust policies
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"devportal:8443": .disableEvaluation
]
// Create custom manager
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
let manager = Alamofire.SessionManager(
configuration: URLSessionConfiguration.default,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
return manager
}()
/**
Calls the Login Web Service to authenticate the user
*/
public func login(username:String, password: String){
// Handle Authentication challenge
let delegate: Alamofire.SessionDelegate = LoginService.Manager.delegate
delegate.sessionDidReceiveChallenge = { session, challenge in
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
disposition = URLSession.AuthChallengeDisposition.useCredential
credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
} else {
if challenge.previousFailureCount > 0 {
disposition = .cancelAuthenticationChallenge
} else {
credential = LoginService.Manager.session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
return (disposition, credential)
}
//Web service Request
let parameters = [
"username": "TEST",
"password": "PASSWORD",
]
let header: HTTPHeaders = ["Accept": "application/json"]
LoginService.Manager.request("https://devportal:8443/rest/login", method: .post, parameters: parameters, encoding: JSONEncoding(options: []),headers :header).responseJSON { response in
debugPrint(response)
if let json = response.result.value {
print("JSON: \(json)")
}
}
}
}
You should also configure your plist as below
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>devportal</key>
<dict>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
</plist>
Do not enter IP or port numbers in your NSExceptiondomains. It won't
work. If you are trying to connect to a web server with IP address,
map the IP address to a domain by adding a host entry in etc/hosts
file in your mac and then use the domain name in NSExceptionDomains
IMPORTANT: Do not use this code in production as this puts your users
information at risk, by bypassing auth challenge.
Not suggesting for production use-cases
//Use this manager class
class APIManager {
static var Manager: Alamofire.Session = {
let manager = ServerTrustManager(evaluators: ["your endpoint": DisabledTrustEvaluator()])
let session = Session(serverTrustManager: manager)
return session
}()
}
//Call APIs using this manager
APIManager.Manager.request("API")
Related
I'm trying to configure Local Push Connectivity. I already have Local Push Entitlement, and have install a provisioning profile with local push entitlement. It's build fine but when app start, PushProvider didn't active and start and Push Manager show error nil. I have done every instructions that sample code have provided.
This is my project.
In my application target, I have a bundle id com.my_team_name.my_app_name
and in the app group name group.com.my_team_name.my_app_name
In the .entitlement, I've set the required configuration:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>app-push-provider</string>
</array>
<key>com.apple.developer.networking.wifi-info</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.my_team_name.my_app_name</string>
</array>
</dict>
</plist>
Info.plist has noting to change
And I have a PushManager Class with this code
class AppPushManager: NSObject, NEAppPushDelegate{
func appPushManager(_ manager: NEAppPushManager, didReceiveIncomingCallWithUserInfo userInfo: [AnyHashable : Any] = [:]) {
}
static let shared = AppPushManager()
private var pushManager: NEAppPushManager = NEAppPushManager()
private let pushManagerDescription = "PushDefaultConfiguration"
private let pushProviderBundleIdentifier = "com.my_team_name.my_app_name.PushProvider"
func initialize() {
if pushManager.delegate == nil {
pushManager.delegate = self
}
pushManager.localizedDescription = pushManagerDescription
pushManager.providerBundleIdentifier = pushProviderBundleIdentifier
pushManager.isEnabled = true
pushManager.providerConfiguration = [
"host": "my_server.local"
]
pushManager.matchSSIDs = ["my_wifi_ssid"]
pushManager.saveToPreferences(completionHandler: { error in
print("error? \(String(describing: error))")
print("is active: \(pushManager.isActive)")
})
}
}
In my extension, A PushProvider Target. I have a bundle id com.my_team_name.my_app_name.PushProvider
and in the app group name group.com.my_team_name.my_app_name
In the Info.plist of my extension, I've added the required configuration:
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.app-push</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PushProvider</string>
</dict>
the .entitlement file have the same contents as the application.
and I have created the extension class "PushProvider.swift" as follow:
class PushProvider: NEAppPushProvider {
...
}
When I run the application, I got this printed out
error? nil
is active: false
I think it might be something with providerBundleIdentifier in Push Manager. Have anyone know what I've missing?
As per my requirement i should not make Allow Arbitrary Loads = true. So i set to false.
And i am allowing the trust certificate on my URLsession delegate.
My url : https://sample-app.10.names.io
code :
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
//Trust the certificate even if not valid
let urlCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(.useCredential, urlCredential)
}
My error :
Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?,
If I make Allow Arbitrary Loads = true, then only its working. But as per my requirement i should not change to true. Any suggestion would be helpful.
Thanks
Update:
I tried this below too :
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>https://sample-app.10.names.io</key>
<dict>
<!--Include to allow subdomains-->
<key>NSIncludesSubdomains</key>
<true/>
<!--Include to allow HTTP requests-->
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<!--Include to specify minimum TLS version-->
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
</dict>
You can use the terminal command
nscurl --ats-diagnostics --verbose https://sample-app.10.names.io
to test your server for ATS compliance.
Doing so reveals that your server only passes when perfect forward secrecy is disabled. It would seem that your server does not support ECDHE ciphers.
You can configure ATS to ignore the perfect forward secrecy requirement by specifying NSExceptionRequiresForwardSecrecy in your ATS configuration exception domains, but really you should patch your server to use newer TLS code. Otherwise it is vulnerable to replay MITM attacks.
If you are actively using Alamofire in your project, I suggest you use the built-in Session that can be configured to use your server trust certificate without much hassle. Here is some code on how it is setup in one of my projects.
class SessionManagerProvider {
// MARK: - State
let hosts: [String]
let disableEvaluation: Bool
// MARK: - Init
init(urls: [URL], disableEvaluation: Bool = false) {
hosts = urls.compactMap { $0.host }
self.disableEvaluation = disableEvaluation
}
// MARK: - Factory
func make() -> Session {
// Configure network client with SSL pinning.
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = Constants.Backend.timeoutIntervalForRequest
configuration.timeoutIntervalForResource = Constants.Backend.timeoutIntervalForResource
// Allow more connections than API requests to avoid an issue, when URLSession starts to
// time-out requests when there are too many connections.
configuration.httpMaximumConnectionsPerHost = Constants.maxConcurrentApiCalls * 2
let policies = serverTrustPolicies(disableEvaluation: disableEvaluation)
let securityManager = ServerTrustManager(evaluators: policies)
let sessionManager = Session(configuration: configuration, serverTrustManager: securityManager)
return sessionManager
}
private func serverTrustPolicies(disableEvaluation: Bool) -> [String: ServerTrustEvaluating] {
var policies: [String: ServerTrustEvaluating] = [:]
for host in hosts {
if disableEvaluation {
policies[host] = DisabledTrustEvaluator()
} else {
policies[host] = PublicKeysTrustEvaluator(
performDefaultValidation: true,
validateHost: true
)
}
}
return policies
}
}
I'm having this problem with reading from a txt file from a website without a certificate even though I have allowed it in Info.plist.
My Swift code looks like this:
let url = URL(string: "http://newskit.matsworld.io/domaci/text.txt")!
let task = URLSession.shared.downloadTask(with: url) { localURL, urlResponse, error in
if let localURL = localURL {
if let string = try? String(contentsOf: localURL) {
print(string)
}
}
}
task.resume()
And the Info.plist looks like this:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>http://newskit.matsworld.io</key>
<dict>
<key>ExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>IncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
I'm stuck with this and can't figure it out. Thanks for help!
The domain key is supposed to be just the domain without the scheme (http) and without subdomains if IncludesSubdomains is specified
<key>matsworld.io</key>
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"…
Hi I want to make Https Request in Swift. Currently im accessing local server through ip address. Local Server has one SSL Certificate by accessing the certificate want to make request to server currently im doing like this.
Alamofire.request(.GET, https://ipaddress//, parameters: [param], headers: headers)
.responseJSON { response in
print(response.request) // original URL request
print(response.response) // URL response
print(response.data) // server data
print(response.result) // result of response serialization
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}
I have used the above code for making request and in plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>192.168.2.223:1021(my local ip)</key>
<dict>
Include to allow subdomains-->
<key>NSIncludesSubdomains</key>
<true/>
<!--Include to allow insecure HTTP requests-->
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<!--Include to specify minimum TLS version-->
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
</dict>
in plist i have given like this but im still getting error like
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
IMPORTANT: DO NOT USE THIS IN PRODUCTION. Basically, with Alamofire, you can bypass the authentication for app development and testing purpose. Make sure you remove it before the app is on the App Store or in production:-
func bypassURLAuthentication() {
let manager = Alamofire.Manager.sharedInstance
manager.delegate.sessionDidReceiveChallenge = { session, challenge in
var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling
var credential: NSURLCredential?
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
disposition = NSURLSessionAuthChallengeDisposition.UseCredential
credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
} else {
if challenge.previousFailureCount > 0 {
disposition = .CancelAuthenticationChallenge
} else {
credential = manager.session.configuration.URLCredentialStorage?.defaultCredentialForProtectionSpace(challenge.protectionSpace)
if credential != nil {
disposition = .UseCredential
}
}
}
return (disposition, credential)
}
}
Thank You!
Let me know if this helps. :)