Linux环境Swift 4提取CMS/PKCS#7证书并验证容器签名
我之前在Linux Swift项目里折腾过OpenSSL处理CMS签名,太懂这种对着C接口抓瞎的感觉了😅 既然你已经搞定了PKCS#7的类型校验,那接下来提取证书和验证签名的核心步骤我给你拆解清楚,结合Swift封装来实现:
第一步:从PKCS#7 SignedData中提取签名证书
PKCS7的签名数据存在PKCS7_SIGNED结构体里,我们需要先拿到这个结构体指针,再从中取出证书栈(STACK_OF(X509)),最后遍历栈提取单个证书。
假设你的PKCS7封装类已经持有了OpaquePointer类型的PKCS7指针,可以加一个这样的方法:
func extractSignerCertificates() throws -> [X509] { guard let pkcs7Ptr = self.pkcs7Ptr else { throw OpenSSLError.invalidPKCS7 } // 二次校验类型(可选但稳妥) guard PKCS7_type_is_signed(pkcs7Ptr) == 1 else { throw OpenSSLError.notSignedData } // 获取SignedData结构体 guard let signedData = PKCS7_get_signedData(pkcs7Ptr) else { throw OpenSSLError.failedToGetSignedData } // 取出证书栈 guard let certStack = PKCS7_SIGNED_get_certs(signedData) else { throw OpenSSLError.noCertificatesFound } // 遍历栈转成X509封装对象 var certs: [X509] = [] let certCount = sk_X509_num(certStack) for i in 0..<certCount { if let certPtr = sk_X509_value(certStack, i) { // 假设你有X509封装类,这里初始化时接管指针所有权 let cert = X509(pointer: certPtr) certs.append(cert) } } return certs }
⚠️ 注意:OpenSSL的STACK_OF系列必须用对应的sk_*函数操作(比如sk_X509_num取数量、sk_X509_value取元素),而且X509对象的内存一定要在封装类的deinit里调用X509_free释放,避免内存泄漏。
第二步:验证CMS签名的完整性
核心用OpenSSL的PKCS7_verify函数,它需要区分内嵌式签名(签名里包含原始数据)和分离式签名(签名与数据分开存储)两种场景:
func verifySignature(originalData: Data? = nil, trustStore: X509Store? = nil) throws -> Bool { guard let pkcs7Ptr = self.pkcs7Ptr else { throw OpenSSLError.invalidPKCS7 } // 准备原始数据的BIO(分离式签名需要传,内嵌式传nil) var inBio: OpaquePointer? = nil if let data = originalData { inBio = BIO_new_mem_buf(data.withUnsafeBytes { $0.baseAddress }, Int32(data.count)) defer { BIO_free(inBio) } } // 准备信任存储(验证证书链用,不需要的话传nil) let storePtr: OpaquePointer? let shouldFreeStore: Bool if let store = trustStore { storePtr = store.storePtr shouldFreeStore = false } else { storePtr = X509_STORE_new() shouldFreeStore = true } defer { if shouldFreeStore, let ptr = storePtr { X509_STORE_free(ptr) } } // 设置验证标志:如果不需要验证证书链,用PKCS7_NOVERIFY;否则默认验证 let flags = trustStore == nil ? PKCS7_NOVERIFY : 0 // 执行签名验证 let verifyResult = PKCS7_verify(pkcs7Ptr, nil, storePtr, inBio, nil, flags) guard verifyResult == 1 else { // 提取OpenSSL错误信息用于调试 let errCode = ERR_get_error() var errBuffer = [Int8](repeating: 0, count: 1024) ERR_error_string(errCode, &errBuffer) throw OpenSSLError.signatureVerificationFailed(message: String(cString: errBuffer)) } return true }
关键细节:
- 如果是内嵌式签名,直接传
originalData: nil,PKCS7_verify会自动从PKCS7结构体里提取原始数据验证。 - 如果要验证证书链的可信性,需要先构建
X509_STORE,把信任的根证书加进去:// 构建信任存储示例 let trustStore = X509Store() let rootCertData = try Data(contentsOf: URL(fileURLWithPath: "/path/to/root.cer")) let rootCert = try X509(derData: rootCertData) X509_STORE_add_cert(trustStore.storePtr, rootCert.certPtr) - 错误处理一定要拿
ERR_get_error()提取OpenSSL的错误栈,否则根本不知道哪里出问题。
第三步:避坑提醒
- 内存管理是生命线:所有OpenSSL的C对象(BIO、PKCS7、X509等)都要手动释放,最好在Swift封装类的
deinit里调用对应的*_free函数。 - 版本兼容性:不同Linux发行版的OpenSSL版本可能有差异(比如1.1.0和3.0的函数名略有变化),测试时要对应目标环境的版本。
- 编码格式适配:如果你的CMS负载是PEM格式,需要先用
d2i_PKCS7_bio解码成PKCS7结构体再处理。
内容的提问来源于stack exchange,提问作者Shadowman
相关产品推荐
相关产品推荐

