📜 ⬆️ ⬇️

Authorization using client SSL certificates in IOS and Android

The Secure Sockets Layer (SSL) data transfer protocol, in addition to providing secure data transmission, also allows you to implement client authorization using client SSL certificates. This article is a practical guide to the implementation of this type of authorization in mobile applications on IOS and Android.

The process of organizing the work of the server providing this type of authorization is not covered in the article, but at the end there are links on this topic.

The authorization process is as follows. When a client goes to a closed area, the server requests a certificate from the client, if the check is successful, then the client gets access to the closed content, otherwise the client may receive an error “ No required SSL certificate was sent ”.

To organize the connection, we generated the client certificate, as well as created a certificate signing request, which resulted in the client.csr file. Then we sent this file to the service provider and received our signed client certificate necessary for authentication on the remote server.
')
Connection testing can be done using the curl utility.

curl cert client.crt key client.key k someserive.com

However, it is worth noting that the latest version of curl 7.30.0 in OS X is broken and cannot be used to organize testing ( http://curl.haxx.se/mail/archive-2013-10/0036.html ).

To transfer the client certificate, we will use a file in the PKCS # 12 format. In PKCS # 12 files, both the private key and the certificate are stored at the same time (of course, in encrypted form). The approximate organization of the PKCS # 12 file is shown in the figure.



To convert your client.crt to a PKCS # 12 file, use the following command:

openssl pkcs12 export in client.crt inkey client.key out client.p12

After we received the file in the PKCS # 12 format, we can proceed to the development and testing of our mobile application. Let's start with iOS.

1. Implement the iOS version of the application

You need to connect to your project Security.Framework
In order to make a request, we need to extract a digital certificate and its associated private key (SecIdentityRef) from PKCS # 12. The presence of this object will allow us to obtain the appropriate NSURLCredential.
So, we implement the extractIdentityAndTrust function.

OSStatus extractIdentityAndTrust(CFDataRef inP12data, SecIdentityRef *identity) { OSStatus securityError = errSecSuccess; CFStringRef password = CFSTR(""); const void *keys[] = { kSecImportExportPassphrase }; const void *values[] = { password }; CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); securityError = SecPKCS12Import(inP12data, options, &items); if (securityError == 0) { CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0); const void *tempIdentity = NULL; tempIdentity = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity); *identity = (SecIdentityRef)tempIdentity; } if (options) { CFRelease(options); } return securityError; } 

We perform extraction using the SecPKCS12Import function, it is unforgettable to specify the password for the serial certificate.
Next, we implement the canAuthenticateAgainstProtectionSpace delegate, a call to this delegate allows us to define the server properties, namely the protocol, the authorization mechanism. With us, the implementation of this delegate will be simple; let us indicate that we are processing any authentication method provided by the server.

 - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { return YES; } 

We process possible errors:

 - (void)connection:(NSURLConnection*) connection didFailWithError:(NSError *)error { NSLog(@"Did recieve error: %@", [error localizedDescription]); NSLog(@"%@", [error userInfo]); } 

We now turn to the implementation of the authentication mechanism itself. Implement the didRecieveAuthentificationChallenge delegate:

 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { NSLog(@"Authentication challenge"); // load cert NSString *path = [[NSBundle mainBundle] pathForResource:@"keystore" ofType:@"p12"]; NSData *p12data = [NSData dataWithContentsOfFile:path]; CFDataRef inP12data = (__bridge CFDataRef)p12data; SecIdentityRef myIdentity; OSStatus status = extractIdentityAndTrust(inP12data, &myIdentity); SecCertificateRef myCertificate; SecIdentityCopyCertificate(myIdentity, &myCertificate); const void *certs[] = { myCertificate }; CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL); NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:(__bridge NSArray*)certsArray persistence: NSURLCredentialPersistenceForSession]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } 

We load our certificate, extract the data we need from it, create the NSURLCredential, transfer the necessary information, save the authentication data only in the context of the current session.

Well, for the sake of completeness, here is the code preparing the NSURLConnection:

 NSString *key = @"test"; NSError *jsonSerializationError = nil; NSMutableDictionary *projectDictionary = [NSMutableDictionary dictionaryWithCapacity:1]; [projectDictionary setObject:key forKey:@"test"]; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:projectDictionary options:nil error:&jsonSerializationError]; NSURL *requestUrl = [[NSURL alloc] initWithString:@"https://your_service"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0]; [request setHTTPMethod:@"POST"]; [request setValue:@"UTF-8" forHTTPHeaderField:@"content-charset"]; [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; [request setValue:[NSString stringWithFormat:@"%d", [jsonData length]] forHTTPHeaderField:@"Content-Length"]; [request setHTTPBody: jsonData]; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; [connection start]; 


Implementation of the delegate didReceiveData will not result.

2. Implement the Android version of the application

I'll start right away with the code:

 KeyStore keystore = KeyStore.getInstance("PKCS12"); keystore.load(getResources().openRawResource(R.raw.keystore), "".toCharArray()); SSLSocketFactory sslSocketFactory = new AdditionalKeyStoresSSLSocketFactory(keystore); HttpParams params = new BasicHttpParams(); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); HttpProtocolParams.setUseExpectContinue(params, true); final SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); registry.register(new Scheme("https", sslSocketFactory, 3123)); ThreadSafeClientConnManager manager = new ThreadSafeClientConnManager(params, registry); DefaultHttpClient httpclient = new DefaultHttpClient(manager, params); HttpPost httpPostRequest = new HttpPost("https://your_service"); // datas - array which contains data to send to server StringEntity se = new StringEntity(datas[0].toString(), HTTP.UTF_8); // Set HTTP parameters httpPostRequest.setEntity(se); httpPostRequest.setHeader("Accept", "application/json"); httpPostRequest.setHeader("Content-Type", "application/json"); HttpResponse response = httpclient.execute(httpPostRequest); 

In our case, we get an instance of the corresponding KeyStore (PKCS12), download our certificate from the resources, specify the password as the second argument. Next we create an instance of SSLSocketFactory, using our own implementation of SSLSocketFactory, which allows us to initialize the SSL context using our certificate. The factory code is given below. Next, we configure the connection parameters, register our factory, specify the port to which we will send the request, form the corresponding POST and execute the request.

Factory code:
 import java.io.IOException; import java.net.Socket; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import org.apache.http.conn.ssl.SSLSocketFactory; /** * Allows you to trust certificates from additional KeyStores in addition to * the default KeyStore */ public class AdditionalKeyStoresSSLSocketFactory extends SSLSocketFactory { protected SSLContext sslContext = SSLContext.getInstance("TLS"); public AdditionalKeyStoresSSLSocketFactory(KeyStore keyStore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(null, null, null, null, null, null); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, "".toCharArray()); sslContext.init(kmf.getKeyManagers(), new TrustManager[]{new ClientKeyStoresTrustManager(keyStore)}, new SecureRandom()); } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); } @Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); } /** * Based on http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#X509TrustManager */ public static class ClientKeyStoresTrustManager implements X509TrustManager { protected ArrayList<X509TrustManager> x509TrustManagers = new ArrayList<X509TrustManager>(); protected ClientKeyStoresTrustManager(KeyStore... additionalkeyStores) { final ArrayList<TrustManagerFactory> factories = new ArrayList<TrustManagerFactory>(); try { // The default Trustmanager with default keystore final TrustManagerFactory original = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); original.init((KeyStore) null); factories.add(original); for ( KeyStore keyStore : additionalkeyStores ) { final TrustManagerFactory additionalCerts = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); additionalCerts.init(keyStore); factories.add(additionalCerts); } } catch (Exception e) { throw new RuntimeException(e); } /* * Iterate over the returned trustmanagers, and hold on * to any that are X509TrustManagers */ for (TrustManagerFactory tmf : factories) for ( TrustManager tm : tmf.getTrustManagers() ) if (tm instanceof X509TrustManager) x509TrustManagers.add( (X509TrustManager) tm ); if ( x509TrustManagers.size() == 0 ) throw new RuntimeException("Couldn't find any X509TrustManagers"); } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { for ( X509TrustManager tm : x509TrustManagers ) { try { tm.checkClientTrusted(chain, authType); return; } catch ( CertificateException e ) { } } throw new CertificateException(); } /* * Loop over the trustmanagers until we find one that accepts our server */ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { for ( X509TrustManager tm : x509TrustManagers ) { try { tm.checkServerTrusted(chain, authType); return; } catch ( CertificateException e ) { } } throw new CertificateException(); } public X509Certificate[] getAcceptedIssuers() { final ArrayList<X509Certificate> list = new ArrayList<X509Certificate>(); for ( X509TrustManager tm : x509TrustManagers ) list.addAll(Arrays.asList(tm.getAcceptedIssuers())); return list.toArray(new X509Certificate[list.size()]); } } } 

Conclusion

We looked at how to authenticate over SSL using a client certificate.

Useful information:
Certificate, Key, and Trust Services Tasks for iOS
PKCS12 Description
Creating .NET web service with client certificate authentication
Certificate Authentication in asp.net
Java 2-way TLS / SSL (Client Certificates) and PKCS12 vs JKS KeyStores

Thanks for attention!

Source: https://habr.com/ru/post/194530/


All Articles