I need to show SSL certificate details of displayed URL in a UIWebView something like Google's Chrome browser shows:
How to obtain this data from UIWebView.
We are intercepting the call at the network level (rather than the UIWebView), and using [NSURLConnectionDelegate connection:willSendRequestForAuthenticationChallenge:]. This gives you a NSURLAuthenticationChallenge instance, and if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust then NSURLAuthenticationChallenge.protectionSpace.serverTrust is a SecTrustRef.
Given the SecTrustRef, you can follow SecCertificateRef: How to get the certificate information? and do something like this:
#import <Security/Security.h>
#import <openssl/x509.h>
X509* X509CertificateFromSecTrust(SecTrustRef trust) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, 0);
CFDataRef certDataRef = SecCertificateCopyData(cert);
NSData *certData = (__bridge NSData *)certDataRef;
const void* certDataBytes = certData.bytes;
X509* result = d2i_X509(NULL, (const unsigned char**)&certDataBytes, certData.length);
CFRelease(certDataRef);
return result;
}
static NSString* X509NameField(X509_NAME* name, char* key) {
if (name == NULL)
return nil;
int nid = OBJ_txt2nid(key);
int index = X509_NAME_get_index_by_NID(name, nid, -1);
X509_NAME_ENTRY *nameEntry = X509_NAME_get_entry(name, index);
if (nameEntry == NULL)
return nil;
ASN1_STRING *nameASN1 = X509_NAME_ENTRY_get_data(nameEntry);
if (nameASN1 == NULL)
return nil;
unsigned char *issuerName = ASN1_STRING_data(nameASN1);
return [NSString stringWithUTF8String:(char *)issuerName];
}
NSString* X509CertificateGetSubjectCommonName(X509* cert) {
if (cert == NULL)
return nil;
X509_NAME *subjectName = X509_get_subject_name(cert);
return X509NameField(subjectName, "CN"); // Common name.
}
NSString* X509CertificateGetIssuerName(X509* certX509) {
if (certX509 == NULL)
return nil;
X509_NAME *issuerX509Name = X509_get_issuer_name(certX509);
if (issuerX509Name == NULL)
return nil;
return X509NameField(issuerX509Name, "O"); // organization
}
This is not a simple piece of work. You'll need to understand OpenSSL's X509 code, the Security framework, and whatever you're doing at the network layer for SSL trust checks. There might be other ways that you can get hold of a SecTrustRef or SecCertificateRef, but I'm not using them if there are.
Related
I've got a CFDataRef that contains a DER-encoded X.509 certificate that I can use to create a SecCertificateRef like so:
CFDataRef binaryDataRef = ... // from third party
SecCertificateRef certRef = SecCertificateCreateWithData (NULL, binaryDataRef);
However in some cases my CFData can contain multiple certs (a cert chain) that have been concatenated together using i2d_X509 by third-party code.
Is there a call on iOS similar to SecCertificateCreateWithData that can decode all of the certificates? SecCertificateCreateWithData is just giving me the first cert.
I figured it out - I can use d2i_X509 to figure out where each cert is, and it handily adjusts the pointer to the next one in the array.
CFArrayRef nmCopyEncodedCertificates(CFDataRef derDataRef) {
CFMutableArrayRef certs = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
if (derDataRef) {
CFIndex bytesRemaining = CFDataGetLength(derDataRef);
const unsigned char *pDerData = CFDataGetBytePtr(derDataRef);
const unsigned char *pCurCertBegin = pDerData;
X509 *certX509 = NULL;
while ((certX509 = d2i_X509(NULL, &pDerData, bytesRemaining)) != NULL && // increments pDerData to next cert
bytesRemaining > 0) {
X509_free(certX509);
long len = pDerData - pCurCertBegin;
if (len > 0) {
CFDataRef certData = CFDataCreate(kCFAllocatorDefault, pCurCertBegin, len);
if (certData) {
SecCertificateRef certRef = SecCertificateCreateWithData (kCFAllocatorDefault, certData);
if (certRef) {
CFArrayAppendValue(certs, certRef);
CFRelease(certRef);
}
CFRelease(certData);
}
bytesRemaining -= len;
pCurCertBegin = pDerData;
} else {
break;
}
}
}
return certs;
}
I am working on an iPhone app that works on GcdAsyncSocket and creates TLS connection, I generate RSA keys and CSR using those and sent CSR to server, server responded with a certificate and some other certificate that is like public key to it. Now I need to make another TLS connection with server and send private key nd 2 certificates back to it. I have gone through many posts but didn't find any way how to achieve this.
If anyone could help and with some code that would be great help.
Thanks.
After spending good amount of time, I was able to resolve the issues using open SSL library. I used following code
+(PKCS12*)convertToP12Certificate:(NSString*)certificate
certificateChain:(NSArray*)certificateChain
publicCertificate:(NSString*) publicCertificate
andPrivateKey:(NSString*)privateKey
{
//we create a x509 from primary certificate which goes as a single entity when creating p12
const char *cert_chars = [certificate cStringUsingEncoding:NSUTF8StringEncoding];
BIO *buffer = BIO_new(BIO_s_mem());
BIO_puts(buffer, cert_chars);
X509 *cert;
cert = PEM_read_bio_X509(buffer, NULL, 0, NULL);
if (cert == NULL) {
NSLog(#"error");
}
X509_print_fp(stdout, cert);
//create a evp from private key which goes as a separate entity while creating p12
const char *privateKey_chars = [privateKey cStringUsingEncoding:NSUTF8StringEncoding];
BIO *privateKeyBuffer = BIO_new(BIO_s_mem());
BIO_puts(privateKeyBuffer, privateKey_chars);
EVP_PKEY *evp;
evp =PEM_read_bio_PrivateKey(privateKeyBuffer, NULL, NULL, "Enc Key");
if (evp == NULL) {
NSLog(#"error");
}
if (!X509_check_private_key(cert, evp))
{
NSLog(#"PK error");
}
PKCS12 *p12;
SSLeay_add_all_algorithms();
ERR_load_crypto_strings();
const char *cert_chars2 = [publicCertificate cStringUsingEncoding:NSUTF8StringEncoding];
BIO *buffer2= BIO_new(BIO_s_mem());
BIO_puts(buffer2, cert_chars2);
X509 *cert2;
cert2 = PEM_read_bio_X509(buffer2, NULL, 0, NULL);
if (cert2 == NULL) {
NSLog(#"error");
}
X509_print_fp(stdout, cert2);
STACK_OF(X509) *sk = sk_X509_new_null();
sk_X509_push(sk, cert2);
for(NSString * tempCertificate in certificateChain)
{
const char *cert_chars3 = [tempCertificate cStringUsingEncoding:NSUTF8StringEncoding];
BIO *buffer3= BIO_new(BIO_s_mem());
BIO_puts(buffer3, cert_chars3);
X509 *cert3;
cert3 = PEM_read_bio_X509(buffer3, NULL, 0, NULL);
if (cert3 == NULL) {
NSLog(#"error");
}
X509_print_fp(stdout, cert3);
sk_X509_push(sk, cert3);
}
p12 = PKCS12_create(P12_Password, P12_Name, evp, cert, sk, 0,0,0,0,0);
return p12;
}
+(NSArray*)getCertificateChainForCertificate:(NSString*)certificate
certificateChain:(NSArray*)certificateChain
publicCertificate:(NSString*) publicCertificate
andPrivateKey:(NSString*)privateKey
{
PKCS12 *p12 = [CryptoHelper convertToP12Certificate:certificate
certificateChain:certificateChain
publicCertificate: publicCertificate
andPrivateKey:privateKey];
NSData *PKCS12Data = [CryptoHelper convertP12ToData:p12];
NSArray *certs = nil;
SecIdentityRef identityRef = nil;
CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;
CFStringRef password = CFSTR(P12_Password);
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import(inPKCS12Data, options, &items);
CFRelease(options);
CFRelease(password);
if (securityError == errSecSuccess)
{
NSLog(#"Success opening p12 certificate. Items: %ld", CFArrayGetCount(items));
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
identityRef = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
if(certificateChain)
{
CFArrayRef certificates = (CFArrayRef)CFDictionaryGetValue(identityDict,kSecImportItemCertChain);
// There are 3 items in array when we retrieve certChain and for TLS connection cert
SecIdentityRef chainIdentity = (SecIdentityRef)CFArrayGetValueAtIndex(certificates,1);
certs = [[NSArray alloc] initWithObjects:(__bridge id)identityRef,(__bridge id)chainIdentity, nil];
}
else
{
certs = [[NSArray alloc] initWithObjects:(__bridge id)identityRef, nil];
}
} else
{
NSLog(#"Error opening Certificate.");
}
return certs;
}
We can pass this array to TLS connection for key GCDAsyncSocketSSLCertificates.
How to get Wi-Fi encryption mode in iOS without private libraries?
The code from the answer above has been posted originally on this website: http://www.codeproject.com/Articles/621213/Non-Standard-Way-to-Get-Inaccessible-Data-from-iOS
By the way, for this code to work you need to include the appropriate header files with #include <mach/mach.h> so that your compiler recognizes NDR_record_t ndr.
However, this whole setup did not actually return me the encryption mode of the current WiFi, but rather the configuration of AirPort (the variable key in the code from above needs to be set to NSString *key = #"Setup:/Network/Interface/en0/AirPort"; before). I tried different values instead of AirPort which I got from running $scutil in the Terminal of my Mac (such as Setup:/Network/Interface/en0/IPv4 or Setup:/Network/Interface/en0/Modem or from this website)
Hope that helps someone having similar issues...
For iOS 5:
aslmsg asl, message;
aslresponse searchResult;
int i;
const char *key, *val;
NSMutableArray *result_dicts = [NSMutableArray array];
asl = asl_new(ASL_TYPE_QUERY);
if (!asl)
{
DDLogCError(#"Failed creating ASL query");
}
asl_set_query(asl, "Sender", "kernel", ASL_QUERY_OP_EQUAL);
asl_set_query(asl, "Message", "AppleBCMWLAN Joined BSS:", ASL_QUERY_OP_PREFIX|ASL_QUERY_OP_EQUAL);
searchResult = asl_search(NULL, asl);
while (NULL != (message = aslresponse_next(searchResult)))
{
NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary];
for (i = 0; (NULL != (key = asl_key(message, i))); i++)
{
NSString *keyString = [NSString stringWithUTF8String:(char *)key];
val = asl_get(message, key);
NSString *string = [NSString stringWithUTF8String:val];
[tmpDict setObject:string forKey:keyString];
}
[result_dicts addObject:tmpDict];
}
aslresponse_free(searchResult);
asl_free(asl);
For iOS 6:
#define kMachPortConfigd "com.apple.SystemConfiguration.configd"
-(NSDictionary *)getSCdata:(NSString *)key
{
if(SYSTEM_VERSION_LESS_THAN(#"6.0"))
{
// It does not work on iOS 5.*
return nil;
}
struct send_body {mach_msg_header_t header; int count; UInt8 *addr; CFIndex size0; int flags; NDR_record_t ndr; CFIndex size; int retB; int rcB; int f24; int f28;};
mach_port_t bootstrapport = MACH_PORT_NULL;
mach_port_t configport = MACH_PORT_NULL;
mach_msg_header_t *msg;
mach_msg_return_t msg_return;
struct send_body send_msg;
// Make request
CFDataRef extRepr;
extRepr = CFStringCreateExternalRepresentation(NULL, (__bridge CFStringRef)(key), kCFStringEncodingUTF8, 0);
// Connect to Mach MIG port of configd
task_get_bootstrap_port(mach_task_self(), &bootstrapport);
bootstrap_look_up2(bootstrapport, kMachPortConfigd, &configport, 0, 8LL);
// Make request
send_msg.count = 1;
send_msg.addr = (UInt8*)CFDataGetBytePtr(extRepr);
send_msg.size0 = CFDataGetLength(extRepr);
send_msg.size = CFDataGetLength(extRepr);
send_msg.flags = 0x1000100u;
send_msg.ndr = NDR_record;
// Make message header
msg = &(send_msg.header);
msg->msgh_bits = 0x80001513u;
msg->msgh_remote_port = configport;
msg->msgh_local_port = mig_get_reply_port();
msg->msgh_id = 20010;
// Request server
msg_return = mach_msg(msg, 3, 0x34u, 0x44u, msg->msgh_local_port, 0, 0);
if(msg_return)
{
if (msg_return - 0x10000002u >= 2 && msg_return != 0x10000010 )
{
mig_dealloc_reply_port(msg->msgh_local_port);
}
else
{
mig_put_reply_port(msg->msgh_local_port);
}
}
else if ( msg->msgh_id != 71 && msg->msgh_id == 20110 && msg->msgh_bits <= -1 )
{
if ((send_msg.flags & 0xFF000000) == 0x1000000)
{
CFDataRef deserializedData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, send_msg.addr,send_msg.size0, kCFAllocatorNull);
CFPropertyListRef proplist = CFPropertyListCreateWithData(kCFAllocatorDefault, deserializedData, kCFPropertyListImmutable, NULL, NULL);
mig_dealloc_reply_port(msg->msgh_local_port);
mach_port_deallocate(mach_task_self(), bootstrapport);
mach_port_deallocate(mach_task_self(), configport);
mach_msg_destroy(msg);
NSDictionary *property_list = (__bridge NSDictionary*)proplist;
if(proplist)
CFRelease(proplist);
CFRelease(deserializedData);
CFRelease(extRepr);
return property_list;
}
}
mig_dealloc_reply_port(msg->msgh_local_port);
mach_port_deallocate(mach_task_self(), bootstrapport);
mach_port_deallocate(mach_task_self(), configport);
mach_msg_destroy(msg);
CFRelease(extRepr);
return nil;
}
How to get Wi-Fi encryption mode in iOS without private libraries?
The code from the answer above has been posted originally on this website: http://www.codeproject.com/Articles/621213/Non-Standard-Way-to-Get-Inaccessible-Data-from-iOS
By the way, for this code to work you need to include the appropriate header files with #include <mach/mach.h> so that your compiler recognizes NDR_record_t ndr.
However, this whole setup did not actually return me the encryption mode of the current WiFi, but rather the configuration of AirPort (the variable key in the code from above needs to be set to NSString *key = #"Setup:/Network/Interface/en0/AirPort"; before). I tried different values instead of AirPort which I got from running $scutil in the Terminal of my Mac (such as Setup:/Network/Interface/en0/IPv4 or Setup:/Network/Interface/en0/Modem or from this website)
Hope that helps someone having similar issues...
For iOS 5:
aslmsg asl, message;
aslresponse searchResult;
int i;
const char *key, *val;
NSMutableArray *result_dicts = [NSMutableArray array];
asl = asl_new(ASL_TYPE_QUERY);
if (!asl)
{
DDLogCError(#"Failed creating ASL query");
}
asl_set_query(asl, "Sender", "kernel", ASL_QUERY_OP_EQUAL);
asl_set_query(asl, "Message", "AppleBCMWLAN Joined BSS:", ASL_QUERY_OP_PREFIX|ASL_QUERY_OP_EQUAL);
searchResult = asl_search(NULL, asl);
while (NULL != (message = aslresponse_next(searchResult)))
{
NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary];
for (i = 0; (NULL != (key = asl_key(message, i))); i++)
{
NSString *keyString = [NSString stringWithUTF8String:(char *)key];
val = asl_get(message, key);
NSString *string = [NSString stringWithUTF8String:val];
[tmpDict setObject:string forKey:keyString];
}
[result_dicts addObject:tmpDict];
}
aslresponse_free(searchResult);
asl_free(asl);
For iOS 6:
#define kMachPortConfigd "com.apple.SystemConfiguration.configd"
-(NSDictionary *)getSCdata:(NSString *)key
{
if(SYSTEM_VERSION_LESS_THAN(#"6.0"))
{
// It does not work on iOS 5.*
return nil;
}
struct send_body {mach_msg_header_t header; int count; UInt8 *addr; CFIndex size0; int flags; NDR_record_t ndr; CFIndex size; int retB; int rcB; int f24; int f28;};
mach_port_t bootstrapport = MACH_PORT_NULL;
mach_port_t configport = MACH_PORT_NULL;
mach_msg_header_t *msg;
mach_msg_return_t msg_return;
struct send_body send_msg;
// Make request
CFDataRef extRepr;
extRepr = CFStringCreateExternalRepresentation(NULL, (__bridge CFStringRef)(key), kCFStringEncodingUTF8, 0);
// Connect to Mach MIG port of configd
task_get_bootstrap_port(mach_task_self(), &bootstrapport);
bootstrap_look_up2(bootstrapport, kMachPortConfigd, &configport, 0, 8LL);
// Make request
send_msg.count = 1;
send_msg.addr = (UInt8*)CFDataGetBytePtr(extRepr);
send_msg.size0 = CFDataGetLength(extRepr);
send_msg.size = CFDataGetLength(extRepr);
send_msg.flags = 0x1000100u;
send_msg.ndr = NDR_record;
// Make message header
msg = &(send_msg.header);
msg->msgh_bits = 0x80001513u;
msg->msgh_remote_port = configport;
msg->msgh_local_port = mig_get_reply_port();
msg->msgh_id = 20010;
// Request server
msg_return = mach_msg(msg, 3, 0x34u, 0x44u, msg->msgh_local_port, 0, 0);
if(msg_return)
{
if (msg_return - 0x10000002u >= 2 && msg_return != 0x10000010 )
{
mig_dealloc_reply_port(msg->msgh_local_port);
}
else
{
mig_put_reply_port(msg->msgh_local_port);
}
}
else if ( msg->msgh_id != 71 && msg->msgh_id == 20110 && msg->msgh_bits <= -1 )
{
if ((send_msg.flags & 0xFF000000) == 0x1000000)
{
CFDataRef deserializedData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, send_msg.addr,send_msg.size0, kCFAllocatorNull);
CFPropertyListRef proplist = CFPropertyListCreateWithData(kCFAllocatorDefault, deserializedData, kCFPropertyListImmutable, NULL, NULL);
mig_dealloc_reply_port(msg->msgh_local_port);
mach_port_deallocate(mach_task_self(), bootstrapport);
mach_port_deallocate(mach_task_self(), configport);
mach_msg_destroy(msg);
NSDictionary *property_list = (__bridge NSDictionary*)proplist;
if(proplist)
CFRelease(proplist);
CFRelease(deserializedData);
CFRelease(extRepr);
return property_list;
}
}
mig_dealloc_reply_port(msg->msgh_local_port);
mach_port_deallocate(mach_task_self(), bootstrapport);
mach_port_deallocate(mach_task_self(), configport);
mach_msg_destroy(msg);
CFRelease(extRepr);
return nil;
}
My application seems to work fine most of the time.
When it runs in China, however, it sometimes fails to give me my the IP address of our server. I use this to get the IP address, asynchronously:
//...
CFStringRef hostAddress = CFStringCreateWithCString( kCFAllocatorDefault, hostAddressStr, kCFStringEncodingASCII );
CFHostRef theHostRef = CFHostCreateWithName( kCFAllocatorDefault, hostAddress );
CFRelease(hostAddress);
CFHostClientContext context;
context.version = 0;
context.retain = nil;
context.release = nil;
context.copyDescription = nil;
context.info = self;
Boolean set_ok = CFHostSetClient( theHostRef, myCFHostClientCallBack, &context );
CFStreamError cfError;
CFHostScheduleWithRunLoop( theHostRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
Boolean is_resolving = CFHostStartInfoResolution( theHostRef, kCFHostAddresses, &cfError );
//...
void myCFHostClientCallBack( CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void* info) {
int count = CFArrayGetCount(arrayRef);
for( int index = 0; index < count; ++index ) {
CFDataRef saData = (CFDataRef)CFArrayGetValueAtIndex(arrayRef, index);
struct sockaddr_in* remoteAddr = (struct sockaddr_in*)CFDataGetBytePtr(saData);
if( remoteAddr != NULL) {
char* addressString = inet_ntoa(remoteAddr->sin_addr);
if( addressString != NULL) {
if( resolvedAddressString == NULL ) {
resolvedAddressString = addressString;
}
}
}
}
// ...
}
I suspect that iOS performs a domain name to ip address lookup using whatever DNS is locally available, but I have strong suspicions that that DNS fails for some reason, some of the time.
Is there a easy way for me to make a similar query using a public dns server, such as google, in case the ISP provided DNS server fails to return the IP address of our server?
I have not found an API for this, so if possible, I'd like to have some link to some simple sample code to acheive this.