web-dev-qa-db-ja.com

iOS用のクライアント証明書とサーバー認証を実装する方法

私は最近、非常に骨の折れるプロセスを経て、非常にシンプルでありながら、どの場所でも本質的に見つけられないように見えるものを構築しました。ここにすべてを入れて、何か問題がないかどうかを尋ね、そうでない場合は、この情報が必要な人を支援したいと思います。

背景:私がセキュリティを提供しようとしていた製品/サービスは、PCまたはiPadのカスタムクライアントアプリからのみアクセスできるWindowsサーバー上のWCFサービスを中心に構築されています。顧客ごとに1つのサーバー、ブラウザーアクセスなし。すべては、商用CAからのWindows標準メカニズムと証明書を使用した認証と承認ですでにTLSで保護されています。

アクセスをさらに制限するために、クライアント/サーバー証明書は、自己署名証明書を使用してWindowsプラットフォームに実装されました(パブリック/ブラウザーアクセスのない相互認証がある場合は、反対の主張にもかかわらず、商用CAは必要ありません)。管理する)。

これらすべてをiPadで機能させることは、驚くほどの量の偽情報または部分的に正しい推奨事項を伴う、ひどく文書化された悪夢でした。以下では、最高の情報源にリンクしようとしましたが、誤って帰属を見逃してしまった場合はお詫び申し上げます。この投稿について何か間違っている/誤解を招くものがあればコメントしてください。

ありがとう

9
saminpa

主な手順は次のとおりです。

  1. 証明書を生成するためのシステムを作成します(本番システムの場合は簡単ですが重要です)
  2. 証明書をiPadに転送します(アプリストアバンドルには組み込まれていません!)
  3. 受信したすべての資格情報をアプリのキーチェーンに保存します(ここでAppleはそれらが属していると言います)
  4. NSURLConnectionsで使用するために、保存された資格情報をキーチェーンから取得します
  5. 実際にサーバー証明書を認証し、クライアントの資格情報を返します

ステップ1.証明書を生成します

参照: http://developer-should-know.tumblr.com/post/127063737582/how-to-create-your-own-pki-with-openssl

他の方法を使用することもできますが、OpenSSL for Windows [ http://slproweb.com/products.html] は、標準のインターフェイスがcmdlineであり、ドキュメントに従うのが難しいことを除けば、かなり素晴らしいです。

誰かが前もって私に説明してくれたらいいのにと思いますが、そうではありません。[a]アプリはルートレベルのディレクトリにインストールされ、コマンドラインで指定されていない設定にデフォルトで使用される構成ファイルが含まれています[b]中間ファイルと出力ファイルの場所は、構成ファイルで指定する必要があります[c]コマンドを実行する前に特定のファイルを手動で作成する必要があります[d]実行しようとしていることに適したファイル/フォルダー構造を構築してから、それに応じてcfgファイルをカスタマイズします。

私の場合、これは私の会社の1つのRootCA、顧客ごとの中間証明書(クライアント証明書のみを作成するように設定)、顧客ごとのサーバー証明書、および必要に応じてクライアント証明書を意味しました。 (これは最小限の構成であり、CA /クライアントのペアを使用せず、ルートをロックボックスに保持します)これが私のファイル構造です:

c:\sslcert
    root
    certs
        YourCompany (duplicate this structure as required)
             intermediate
             server
             client
             crl (optional)

トップレベルのsslcertフォルダー内

.rnd        (empty file)
certindex.txt   (empty file)
serial.txt  (Text file seeded with the text “01”, hold the quotes)

ルートフォルダ内

RootCA.cfg

Certs\templateフォルダー内

IntermediateCA.cfg

作業ディレクトリを設定し、OpenSSL cd\sslcert c:\ OpenSSL-Win32\bin\openssl.exeを起動します

ルートキーと証明書を1つのステップで作成する

req -config ./root/RootCA.cfg -new -x509 -days 7300 -extensions v3_ca -keyout root/YourCompanyRootCAkey.pem -out root/YourCompanyRootCAcert.cer

初心者向けの注意:-extensionsを使用すると、同じcfgファイル内のいくつかのサブセクションの1つを適用することを選択できます。

キーと証明書を確認する(オプション)

x509 -noout -text -in root/YourCompanyRootCAcert.cer

新しい中間証明書をリクエストする

req -config certs/YourCompany/IntermediateCA.cfg -new -keyout certs/YourCompany/intermediate/intermediateCAkey.pem -out certs/YourCompany/intermediate/intermediateCAreq.pem  

中間証明書に署名するルート構成にあるルート証明書を使用する

ca -config root/RootCA.cfg -extensions v3_intermediate_ca -days 3650 -notext -in certs/YourCompany/intermediate/intermediateCAreq.pem -out certs/YourCompany/intermediate/YourCompanyIntermediateCAcert.cer

キーと証明書を確認する(オプション)

x509 -noout -text -in certs/YourCompany/intermediate/YourCompanyIntermediateCAcert.cer

中間証明書とルート証明書を連結して証明書チェーンファイルを作成します(これはコマンドラインからの単純な追加です。新しいチェーンは最終的なp12パッケージに追加されます)

c:\sslcert> type c:\sslcert\certs\YourCompany\intermediate\YourCompanyIntermediateCAcert.cer c:\sslcert\root\YourCompanyRootCAcert.cer > c:\sslcert\certs\YourCompany\intermediate\YourCompanyCAchain.cer

新しいクライアントキーと証明書を要求する

genrsa -aes256 -out certs/YourCompany/client/YourCompanyClientkey.pem 2048
req -config certs/YourCompany/IntermediateCA.cfg -key 
certs/YourCompany/client/YourCompanyClientkey.pem -new -sha256 -out         certs/YourCompany/client/YourCompanyClientreq.pem

クライアント証明書に署名してテストする中間権限を持つ

ca -config certs/YourCompany/IntermediateCA.cfg -extensions usr_cert -days 1095 -notext -md sha256 -in certs/YourCompany/client/YourCompanyClientreq.pem -out certs/YourCompany/client/YourCompanyClientcert.cer
x509 -noout -text -in certs/YourCompany/client/YourCompanyClientcert.cer
verify -CAfile certs/YourCompany/intermediate/YourCompanyCAchain.cer certs/YourCompany/client/YourCompanyClientcert.cer

パッケージクライアント証明書

pkcs12 -export -in certs/YourCompany/client/YourCompanyClientcert.cer -name “YourCompany Smips Client” -inkey certs/YourCompany/client/YourCompanyClientkey.pem -certfile certs/YourCompany/intermediate/YourCompanyCAchain.cer -out certs/YourCompany/client/YourCompanyClientWithName.p12

電子メール/ iTunesからiOSにインポートするためにpkcsの名前を変更します

c:\sslcert> copy c:\sslcert\certs\YourCompany\client\YourCompanyClient.p12 c:\sslcert\certs\YourCompany\client\YourCompanyClient.yourext12

新しいサーバーキーと証明書を要求する

genrsa -aes256 -out certs/YourCompany/server/YourCompanyServerkey.pem 2048
req -config certs/YourCompany/IntermediateCA.cfg -key certs/YourCompany/server/YourCompanyServerkey.pem -new -sha256 -out certs/YourCompany/server/YourCompanyServerreq.pem

サーバー証明書に署名してテストする中間権限を持つ

ca -config certs/YourCompany/IntermediateCA.cfg -extensions server_cert -days 1095 -notext -md sha256 -in certs/YourCompany/server/YourCompanyServerreq.pem -out certs/YourCompany/server/YourCompanyServercert.cer
x509 -noout -text -in certs/YourCompany/server/YourCompanyServercert.cer
verify -CAfile certs/YourCompany/intermediate/YourCompanyCAchain.cer certs/YourCompany/server/YourCompanyServercert.cer

パッケージサーバー証明書

pkcs12 -export -in certs/YourCompany/server/YourCompanyServercert.cer -name “YourCompany Smips Server” -inkey certs/YourCompany/server/YourCompanyServerkey.pem -certfile certs/YourCompany/intermediate/YourCompanyCAchain.cer -out certs/YourCompany/server/YourCompanyServer.p12

Cfgファイルは次のとおりです。ルート

dir                 = .

[ ca ]
default_ca              = CA_default

[ CA_default ]
serial              = $dir/serial.txt
database                = $dir/certindex.txt
new_certs_dir           = $dir/certs
certs                   = $dir/certs
private_key             = $dir/root/yourcompanyRootCAkey.pem
certificate             = $dir/root/yourcompanyRootCAcert.cer
default_days            = 7300
default_md              = sha256
preserve                = no
email_in_dn             = no
nameopt             = default_ca 
certopt             = default_ca 
policy              = policy_strict

[ policy_strict ]
countryName                 = match
stateOrProvinceName         = match
organizationName            = match
organizationalUnitName      = optional
commonName                  = supplied
emailAddress                = optional

[ req ]
default_bits            = 4096      # Size of keys
default_keyfile         = key.pem       # name of generated keys
default_md              = sha256        # message digest algorithm
string_mask             = nombstr       # permitted characters
distinguished_name      = req_distinguished_name
x509_extensions         = v3_ca

[ req_distinguished_name ]
0.organizationName           = Organization Name
organizationalUnitName       = Organizational Unit Name
emailAddress                 = Email Address
emailAddress_max            = 40
localityName            = Locality Name (city, district)
stateOrProvinceName     = State or Province Name (full name)
countryName             = Country Name (2 letter code)
countryName_min         = 2
countryName_max         = 2
commonName              = Common Name (hostname, IP, or your name)
commonName_max          = 64

0.organizationName_default  = yourcompany
organizationalUnitName_default  = yourcompanyRoot Certification
emailAddress_default        = [email protected]
localityName_default        = Okeefenokee
stateOrProvinceName_default = Wisconsin
countryName_default     = US

[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ crl_ext ]
authorityKeyIdentifier=keyid:always

中級

dir = .

# [For non-command-line folks, everything is keyed to the working directory here (.) so if your working Prompt says c:\sslcerts> then the cfg will look for serial.txt at c:\sslcerts\serial.txt and bomb if it doesn’t find things laid out accordingly. Thats why you set up a directory structure to match these entries]

[ ca ]
default_ca              = CA_default

[ CA_default ]
serial                  = $dir/serial.txt
database                = $dir/certindex.txt
crl_dir                 = $dir/certs/yourcompany/crl
new_certs_dir               = $dir/certs
certs                   = $dir/certs
private_key             = $dir/certs/yourcompany/intermediate/IntermediateCAkey.pem
certificate             = $dir/certs/yourcompany/intermediate/yourcompanyIntermediateCAcert.cer
default_days                = 3650
default_md              = sha256
preserve                = no
email_in_dn             = no
nameopt                 = default_ca
certopt                 = default_ca 
crlnumber               = $dir/certs/yourcompany/crl/crlnumber
crl                 = $dir/certs/yourcompany/crl/crl.pem
crl_extensions              = crl_ext
default_crl_days            = 365
policy                  = policy_loose

[ policy_loose ]
countryName                     = optional
stateOrProvinceName             = optional
localityName                    = optional
organizationName                = optional
organizationalUnitName          = optional
commonName                      = supplied
emailAddress                    = optional

[ req ]
default_bits                = 4096              # Size of keys
default_keyfile             = $dir/certs/yourcompany/intermediate/IntermediateCAkey.pem
default_md              = sha256            # message digest 

# the old default was md1 - change this]

algorithm
string_mask             = nombstr           # permitted characters
distinguished_name          = req_distinguished_name
x509_extensions             = v3_intermediate_ca

[ req_distinguished_name ]
0.organizationName                  = Organization Name
organizationalUnitName              = Organizational Unit Name
emailAddress                        = Email Address
emailAddress_max            = 40
localityName                = Locality Name (city, district)
stateOrProvinceName         = State or Province Name (full name)
countryName             = Country Name (2 letter code)
countryName_min             = 2
countryName_max             = 2
commonName              = Common Name (hostname, IP, or your name)
commonName_max              = 64

0.organizationName_default      = yourcompany
organizationalUnitName_default      = yourcompany Intermediate Certification
emailAddress_default            = [email protected]
localityName_default            = Okeefenokee
stateOrProvinceName_default     = Wisconsin [should be spelled out]
countryName_default         = US

[ v3_intermediate_ca ]
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid:always,issuer
basicConstraints            = critical, CA:true, pathlen:0
keyUsage                = critical, digitalSignature, cRLSign, keyCertSign

# Important - the pathlen parameter prevents this cert from being used to create new intermediate certs. The subsequent subsections for server and client certs allows you to specify their type and intended usage, as distinct from the intermediate cert, in the same cfg file 

[ usr_cert ]
basicConstraints            = CA:FALSE
nsCertType              = client, email
nsComment               = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid,issuer
keyUsage                = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage            = clientAuth, emailProtection

[ server_cert ]
basicConstraints            = CA:FALSE
nsCertType              = server
nsComment               = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid,issuer:always
keyUsage                = critical, digitalSignature, keyEncipherment
extendedKeyUsage            = serverAuth

[ crl_ext ]
authorityKeyIdentifier          = keyid:always

2。証明書をiPadに転送する

参照: アプリを登録してiPadのアプリでPDFファイルを開く方法

Appleは、アプリで処理される新しいファイルタイプを登録し、新しいカスタム拡張子で名前が変更されたp12ファイルをデバイス(手動または電子メール)に転送して、クライアント証明書をインストールすることをお勧めします。 p12ファイルには、上記の手順1で定義したように、公開証明書チェーンとクライアント証明書情報が含まれている必要があります。このようなファイルを開こうとすると、デバイスは、処理する必要のあるアプリデリゲートに開始/ウェイクアップを送信します(ウェイクの可能性があるため、didloadでは送信されません)。

これはv8または9で少し変更されましたが、7をサポートする必要があるため、これは非推奨のハンドラー用です。ただし、同じ解決策であり、以下のスクリーンショットに示すように、アプリのplistファイルに追加することから始まります。

別のアプリによって要求される可能性が低い2つの新しいアイコンとファイル拡張子が必要になることに注意してください

enter image description here

enter image description here

次に、自明であるはずのデリゲート/ハンドラーが必要です。この部分は通常の制御フローとは関係がないため、AppDelegate.mですべてのデリゲート処理を処理しています。 (これはとても間違っていますか?)必要に応じてメソッド/変数を設定し、ファイルの存在についての妄想的な余分なチェックを無視してください...

参照: https://www.raywenderlich.com/6475/basic-security-in-ios-5-tutorial-part-1

- (BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication
           annotation:(id)annotation {

    if (url) {

        self.p12Data = [NSData dataWithContentsOfFile:[url path]];

        if (!p12Data) {
            [self messageBox:@"Warning" : @"Failed to read data file, cancelling certificate import"];
        }
        else {
            [self presentAlertViewForPassPhrase];
        }

        NSFileManager * fileManager = [NSFileManager defaultManager];
        if ( [fileManager fileExistsAtPath:[url path]] ) {
            [fileManager removeItemAtPath:[url path] error:NULL];
        }
    }

    return YES;
}

- (void)presentAlertViewForPassPhrase {

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Certificate Credentials"
                                                    message:@"Please enter the passphrase for your certificate"
                                                   delegate:self
                                          cancelButtonTitle:@"Cancel"
                                          otherButtonTitles:@"Done", nil];
    [alert setAlertViewStyle:UIAlertViewStyleSecureTextInput];
    [alert show];
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {

    if (buttonIndex == 1) {                                             // User selected "Done"
        UITextField *ppField = [alertView textFieldAtIndex:0];
        if ([ppField.text length] > 0) {
            [self loadCertificates:ppField.text];
        }
        //Handle Else
    }
    else
    {                                                                   // User selected "Cancel"
        [self messageBox:@"Information" : @"Certificate import cancelled"];
    }
}

3。受信した認証情報をアプリのキーチェーンに保存します

生のp12データが得られたので、次に何をすべきかを簡単に理解できるはずです...ではありません。すべてのドキュメントは名前/ pwdストレージに関するもののようで、恐ろしい数のポスターがサーバー証明書をファイルシステムに保存することを示唆しています。これは問題ありませんが、キーチェーンとApple =それが目的だと言っています。最後になりましたが、保存された証明書をどのように区別し、どのように更新しますか?

簡単に言えば、更新か初期ロードかを確認するために機能しないすべての種類のことを試した後、完全な削除/再保存を行うことにしました-それ以外に、それは私のものなので、最初にやりたかったことですアプリチェーン。これはすべてCFのものであり、必要のないものを移植することを拒否するため、ARCを使用していません。私が知る限り、CFを割り当て、NSにキャストし、使用後にCFReleaseを実行する限り、警告はありません。

これらは重要な参考資料です。

iOSアプリケーションのすべてのキーチェーンアイテムを列挙します

[キーチェーンがどのように見えるかを視覚化するのに不可欠]

アプリにアクセスできるすべてのキーチェーンアイテムを削除するにはどうすればよいですか?

キーホルダーアイテムがユニークな理由(iOSの場合)?

http://help.sap.com/saphelp_smp307sdk/helpdata/en/7c/03830b70061014a937d8267bb3f358/content.htm

[ https://developer.Apple.com/library/ios/samplecode/AdvancedURLConnections/Listings/Credentials_m.html 、つまり:

// IMPORTANT: SecCertificateRef's are not uniqued (that is, you can get two
// different SecCertificateRef values that described the same fundamental
// certificate in the keychain), nor can they be compared with CFEqual. So
// we match up certificates based on their data values.

要約すると、(duh)最も簡単な方法は、証明書にラベルを割り当てて、一意に検索し、IDを保存すると、キーと証明書に自動的に分割されることを理解できるようにすることです。確かに-交換にいくつかの困難をもたらしました。

コード(説明は次のとおりです):

- (void) loadCertificates:(NSString *)passPhrase {

    BOOL lastError = false;
    NSMutableDictionary * p12Options = [[NSMutableDictionary alloc] init];
    [p12Options setObject:passPhrase forKey:(id)kSecImportExportPassphrase];
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    OSStatus err = SecPKCS12Import((CFDataRef)p12Data, (CFDictionaryRef)p12Options, &items);
    if (err != noErr) {
        [self messageBox:@"Error" : @"Unable to extract security information with the supplied credentials. Please retry"];
        lastError = true;
    }
    if (!lastError && err == noErr && CFArrayGetCount(items) > 0) {
        CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
        //Clean-up

        NSArray *secItemClasses = [NSArray arrayWithObjects:
                                   (id)kSecClassCertificate,
                                   (id)kSecClassKey,
                                   (id)kSecClassIdentity,
                                   nil];

        for (id secItemClass in secItemClasses) {
            NSDictionary *spec = @{(id)kSecClass: secItemClass};
            err = SecItemDelete((CFDictionaryRef)spec);
        }

        //Client Identity & Certificate

        SecIdentityRef clientIdentity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);

        NSDictionary *addIdentityQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                          kClientIdentityLabel, kSecAttrLabel,
                                          (id)clientIdentity, kSecValueRef,
                                          nil];
        err = SecItemAdd((CFDictionaryRef)addIdentityQuery, NULL);
        if (err == errSecDuplicateItem) {
            NSLog(@"Duplicate identity");
        }
        if (err != noErr) {
            [self messageBox:@"Warning" : @"Failed to save the new identity"];
            lastError = true;
        }
        //Server Certificate
        CFArrayRef chain = CFDictionaryGetValue(identityDict, kSecImportItemCertChain);
        CFIndex N = CFArrayGetCount(chain);
        BOOL brk = false;
        for (CFIndex i=0; (i < N) && (brk == false); i++) {
            SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(chain, i);
            CFStringRef summary = SecCertificateCopySubjectSummary(cert);
            NSString* strSummary = [[NSString alloc] initWithString:(NSString *)summary];
            if ([strSummary containsString:@"Root"] || (i == N)) {

                NSDictionary *addCertQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                              kServerCertificateLabel, kSecAttrLabel,
                                              (id)cert, kSecValueRef,
                                              nil];
                err = SecItemAdd((CFDictionaryRef)addCertQuery, NULL);
                if (err == errSecDuplicateItem) {
                    NSLog(@"Duplicate root certificate");
            }
            if (err != noErr) {
                [self messageBox:@"Warning" : @"Failed to save the new server certificate"];
                lastError = true;
            }
            brk = true;
        }
        [strSummary release];
        CFRelease(summary);
    }
}
else {
    [self messageBox:@"Error" : @"Unable to extract security information with the supplied credentials. Please retry"];
    lastError = true;
}
    [p12Options release];
    CFRelease(items);
    if (!lastError) [self messageBox:@"Information" : @"Certificate import succeeded"];
}

ここで、kClientIdentityLabelとkServerCertificateLabelは任意のラベルです。

KSec関数は多すぎて複雑すぎて、ここで詳細に説明することはできません。すべてがクリアされ、抽出されたクライアントIDが保存され、続いてルートCAが抽出され、それが個別に保存されると言えば十分です。なぜループなのか?ルートがチェーンの最後にあると仮定することが技術的に正しいかどうかはわかりませんでしたが、p12を生成すると、コードは今のところそこにあります。

KSecからのエラーはエンコードされているため、このサイトは不可欠であることに注意してください: https://www.osstatus.com

4。キーチェーンから保存された資格情報を取得します

資格情報がキーチェーンに含まれると、次のようにそれらを抽出できます(失敗モードでは何かが必要になります)。

- (void) reloadCredentials {

    self.clientCredential = nil;
    self.serverCertificateData = nil;

    if (self.useClientCertificateIfPresent) {

        NSDictionary* idQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                 kClientIdentityLabel,            kSecAttrLabel,
                                 (id)kSecClassIdentity,           kSecClass,
                                 kCFBooleanTrue,                  kSecReturnRef,
                                 kSecMatchLimitAll,               kSecMatchLimit,
                                 nil];
        CFArrayRef result = nil;
        OSStatus err = SecItemCopyMatching((CFDictionaryRef)idQuery, (CFTypeRef*)&result);
        if (err == errSecItemNotFound) {
            [self messageBox:@"Warning" : @"Client credentials not found. Server connection may fail"];
        }
        else if (err == noErr && result != nil ) {

            SecIdentityRef clientIdentity = (SecIdentityRef)CFArrayGetValueAtIndex(result, 0);

            SecCertificateRef clientCertificate;
            SecIdentityCopyCertificate(clientIdentity, &clientCertificate);
            const void *certs[] = { clientCertificate };
            CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL);
            self.clientCredential = [NSURLCredential credentialWithIdentity:clientIdentity certificates:(NSArray*)certsArray
                                                                      persistence:NSURLCredentialPersistenceNone];
            CFRelease(certsArray);
            CFRelease(clientCertificate);
            CFRelease(result);
        }
        else {
            [self messageBox:@"Warning" : @"Client or Server credentials not found. Server connection may fail"];
        }

        NSDictionary* serverCertQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                         kServerCertificateLabel,         kSecAttrLabel,
                                         (id)kSecClassCertificate,        kSecClass,
                                         kCFBooleanTrue,                  kSecReturnRef,
                                         kSecMatchLimitAll,               kSecMatchLimit,
                                         nil];
        CFArrayRef result1 = nil;
        err = SecItemCopyMatching((CFDictionaryRef)serverCertQuery, (CFTypeRef*)&result1);
        if (err == errSecItemNotFound) {
            [self messageBox:@"Warning" : @"Server certificate not found. Server connection may fail"];
        }
        else if (err == noErr && result1 != nil ) {

            SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(result1, 0);
            CFDataRef certRefData = SecCertificateCopyData(certRef);
            self.serverCertificateData = (NSData *)certRefData;
            CFRelease(certRefData);
            CFRelease(result1);
        }
        else {
            [self messageBox:@"Warning" : @"Client or Server credentials not found. Server connection may fail"];
        }
    }
}

5。サーバー証明書を認証し、クライアントの資格情報を返します

フーボーイ。これは、取得した証明書を実際に使用する方法を説明するための編集です(簡単な明らかな部分であるはずでした...)

まず、Appleのすでに疑わしいドキュメントは、新しいApplication Transport Securityフレームワークによって廃止されています(たとえば、 http://useyourloaf.com/blog/app-transport-security/ を参照)。ここでは詳しく説明しませんが、デフォルトでは常にhttpsと信頼できる証明書を使用するように全員に強制するという考え方です。私のシナリオでは、専用クライアントとプライベートサーバー間の証明書ピンニングと相互認証を使用して、次のように辞書をplistに追加することで、この機能を安全にオフにできます。

enter image description here

次に、ステップ4では、チャレンジがヒットするとすぐに応答するクライアント資格情報をすでに持っていますが、サーバー証明書はSecCertificateCopyDataによって作成されたDER形式のNSDataとして変動しており、チャレンジが到着したときに何が起こるかが明確ではありません。

「X.509標準」のセクション6( https://tools.ietf.org/html/rfc528 )のアルゴリズムを実装することになっていることがわかりました。幸いなことに、これはiOS SecTrustEvaluate関数によって舞台裏で実装されていますが、構築するための足場と理解するための奇妙なものがあります。

[わずかな問題-スペースが不足しました!!このステップの終わりを含む新しい質問を追加しました。]

https://stackoverflow.com/questions/35964632/correctly-use-a-pinned-self-signed-certificate-in-ios-9-2

[他の投稿から続く]

以上です。品質が悪いので申し訳ありませんが、まだ新鮮なうちに一緒に叩きたいと思いました。エラーが見つかった場合は、投稿を更新します。

これがお役に立てば幸いです。これが非常に優れた本への最後のリンクです。この本は、とりわけ、商用CAを信頼することについての忍び寄りを提供します...

https://www.cs.auckland.ac.nz/~pgut001/pubs/book.pdf

15
saminpa

[継続リンクが反対票を投じて閉じられ、上記に収まらない追加情報の要求が2つあるため、別の回答を追加できることに気づきました。以下の答えは、削除された投稿の質問から始まります]

...私がまだはっきりしていない部分は、アンカー証明書とピン留めを実装するために新しい信頼とポリシーを作成しなければならなかった理由です。

サーバーから受信した信頼にアンカーを追加しただけでは、サーバーから受信したNSURLCredentialへのポインターを正常に返すことができず、送信者(?)によって変更および拒否されたようです。

問題は、これは本当に適切な処理なのか、それとも凝縮できるのかということです。これは少し面倒になっていますが、「機能する」という理由だけで何かを受け入れたくありません。私の現在の解決策を以下に示します。

ステップ4では、操作なしでそのタイプのチャレンジに応答するクライアント資格情報をすでに持っていますが、サーバー証明書はSecCertificateCopyDataによって作成されたDER形式のNSDataとして浮かんでおり、そのチャレンジが到着したときに何が起こるかは明確ではありません。

「X.509標準」のセクション6( https://tools.ietf.org/html/rfc528 )のアルゴリズムを実装することになっていることがわかりました。幸いなことに、これはiOS SecTrustEvaluate関数によって舞台裏で実装されていますが、構築するための足場と理解するための奇妙なものがあります。最初のコード(私の元のソースへの帽子のヒントに続く):

SecTrustEvaluateは常にSecPolicyCreateSSLを使用してkSecTrustResultRecoverableTrustFailureを返します

    - (void)_handleServerTrustChallenge {

    OSStatus status;
    BOOL trusted = false;
    SecTrustResultType trustResult;
    SecTrustRef serverTrust = self.challenge.protectionSpace.serverTrust;
    NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; //candidate credential for return to sender if valid

    if (svDelegate.serverCertificateData) {

        //locally stored information
        SecCertificateRef storedCertificate = SecCertificateCreateWithData(NULL, (CFDataRef)svDelegate.serverCertificateData);
        NSMutableArray *anchorCertArray = [NSMutableArray arrayWithCapacity:1];
        [anchorCertArray addObject:(id)storedCertificate];

        //incoming credentials from server
        NSMutableArray *receivedCertChain = [NSMutableArray array];
        for(int i = 0; i < SecTrustGetCertificateCount(serverTrust); i++)
            [receivedCertChain addObject:(id) SecTrustGetCertificateAtIndex(serverTrust,i))];

        //new custom policy object to use in creating new trust
        //YES indicates extendedKeyUsage is set to serverAuth; effectively ignore server name check by specifying incoming name
        SecPolicyRef newPolicyRef = SecPolicyCreateSSL(YES, (CFStringRef)self.challenge.protectionSpace.Host);

        //create and evaluate new trust with pinned certificate
        SecTrustRef newTrustRef = NULL;
        SecTrustCreateWithCertificates((CFArrayRef) receivedCertChain, newPolicyRef, &newTrustRef);
        status = SecTrustSetAnchorCertificates(newTrustRef, (CFArrayRef) anchorCertArray);
        if (status == noErr) status = SecTrustSetAnchorCertificatesOnly(newTrustRef, TRUE);
        if (status == noErr) status = SecTrustEvaluate(newTrustRef, &trustResult);  

        //----- debug -------
        //CFShow(newPolicyRef);
        //NSLog(@"%@", receivedCertChain);     

        CFRelease(newTrustRef);
        CFRelease(newPolicyRef);
        CFRelease(storedCertificate);
    }
    else {  //Server certificate not stored, rely on standard trusted Root CA authorities

        status = SecTrustEvaluate(serverTrust, &trustResult);
    }

    trusted = (status == noErr) && (trustResult == kSecTrustResultUnspecified);

    if (!trusted) credential = nil;
    [self stopWithCredential:credential];
    [self.delegate challengeHandlerDidFinish:self];
}

そのため、最初にサーバー証明書が読み込まれたかどうかを確認します(それ以外の場合は、従来の信頼できるCA方式を使用します)。

次に、評価する「信頼オブジェクト」を選択します。サーバーからネットワーク経由で受信した信頼オブジェクトの作業コピーを作成せずにこれを行うことができませんでした。直接使用した場合、「NSURLCredential * credential = [NSURLCredential credentialForTrust:serverTrust]」参照がどういうわけか台無しになりました。しかし、本当に恐ろしいAppleのドキュメントから、これはコーシャのアプローチであるように見えます(たとえば、x.509 rfcを理解したい場合は、スキミングすることをお勧めします)。

https://developer.Apple.com/library/ios/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html

[ https://developer.Apple.com/library/ios/technotes/tn2232/_index.html#//Apple_ref/doc/uid/DTS40012884-CH1-SECCUSTOMROOT] [2]

信頼には、「ポリシー」、評価する受信証明書チェーン、および基本的に任意の座標系でオリジンを定義する1つ以上の「アンカー証明書」が必要です。ルート証明書でなくても、すべてがゼロ点の下流で検証されます。 。

したがって、着信チェーンと保存された証明書を配列にロードして、新しい信頼に提供し、SecPolicyCreateSSLを使用して新しいポリシーを作成します。これにより、serverAuthに対して証明書が発行されたことを確認する必要があること、および着信サーバーが名前は無視する必要があります(インフラストラクチャの柔軟性を確保するため)。

次に、新しいポリシーと認証される証明書配列を使用して、新しい信頼を作成します。次に、アンカーを設定し、チェーンがiOSキーチェーン内のものだけでなく、アンカー証明書に対してのみ評価されるようにします。

信頼を評価するとき、kSecTrustResultUnspecifiedを受け入れて先に進まない、またはもっと前向きなことをするのは奇妙に思えるかもしれません。実際、続行とは、ユーザーインターフェイスからのオーバーライドに従っていることを意味するため、実際には問題があります。指定されていないということは、指定されたポリシーに従って何も問題がないことを意味します。

最後に、着信する信頼オブジェクト(新しいものではない)から資格情報を返すと、すべてがゴールデンになります...

0
saminpa