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读取和存储
- Identity
var certificate: SecCertificate?
let status = SecIdentityCopyCertificate(identity, &certificate)
guard status == errSecSuccess else { throw <# an error #>
- Der-Encode Data from File
// 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)
- KeyChain
- add to KeyChain
// 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
}
- read from keyChain
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
属性解析
- 官方API: SecCertificateCopy系列函数(注: 官方iOS 平台API 只提供了少量的属性访问)
- 第三方解析: https://github.com/filom/ASN1Decoder
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.
读取和存储
-
An identity.:
- load p12 file
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
}
- get SecIdentity from p12Attrs
func testParseP12File() {
let p12Attrs = loadP12File()
// parse p12 file
let identity = p12Attrs[kSecImportItemIdentity as String] as! SecIdentity
}
- add to key chain
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
}
- load from key chain
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
}
- A trust.
// 获取SecTrust 的 leaf cert public key
let publicKey = SecTrustCopyPublicKey(trust)
- Another key.
//通过 privateKey 计算 publicKey
let publicKey = SecKeyCopyPublicKey(privateKey)
- SecCertificate
// 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
}
- Data.
- generate Data from SecKey
let key = <# a key #>
var error: Unmanaged<CFError>?
guard let data = SecKeyCopyExternalRepresentation(key, &error) as Data else {
throw error!.takeRetainedValue() as Error
}
- generate SecKey from Data
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
证书评估策略
-
func SecPolicyCreateBasicX509() -> SecPolicy
,通常用于创建SecTrust
-
func SecPolicyCreateRevocation(_ revocationFlags: CFOptionFlags) -> SecPolicy?
, 这个很少使用,并不知道具体的使用场景
-
func SecPolicyCreateSSL(_ server: Bool, _ hostname: CFString?) -> SecPolicy
, 用于SSL 证书认证
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
}
网友评论