I've been trying to use Alamofire to pin a PEM certificate using these methods:
SSL pinning in iOS - Swift edition
Adding certificate pinning to your iOS app with Alamofire
I've also tried using AFNetworking:
NSData *certificateData = [NSData dataWithContentsOfFile:certificatePath];
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certificateData);
NSArray *arr = [[NSArray alloc] initWithObjects:(__bridge_transfer NSData *)SecCertificateCopyData(cert), nil];
[securityPolicy setPinnedCertificates:arr];
Most of the time I will get -999 and -1012 errors. When running a curl command, it works as it should. However, it does not seem to work when converting the PEM certificate to a DER format.
Related
I know Google uses Certificate Pinning, and can allow user installed Certs to override the pinning inorder for compatibility with some corporation MITM's. Can AFNetworking supports type of override?
I found this resource that details how to do what I think you are asking using AFNetworking.
http://initwithfunk.com/blog/2014/03/12/afnetworking-ssl-pinning-with-self-signed-certificates/
Here is the relevant code example:
// This usually would be a subclass of AFHTTPSessionManager
AFHTTPSessionManager *client = [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL sessionConfiguration:configuration];
client.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
client.securityPolicy.allowInvalidCertificates = YES;
The code is from AFNetworking 2.2.1, but should work for the most recent version.
I keep seeing disjointed accounts all over the web of what the .cer file is and how to generate it so that the app can properly communicate with the server via https.
To quote this source, one person says:
1) You now need .cer files in your bundle for all certificates in the chain. So, at least, two certificate files must be present (your certificate and the root CA). In our case, we needed an intermediate as well.
2) These certificates must be in a particular order. Specifically, the certificate that certifies the host that you are communicating with must be first in the list of pinned certificates/keys. It doesn't look like the order of the rest of them matter. This is because AFSecurityPolicy uses the first object in the array to validate the host name.
3) The host validation fails for wildcard certificates if you are communicating with the root domain. For example, consider a certificate pointed at *.foo.com. If you are communicating with foo.com, the host check will fail because the host component counts will not match. I understand the need for this early optimization, but it fails before it even reaches the logic that specifically handles wildcard domains.
Official documentation backs that up here.
I suppose that in order to generate this, one must cat the contents of their whole cert chain into a .cer file in a specific order. I remember seeing this posted somewhere, but can't seem to find it.
The question is, what is the undebatable method of creating a .cer file for AFNetworking?
UPDATE:
After more research, it seems to me that you simply take the .crt file and perform this on it:
openssl x509 -in www.mydomain.com.crt -out www.mydomain.com.cer -outform der
However, even after doing this and attaching the .cer to my app bundle, I get an error here:
+ (NSArray *)pinnedPublicKeys {
static NSArray *_pinnedPublicKeys = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSArray *pinnedCertificates = [self pinnedCertificates];
NSMutableArray *publicKeys = [NSMutableArray arrayWithCapacity:[pinnedCertificates count]];
for (NSData *data in pinnedCertificates) {
SecCertificateRef allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)data);
NSParameterAssert(allowedCertificate);
The error comes in at the last line. I suppose that in the preceding line where it attempts to assign an allowedCertificate, SecCertificateCreateWithData has the following description:
/*!
#function SecCertificateCreateWithData
#abstract Create a certificate given it's DER representation as a CFData.
#param allocator CFAllocator to allocate the certificate with.
#param certificate DER encoded X.509 certificate.
#result Return NULL if the passed-in data is not a valid DER-encoded
X.509 certificate, return a SecCertificateRef otherwise.
*/
Clearly it is returning a NULL, but I don't know why. My specific certificate is in the correct format. However, I did notice that there were other 'certificates' in the pinnedCertificates array, but I have no idea where it is getting them. Nor can I seem to find them or print them out. As far as I know, I only have the one in the app bundle, but it seems to show more than that.
The error generated from the assert on the last line is:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: allowedCertificate'
Assuming you are using AFNetworking 2.5 the steps described below:
Install valid certificate on your server
Create .cer file openssl x509 -in www_yourdomain_com.crt -out www_yourdomain_com.cer -outform der
Add .cer file to app project. IMPORTANT: Never add private key to your project!!!
Setup AFNetworking secure policy
- (AFSecurityPolicy *)securityPolicy {
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
[securityPolicy setAllowInvalidCertificates:NO];
[securityPolicy setValidatesDomainName:YES];
return securityPolicy;
}
Set policy on network manager when making network call
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager setSecurityPolicy:[self securityPolicy]];
There's no need to manually load .cer file, if you're using policyWithPinningMode:pinningMode method to create security policy AFNetworking automatically pins them.
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!
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!
I have a tomcat server that uses a self signed SSL certificate and is running a web service. I am trying to connect to the web service with Restkit. However, I am getting an error related to certificate validity. Here is my code:
NSURL *url=[NSURL URLWithString:baseURL];
RKClient *client = [RKClient clientWithBaseURL:url];
client.disableCertificateValidation=YES;
RKRequest *request = [client requestWithResourcePath:[NSString stringWithFormat:#"/addEvent?deviceID=%#&eventID=%#",deviceID,eventID]];
request.disableCertificateValidation=YES;
request.delegate=self;
RKResponse *response = [request sendSynchronously];
This request fails with the following error:
2013-01-09 15:11:53.931 Mobile_ACPL[5761:907] The certificate for this server is invalid. You might be connecting to a server that is pretending to be “notify.acpl.lib.in.us” which could put your confidential information at risk.
I get this error even though I have set disableCertificateValidation to YES. How can I get this working?
EDIT: I attempted adding the certificate as shown here: https://github.com/RestKit/RestKit/pull/131
I still get the same result.
EDIT 2: It looks like the error message is being set at this line in RKRequest.m:
payload = [NSURLConnection sendSynchronousRequest:_URLRequest returningResponse:&URLResponse error:&error];
NSURLConnection does not cater for authentication challenges in synchronous calls. You need to make asynchronous calls for this to work.
In my case, I was setting disableCertificateValidation on RKClient but I was using a RKObjectManager which used a different RKClient. The following line, placed after the RKObjectManager initialization, did the trick:
[RKObjectManager sharedManager].client.disableCertificateValidation = YES;
if you are using RestKit using
client.allowsInvalidSSLCertificate = YES;
won't work, instead do this:
if you added rest kit manually to your project, click on RestKit.xcodeproj go to project > Build Settings > Preprocessor Macros
and add _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_=1
thats finished.