ios进阶

SecCertificate,SecKey,SecTrust详解

2019-05-02  本文已影响0人  狼性刀锋

Security Framework 总结

SecCertificate

A digital certificate is a collection of data used to securely distribute the public half of a public/private key pair.

结构

image.png

读取和存储

var certificate: SecCertificate?
let status = SecIdentityCopyCertificate(identity, &certificate)
guard status == errSecSuccess else { throw <# an error #> 
// get data for certificate
let certificate = <# a certificate #>
let certData = SecCertificateCopyData(certificate) as Data

// create  certificate with data
let certificate = SecCertificateCreateWithData(nil, certData as CFData)

//  self.certificate is SecCertificate instance
        let addquery: [String: Any] = [
            kSecClass as String: kSecClassCertificate,
            kSecValueRef as String: self.certificate,
            kSecAttrLabel as String: "My Certificate",
        ]

        var status = SecItemAdd(addquery as CFDictionary, nil)
        guard status == errSecSuccess else {
            XCTFail()
            return
        }

        let getquery: [String: Any] = [
            kSecClass as String: kSecClassCertificate,
            kSecAttrLabel as String: "My Certificate",
            kSecReturnRef as String: kCFBooleanTrue,
        ]

        var item: CFTypeRef?
        status = SecItemCopyMatching(getquery as CFDictionary, &item)
        guard status == errSecSuccess else {
            XCTFail()
            return
        }
        let certificate = item as! SecCertificate

属性解析

SecKey

SecKey即可以是public key 也可以是 private key.存在一下三种编码方式:

For an RSA key, the function returns data in the PKCS #1 format.

For an elliptic curve public key, the format follows the ANSI X9.63 standard using a byte string of 04 || X || Y.

For an elliptic curve private key, the output is formatted as the public key concatenated with the big endian encoding of the secret scalar, or 04 || X || Y || K.

读取和存储

    func loadP12File() -> Dictionary<String, Any> {
        var url = Bundle(for: P12FileStorageAndParseTests.self).resourceURL!
        url.appendPathComponent("fitchAppleAccount.p12")
        
        let data = try! Data(contentsOf: url)
        
        let password = "******"
        let options = [kSecImportExportPassphrase as String: password]
        
        var rawItems: CFArray?
        // suport DER 和 PEM Encode
        let status = SecPKCS12Import(data as CFData,
                                     options as CFDictionary,
                                     &rawItems)
        
        guard status == errSecSuccess else {
            fatalError("parse p12 file failure")
        }
        // p12 文件可以包含多个证书和私钥
        let items = rawItems! as! Array<Dictionary<String, Any>>
        let firstItem = items[0]
        return firstItem
    }

    func testParseP12File() {
        let p12Attrs = loadP12File()

        // parse p12 file
        let identity = p12Attrs[kSecImportItemIdentity as String] as! SecIdentity

    }
  let persistentRef = addToKeyChain() as! NSData
    
// 一旦存储成功, 需要将这个唯一标识符进行永久性存储    
  UserDefaults.standard.set(persistentRef, forKey: "kSecReturnPersistentRef")

    func addToKeyChain() -> CFTypeRef? {
        let p12Attrs = loadP12File()
        
        // parse p12 file
        let identity = p12Attrs[kSecImportItemIdentity as String] as! SecIdentity
        
        var addResult: CFTypeRef?
        let addquery: [String: Any] = [
            kSecValueRef as String: identity,
            kSecReturnPersistentRef as String: true,
            ]

        let status = SecItemAdd(addquery as CFDictionary, &addResult)
        guard status == errSecSuccess else {
            print("addToKeyChain failure: \(status)")
            return nil
        }
        
        return addResult
    }


     UserDefaults.standard.set(persistentRef, forKey: "kSecReturnPersistentRef")

        
        let identity = queryItem(persistentRef: persistentRef)!
        
        // get  private key
        var privateKey: SecKey?
        var status = SecIdentityCopyPrivateKey(identity, &privateKey)
        
        XCTAssert(status == noErr)
        XCTAssert(privateKey != nil)
        
        // get public key
        var certificate: SecCertificate?
        status = SecIdentityCopyCertificate(identity, &certificate)
        
        XCTAssert(status == noErr)
        XCTAssert(certificate != nil)
        let publicKey = SecCertificateCopyKey(certificate!)
        XCTAssert(publicKey != nil)

    func queryItem(persistentRef: NSData) -> SecIdentity? {
        let getquery: [String: Any] = [
            kSecReturnRef as String: kCFBooleanTrue,
            kSecValuePersistentRef as String: persistentRef,
            ]
        
        var item: CFTypeRef?
        let status = SecItemCopyMatching(getquery as CFDictionary, &item)
        guard status == errSecSuccess else {
           
            print("queryItem failure \(status)")
            return nil
        }
        return item as! SecIdentity
    }
// 获取SecTrust 的 leaf cert public key
let publicKey = SecTrustCopyPublicKey(trust)
//通过 privateKey 计算 publicKey
let publicKey = SecKeyCopyPublicKey(privateKey)

// this code from Alamofire
    private static func publicKey(for certificate: SecCertificate) -> SecKey? {
        var publicKey: SecKey?

        let policy = SecPolicyCreateBasicX509()
        var trust: SecTrust?
        let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust)

        if let trust = trust, trustCreationStatus == errSecSuccess {
            publicKey = SecTrustCopyPublicKey(trust)
        }

        return publicKey
    }
let key = <# a key #>
var error: Unmanaged<CFError>?
guard let data = SecKeyCopyExternalRepresentation(key, &error) as Data else {
    throw error!.takeRetainedValue() as Error
}
let options: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
                              kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
                              kSecAttrKeySizeInBits as String : 2048]
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateWithData(data as CFData,
                                     options as CFDictionary,
                                     &error) else {
                                        throw error!.takeRetainedValue() as Error
}

Note: 使用苹果官方API生成的PublicData和使用OpenSSL 生成的PublicData 存在一些差异,苹果的缺少头部信息, OpenSSL拥有头部信息,所以建议使用一下方案任意一种解决问题:

*  对苹果API生成的PublicKeyData拼接额外的头部信息
*  对OpenSSL生成的PublicKeyData,去除头部信息
*  放弃OpenSSL,使用苹果API生成PublicKeyData

其他功能

SecIdentity

用于检索SecCertificate 和 SecKey from keyChain, 使用示例已在前面描述。

SecPolicy

证书评估策略

SecTrust

用于证书评估

创建

let policy = SecPolicyCreateBasicX509()
var optionalTrust: SecTrust?
var status = SecTrustCreateWithCertificates(certArray as AnyObject,
                                            policy,
                                            &optionalTrust)

证书评估以及解析结果

if status == errSecSuccess {
    let trust = optionalTrust!    // Safe to force unwrap now
    SecTrustEvaluateAsync(trust, DispatchQueue.global()) {
        _, trustResult in
        switch trustResult {
        case .proceed, .unspecified:
            let publicKey = SecTrustCopyPublicKey(trust)
            
            // Use key . . .
        case .recoverableTrustFailure:
            print("Trust failed recoverably")
        default:
            print("Trust failed absolutely")
        }
    }
}

: SecTrustEvaluateAsync 是最新的API, 现在主要用的还是SecTrustEvaluate

实例解析

    func testValidateTrust() {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
        
        // 这是一个合法的ServerTrust, 拥有leafCert, intermediateCert, rootCert
        // 注意:serverTrust的certificates数组一定是这个顺序
        let serverTrust = createDefaultTrust()
        XCTAssert(certificates(for: serverTrust).count == 3)
        
        
        // 设置认证策略
        // 进行域名认证可以有效防止: A站使用B站证书,但B站的证书是合法的, 从而最终导致和A站认证通过情况出现
        let policy = SecPolicyCreateSSL(true,  "test.alamofire.org" as CFString)
        SecTrustSetPolicies(serverTrust, policy)
        
        
        // 设置SecTrustSetAnchorCertificates后SecTrustGetCertificateCount的值可能改变
        // * 取决于证书AnchorCertificates 具体是leaf or Intermediate or root , 那么对应的数量就是1,2,3
        // * 如果AnchorCertificates 证书非法,则数量不变
        
        
        // root cert是一张自签名证书,所以需要SecTrustSetAnchorCertificates 信任根证书,才能完成认证
        // 如果root cert 是第三方机构颁发的则不用,设置该属性,也可以通过
        // 认证通过
        SecTrustSetAnchorCertificates(serverTrust, [RootCertificate.root.asCertificate()] as CFArray)
        SecTrustSetAnchorCertificatesOnly(serverTrust, true)
        XCTAssert(trustIsValid(serverTrust))
        
        
        // 信任 intermediateCert Cert 认证通过
        SecTrustSetAnchorCertificates(serverTrust, [IntermediateCert.ca1.asCertificate()] as CFArray)
        SecTrustSetAnchorCertificatesOnly(serverTrust, true)
        XCTAssert(trustIsValid(serverTrust))
        
 
        
        // 信任Leaf Cert 认证通过
        SecTrustSetAnchorCertificates(serverTrust, [LeafCertCA1.signed.asCertificate()] as CFArray)
        SecTrustSetAnchorCertificatesOnly(serverTrust, true)
        XCTAssert(trustIsValid(serverTrust))
        
        // 信任一张不在SeverTrust证书列表中的证书, 认证不通过
        SecTrustSetAnchorCertificates(serverTrust, [LeafCertCA2.expired.asCertificate()] as CFArray)
        SecTrustSetAnchorCertificatesOnly(serverTrust, true)
        XCTAssert(trustIsValid(serverTrust) == false)
        
        
        // 信任两张证书: 一张在SeverTrust证书列表中, 一张不在, 认证通过
        SecTrustSetAnchorCertificates(serverTrust, [LeafCertCA2.expired.asCertificate(), LeafCertCA1.signed.asCertificate()] as CFArray)
        SecTrustSetAnchorCertificatesOnly(serverTrust, true)
        XCTAssert(trustIsValid(serverTrust) == true)
        
        
    }
    
    func testValidateExpiredCert() {
  
        /// leaf cert 已经过期的ServerTrust
        let serverTrust = createExpiredTrust()
        XCTAssert(certificates(for: serverTrust).count == 3)
        
        
        // 不认证域名
        let policy = SecPolicyCreateSSL(true,  nil)
        SecTrustSetPolicies(serverTrust, policy)
        
        // 无论信任对应的RootCert, IntermediateCert or leafCert 都无法通过评估
        SecTrustSetAnchorCertificates(serverTrust, [RootCertificate.root.asCertificate()] as CFArray)
        SecTrustSetAnchorCertificatesOnly(serverTrust, true)
        
        XCTAssert(trustIsValid(serverTrust) == false)
        
        
        
        SecTrustSetAnchorCertificates(serverTrust, [IntermediateCert.ca2.asCertificate()] as CFArray)
        SecTrustSetAnchorCertificatesOnly(serverTrust, true)
        
        XCTAssert(trustIsValid(serverTrust) == false)
        
        SecTrustSetAnchorCertificates(serverTrust, [LeafCertCA2.expired.asCertificate()] as CFArray)
        SecTrustSetAnchorCertificatesOnly(serverTrust, true)
        
        XCTAssert(trustIsValid(serverTrust) == false)
    }

Alomofire 服务端认证策略解析

    public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
        var serverTrustIsValid = false

        switch self {
// 默认策略,只要证书合法即可通过评估
// 例如用户访问 简书、百度等证书合法网页
        case let .performDefaultEvaluation(validateHost):
            let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
            SecTrustSetPolicies(serverTrust, policy)

            serverTrustIsValid = trustIsValid(serverTrust)
// 并不确定具体使用场景,不做评价
        case let .performRevokedEvaluation(validateHost, revocationFlags):
            let defaultPolicy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
            let revokedPolicy = SecPolicyCreateRevocation(revocationFlags)
            SecTrustSetPolicies(serverTrust, [defaultPolicy, revokedPolicy] as CFTypeRef)

            serverTrustIsValid = trustIsValid(serverTrust)
// 校验服务端证书
        case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
            if validateCertificateChain {
                let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
                SecTrustSetPolicies(serverTrust, policy)
 // 设置信任证书,如果ServerTrust的证书不在该列表中则无法通过评估
// 设置RootCert 或者 intermediateCert 可能导致非预期的网页访问, 建议只将leaf cert 加入信任列表
// leaf cert 容易过期,建议提前准备好多张不同日期的leaf cert 或者设置允许leaf cert过期

                SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray)
                SecTrustSetAnchorCertificatesOnly(serverTrust, true)

                serverTrustIsValid = trustIsValid(serverTrust)
            } else {
// 这里只要服务端的任何一张符合条件的cert,即可通过评估
// 相对于validateCertificateChain,会显得宽松一些
// 例如: 服务端的leafCert过期了,而客户端仍然存有这张过期的leafCert, 认证仍然可以通过
                let serverCertificatesDataArray = certificateData(for: serverTrust)
                let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)

                outerLoop: for serverCertificateData in serverCertificatesDataArray {
                    for pinnedCertificateData in pinnedCertificatesDataArray {
                        if serverCertificateData == pinnedCertificateData {
                            serverTrustIsValid = true
                            break outerLoop
                        }
                    }
                }
            }
// 校验SecKey  流程几乎和 校验Certificate一致,只是将Certificate转换成为SecKey罢了
        case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost):
            var certificateChainEvaluationPassed = true

            if validateCertificateChain {
                let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
                SecTrustSetPolicies(serverTrust, policy)

                certificateChainEvaluationPassed = trustIsValid(serverTrust)
            }

            if certificateChainEvaluationPassed {
                outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] {
                    for pinnedPublicKey in pinnedPublicKeys as [AnyObject] {
                        if serverPublicKey.isEqual(pinnedPublicKey) {
                            serverTrustIsValid = true
                            break outerLoop
                        }
                    }
                }
            }
// 放弃评估,直接通过,强烈建议不要用在生成环境
        case .disableEvaluation:
            serverTrustIsValid = true
// 用于自定义评估策略,通常在上述几个策略不符合要求的时候使用
        case let .customEvaluation(closure):
            serverTrustIsValid = closure(serverTrust, host)
        }

        return serverTrustIsValid
    }

代码传送门

https://github.com/a5978445/SecurityPractice.git

上一篇 下一篇

猜你喜欢

热点阅读