You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用两个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 04:45:35