iOSおよびAndroidでクライアントSSL証明書を使用した承認

Secure Sockets Layer(SSL)プロトコルは、安全なデータ転送を提供することに加えて、SSLクライアント証明書を使用してクライアント認証を実装することもできます。 この記事は、IOSおよびAndroidのモバイルアプリケーションでこのタイプの認証を実装するための実用的なガイドです。



このタイプの許可を提供するサーバーの操作を編成するプロセスは、この記事では考慮されていませんが、このトピックへのリンクは最後に提供されています。



承認プロセスは次のとおりです。 クライアントが閉じた領域に移動すると、サーバーはクライアントに証明書を要求します。チェックが成功した場合、クライアントは閉じたコンテンツにアクセスします。



接続を整理するために、クライアント証明書を生成し、証明書への署名要求も作成しました。その結果、client.csrファイルを受け取りました。 次に、このファイルをサービスプロバイダーに送信し、リモートサーバーでの認証に必要な署名済みクライアント証明書を受け取りました。



接続テストは、curlユーティリティを使用して実行できます。



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



ただし、OS Xの最新バージョンのcurl 7.30.0では破損しており、テストの整理に使用できないことに注意してくださいhttp://curl.haxx.se/mail/archive-2013-10/0036.html )。



クライアント証明書を転送するには、PKCS#12形式のファイルを使用します。 PKCS#12ファイルは、秘密鍵と証明書の両方を(もちろん、暗号化された形式で)保存します。 PKCS#12ファイルの構成の例を示します。







次のコマンドを使用して、client.crtをPKCS#12形式のファイルに変換できます。



client.crtのopenssl pkcs12エクスポートinkey client.key out client.p12



PKCS#12形式のファイルを受け取ったら、モバイルアプリケーションの開発とテストに進むことができます。 iOSから始めましょう。



1.アプリケーションのiOSバージョンを実装します


Security.Frameworkプロジェクトに接続する必要があります

要求を完了するには、PKCS#12からデジタル証明書と関連する秘密キー(SecIdentityRef)を抽出する必要があります。 このオブジェクトの存在により、対応するNSURLCredentialを取得できます。

そこで、関数extractIdentityAndTrustを実装します。



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; }
      
      





SecPKCS12Import関数を使用して抽出します。証明書のパスワードを忘れずに指定します。

次に、canAuthenticateAgainstProtectionSpaceデリゲートを実装します;このデリゲートを呼び出すと、サーバーのプロパティ、つまりプロトコル、承認メカニズムを決定できます。 このデリゲートの実装は単純であり、サーバーによって提示された認証方法を処理していることを示します。



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





考えられるエラーを処理します。



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





次に、認証メカニズムを直接実装することに進みましょう。 didRecieveAuthentificationChallengeデリゲートを実装します。



 - (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]; }
      
      





証明書を読み込み、必要なデータを抽出し、NSURLCredentialを作成し、必要な情報を転送し、現在のセッションのコンテキストでのみ認証データを保存します。



さて、完全を期すために、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];
      
      







didReceiveDataデリゲートの実装は提供しません。



2. Androidバージョンのアプリケーションを実装します


コードからすぐに始めます。



 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);
      
      





ケース(PKCS12)で対応するキーストアのインスタンスを取得し、リソースから証明書をロードし、2番目の引数としてパスワードを指定します。 次に、SSLSocketFactoryの独自の実装を使用してSSLSocketFactoryのインスタンスを作成します。これにより、証明書を使用してSSLコンテキストを初期化できます。 工場出荷時のコードは次のとおりです。 次に、接続パラメーターを構成し、ファクトリーを登録し、リクエストの送信先ポートを指定し、対応するPOSTを形成してリクエストを実行します。



工場コード:

 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()]); } } }
      
      





おわりに


クライアント証明書を使用してSSLを使用して認証する方法を検討しました。



有用な情報:

iOSの証明書、キー、および信頼サービスのタスク

PKCS12の説明

クライアント証明書認証を使用した.NET Webサービスの作成

asp.netでの証明書認証

Java 2-way TLS / SSL(クライアント証明書)およびPKCS12とJKSキーストア



ご清聴ありがとうございました!




All Articles