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

How to use openssl to get self signed certificate and validate it in a URLSession authentication challenge

我正在尝试在 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)
        }
    }

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

                if let certPath2 = Bundle.main.path(forResource: "remotestash-cert-signed", ofType: "der"),
                   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)
        }
    }