I'm trying to connect my app to a development server that has a server certificate with the wrong hostname, but it is signed by a trusted anchor certificate. When I evaluate the server trust object, it fails as expected. I am trying to change the trust evaluation policy for the server's hostname, but it doesn't seem to help.
// In -connection:willSendRequestForAuthenticationChallenge:
// NSURLAuthenticationMethodServerTrust
SecTrustRef trust = [challenge.protectionSpace serverTrust];
SecTrustResultType trustResult;
SecTrustEvaluate(trust, &trustResult);
// trustResult == kSecTrustResultRecoverableTrustFailure
SecPolicyRef policyOverride = SecPolicyCreateSSL(true, (CFStringRef)#"devhost");
CFArrayRef policies = (CFArrayRef)#[policyOverride];
SecTrustSetPolicies(trust, policies);
CFRelease(policyOverride);
SecTrustEvaluate(trust, &trustResult);
// trustResult == kSecTrustResultRecoverableTrustFailure
As far as I understand, the second time I call SecTrustEvaluate(), it should be returning kSecTrustResultUnspecified. I have connected to the dev server using "devhost" when I initialized the NSURLConnection, and challenge.protectionSpace.host == #"devhost" as well. What am I doing wrong here?
I was using the wrong host name in the call to SecPolicyCreateSSL.
When using SecPolicyCreateSSL to override hostname validation, the hostname argument should be one that matches the certificate you're validating. Then the validation pretends that the host you're communicating with has the newly specified hostname.
In my case, the server has a certificate for "*.mydomain.tld", so I call
SecPolicyCreateSSL(true, (CFStringRef)#"devhost.mydomain.tld");
and then the certificate chain can be successfully validated.
Related
By default, example.com resolve to 123.123.123.123,
But If I want it to be resolved to 100.100.100.100.
For http, I can simply change the url to http://100.100.100.100 with a header "Host: example.com".
But it's not working for HTTPS.(Error: SSL certificate problem: Invalid certificate chain).
My question is not why, and I do not want to skip the certificate validation.
How can I get the same effect in Objective-C like curl's
--resolve option:
--resolve <host:port:address>
Provide a custom address for a specific host and port pair. Using this, you can make the curl requests(s)
use a specified address and prevent the otherwise normally resolved address to be used. Consider it a sort
of /etc/hosts alternative provided on the command line. The port number should be the number used for the
specific protocol the host will be used for. It means you need several entries if you want to provide
address for the same host but different ports.
In other words, How to make custom DNS query in HTTPS requests in Objective-C?
When you are using https, the address that you use in your request, and the address given to you by the certificate returned by the server, must agree.
If you send a request to https://100.100.100.100 then the server must return a certificate for 100.100.100.100. Even if you connected successfully to https:// www.xyz.com, and www.xyz.com resolved to 100.100.100.100, connecting to https://100.100.100.100 isn't going to work, cannot work, and absolutely must not work, because the server will return a certificate for www.xyz.com and not for 100.100.100.100.
I see following options:
Use your own DNS server with corresponding configuration of host/ip entry
If you want to stick with Objective C, there is a guideline frome apple Overriding SSL Chain Validation Correctly
Use libcurl which supports the feature you mentioned: http://curl.haxx.se/libcurl/c/resolve.html
example
#include <stdio.h>
#include <curl/curl.h>
int main(void)
{
CURL *curl;
CURLcode res = CURLE_OK;
struct curl_slist *host = NULL;
/* Each single name resolve string should be written using the format
HOST:PORT:ADDRESS where HOST is the name libcurl will try to resolve,
PORT is the port number of the service where libcurl wants to connect to
the HOST and ADDRESS is the numerical IP address
*/
host = curl_slist_append(NULL, "example.com:80:127.0.0.1");
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_RESOLVE, host);
curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
res = curl_easy_perform(curl);
/* always cleanup */
curl_easy_cleanup(curl);
}
curl_slist_free_all(host);
return (int)res;
}
Update:
Since author don't want to skip certificate validation this is not an option now:
You can try to ignore ssl certificate in AFNetworking in your case
I want to allow invalid SSL certificates with AFNetworking
I'm pulling my hair out on this one and would greatly appreciate some assistance. Unfortunately my experience with SSL is quite limited so I don't know where I'm going wrong.
I need to develop an iOS app that acts as an server with mutual SSL authentication with the client. I'm using the GCDAsyncSocket library and I've managed to get the server part working fine (without SSL), however, I'm stuck getting the SSL working. I've had a look at this post, however, it doesn't explain the steps I need to perform to achieve what I want.
In terms of the certificate setup, I have the following p12 certificates:
Self-signed Server Root CA certificate
Server Certificate issued by the CA above (ServerCert).
Self-signed Client Root CA certificate
Client Certificate issued by the CA above (ClientCert).
I then installed both Root CA certificates on the iOS device and bundled the serverCert certificate in my app. When a new socket is accepted, I extract the certificate info from the p12 file (according to the apple certificate programming guide), and setup the SSL settings as follows prior to starting the SSL session:
-(NSDictionary*) loadSSLSettings
{
// Configure SSL/TLS settings
NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:4];
// Configure this connection as the server
[settings setObject:[NSNumber numberWithBool:YES]
forKey:(NSString *)kCFStreamSSLIsServer];
CFArrayRef certsArray = [self loadCertificates];
[settings setObject:(id)CFBridgingRelease(certsArray) forKey:(NSString *)kCFStreamSSLCertificates];
[settings setObject:NSStreamSocketSecurityLevelNegotiatedSSL forKey:(NSString *)kCFStreamSSLLevel];
[settings setObject:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamSSLAllowsAnyRoot];
return settings;
}
-(CFArrayRef) loadCertificates
{
// Load Certificate
NSString *path = [[NSBundle mainBundle] pathForResource:#"ServerCert" ofType:#"p12"];
NSData *p12data = [NSData dataWithContentsOfFile:path];
CFDataRef inP12data = (__bridge CFDataRef)p12data;
SecIdentityRef myIdentity;
SecTrustRef myTrust;
extractIdentityAndTrust(inP12data, &myIdentity, &myTrust);
SecCertificateRef myCertificate;
SecIdentityCopyCertificate(myIdentity, &myCertificate);
const void *certs[] = { myCertificate };
CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL);
return certsArray;
}
This code seems load the certificate fine, but then when I make a client connection (this is without even trying mutual SSL) using openSSL, such as:
openssl s_client -connect 192.168.2.8:1700 -state -debug
I get the following output:
> SSL_connect:before/connect initialization
> SSL_connect:SSLv2/v3 write client hello A
> SSL_connect:error in SSLv2/v3 read server hello A
And the iOS log produces this from the GCDAsyncSocket library:
Error in CFStreamSetProperty, code: 8
All I can tell is that code 8 is 'Other'.
I'm at a loss at to what is going wrong...perhaps I'm doing something fundamentally wrong, and if so I'd greatly appreciate somebody pointing it out :)
Also, once I get past this step, how do I go about validating the client certificate when it sends one through?
If I've left out any vital information, please let me know and I'll happily add it.
Thanks!
I would like to encrypt and secure a network connection agains MITM attacks for an iOS application. Since the application will only connect to one server, there is no need to have the certificate signed by a CA like VeriSign. I want to self-sign the certificate and distribute it with the application.
I tried this but end up with kSecTrustResultRecoverableTrustFailure and can not figure out where I went wrong. Can someone look over it and identify the problem or point me into a direction on how to debug this? Is it a problem because I use/test on localhost?
I think it is a problem in the creation of the certificate or setup of the server but I don't know what it is. I tested it with openssl s_client and it seems to work but iOS does not accept it (see below). I could accept kSecTrustResultRecoverableTrustFailure as success but would rather avoid it.
Creation of certificate
My openssl.cnf. The last line specifies subjectAltName and should be the only important one.
[ req ]
default_bits = 2048 # Size of keys
default_keyfile = key.pem # name of generated keys
default_md = sha256 # message digest algorithm
string_mask = nombstr # permitted characters
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
# Variable name Prompt string
# #---------------------- ----------------------------------
0.organizationName = Organization Name (company)
organizationalUnitName = Organizational Unit Name (department, division)
emailAddress = Email Address
emailAddress_max = 40
localityName = Locality Name (city, district)
stateOrProvinceName = State or Province Name (full name)
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
commonName = Common Name (hostname, IP, or your name)
commonName_max = 64
# Default values for the above, for consistency and less typing.
# Variable name Value
#------------------------------ ------------------------------
0.organizationName_default = The Sample Company
localityName_default = Metropolis
stateOrProvinceName_default = New York
countryName_default = US
[ server ]
basicConstraints = critical,CA:FALSE
keyUsage = digitalSignature, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
nsCertType = server
subjectAltName = IP:127.0.0.1,DNS:localhost
This is how I create the certificate. I use sha256 since md5 seems not supported. Afterwards I transform the certificate to DER format which iOS needs.
macbook:~/Documents/app/https-test/cert$ openssl req -x509 -sha256 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 356 -nodes -config openssl.cnf
Generating a 2048 bit RSA private key
..+++
............................................................+++
writing new private key to 'key.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:DE
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:com
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
macbook:~/Documents/app/https-test/cert$ ls
cert.der cert.pem key.pem openssl.cnf
macbook:~/Documents/app/https-test/cert$ openssl x509 -in cert.pem -outform der -out cert.der
Server
The server is a node.js server that accepts https requests.
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('../cert/key.pem'),
cert: fs.readFileSync('../cert/cert.pem')
};
https.createServer(options, function (req, res) {
res.writeHead(200);
res.end("test return\n");
}).listen(8443);
I tested this server with the following output:
macbook:~/Documents/app/https-test/server$ openssl s_client -showcerts -host localhost -port 8443 -CAfile ../cert/cert.pem
CONNECTED(00000003)
depth=0 /O=The Sample Company/L=Metropolis/ST=New York/C=US
verify return:1
---
Certificate chain
0 s:/O=The Sample Company/L=Metropolis/ST=New York/C=US
i:/O=The Sample Company/L=Metropolis/ST=New York/C=US
-----BEGIN CERTIFICATE-----
MIIDIDCCAggCCQClnXQ2tGOF1jANBgkqhkiG9w0BAQsFADBSMRswGQYDVQQKExJU
aGUgU2FtcGxlIENvbXBhbnkxEzARBgNVBAcTCk1ldHJvcG9saXMxETAPBgNVBAgT
CE5ldyBZb3JrMQswCQYDVQQGEwJVUzAeFw0xNDAyMjQwMDEwMTJaFw0xNTAyMTUw
MDEwMTJaMFIxGzAZBgNVBAoTElRoZSBTYW1wbGUgQ29tcGFueTETMBEGA1UEBxMK
TWV0cm9wb2xpczERMA8GA1UECBMITmV3IFlvcmsxCzAJBgNVBAYTAlVTMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzudBEZmW78S9EnxQzObf778qIBRf
/pUPVKSC31/8iVLM3w71GtHxI39Gt+WwAhMRRKmO+EhsFWDmQZfg3GNsws4Uj/uO
8I6Xp9rF7IWBIAZ37X2nUPD/qEU4+SFmiNi8POaXPt+5mQQLYFfun5YzpZPoiPpi
wuIkcgY8mOJlNv7Hr4AnyMtMMnscZis+ELVky5Q/mDsiamHzPPGjjKYKDMfwYj8S
yAz0GLKrcHBBm4Re++mefJU0sdapAYEliAJdTs0aBA5lxcRBzkKlFwxgsQrhtwL9
xBY+RC/PbnVWRF/YVrd7o6JvXmWOPFDlbL99v9tGGjoUyFDeLoIMaqaGmwIDAQAB
MA0GCSqGSIb3DQEBCwUAA4IBAQAdnvu4W6GoWkAALpvpEgXBMKq2sApLHib+i8Be
+LrAS/zA1GxlMqswUBUvtGuQq88oGWC/eU3n3PvRE2tuIARg4ZSGo2/KdYfvOFYy
O7hnwdlAYirdj3XKUnomj0sVgeAjJV4xSha7aOzs9mNyLquJvewBEAvQdJnPRYfS
LwSUq5kbbiHyFWHmJnTUfLpfKj0w+LNO4Jrb0GdFs7ZWq3R0Mscig668Htue4xST
jWEh0f/ZcWLK+UVvTvpMb9DTM8oOV94EHt+slaIMEzD2hWjtLcwGfUzX5qYU450v
Kt1b40tBHRHi8ytstg4qdLlwf0NpXejcLQiW1CgNZoEIBtP+
-----END CERTIFICATE-----
---
Server certificate
subject=/O=The Sample Company/L=Metropolis/ST=New York/C=US
issuer=/O=The Sample Company/L=Metropolis/ST=New York/C=US
---
No client certificate CA names sent
---
SSL handshake has read 983 bytes and written 468 bytes
---
New, TLSv1/SSLv3, Cipher is AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1
Cipher : AES256-SHA
Session-ID: C8901BBE04CB24444E0DDEA60EB7A72A64822E652973AD1D16E27D1E1F29F828
Session-ID-ctx:
Master-Key: D143A0F58C848B0E1BCA7BDF22EEBC326F811961CC10FF3A653715A8D8F96F5825AFC6D200F334D2E1581BFECA940111
Key-Arg : None
Start Time: 1393256956
Timeout : 300 (sec)
Verify return code: 0 (ok)
---
^C
iOS app
The app uses this NSURLConnectionDelegate https://gist.github.com/dhoerl/2051599
The iOS code you linked to expects the server's certificate to be in the device's trusted root certificate store, or at least signed by a trusted root certificate authority. The error you are getting suggests that this is not the case.
That error means the certificate is not trusted. By definition, a self-signed certificate is not trusted because it isn't signed by a trusted root certificate authority (so there's no way to verify that the signer of the certificate is who they say they are).
If you just want the benefits of SSL encryption without the protection from MITM attacks, you can bypass the server check by doing something like the following in the NSURLConnection delegate's didReceiveAuthenticationChallenge method:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
Note that this will not prevent MITM attacks, since you are now allowing a connection to any SSL host, but if you truly want that kind of protection you shouldn't use a self-signed certificate. If you just want the encryption offered by SSL, a self-signed certificate is fine.
That said, you can do server authentication if you are bundling the server certificate in with your application - this is known as certificate pinning. You would need to add code in the didReceiveAuthenticationChallenge method above to compare the server's certificate with the one that's embedded in your application and have it trust ONLY that specific certificate and no other. This of course means that if the certificate on your server ever expires or changes, your clients will no longer be able to connect (until you rebuild and redistribute your application with the new certificate). It also means that if your server's private key is ever stolen or compromised, you won't be able to revoke it and issue a new one, and the Bad Guys will be able to impersonate your server to any clients that try to connect using the compromised key. Using a certificate issued by a trusted root CA avoids both problems, and is still the recommended way to go if you truly need server authentication. That way you can revoke the certificate if you ever need to, issue a new one, and everything will still work.
#1703536 's solution almost did it, I only had to implement another URLConnection's delegate message :
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
Concerns about security doing that are well explained in the other answer ;-)
I know that if I use following nsurlconnectiondelegate it will be fixed
– connection:willSendRequestForAuthenticationChallenge: –
connection:canAuthenticateAgainstProtectionSpace
But I am trying to use
sendAsynchronousRequest:queue:completionHandler:
So you don't get the callback. I looked into apple docs it say following
If authentication is required in order to download the request, the required credentials must be specified as part of the URL. If authentication fails, or credentials are missing, the connection will attempt to continue without credentials.
I could not figure out how to do that. When I looked up all I got is this private call
+(void)setAllowsAnyHTTPSCertificate:(BOOL)inAllow forHost:(NSString *)inHost;
Any idea how to do this?
Following is the error I get
The certificate for this server is invalid. You might be connecting to
a server that is pretending to be “example.com=0x8b34da0
{NSErrorFailingURLStringKey=https://example.com/test/,
NSLocalizedRecoverySuggestion=Would you like to connect to the server
anyway?, NSErrorFailingURLKey=https://example.com/test/,
NSLocalizedDescription=The certificate for this server is invalid. You
might be connecting to a server that is pretending to be “example.com”
which could put your confidential information at risk.,
NSUnderlyingError=0xa26c1c0 "The certificate for this server is
invalid. You might be connecting to a server that is pretending to be
“example.com” which could put your confidential information at risk.",
NSURLErrorFailingURLPeerTrustErrorKey=
The webserver which you are using is asking for Server Trust Authentication, you need to properly respond with the appropriate action. You need to implement connection:willSendRequestForAuthenticationChallenge: delegate method and use SecTrustRef to authenticate it.
More information can be found here:-
https://developer.apple.com/library/ios/technotes/tn2232/_index.html
This was my code to fix error:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
id<NSURLAuthenticationChallengeSender> sender = [challenge sender];
if ([[protectionSpace authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust])
{
SecTrustRef trust = [[challenge protectionSpace] serverTrust];
NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:trust];
[sender useCredential:credential forAuthenticationChallenge:challenge];
}
else
{
[sender performDefaultHandlingForAuthenticationChallenge:challenge];
}
}
Try this.
Initiate your session using custom session config as shown below:
let session = URLSession(configuration: URLSessionConfiguration.ephemeral,
delegate: self,
delegateQueue: nil)
Implement the following delegate callback method:
public func urlSession(_: URLSession, task _: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let serverTrust = challenge.protectionSpace.serverTrust else {
return completionHandler(URLSession.AuthChallengeDisposition.useCredential, nil)
}
return completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: serverTrust))
}
If you are using AFNetworking, you can use this code:
(Just as a temp client-side solution!)
AFHTTPSessionManager * apiManager = [AFHTTPSessionManager initWithBaseURL:[NSURL URLWithString:baseURL];
AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
policy.allowInvalidCertificates = YES;
apiManager.securityPolicy = policy;
you can't fix it with the way you are trying
either drop to CFNetworking to allow bad certs
use NSConnection with a delegate and an undoc'd method
use the private API you found
all not good. CFNetwork would have to be OK for apple for now but the other 2 methods aren't even appstore-safe
Better get the server fixed. Thats the easiest and CLEANEST
This issue cannot be fixed with the way you are trying with blocks. you need to set delegates and implement the authentication challenge delegates to bypass the certificate validation.
Best solution is to either create a right certificate (make sure it is not self-signed) or change the protocol to HTTP if you are fine with it.
In my case, this error occurred due to my firewall blocked the required url. it's worked fine after removing firewall restrictions
That is a certificate error. you need to change your settings so that your program/os ignores the certificate, or add the url/certificate to a trusted list.
Sorry that is authentication, certificate is authentication. I took a look, and I found this article.
Not sure if it will resolve your issue, but it basically states, that they don't cover the case of connecting to a site with how a certificate in the documentation.
http://www.cocoanetics.com/2009/11/ignoring-certificate-errors-on-nsurlrequest/
In my case, this error occurred due to my system date. It was set as an old date, and the certificate is not effective from that old date. After correct the date, it works.
So it seems that ASIHTTPRequest allows you to ignore the certificates on https:// endpoints. I'm currently using MKNetworkKit and have implemented all my calls. Unfortunately, our testing server is on https but does not have a SSL certificate.
I'm able to connect fine using a curl with the -k command. I've tried various things in MKNetworkKit to ignore the NSURLAuthenticationChallenge, but to no avail. The latest thing I tried was the following:
op.authHandler = ^(NSURLAuthenticationChallenge *challenge)
{
NSURLCredential *credential = [NSURLCredential credentialWithUser:_userName password:password persistence:NSURLCredentialPersistenceNone];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
};
This allowed me to actually get a 401 error returned (instead of being blank). Looking at the curl string, MKNetworkKit strips my username/password when the above block hits. I'm not sure if that's progress or not.
Anyone know how to simply ignore the SSL certificate?
Edit:
I had to update MKNetwork kit to get the new method ShouldContinueWithInvalidCertificate on MKNetworkOperation and my testing server got the certError fixed.
However, now I'm having a weird error happening. I'm still unable to get any return from two specific endpoints on the server. I look at the request, copy it into the command as a curl, and it immediately returns results. I'm not getting an error either from the operation.
What's happening here?
The MKNetworkOperation class has a property called shouldContinueWithInvalidCertificate which is defaulted to NO. All you have to do is set it to YES, and it will ignore the certs.
The comments:
/*!
* #abstract Boolean variable that states whether the operation should continue if the certificate is invalid.
* #property shouldContinueWithInvalidCertificate
*
* #discussion
* If you set this property to YES, the operation will continue as if the certificate was valid (if you use Server Trust Auth)
* The default value is NO. MKNetworkKit will not run an operation with a server that is not trusted.
*/