CredStore Perform Query error - ios

I am running into an issue while doing API calls to my apps backend, every connection now prompts with
CredStore - performQuery - Error copying matching creds. Error=-25300, query={
atyp = http;
class = inet;
"m_Limit" = "m_LimitAll";
ptcl = http;
"r_Attributes" = 1;
srvr = "myappsurl.com";
sync = syna;
}
I am a little lost as I am not sure what is causing this, or what CredStore even does.
What purpose does CredStore serve in iOS?

This error occurs when trying to retrieve an URLCredential from URLCredentialStorage for an unknown URLProtectionSpace.
e.g.
let protectionSpace = URLProtectionSpace.init(host: host,
port: port,
protocol: "http",
realm: nil,
authenticationMethod: nil)
var credential: URLCredential? = URLCredentialStorage.shared.defaultCredential(for: protectionSpace)
produces
CredStore - performQuery - Error copying matching creds. Error=-25300, query={
class = inet;
"m_Limit" = "m_LimitAll";
ptcl = http;
"r_Attributes" = 1;
srvr = host;
sync = syna;
}
Give it a credential for the protection space:
let userCredential = URLCredential(user: user,
password: password,
persistence: .permanent)
URLCredentialStorage.shared.setDefaultCredential(userCredential, for: protectionSpace)
and the error goes away next time you try to retrieve the credential.
I am a little lost as I am not sure what is causing this, or what
CredStore even does. What purpose does CredStore serve in iOS?
Credential storage on iOS allows users to securely store certificate-based or password-based credentials on the device either temporarily or permanently to the keychain.
I suspect that you have some sort of authentication on your backend server and that server is requesting an authentication challenge to your app (for which no credential exists).
It can probably be safely ignored as returning nil from the URLCredentialStorage is a valid response

I'm not sure why do we get this error when perform requests with Alamofire, but if you do API requests with some token in HTTP headers, you maybe don't need credentials store at all. So we can disable it for our request:
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = ourHeaders
// disable default credential store
configuration.urlCredentialStorage = nil
let manager = Alamofire.SessionManager(configuration: configuration)
...
No errors after such change.

This same issue happens to me and I found that if your API URL does not contain a "/" at the end of URL then iOS does not send "Authorization" value to the server. Due to which you will see a message like posted in question in the console.
So Simply add "/" at the end of URL
https://example.com/api/devices/

In my case, I was not initialising Stripe SDK with API key.
STPPaymentConfiguration.shared().publishableKey = publishableKey
In case of any Stripe operation, we can print the error log, its easy to understand.
print(error.debugDescription)

This is transport error, let's add transport permission like this in plist file:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Be careful as that enables connection to any server from your app. Read more on App Transport Security before proceeding. See comment by #kezi

I edited the String that contains the URL to fix this issue:
var myUrl = "http://myurl.com"
myUrl = myUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)!
let url = URL(string: myUrl)

If you get this error, when using AVPlayer, just call .play() on main thread

The cause of me getting this error was due to me accidentally using two spaces between the "Bearer" and access token in my Authorization header.
Incorrect:
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
Correct:
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
Simple mistake, but it took a while to find it.

OK, I had this error, and fought with it for a long time (years) when interacting with my Ruby on Rails app.
I had default credentials set up as described in the accepted answer, but still got the error, and have been relying on a didReceiveChallenge response to supply the credentials - fortunately that worked as a work around.
But! I've just found the solution!
I was working on a hunch that that the protectedSpace fields did not match the Authorization challenge from the Ruby on Rails server - and I looked into the realm field, which seemed to be the only one that was being left undefined.
I started by printing out the server response headers, and although I was able to examine these, they did not include the WWW-Authorization field that would have included the realm field.
I thought this was maybe because my Rails app wasn't specifying the realm, so I started looking at the Rails side of things.
I found I could specify the realm in the call to,
authenticate_or_request_with_http_basic
...which I am using for HTTP Basic authentication.
I wasn't specifying a realm already, so added one,
authenticate_or_request_with_http_basic("My Rails App")
I then added the corresponding string to the protectionSpace,
NSURLProtectionSpace *protectionSpace =
[[NSURLProtectionSpace alloc] initWithHost:#"myrailsapp.com"
port:443
protocol:NSURLProtectionSpaceHTTPS
realm:#"My Rails App"
authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
Voila! That worked, and I no longer get the,
CredStore - performQuery - Error copying matching creds. Error=-25300
Even after specifying the realm in the Rails app, I still don't see it passed in the HTTP header, I don't know why, but at least it works.

The error may also be caused by a Content Security Policy (CSP) that may be too restrictive. In our case, we needed a CSP that is more or less completely open and allows everything. Keep in mind that opening the CSP can be a great security issue (depending on what exactly you're doing in the app).

I got this issue when I tried to open a http-page inside a web-view. But this page contained an popup which was opened first.
When backend team removed this popup everything became OK.

My issue was base64 encoding of an image that was being sent with a rest call
I had previously used
let strBase64 = imageData.base64EncodedString(options: .lineLength64Characters)
But 50% of the time I would get the error above.
I used the following instead which solved my problem...
let strBase64 = imageData.base64EncodedString()

Had the same issue with Twitter sign in. Turned out I used the the wrong API key.

I remove .cURLDescription
on
AF.request(url)
and that log is gone

When working with the Stripe IOS SDK, I found that I should have added the publishable key from stripe.
This is set in AppDelegate, as found in https://stripe.com/docs/development/quickstart, step 2.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
StripeAPI.defaultPublishableKey = "pk_test_....."
return true
}

let credentialData = "\(user):\(password)".data(using: String.Encoding.utf8)!
let base64Credentials = credentialData.base64EncodedString(options: [])
let headers = ["Authorization": "Basic \(base64Credentials)"]
Alamofire.request(url, method: .get, parameters: params,encoding: URLEncoding.default,headers: headers)
.responseJSON{
response in
guard let value = response.result.value else {return}
print(value)
}

Related

Alamofire 5 AF.upload() fails to send an image to a server with an invalid certificate

I am sending an image, using the following call:
AF.upload(...)
to a "Development server" that has an invalid certificate, and consequently, I get the following error:
NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be ...
I have already solved this problem for "AF.request(...)" calls; that is, I can perform "AF.request(...)" calls to a server with an invalid certificate using the following code:
#if DEBUGDEV
//To enable connections with wrong certificate
private let session: Session = {
let evaluators: [String: ServerTrustEvaluating] = [
"api.my.server.dev.api.group.com": DisabledEvaluator()
]
let manager = ServerTrustManager(allHostsMustBeEvaluated: false,
evaluators: evaluators)
let configuration = URLSessionConfiguration.af.default
return Session(configuration: configuration,
serverTrustManager: manager,
eventMonitors: [ AlamofireLogger() ])
}()
#else
private let session: Session = Session.default
#endif
And then calling:
let request = self.session.request(urlConvertible)
In addition I have modified the Info.plist file to contain "NSAppTransportSecurity -> NSExceptionDomains -> "api.my.server.dev.api.group.com" -> "NSThirdPartyExceptionAllowsInsecureHTTPLoads = false" and other entries that can be found in other StackOverFlow" posts.
However; the previous code works for "AF.request(...)", but not for "AF.upload(...)", so I would need a way to make "AF.upload(..)" work properly for servers with and invalid certificate.
Is it a way to "insert" the "Session" inside the "AF.upload(...)" call ? or
Is another way so that "AF.upload(...)" can connect to servers with an invalid certificate?
After reviewing and updating my question, I have realised that the answer is easy; that is, in the same way I do not call
AF.request(...)
but
self.session.request(...)
I can do the same with upload; that is, to call:
self.session.upload(...)
instead of calling:
AF.upload(...)

SFAuthenticationSession completion handler not called

I am trying to implement an approach to exchange cookies from Safari and App. I am using SFAuthenticationSession since the cookies sharing was disabled. I read through the topic and it seems this is the best solution to achieve this. There are not too many blogs or repos to use as an example.
I have implemented the changes redirect in the server side as following.
First I store the cookie as https://example.com/?cookie=12345. Then from the app I start an Authentication Session pointing to https://example.com/getcookie which redirects to customapp://dummy/cookies?cookie=12345
Once stated this. The swift implementation is the following (thanks to this):
let callbackUrl = "customapp://dummy/cookies"
let authURL = "https://example.com/getcookie"
self.authSession = SFAuthenticationSession(url: URL(string: authURL)!, callbackURLScheme: callbackUrl, completionHandler: { (callBack:URL?, error:Error? ) in
guard error == nil, let successURL = callBack else {
return
}
let cookie = self.parseQuery(url: (successURL.absoluteString), param: "cookie")
print(cookie!)
})
self.authSession?.start()
You may notice I am not interested on signing in but getting a cookie stored previously.
Can anyone please advice? My problem is that although the site is redirecting, the completion handler is not called, so I can't parse the callback url.
UPDATE
I found out I was missing the protocol in the Info.plist. After adding the custom protocol to it, the handler was called. Nevertheless, the handler was only called the second time I engaged the Authentication Session.
Any clue?

Sharing to specific Facebook page using Swift SDK GraphSharer

In my iOS app I'm trying to use the Facebook Swift SDK so that a user can post a URL to a specific Facebook page that they manage.
I've managed to get the following code working - but it only shares the post to my home timeline:
let content = LinkShareContent(url: URL(string: urlString)!)
let sharer = GraphSharer(content: content)
sharer.failsOnInvalidData = true
sharer.completion = {
(result) in
print(result)
}
do {
try sharer.share()
}
catch (let error) {
print(error.localizedDescription)
}
I believe that I need to use the graphNode property to specify the destination page and that the correct format is as follows:
sharer.graphNode = "/\(pageId)"
However, when I set this property before posting, I am getting an error:
failed(Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL"
I've tried a dozen different formats to specify the node but they all return the same error and I can't find any working examples.
I managed to resolved this issue. It turns out that the graphNode path above is correct but I was looking in the wrong place due to the error message I was getting. Instead I just needed to specify the accessToken parameter.
graphSharer.accesstoken = myPageAccessToken
Note: The access token is not the user's current access token, but an access token you can retrieve when calling the /me/accounts API.
var graphPath = "/me/accounts"
I also then had an issue posting to the page because I had only requested the publish_actions permission. From the documentation, this appears to be suitable but it only worked where I requested further permissions as follows:
loginManager.logIn(withPublishPermissions: ["publish_actions",
"manage_pages", "publish_pages"], ...
Hopefully this will be of use to someone in future. :-)

Alamofire ServerTrustPolicy Certificate Pinning Not Blocking Charles Proxy Swift 3

I've searched far and wide and have not been able to find an answer for my question. To make our app more secure, we've been told to use "certificate pinning". We already make use of the Alamofire library for all our API calls, so it seems natural to use the ServerTrustPolicyManager included as a means to implement certificate pinning. I've included the proper certificates in my app bundle, and here is the code I use to configure my SessionManager for Alamofire:
let url = "https://www.mycompany.com"
var manager: SessionManager? {
let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true
)
let serverTrustPolicies: [String: ServerTrustPolicy] = [
url: serverTrustPolicy
]
let config = URLSessionConfiguration.default
return SessionManager(configuration: config, serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
}
Now when I want to make an API request, I have this method:
func request(routerRequest request: URLRequestConvertible) -> DataRequest {
assert(url != "", "A base URL string must be set in order to make request")
print("URL: \(url) : \(request)")
return (manager ?? SessionManager.default).request(request)
}
Now the problem that I'm having is that this still works when I use something like a Charles Proxy ... all the requests are still going through. Shouldn't certificate pinning prevent something like a Charles Proxy from being able to work because the certificates won't match?
I've tested other apps that properly use certificate pinning, and they will block any kind of proxy (Charles or other) from making a connection. If I try running an app like Uber or my Wells Fargo banking app while I have Charles enabled, every request will get rejected and I'll see an error that says something like "Couldn't complete request, ssl certificate is invalid" (that's not verbatim).
I feel like there is a step that I'm missing after configuring my SessionManager. Most of the documentation that I've read and help I've come across seem to imply that after configuring the manager to enable certificate pinning, it should reject any request with an invalid certificate. Can anyone help me? What am I missing?
I appreciate any and all help. Thanks in advance!
I'm going to answer my own question, only because I want to possibly help anyone else with this same problem in the future. When I was configuring the serverTrustPolicies above, you create a dictionary of String : ServerTrustPolicy, my error lied in the String for the server name.
let serverTrustPolicies: [String: ServerTrustPolicy] = [
url: serverTrustPolicy
]
My url property was the baseURL we use for our API, which was https://www.mycompany.com/api - this was causing all my issues. Once I adjusted that to just be the domain www.mycompany.com, the pinning worked just as expected! Now when I run Charles Proxy with pinning enabled, I get all my requests rejected, and Charles puts out an error that says "No request was made, possibly the SSL Certificate was rejected."
Instead of having to do any serious String manipulation, I added this extension to use in the future - in case you have multiple baseURL's that you need to extract the domain out of:
extension String {
public func getDomain() -> String? {
guard let url = URL(string: self) else { return nil }
return url.host
}
}
Now you can do something like this:
let serverTrustPolicies: [String: ServerTrustPolicy] = [
url.getDomain() ?? url : serverTrustPolicy
]
Hope this helps someone else in the future! Goodluck

How can I retrieve a file using WKWebView?

There is a file (CSV) that I want to download. It is behind a login screen on a website. I wanted to show a WKWebView to allow the user to log in and then have the app download the file after they had logged in.
I've tried downloading the file outside of WKWebView after the user has logged in to the website, but the session data seems to be sandboxed because it downloads an html document with the login form instead of the desired file.
I've also tried adding a WKUserScript to the WKUserContentController object, but the script doesn't get run when a non-HTML file is loaded.
Is there a way for me to access this file while allowing users to log in via the WKWebView?
Right now, WKWebView instances will ignore any of the default networking storages (NSURLCache, NSHTTPCookieStorage, NSCredentialStorage) and also the standard networking classes you can use to customize the network requests (NSURLProtocol, etc.).
So the cookies of the WKWebView instance are not stored in the standard Cookie storage of your App, and so NSURLSession/NSURLConnection which only uses the standard Cookie storage has no access to the cookies of WKWebView (and exactly this is probably the problem you have: the „login status“ is most likely stored in a cookie, but NSURLSession/NSURLConnection won’t see the cookie).
The same is the case for the cache, for the credentials etc. WKWebView has its own private storages and therefore does not play well with the standard Cocoa networking classes.
You also can’t customize the requests (add your own custom HTTP headers, modify existing headers, etc), use your own custom URL schemes etc, because also NSURLProtocol is not supported by WKWebView.
So right now WKWebView is pretty useless for many Apps, because it does not participate with the standard networking APIs of Cocoa.
I still hope that Apple will change this until iOS 8 gets released, because otherwise WKWebView will be useless for many Apps, and we are probably stick with UIWebView a little bit longer.
So send bug reports to Apple, so Apple gets to know that these issues are serious and needs to be fixed.
Have you checked the response cookies coming back from the request. You could use a delegate method like this.
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
for (NSHTTPCookie *cookie in cookies) {
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
}
decisionHandler(WKNavigationResponsePolicyAllow);
}
I've accomplished something similar to what you're trying to do:
use the web view to login as you're already doing
set a navigationDelegate on your webview
implement -webView:decidePolicyForNavigationResponse:decisionHandler in your delegate
when that delegate method is called (after the user logs in), you can inspect navigationResponse.response (cast it to NSHTTPURLResponse*) and look for a Set-Cookie header that contains the session info you'll need for authenticated sessions.
you can then download the CSV by manually specifying the Cookie header in your request with the cookies specified in the response.
Note that the delegate methods are only called for "main frame" requests. Which means that AJAX requests or inner frames will not trigger it. The whole page must refresh for this to work.
If you need to trigger behavior for AJAX requests, iframes etc you'll need to inject some javascript.
You can obtain the cookie via Javascript:
You could then use the obtained cookie to download the file manually:
webView.evaluateJavaScript("(function() { return document.cookie })()", completionHandler: { (response, error) -> Void in
let cookie = response as! String
let request = NSMutableURLRequest(URL: docURL)
request.setValue(cookie, forHTTPHeaderField: "Cookie")
NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, response, error) in
// Your CSV file will be in the response object
}).resume()
})
I do not seem to have the session cookie when I try a request from userContentController didReceiveScriptMessage.
However, when called from decidePolicyForNavigationAction, the following code detects that I'm logged in.
let urlPath: String = "<api endpoint at url at which you are logged in>"
let url = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
let string1 = NSString(data: data, encoding: NSUTF8StringEncoding)
println(string1)
println(data)
if(error != nil) {
println("Error sending token to server")
// Print any error to the console
println(error.localizedDescription)
}
var err: NSError?
})
task.resume()

Resources