To improve my app's security and protect the user from MITM attacks I'm trying to do SSL pinning with my self-signed certificate following the content of this post.
So I'm using the following code to compare the certificate that I get from the server with the one that bundled in the app.
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
NSLog(#"Remote Certificate Data Length: %d",[remoteCertificateData length]);
NSString *cerPath = [[NSBundle mainBundle] pathForResource:#"apache" ofType:#"crt"];
NSData *localCertData = [NSData dataWithContentsOfFile:cerPath];
NSLog(#"Local Certificate Data Length: %d",[localCertData length]);
if ([remoteCertificateData isEqualToData:localCertData]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
The only things that are different between my code and the one in the blog post I linked are the name and the extension (.cer to .crt) for the resource representing my certificate and the two NSLogs I added that will come handy later to show what the problem is.
In fact when this code is executed I get this output:
2013-05-22 16:08:53.331 HTTPS Test[5379:c07] Remote Certificate Data Length: 880
2013-05-22 16:09:01.346 HTTPS Test[5379:c07] Local Certificate Data Length: 1249
Obviously the comparison between the Local and the Remote certificates fails because the length of the data is different and so it also fails the pinning.
Why does this happen and how could I solve this problem?
I had the same issue. The problem is probably because you have not converted your .crt file to the correct format. iOS & OSX are looking for your certificate to be in .der format. You need to use openssl to convert it. Here is a very helpful article on this topic. My public certificate came from an Apache server (I am assuming that yours did as well). After looking over openssl documentation I was able to figure out how to get this to work.
1) Open Terminal and change directory to the location of your .crt.
2) Execute this command:
openssl x509 -in your_cert.crt -outform der -out your_output_name.der
This will create an output file named 'your_output_file.der'. You must import this into your xCode project and reference it in the
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
method of your NSURLConnectionDelegate implementation.
I hope this helps!
Related
I'm doing a SSL pinning check for a website and I need to tap into the didReceiveAuthenticationChallenge in order to do so. However when I am debugging the application I noticed that the challenge is being called 3 times before finishing and afterwards I end up with NSURLErrorDomainCode=-999.
Small snippet of how my code looks:
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler
{
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
What I found odd is that on the third attempt the certificate on the bottom line of the snippet, is returning nil. But for the first 2 runs it is returning the same server certificate again.
Is this a normal behaviour from this method? The server only has one certificate installed that I am comparing against. I don't know if it might be relevant to add that I am using the React-Native-Webview solution for my application.
I have write the https connection.
I am self signed the certificate file.
So I generate the keystore and crt file. The keystore file is using the tomcat and direct to use the https server.
The the crt file can download from server to install in the iPhone device.
But now I want to wrapping the crt file in the my application code.
How can I load the crt file in app and encrypt data , then the server can check the key and the keystore is match.
I don't what to download crt file from server install in the iPhone, it will more the setup to upload or download data.
my https method is below:
-(void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if( [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
MYLog(#"server is trust certificate credential:%#",credential);
if(credential){
disposition = NSURLSessionAuthChallengeUseCredential;
}else{
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}else{
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
if(completionHandler){
completionHandler(disposition,credential);
}
}
Thank you very much.
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!
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
Very, very frustrating! I've been pulling my hair for hours with this. I'm using a self-signed certificate on my Linode server. The port is 8000, couldn't get it to work on 443. I don't believe this is the reason though. Here's my code, it's 99% boilerplate:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://www.myserver.com:8000/test.json"]];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
At the bottom:
#pragma mark NSURLConnectionDelegate
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
NSLog(#"protectionSpace: %#", [protectionSpace authenticationMethod]);
// We only know how to handle NTLM authentication.
if([[protectionSpace authenticationMethod] isEqualToString:NSURLAuthenticationMethodNTLM])
return YES;
// Explicitly reject ServerTrust. This is occasionally sent by IIS.
if([[protectionSpace authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust])
return NO;
return NO;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"%#", response);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"%#", data);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"didFailWithError");
NSLog([NSString stringWithFormat:#"Connection failed: %#", [error description]]);
}
OMG HELP!
UPDATE
It worked with this delegate method. I'm receiving the response, but there is a problem.
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
[[challenge sender] useCredential:[NSURLCredential
credentialWithUser:#"user"
password:#"password"
persistence:NSURLCredentialPersistencePermanent] forAuthenticationChallenge:challenge];
}
The "user" and "password" that I have provided are completely random and aren't checked by the server. How can I verify the credentials before accepting the connection on my server?
EDIT: I'm running a Node.js server
Getting the corresponding error description may help:
So, first the error domain kCFStreamErrorDomainSSL means that the error code is an SSL error code as defined in Security/SecureTransport.h:
kCFStreamErrorDomainSSL, -9813 means:
errSSLNoRootCert = -9813, /* cert chain not verified by root */
And that simply means, you have no trusted root certificate and the connection fails because of that authentication failure.
Provide a root certificate on the device for the server trust authentication and you are fine.
There are a few approaches to implement server trust authentication with self-signed certificates, the one more secure than the other.
The simplest approach requires a self-signed certificate which is stored in the bundle of the app, then retrieved and simply byte-compared. Here is an example:
Implementing server trust authentication with a self-signed certificate.
These are a must read also: Technical Note TN2232
HTTPS Server Trust Evaluation and Technical Q&A QA1360 Describing the kSecTrustResultUnspecified error.
The more preferred approach is to use a CA (Certificate Authority) which you can be yourself. That is, you create your own CA and your certificates signed with this CA.
The steps are similar:
Bundel the DER file of your CA's root certificate in your app.
Handle the server trust authentication as follows:
get the authentication challenge
retrieve the trust object from the challenge
create a certificate object from the data in your bundle
set the certificate object as an anchor to the trust object using function SecTrustSetAnchorCertificates.
evaluate the trust
not sure if this will actually fix the problem, but it may help. you should be using
– connection:willSendRequestForAuthenticationChallenge:
since the other methods are deprecated. take a look at the overview of the NSURLConnectionDelegate protocol
I have asp.net site (https) hosted on IIS.My phonegap app on Mac machine(for iOS) is in same local network.
This website act as a handler for this phonegap app's request,but I am not able to debug (i.e send a request to the machine hosting website in local network).I don't think this is External URL or whitelisting issue,but it appears to be SSL/TLS issue,it is not able to proceed because of this certificate error.
How to resolve this,Please help.
I am not familiar with objective C environment.
any Ideas on how to resolve this issue.
Any help would be greatly appreciated.
Regards,
Suraj
You have to deal with NSURLConnection delegation as your domain is local your application not trust your certificate. this code will help you to accept any certificate.
- (BOOL)connection:(NSURLConnection *)conn canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
// A delegate method called by the NSURLConnection when something happens with the
// connection security-wise.
{
NSLog(#"%#",protectionSpace.authenticationMethod);
return YES;
}
// Called if the HTTP request receives an authentication challenge.
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if([challenge previousFailureCount] == 0) {
NSURLCredential *newCredential;
newCredential=[NSURLCredential credentialWithUser:self.username password:self.password persistence:NSURLCredentialPersistenceNone];
[[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
or use this reference
How to use NSURLConnection to connect with SSL for an untrusted cert?