Swift中从X509 PEM文件获取身份与证书CN的技术问题
我正在开发一个通过TLS连接服务器的Swift应用,需要设置供服务器验证的身份。手头有一个X509编码的PEM文件,该证书本地系统不认可,但服务器可验证,因此无需本地信任。我不需要导入证书到钥匙串,只需要获取可用的身份,但使用SecPKCS12Import和SecItemImport时,均返回成功码却无数据返回。
问题一:SecPKCS12Import返回成功但importArray为空?
首次调用以下代码返回成功,但importArray为空:
var importStatus = SecPKCS12Import(pkcs12Data as CFData, importOptions as CFDictionary, &importArray)
再次运行会返回errSecDuplicateItem,我不想把证书存到钥匙串,但好像被迫存储了。导入时的选项设置如下:
let importOptions: [String: Any] = [ kSecImportExportPassphrase as String: password, kSecImportItemLabel as String: identifier ]
问题二:导入时设置kSecImportItemLabel后,能否通过该标签检索身份?
尝试从钥匙串检索身份时,传入UUID作为identifier返回的是Apple身份而非目标证书;硬编码证书CN时能获取正确身份:
let identityQuery = [ kSecClass: kSecClassIdentity, kSecReturnRef: true, kSecAttrLabel: identifier ] as NSDictionary var identityItem: CFTypeRef? let status = SecItemCopyMatching(identityQuery as CFDictionary, &identityItem)
我是否操作有误?
问题三:能否从.pem文件提取CN,或转换为.der格式?
目前找到的Apple API只有SecCertificateCopyCommonName能获取CN,但它要求证书为.der格式而非.pkcs12格式,陷入两难。
另外,作为替代方案尝试SecItemImport,传入相同的pkcs12Data返回成功但items为空:
var inputFormat: SecExternalFormat = .formatPKCS12; var items : CFArray? let flags: SecItemImportExportFlags = SecItemImportExportFlags(rawValue: 0) var parameters = SecItemImportExportKeyParameters() parameters.accessRef = nil var itemType: SecExternalItemType = .itemTypeCertificate // Set import options let status = SecItemImport(pkcs12Data as CFData, nil, &inputFormat, &itemType, flags, ¶meters, nil, // importKeychain &items)
问题解答
针对问题一:importArray为空的原因
SecPKCS12Import返回成功但数组为空,核心原因是你传入的pkcs12Data并非有效的PKCS#12格式数据。你提到手里的是X509编码的PEM文件,而PKCS#12(.p12/.pfx)是包含证书和私钥的容器格式,单纯的PEM证书无法被SecPKCS12Import正确解析。
另外,errSecDuplicateItem说明首次调用其实已经将无效/空的条目存入钥匙串了,这是API在格式识别异常时的异常表现。
针对问题二:标签检索身份的问题
你操作的问题在于:当用SecPKCS12Import导入无效的PKCS#12数据时,设置的kSecImportItemLabel并没有正确关联到目标证书(因为根本没解析出有效证书)。而硬编码CN能找到正确身份,说明你之前可能通过其他方式导入过该证书,此时CN是证书本身的属性,而非你设置的标签。
正确做法是先确保导入的是有效的PKCS#12文件(包含证书+私钥),此时设置的kSecImportItemLabel会被正确关联到身份条目,之后就能通过该标签检索到对应的身份。
针对问题三:从PEM提取CN或转换为DER格式
完全可以处理PEM文件:
- PEM转DER格式:PEM是DER的Base64编码版本,只需去掉PEM头部(
-----BEGIN CERTIFICATE-----)和尾部(-----END CERTIFICATE-----),再对剩余的Base64字符串进行解码,就能得到DER格式的数据。示例代码:
func pemToDER(pemString: String) -> Data? { let cleaned = pemString.components(separatedBy: .newlines) .filter { !$0.hasPrefix("-----") } .joined() return Data(base64Encoded: cleaned) }
- 从PEM提取CN:先转成DER格式,再用
SecCertificateCreateWithData创建证书对象,最后调用SecCertificateCopyCommonName获取CN:
func getCNFromPEM(pemString: String) -> String? { guard let derData = pemToDER(pemString: pemString), let cert = SecCertificateCreateWithData(nil, derData as CFData), let commonName = SecCertificateCopyCommonName(cert) else { return nil } return commonName as String }
关于SecItemImport返回空数组的问题
同样是因为传入的不是有效的PKCS#12数据,SecItemImport无法解析出任何条目。如果要导入单纯的PEM证书,需要将inputFormat设置为.formatPEM,itemType设置为.itemTypeCertificate,同时确保PEM数据是有效的证书内容。
内容的提问来源于stack exchange,提问作者David Dworkin

