0.0 簡述
文章內容包括:
- AFNetworking簡介
- ATS和HTTPS介紹
- AF中的證書驗證介紹
- 如何建立服務端和客戶端自簽名證書
- 如何建立簡單的https伺服器
- 對CA正式證書和自簽名證書的各種情況進行程式碼驗證
文中所涉及的檔案和指令碼程式碼請看這裡。
1.0 AFNetworking簡介
AFNetworking(下面簡稱AF)是一個優秀的網路框架,從事iOS開發工作的同學幾乎都用過它。
同時,AF也是一個簡單,高效的網路框架。
AF3.0版本(3.2.1)是對NSURLSession的封裝。NSURLSession是蘋果公司的HTTP協議實現,它儘可能完整地實現了所有功能,但是同蘋果的Autolayout有相同的問題,就是API複雜難用。
因此在專案實踐中,即使我們不使用AF,我們也需要對NSURLSession進行適度封裝才能夠得心應手。AF幫你做了這件事,而且可能做的更好。
AF將NSURLSession的複雜呼叫封裝到框架內部,並向外提供了更加簡單易懂的介面,它主要包含如下功能:
- 提供了AFHTTPSessionManager用於HTTP請求(GET,POST,...)
- 提供AFURLRequestSerialization用於請求封裝,新增引數,設定header,傳遞資料
- 提供AFURLResponseSerialization用於服務端返回資料的解析和過濾
- 提供AFSecurityPolicy用於HTTPS協議證書驗證
- 提供了AFNetworkReachabilityManager用於網路狀態監聽
- 提供了UIKit主要可用於圖片快取,類似於SDWebImage
AF3.0的程式碼足夠簡單,各個模組也很容易理解,就不過多介紹了,我們著重分析一下AFSecurityPolicy
這個模組。
2.0 ATS
iOS9.0版本中,包含了一個叫ATS的驗證機制,要求App網路請求必須是安全的。主要包含2點:
- 必須使用https
- https證書必須是公信機構頒發的證書
對於其中上面的第二點,在程式碼層次沒有強制要求,使用自簽名證書也是可以正常請求的,可能會在稽核階段有此要求。
3.0 AF中的證書驗證
AF中實現了對服務端證書的驗證功能,驗證通過之後,即可正常進行網路請求。
但是它沒有實現客戶端證書,所以如果伺服器要求雙向驗證的時候,我們就需要對AF進行一些擴充套件了。
關於https的介紹可以參考這裡。
服務端驗證證書的程式碼在:AFURLSessionManager.m
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
複製程式碼
在NSURLSession中,當請求https的介面時,會觸發- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
回撥,在這個回撥中,你需要驗證服務端傳送過來的證書,並返回一個NSURLCredential
物件。
其中 disposition
這個變數用於表示你對證書的驗證結果,NSURLSessionAuthChallengeUseCredential
表示驗證通過,其他值都表示驗證失敗。
challenge.protectionSpace.authenticationMethod
這個列舉字串表示的是回撥觸發的原因,其中,NSURLAuthenticationMethodServerTrust
表示服務端發來證書,NSURLAuthenticationMethodClientCertificate
表示服務端請求驗證客戶端證書。
驗證證書的方法在AFSecurityPolicy.m中
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
// According to the docs, you should only trust your provided certs for evaluation.
// Pinned certificates are added to the trust. Without pinned certificates,
// there is nothing to evaluate against.
//
// From Apple Docs:
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
// Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}
複製程式碼
程式碼解析:
-
函式第一行就是一長串的邏輯判斷,乍一看,這裡看的人很懵,它包含的資訊很多。但實際上它的作用是用來處理
服務端自簽名證書
的。其他情況無需考慮此處邏輯。根據後面程式碼來看,如果你服務端證書使用的是自簽名證書,AFSecurityPolicy
的allowInvalidCertificates
屬性必須設為YES,所以這裡判斷會帶上self.allowInvalidCertificates
。 -
接下來就是驗證服務端證書的過程,
SSLPinningMode
有3個值,AFSSLPinningModeNone
表示服務端使用的是CA機構簽發的正式證書
,另外2個值表示服務端使用的是自簽名證書。 -
AFServerTrustIsValid
這個函式使用的是Security.framework
中的方法,用於驗證服務端傳送來的證書是否是可信任的,只要證書鏈中任何一個證書是已經信任的證書,那麼這個服務端證書就是合法的。詳細過程已經被Security.framework
處理了,不需要我們做額外工作。關於證書鏈可以參考這裡。 -
第三部分程式碼就是
服務端自簽名證書
的驗證了,這種情況下,需要把服務端證書也放到客戶端中一份。根據SSLPinningMode
,你可以選擇使用服務端證書
或者服務端證書內的公鑰
。 -
AFSSLPinningModeCertificate
表示客戶端需要儲存一個服務端根證書
,用於驗證服務端證書是否合法。客戶端需要將服務端證書的證書鏈上的任意一個證書拖入xcode工程中。 -
自簽名證書需要設定
pinnedCertificates
屬性,把拖入xcode的證書載入到記憶體中,儲存在pinnedCertificates
陣列中。通過SecTrustSetAnchorCertificates
方法把陣列中的證書同服務端返回的證書做證書鏈繫結,然後就可以用AFServerTrustIsValid
方法驗證證書是否合法了,如果服務端證書和我們客戶端儲存的證書可以正確匹配,這個函式就會返回YES。 -
AFSSLPinningModePublicKey
表示客戶端需要儲存一個服務端根證書公鑰
,用於驗證服務端證書是否合法。客戶端需要將服務端證書鏈上的任意一證書的公鑰拖入xcode工程中。 -
若使用公鑰驗證,則需要從服務端證書中取出公鑰,同時取出客戶端中儲存的公鑰,逐一比較,如果有匹配的就認為驗證成功。
根據上述分析,客戶端對於證書的使用,有下面的3種情況:
- 服務端使用CA機構頒發的正式證書
- 服務端使用自簽名證書
- 服務端要驗證客戶端證書時,客戶端使用自簽名證書
4.0 證書驗證實踐
我們對上面所述3種證書使用情形進行逐一驗證。
驗證之前,我們需要做3個準備工作:
- 第一是要把所需的證書建立出來
- 第二是搭建簡單的伺服器用於測試
- 第三是建立客戶端工程引入AF3.0準備測試
4.1 建立證書
https使用的證書都是基於X.509格式的。
CA機構的正式證書一般是要花錢購買的,當然也有免費的,我之前在阿里雲買過免費的證書。一般申請通過後,你可以把證書下載下來,其中主要包含私鑰和各種格式的證書。
自簽名的證書就比較容易了,在mac中可以使用openssl命令來生成。
我寫了一個簡單的指令碼,用於生成各種自簽名證書,你可以把它儲存到檔案(檔名為:create.sh)中,在終端裡執行。
指令碼會生成3種證書:根證書,客戶端證書,服務端證書。
其中不同的證書沒有本質區別,只是用在不同的地方而已。
每種證書包含5個檔案,分別是:
- .der格式證書
- .pem格式證書
- .p12格式證書
- .pem格式私鑰
- .csr格式證書申請檔案
#!/bin/sh
locale='CN' #地區
province='Beijing' #省份
city=$province #城市
company='xxx' #公司
unit='yyy' #部門
hostname='127.0.0.1' #域名
email='hr@suning.com' #郵箱
#clean
function clean(){
echo '清理檔案...'
ls | grep -v create.sh | xargs rm -rf
}
#用法
function usage(){
echo 'usage: ./create.sh
[-l [localevalue]]
[-p [provincevalue]]
[-c [cityvalue]]
[-d [companyvalue]]
[-u [unitvalue]]
[-h [hostnamevalue]]
[-e [emailvalue]]
'
exit
}
#引數
if [ $# -gt 0 ]; then
while getopts "cl:p:c:d:u:h:e" arg;
do
case $arg in
c)
clean && exit
;;
l)
locale=$OPTARG
;;
p)
province=$OPTARG
;;
c)
city=$OPTARG
;;
d)
company=$OPTARG
;;
u)
unit=$OPTARG
;;
h)
hostname=$OPTARG
;;
e)
email=$OPTARG
;;
?)
usage
;;
esac
done
fi
clean
echo '開始建立根證書...'
openssl genrsa -out ca-private-key.pem 1024
openssl req -new -out ca-req.csr -key ca-private-key.pem <<EOF
${locale}
${province}
${city}
${company}
${unit}
${hostname}
${email}
EOF
openssl x509 -req -in ca-req.csr -out ca-cert.pem -outform PEM -signkey ca-private-key.pem -days 3650
openssl x509 -req -in ca-req.csr -out ca-cert.der -outform DER -signkey ca-private-key.pem -days 3650
echo '請輸入根證書p12檔案密碼,直接回車表示密碼為空字串...'
openssl pkcs12 -export -clcerts -in ca-cert.pem -inkey ca-private-key.pem -out ca-cert.p12
echo '開始建立服務端證書...'
openssl genrsa -out server-private-key.pem 1024
openssl req -new -out server-req.csr -key server-private-key.pem << EOF
${locale}
${province}
${city}
${company}
${unit}
${hostname}
${email}
EOF
openssl x509 -req -in server-req.csr -out server-cert.pem -outform PEM -signkey server-private-key.pem -CA ca-cert.pem -CAkey ca-private-key.pem -CAcreateserial -days 3650
openssl x509 -req -in server-req.csr -out server-cert.der -outform DER -signkey server-private-key.pem -CA ca-cert.pem -CAkey ca-private-key.pem -CAcreateserial -days 3650
echo '請輸入服務端證書p12檔案密碼,直接回車表示密碼為空字串...'
openssl pkcs12 -export -clcerts -in server-cert.pem -inkey server-private-key.pem -out server-cert.p12
echo '開始建立客戶端證書...'
openssl genrsa -out client-private-key.pem 1024
openssl req -new -out client-req.csr -key client-private-key.pem << EOF
${locale}
${province}
${city}
${company}
${unit}
${hostname}
${email}
EOF
openssl x509 -req -in client-req.csr -out client-cert.pem -outform PEM -signkey client-private-key.pem -CA ca-cert.pem -CAkey ca-private-key.pem -CAcreateserial -days 3650
openssl x509 -req -in client-req.csr -out client-cert.der -outform DER -signkey client-private-key.pem -CA ca-cert.pem -CAkey ca-private-key.pem -CAcreateserial -days 3650
echo '請輸入客戶端證書p12檔案密碼,直接回車表示密碼為空字串...'
openssl pkcs12 -export -clcerts -in client-cert.pem -inkey client-private-key.pem -out client-cert.p12
echo 'finishied'
複製程式碼
你可以按照步驟操作:
-
複製指令碼內容,儲存到檔案中,檔名為
create.sh
-
開啟終端,通過
cd
命令進入create.sh
所在的資料夾 -
在終端內輸入:
chmod +x create.sh
點選回車 -
在終端輸入:
./create.sh -h
,此時會列印用法usage: ./create.sh [-l [localevalue]] [-p [provincevalue]] [-c [cityvalue]] [-d [companyvalue]] [-u [unitvalue]] [-h [hostnamevalue]] [-e [emailvalue]] 複製程式碼
指令碼有下面幾種用法:
./create.sh -h
列印用法./create.sh -c
會清空生成的所有檔案./create.sh
直接回車,會使用預設引數生成證書./create.sh + 用法中所述選項
會使用自定義的引數生成證書
指令碼執行成功後,應該會生成下面的檔案:
4.2 搭建簡單的HTTPS伺服器
我們使用nodejs來搭建https伺服器,請按照如下步驟操作:
- 首先下載nodejs並安裝
- 建立一個資料夾,資料夾內建立一個檔案,名字為
package.json
,內容如下:
{
"name": "test-https",
"version": "1.0.0",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"debug": true,
"dependencies": {
"koa": "2.5.2",
"koa-router": "7.4.0"
}
}
複製程式碼
- 建立另一個檔案,名字為
app.js
,內容如下:
const Koa = require('koa');
const https = require('https');
const fs = require('fs');
const router = require('koa-router')();
const app = new Koa();
//路由
router.get('/', (ctx, next) => {
ctx.response.body = 'this is a simple node js https server response';
})
app.use(router.routes());
//https
https.createServer({
key: fs.readFileSync('./yourServerCertPrivatekey.key'),
cert: fs.readFileSync('./yourServerCert.pem'),
requestCert: true,
ca:[fs.readFileSync('./yourClientCert.pem')]
}, app.callback()).listen(3000);
console.log(`https app started at port 3000`)
複製程式碼
- 開啟終端,使用
cd
命令進入我們建立的伺服器資料夾,然後執行命令:npm install
,等待命令完成(可能會比較慢,根據網路情況而定)。如出現下列字樣表示安裝成功(不一定完全相同):
added 40 packages from 21 contributors and audited 53 packages in 8.446s
found 0 vulnerabilities
複製程式碼
- 至此我們的簡易https伺服器就搭建完成了。我們可以使用命令:
node app.js
來啟動伺服器。但是你會發現會報錯,這是因為fs.readFileSync(filename)
這句程式碼表示要讀取一個證書檔案,要確保檔案存在才可以。我們後續根據需求來修改此處檔案路徑即可。 - 伺服器啟動成功後,你可以在終端看到下面的文字:
https app started at port 3000
複製程式碼
4.3 建立客戶端工程
這個比較簡單,就不多說了。我們使用下列基本程式碼來做證書測試。
-(void) test{
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
//HTTPS驗證程式碼,我們主要修改這裡
AFSecurityPolicy *policy = [AFSecurityPolicy defaultPolicy];
policy.validatesDomainName = NO;//不驗證域名,是為了測試方便,否則你需要修改host檔案了
manager.securityPolicy = policy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
//請求地址就寫這個
[manager GET:@"https://127.0.0.1:3000/" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"succ and response = [%@]", [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"fail");
}];
}
複製程式碼
4.4 服務端使用CA機構頒發的正式證書
這個是最簡單的情況,AF已經支援,我們不需要做任何額外工作就能夠支援。
首先,我們將服務端的程式碼中的證書路徑指向我們在CA機構申請好的服務端證書路徑,其中key
表示證書私鑰,cert
表示pem格式證書。另外將requestCert
和ca
這兩個欄位先刪除,然後重新啟動伺服器。像下面這樣:
... ...
//https
https.createServer({
key: fs.readFileSync(這裡改成你的私鑰路徑),
cert: fs.readFileSync(這裡改成你的pem格式證書路徑)
}, app.callback()).listen(3000);
... ...
複製程式碼
然後,客戶端的程式碼不需要修改。直接執行xcode,正常情況下你可以看到如下輸出:
succ and response = [this is a simple node js https server response]
複製程式碼
4.5 服務端使用自簽名證書
服務端程式碼不變,只是將證書和私鑰路徑修改為我們自簽名的證書路徑。
上文中,我們已經建立過自簽名的證書。
首先把證書資料夾的私鑰檔案server-private-key.pem
和證書檔案server-cert.pem
複製到伺服器資料夾下。
然後伺服器程式碼修改如下:
... ...
//https
https.createServer({
key: fs.readFileSync('./server-private-key.pem'),
cert: fs.readFileSync('./server-cert.pem')
}, app.callback()).listen(3000);
... ...
複製程式碼
重啟伺服器。
客戶端需要把證書資料夾內的server-cert.der
檔案拖入xcode中,然後將xcode中的證書修改名字為server-cert.cer
。
客戶端程式碼做如下修改(請看註釋):
-(void) test{
//使用伺服器自簽名證書,需要指定baseUrl屬性。
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"https://127.0.0.1:3000"]];
//AFSSLPinningModeCertificate表示使用自簽名證書
AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
//為了測試方便不驗證域名,若要驗證域名,則請求時的域名要和建立證書(建立證書的指令碼執行時可指定域名)時的域名一致
policy.validatesDomainName = NO;
//自簽名伺服器證書需要設定allowInvalidCertificates為YES
policy.allowInvalidCertificates = YES;
//指定本地證書路徑
policy.pinnedCertificates = [AFSecurityPolicy certificatesInBundle:[NSBundle mainBundle]];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:@"https://127.0.0.1:3000/" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"succ and response = [%@]", [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"fail");
}];
}
複製程式碼
執行工程,正常情況下,可以看到正確輸出。
4.6 服務端驗證客戶端證書
這叫做雙向驗證,客戶端驗證服務端無誤之後,服務端也可以驗證客戶端證書,這樣可以保證資料傳輸雙方都是自己想要的目標。
首先,把證書資料夾內的client-cert.pem
檔案複製到伺服器資料夾內。
然後修改服務端程式碼:
... ...
//https
https.createServer({
key: fs.readFileSync('./server-private-key.pem'),
cert: fs.readFileSync('./server-cert.pem'),
requestCert: true,//表示客戶端需要證書
ca:[fs.readFileSync('./client-cert.pem')]//用於匹配客戶端證書
}, app.callback()).listen(3000);
... ...
複製程式碼
重啟伺服器。
客戶端需要把證書資料夾內的client-cert.p12
檔案拖到xcode中。
客戶端請求程式碼不需要修改。
因為AF3.0並沒有提供對客戶端證書的支援,所以我們需要修改AF的程式碼。
找到AFURLSessionManager.m
檔案,在- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
方法。
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
NSString *authMethod = challenge.protectionSpace.authenticationMethod;
if ([authMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else if([authMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]){
NSData *p12Data = [NSData dataWithContentsOfFile:[NSBundle pathForResource:@"client-cert" ofType:@"p12" inDirectory:[NSBundle mainBundle].bundlePath]];
if([p12Data isKindOfClass:[NSData class]]){
SecTrustRef trust = NULL;
SecIdentityRef identity = NULL;
[[self class] extractIdentity:&identity andTrust:&trust fromPKCS12Data:p12Data];
if(identity){
SecCertificateRef certificate = NULL;
SecIdentityCopyCertificate(identity, &certificate);
const void *certs[] = {certificate};
CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);
credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
disposition = NSURLSessionAuthChallengeUseCredential;
}else{
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}else{
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
+ (BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
OSStatus securityError = errSecSuccess;
//客戶端證書密碼
NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject: @""
forKey: (__bridge id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data,(__bridge CFDictionaryRef)optionsDictionary ,&items);
if(securityError == 0) {
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
*outIdentity = (SecIdentityRef)tempIdentity;
const void *tempTrust = NULL;
tempTrust = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust);
*outTrust = (SecTrustRef)tempTrust;
return YES;
} else {
NSLog(@"SecPKCS12Import is failed with error code %d", (int)securityError);
return NO;
}
}
複製程式碼
上述程式碼參考自這裡。
值得注意的有2個地方:
- 一個是
p12
檔案的檔名,我們這裡寫死了client-cert.p12
,可以根據具體情況做修改。 - 還有一個是
p12
檔案的密碼,在extractIdentity:
方法的第三行,可以改成你的p12檔案密碼,密碼可以為空。
程式碼修改好之後,執行工程,可以得到正確的服務端返回。
5.0 總結
文中內容均已經過測試,但仍然可能有錯誤之處,如發現請留言。
文中所涉及的指令碼
,證書
,伺服器程式碼
, 客戶端程式碼
,已經上傳到github中,點這裡,都已經包含了安裝環境,下載後直接開啟就能使用。
--完--