如何用两个PEM文件构建URLCredential?Python转Swift代码问询
从PEM文件生成SecIdentity并实现客户端证书认证的Swift请求
要实现和你提供的Python代码相同的客户端证书认证请求,核心是从cert.pem和key.pem文件生成SecIdentity对象,以下是完整步骤和代码:
1. 解析PEM文件为SecCertificate和SecKey
PEM格式的文件需要先去除头部尾部标记,再转换成Data,然后分别解析为证书和私钥:
import Foundation import Security // 解析PEM格式证书为SecCertificate func parsePEMCertificate(from filePath: String) throws -> SecCertificate { guard let pemString = try? String(contentsOfFile: filePath) else { throw NSError(domain: "PEMParseError", code: 1, userInfo: [NSLocalizedDescriptionKey: "无法读取证书文件"]) } // 去除PEM头部、尾部和换行符 let cleanedPEM = pemString.replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "") .replacingOccurrences(of: "-----END CERTIFICATE-----", with: "") .replacingOccurrences(of: "\n", with: "") .replacingOccurrences(of: "\r", with: "") guard let data = Data(base64Encoded: cleanedPEM) else { throw NSError(domain: "PEMParseError", code: 2, userInfo: [NSLocalizedDescriptionKey: "证书Base64解码失败"]) } guard let certificate = SecCertificateCreateWithData(nil, data as CFData) else { throw NSError(domain: "PEMParseError", code: 3, userInfo: [NSLocalizedDescriptionKey: "无法创建SecCertificate"]) } return certificate } // 解析PEM格式私钥为SecKey func parsePEMPrivateKey(from filePath: String) throws -> SecKey { guard let pemString = try? String(contentsOfFile: filePath) else { throw NSError(domain: "PEMParseError", code: 1, userInfo: [NSLocalizedDescriptionKey: "无法读取私钥文件"]) } // 去除PEM头部、尾部和换行符 let cleanedPEM = pemString.replacingOccurrences(of: "-----BEGIN PRIVATE KEY-----", with: "") .replacingOccurrences(of: "-----END PRIVATE KEY-----", with: "") .replacingOccurrences(of: "\n", with: "") .replacingOccurrences(of: "\r", with: "") guard let data = Data(base64Encoded: cleanedPEM) else { throw NSError(domain: "PEMParseError", code: 2, userInfo: [NSLocalizedDescriptionKey: "私钥Base64解码失败"]) } // 创建SecKey的配置 let attributes: [CFString: Any] = [ kSecAttrKeyType: kSecAttrKeyTypeRSA, kSecAttrKeyClass: kSecAttrKeyClassPrivate ] var error: Unmanaged<CFError>? guard let privateKey = SecKeyCreateWithData(data as CFData, attributes as CFDictionary, &error) else { throw error!.takeRetainedValue() as Error } return privateKey }
2. 从证书和私钥创建SecIdentity
拿到SecCertificate和SecKey后,通过SecIdentityCreateWithCertificate创建SecIdentity:
func createSecIdentity(cert: SecCertificate, privateKey: SecKey) throws -> SecIdentity { var identity: SecIdentity? let status = SecIdentityCreateWithCertificate(nil, cert, &identity) guard status == errSecSuccess, let identity = identity else { throw NSError(domain: "IdentityCreateError", code: Int(status), userInfo: [NSLocalizedDescriptionKey: "无法创建SecIdentity"]) } // 验证私钥是否匹配证书(可选但推荐) guard SecKeyIsAlgorithmSupported(privateKey, .sign, .rsaSignaturePKCS1v15SHA256) else { throw NSError(domain: "IdentityCreateError", code: 4, userInfo: [NSLocalizedDescriptionKey: "私钥算法不支持"]) } return identity }
3. 实现URLSessionDelegate提供客户端证书
自定义Delegate,在urlSession(_:didReceive:completionHandler:)方法中返回包含SecIdentity的URLCredential:
class CertificateAuthDelegate: NSObject, URLSessionDelegate { let identity: SecIdentity init(identity: SecIdentity) { self.identity = identity super.init() } func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate else { completionHandler(.performDefaultHandling, nil) return } let credential = URLCredential(identity: identity, certificates: nil, persistence: .forSession) completionHandler(.useCredential, credential) } }
4. 发起带参数的GET请求
最后组装请求,配置URLSession并发起请求:
func makeAuthenticatedRequest() { let certPath = "cert.pem" let keyPath = "key.pem" do { // 解析证书和私钥 let cert = try parsePEMCertificate(from: certPath) let privateKey = try parsePEMPrivateKey(from: keyPath) // 创建SecIdentity let identity = try createSecIdentity(cert: cert, privateKey: privateKey) // 配置URLSession let delegate = CertificateAuthDelegate(identity: identity) let sessionConfig = URLSessionConfiguration.default let session = URLSession(configuration: sessionConfig, delegate: delegate, delegateQueue: OperationQueue.main) // 构建URL和参数 var components = URLComponents(string: "https://example.com/resource")! components.queryItems = [ URLQueryItem(name: "param_1", value: "value_1"), URLQueryItem(name: "param_2", value: "value_2") ] guard let url = components.url else { print("URL构建失败") return } // 发起请求 let task = session.dataTask(with: url) { data, response, error in if let error = error { print("请求失败: \(error.localizedDescription)") return } guard let data = data, let response = response as? HTTPURLResponse else { print("无响应数据") return } print("请求成功,状态码: \(response.statusCode)") if let responseString = String(data: data, encoding: .utf8) { print("响应内容: \(responseString)") } } task.resume() } catch { print("初始化证书认证失败: \(error.localizedDescription)") } } // 调用请求方法 makeAuthenticatedRequest()
注意事项
- 确保
cert.pem和key.pem文件路径正确,在iOS/macOS项目中要将文件添加到目标的Copy Bundle Resources中 - 如果私钥是加密的(带密码),需要额外处理解密逻辑(上述代码假设私钥未加密)
- 不同私钥格式(如PKCS#8)可能需要调整PEM头部的匹配字符串(比如
-----BEGIN RSA PRIVATE KEY-----)
内容的提问来源于stack exchange,提问作者jacobcan118
相关产品推荐
相关产品推荐

