如何使用 openssl 获取自签名证书并在 URLSession 身份验证质询中对其进行验证

问题描述

我正在尝试在 URLSession 中创建和验证自签名证书,但我正在努力将所有内容组合起来并通过身份验证。我总是得到一个SectrustResultType.recoverableTrustFailure

我不知道我是否使用 openssl 错误地创建了证书,或者我是否没有使用 SecTrust 功能对其进行正确验证。

我使用以下命令颁发证书:

SUBJ="/C=US/O=RemoteStash/OU=server/CN=localhost"
SUBJCA="/C=US/O=RemoteStashCA/OU=CA/CN=authority"

# Create the certificate authority
openssl genrsa -des3 -out remotestash-ca.key 2048
openssl req -x509 -new -nodes -key remotestash-ca.key -sha256 -days 3650 -out remotestash-ca.pem -subj $SUBJCA

# Create new key/cert for the server
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout remotestash-key.pem -out remotestash-cert.pem -subj $SUBJ

# Create the signature request
openssl req -new -key remotestash-key.pem -out remotestash-cert.csr -subj $SUBJ

# Sign the request
openssl x509 -req -in remotestash-cert.csr -CA remotestash-ca.pem -CAkey remotestash-ca.key -CAcreateserial -out remotestash-cert-signed.pem -days 3650 -sha256

# Convert to der format (So i can compare the data of the certificate later)
openssl x509 -outform der -in remotestash-ca.pem -out remotestash-ca.der
openssl x509 -outform der -in remotestash-cert-signed.pem -out remotestash-cert-signed.der

在服务器中我使用 remotestash-cert-signed.pemremotestash-key.pem

现在在 iPhone 应用程序代码中,我实现了如下挑战:

    func urlSession(_ session: URLSession,task: URLSessionTask,didReceive challenge: URLAuthenticationChallenge,completionHandler: @escaping (URLSession.AuthChallengedisposition,URLCredential?) -> Void) {
        // Try to create a serverTrust with the certificate added
        if let trust = challenge.protectionSpace.serverTrust,let certPath = Bundle.main.path(forResource: "remotestash-ca",ofType: "der"),let certData = try? Data(contentsOf: URL(fileURLWithPath: certPath)),let cert = SecCertificateCreateWithData(nil,certData as CFData),let remoteCert = SecTrustGetCertificateAtIndex(trust,0) {
            SecTrustSetAnchorCertificates(trust,[cert] as CFArray)
            SecTrustSetAnchorCertificatesOnly(trust,false)
            
            let remoteCertData = SecCertificatecopyData(remoteCert) as Data
                
            // check if pass trust
            var trustResult : SecTrustResultType = SecTrustResultType.invalid
            SecTrustGetTrustResult(trust,&trustResult)
            if trustResult == .unspecified || trustResult == .proceed {
                completionHandler(.useCredential,URLCredential(trust: trust))
            }else{
                completionHandler(.cancelAuthenticationChallenge,nil)
            }
        }else{
            completionHandler(.cancelAuthenticationChallenge,nil)
        }
    }

我可以通过在上面的质询代码添加以下检查来验证服务器使用的证书是否正确,以证明服务器使用了 remotestash-cert-signed

                if let certPath2 = Bundle.main.path(forResource: "remotestash-cert-signed",let certData2 = try? Data(contentsOf: URL(fileURLWithPath: certPath2)) {
                    if remoteCertData == certData2 {
                        print( "equal" )
                    }else{
                        print( "not equal" )
                    }
                }

但是 SecTrustGetTrustResult 的结果总是 SecTrustResultType.recoverableTrustFailure

知道我的错误在哪里或如何使信任结果成功吗?

解决方法

我想我想出了如何让它发挥作用。

它不起作用,因为信任对象 challenge.protectionSpace.serverTrust 有一个 SSL SecPolicy,这是有道理的,但在这里,当它在具有 IP 地址的本地网络上得到验证时,策略检查失败,主机名不是匹配。

因此,如果我使用 BasicX509 类型的 SecPolicy 创建一个信任对象,那么它只会验证服务器证书是否由所需的 CA 证书签名并且可以正常工作。

这是有效的代码:

    func urlSession(_ session: URLSession,task: URLSessionTask,didReceive challenge: URLAuthenticationChallenge,completionHandler: @escaping (URLSession.AuthChallengeDisposition,URLCredential?) -> Void) {
        // for logging purpose only
        let url  = task.currentRequest?.url ?? URL(fileURLWithPath: "")
        
        // Extract the certificate to validate from the challenge and load our known CA certificate
        guard
            let trust = challenge.protectionSpace.serverTrust,let caCertPath = Bundle.main.path(forResource: "remotestash-ca",ofType: "der"),let caCertData = try? Data(contentsOf: URL(fileURLWithPath: caCertPath)),let caCert = SecCertificateCreateWithData(nil,caCertData as CFData),let remoteCert = SecTrustGetCertificateAtIndex(trust,0)
        else {
            logger.error("\(url) server trust failed to setup manual authentication")
            completionHandler(.cancelAuthenticationChallenge,nil)
            return
        }
            
        // Create a trust object to valid the remote certificate against our certificate authority
        let policy = SecPolicyCreateBasicX509()
        var optionalTrust : SecTrust?
        var trustResult : SecTrustResultType = SecTrustResultType.invalid
        guard
            SecTrustCreateWithCertificates([remoteCert] as CFArray,policy,&optionalTrust) == errSecSuccess,let caTrust = optionalTrust,SecTrustSetAnchorCertificates(caTrust,[caCert] as CFArray) == errSecSuccess,SecTrustGetTrustResult(caTrust,&trustResult) == errSecSuccess
        else{
            logger.error("\(url) server trust failed to execute authentication")
            completionHandler(.cancelAuthenticationChallenge,nil)
            return
        }
        
        switch trustResult{
        case .unspecified,.proceed:
            logger.info("\(url) remotestash certificate valid")
            completionHandler(.useCredential,URLCredential(trust: trust))
        default:
            logger.info("\(url) invalid certificate")
            completionHandler(.cancelAuthenticationChallenge,nil)
        }
    }