iOS证书与Swift和NSURLSession绑定
如何在Swift中将证书固定添加到NSURLSessioniOS证书与Swift和NSURLSession绑定,ios,swift,ssl,owasp,pinning,Ios,Swift,Ssl,Owasp,Pinning,如何在Swift中将证书固定添加到NSURLSession 仅包含Objective-C和NSURLConnection的示例。Swift 3+更新: 只需为nsurlsessionelegate定义一个委托类并实现didReceiveChallenge函数(此代码改编自objective-c OWASP示例): (你可以找到一个) 然后使用openssl为您的网站创建.der文件 openssl s_client -connect my-https-website.com:443 -showc
仅包含Objective-C和NSURLConnection的示例。Swift 3+更新: 只需为
nsurlsessionelegate
定义一个委托类并实现didReceiveChallenge函数(此代码改编自objective-c OWASP示例):
(你可以找到一个)
然后使用openssl为您的网站创建.der
文件
openssl s_client -connect my-https-website.com:443 -showcerts < /dev/null | openssl x509 -outform DER > my-https-website.der
@lifeisfoo答案中的
openssl
命令将在OS X中为某些使用较新密码(如ECDSA)的SSL证书提供一个错误
如果在@lifeisfoo的答案中运行openssl
命令时出现以下错误:
write:errno=54
unable to load certificate
1769:error:0906D06C:PEM routines:PEM_read_bio:no start
line:/BuildRoot/Library/Caches/com.apple.xbs/Sources/OpenSSL098/OpenSSL09
8-59.60.1/src/crypto/pem/pem_lib.c:648:Expecting: TRUSTED CERTIFICATE
您的网站的SSL证书可能使用的算法在OS X的默认openssl
版本(v0.9.X,其中不支持ECDSA)中不受支持
这是解决方法:
要获得正确的.der
文件,您必须首先brew安装openssl
,然后将@lifeisfoo答案中的openssl
命令替换为:
/usr/local/ceral/openssl/1.0.2h_1/bin/openssl[上述命令的其余部分]
自制安装命令:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
希望能有所帮助。这里是Swift 3的更新版本
import Foundation
import Security
class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
// Adapted from OWASP https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#iOS
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
if let serverTrust = challenge.protectionSpace.serverTrust {
var secresult = SecTrustResultType.invalid
let status = SecTrustEvaluate(serverTrust, &secresult)
if(errSecSuccess == status) {
if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
let serverCertificateData = SecCertificateCopyData(serverCertificate)
let data = CFDataGetBytePtr(serverCertificateData);
let size = CFDataGetLength(serverCertificateData);
let cert1 = NSData(bytes: data, length: size)
let file_der = Bundle.main.path(forResource: "name-of-cert-file", ofType: "cer")
if let file = file_der {
if let cert2 = NSData(contentsOfFile: file) {
if cert1.isEqual(to: cert2 as Data) {
completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust))
return
}
}
}
}
}
}
}
// Pinning failed
completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
}
}
多亏了这个网站上的例子:我构建了一个版本,它锁定了公钥,而不是整个证书(如果定期更新证书,会更方便) 更新:删除了强制展开并替换了分区
import Foundation
import CommonCrypto
class SessionDelegate : NSObject, URLSessionDelegate {
private static let rsa2048Asn1Header:[UInt8] = [
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00
];
private static let google_com_pubkey = ["4xVxzbEegwDBoyoGoJlKcwGM7hyquoFg4l+9um5oPOI="];
private static let google_com_full = ["KjLxfxajzmBH0fTH1/oujb6R5fqBiLxl0zrl2xyFT2E="];
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.cancelAuthenticationChallenge, nil);
return;
}
// Set SSL policies for domain name check
let policies = NSMutableArray();
policies.add(SecPolicyCreateSSL(true, (challenge.protectionSpace.host as CFString)));
SecTrustSetPolicies(serverTrust, policies);
var isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil);
if(isServerTrusted && challenge.protectionSpace.host == "www.google.com") {
let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
//Compare public key
if #available(iOS 10.0, *) {
let policy = SecPolicyCreateBasicX509();
let cfCertificates = [certificate] as CFArray;
var trust: SecTrust?
SecTrustCreateWithCertificates(cfCertificates, policy, &trust);
guard trust != nil, let pubKey = SecTrustCopyPublicKey(trust!) else {
completionHandler(.cancelAuthenticationChallenge, nil);
return;
}
var error:Unmanaged<CFError>?
if let pubKeyData = SecKeyCopyExternalRepresentation(pubKey, &error) {
var keyWithHeader = Data(bytes: SessionDelegate.rsa2048Asn1Header);
keyWithHeader.append(pubKeyData as Data);
let sha256Key = sha256(keyWithHeader);
if(!SessionDelegate.google_com_pubkey.contains(sha256Key)) {
isServerTrusted = false;
}
} else {
isServerTrusted = false;
}
} else { //Compare full certificate
let remoteCertificateData = SecCertificateCopyData(certificate!) as Data;
let sha256Data = sha256(remoteCertificateData);
if(!SessionDelegate.google_com_full.contains(sha256Data)) {
isServerTrusted = false;
}
}
}
if(isServerTrusted) {
let credential = URLCredential(trust: serverTrust);
completionHandler(.useCredential, credential);
} else {
completionHandler(.cancelAuthenticationChallenge, nil);
}
}
func sha256(_ data : Data) -> String {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0, CC_LONG(data.count), &hash)
}
return Data(bytes: hash).base64EncodedString();
}
}
<代码>导入基础
导入通用加密
类SessionDelegate:NSObject,URLSessionDelegate{
专用静态let rsa2048Asn1Header:[UInt8]=[
0x30、0x82、0x01、0x22、0x30、0x0d、0x06、0x09、0x2a、0x86、0x48、0x86、,
0xf7、0x0d、0x01、0x01、0x01、0x05、0x00、0x03、0x82、0x01、0x0f、0x00
];
私有静态let google_com_pubkey=[“4xvxzbeegwdboyogojlkcwgm7hyqougf4l+9um5oPOI=“”];
私有静态让google_com_full=[“KjLxfxajzmBH0fTH1/oujb6R5fqBiLxl0zrl2xyFT2E=“”];
func urlSession(usession:urlSession,
didReceive质询:URLAuthenticationChallenge,
completionHandler:@escaping(URLSession.AuthChallengeDisposition,URLCredential?->Void){
guard let serverTrust=challenge.protectionSpace.serverTrust else{
completionHandler(.cancelAuthenticationChallenge,nil);
返回;
}
//为域名检查设置SSL策略
let policies=NSMutableArray();
添加(SecPolicyCreateSSL(true,(challenge.protectionSpace.host作为CFString));
SectrustSetPolicys(服务器信任,策略);
var isServerTrusted=SecTrustEvaluateWithError(serverTrust,nil);
if(isServerTrusted&&challenge.protectionSpace.host==“www.google.com”){
让certificate=SecTrustGetCertificateAtIndex(serverTrust,0);
//比较公钥
如果可用(iOS 10.0,*){
让policy=SecPolicyCreateBasicX509();
让cfCertificates=[certificate]作为CFArray;
var信托:宗派信任?
具有证书的部门(CFC证书、政策和信托);
守卫信任!=nil,让pubKey=SecTrustCopyPublicKey(信任!)否则{
completionHandler(.cancelAuthenticationChallenge,nil);
返回;
}
var错误:非托管?
如果让pubKeyData=SecKeyCopyExternalRepresentation(pubKey,&错误){
var keyWithHeader=数据(字节:sessionlegate.rsa2048Asn1Header);
追加(pubKeyData作为数据);
设sha256Key=sha256(keyWithHeader);
如果(!SessionDelegate.google_com_pubkey.contains(sha256Key)){
isServerTrusted=false;
}
}否则{
isServerTrusted=false;
}
}else{//比较完整证书
让remoteCertificateData=SecCertificateCopyData(证书!)作为数据;
设sha256Data=SH256(remoteCertificateData);
如果(!SessionDelegate.google_com_full.contains(sha256Data)){
isServerTrusted=false;
}
}
}
如果(isServerTrusted){
let credential=URLCredential(信任:serverTrust);
completionHandler(.useCredential,credential);
}否则{
completionHandler(.cancelAuthenticationChallenge,nil);
}
}
func sha256(u数据:数据)->字符串{
var hash=[UInt8](重复:0,计数:Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes{
_=CC_SHA256($0,CC_LONG(data.count)和散列)
}
返回数据(字节:散列).base64EncodedString();
}
}
将网站的证书(作为.cer文件)保存在主捆绑包中。然后使用URLSessionLegate方法:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = challenge.protectionSpace.serverTrust,
SecTrustEvaluate(serverTrust, nil) == errSecSuccess,
let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
reject(with: completionHandler)
return
}
let serverCertData = SecCertificateCopyData(serverCert) as Data
guard
let localCertPath = Bundle.main.path(forResource: "shop.rewe.de", ofType: "cer"),
let localCertData = NSData(contentsOfFile: localCertPath) as Data?,
localCertData == serverCertData else {
reject(with: completionHandler)
return
}
accept(with: serverTrust, completionHandler)
}
请注意,
SecTrustEvaluate
已被弃用,应替换为SecTrustEvaluateWithError
因此:
var secresult=SecTrustResultType.invalid
让status=SecTrustEvaluate(serverTrust和secresult)
如果errSecSuccess==状态{
//进行评估
切换结果{
case.unspecified,.procedure:返回true
默认值:返回false
}
}
我之所以编写//继续求值
部分,是因为您应该验证secresult
,这可能意味着证书实际上无效。您可以选择覆盖此选项,并将任何提出的问题添加为例外,最好是在提示用户做出决定之后
应该是这样的:
如果扇区错误(服务器,无){
//证书有效,请继续。
}
第二个参数将捕获任何错误,但如果您对细节不感兴趣,可以传递ni
import Foundation
import CommonCrypto
class SessionDelegate : NSObject, URLSessionDelegate {
private static let rsa2048Asn1Header:[UInt8] = [
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00
];
private static let google_com_pubkey = ["4xVxzbEegwDBoyoGoJlKcwGM7hyquoFg4l+9um5oPOI="];
private static let google_com_full = ["KjLxfxajzmBH0fTH1/oujb6R5fqBiLxl0zrl2xyFT2E="];
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.cancelAuthenticationChallenge, nil);
return;
}
// Set SSL policies for domain name check
let policies = NSMutableArray();
policies.add(SecPolicyCreateSSL(true, (challenge.protectionSpace.host as CFString)));
SecTrustSetPolicies(serverTrust, policies);
var isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil);
if(isServerTrusted && challenge.protectionSpace.host == "www.google.com") {
let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
//Compare public key
if #available(iOS 10.0, *) {
let policy = SecPolicyCreateBasicX509();
let cfCertificates = [certificate] as CFArray;
var trust: SecTrust?
SecTrustCreateWithCertificates(cfCertificates, policy, &trust);
guard trust != nil, let pubKey = SecTrustCopyPublicKey(trust!) else {
completionHandler(.cancelAuthenticationChallenge, nil);
return;
}
var error:Unmanaged<CFError>?
if let pubKeyData = SecKeyCopyExternalRepresentation(pubKey, &error) {
var keyWithHeader = Data(bytes: SessionDelegate.rsa2048Asn1Header);
keyWithHeader.append(pubKeyData as Data);
let sha256Key = sha256(keyWithHeader);
if(!SessionDelegate.google_com_pubkey.contains(sha256Key)) {
isServerTrusted = false;
}
} else {
isServerTrusted = false;
}
} else { //Compare full certificate
let remoteCertificateData = SecCertificateCopyData(certificate!) as Data;
let sha256Data = sha256(remoteCertificateData);
if(!SessionDelegate.google_com_full.contains(sha256Data)) {
isServerTrusted = false;
}
}
}
if(isServerTrusted) {
let credential = URLCredential(trust: serverTrust);
completionHandler(.useCredential, credential);
} else {
completionHandler(.cancelAuthenticationChallenge, nil);
}
}
func sha256(_ data : Data) -> String {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0, CC_LONG(data.count), &hash)
}
return Data(bytes: hash).base64EncodedString();
}
}
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = challenge.protectionSpace.serverTrust,
SecTrustEvaluate(serverTrust, nil) == errSecSuccess,
let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
reject(with: completionHandler)
return
}
let serverCertData = SecCertificateCopyData(serverCert) as Data
guard
let localCertPath = Bundle.main.path(forResource: "shop.rewe.de", ofType: "cer"),
let localCertData = NSData(contentsOfFile: localCertPath) as Data?,
localCertData == serverCertData else {
reject(with: completionHandler)
return
}
accept(with: serverTrust, completionHandler)
}
func reject(with completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) {
completionHandler(.cancelAuthenticationChallenge, nil)
}
func accept(with serverTrust: SecTrust, _ completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
}
import Foundation
import Security
class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate {
let certFileName = "name-of-cert-file"
let certFileType = "cer"
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
if let serverTrust = challenge.protectionSpace.serverTrust {
var secresult = SecTrustResultType.invalid
let status = SecTrustEvaluate(serverTrust, &secresult)
if(errSecSuccess == status) {
if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
let serverCertificateData = SecCertificateCopyData(serverCertificate)
let data = CFDataGetBytePtr(serverCertificateData);
let size = CFDataGetLength(serverCertificateData);
let certificateOne = NSData(bytes: data, length: size)
let filePath = Bundle.main.path(forResource: self.certFileName,
ofType: self.certFileType)
if let file = filePath {
if let certificateTwo = NSData(contentsOfFile: file) {
if certificateOne.isEqual(to: certificateTwo as Data) {
completionHandler(URLSession.AuthChallengeDisposition.useCredential,
URLCredential(trust:serverTrust))
return
}
}
}
}
}
}
}
// Pinning failed
completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
}
}