cronet从编译到修改之: 支持IP直连
使用cronet,发起请求的时候,如果是ip的形式,会有报错:ERR_CERT_COMMON_NAME_INVALID,完整信息如下:
System.err java.io.IOException: java.util.concurrent.ExecutionException: org.chromium.net.impl.NetworkExceptionImpl: Exception in CronetUrlRequest: net::ERR_CERT_COMMON_NAME_INVALID, ErrorCode=11, InternalErrorCode=-200, Retryable=false
System.err at com.google.net.cronet.okhttptransport.ResponseConverter.getFutureValue(ResponseConverter.java:216)
System.err at com.google.net.cronet.okhttptransport.ResponseConverter.toResponse(ResponseConverter.java:73)
System.err at com.google.net.cronet.okhttptransport.RequestResponseConverter$1.getResponse(RequestResponseConverter.java:124)
System.err at com.google.net.cronet.okhttptransport.RequestResponseConverter$CronetRequestAndOkHttpResponse.getResponse(RequestResponseConverter.java:156)
System.err at com.google.net.cronet.okhttptransport.CronetCallFactory$CronetCall.execute(CronetCallFactory.java:128)
System.err at org.yeshen.http3.MainActivity.httpGet$lambda$4(MainActivity.kt:140)
System.err at org.yeshen.http3.MainActivity.$r8$lambda$39qdYED_5mxuiTtol8rrKa_0_24(Unknown Source:0)
System.err at org.yeshen.http3.MainActivity$$ExternalSyntheticLambda2.run(D8$$SyntheticClass:0)
System.err at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1156)
System.err at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:651)
System.err at java.lang.Thread.run(Thread.java:1119)
定位出错位置的代码在ssl握手的地方。代码细节如下:
/src/net/socket/ssl_client_socket_impl.cc
int SSLClientSocketImpl::DoHandshake() {crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);int rv = SSL_do_handshake(ssl_.get());int net_error = OK;if (rv <= 0) {int ssl_error = SSL_get_error(ssl_.get(), rv);if (ssl_error == SSL_ERROR_WANT_X509_LOOKUP && !send_client_cert_) {return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;}if (ssl_error == SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) {DCHECK(client_private_key_);DCHECK_NE(kSSLClientSocketNoPendingResult, signature_result_);next_handshake_state_ = STATE_HANDSHAKE;return ERR_IO_PENDING;}if (ssl_error == SSL_ERROR_WANT_CERTIFICATE_VERIFY) {DCHECK(cert_verifier_request_);next_handshake_state_ = STATE_HANDSHAKE;return ERR_IO_PENDING;}OpenSSLErrorInfo error_info;net_error = MapLastOpenSSLError(ssl_error, err_tracer, &error_info);if (net_error == ERR_IO_PENDING) {// If not done, stay in this statenext_handshake_state_ = STATE_HANDSHAKE;return ERR_IO_PENDING;}LOG(ERROR) << "handshake failed; returned " << rv << ", SSL error code "<< ssl_error << ", net_error " << net_error;NetLogOpenSSLError(net_log_, NetLogEventType::SSL_HANDSHAKE_ERROR,net_error, ssl_error, error_info);}next_handshake_state_ = STATE_HANDSHAKE_COMPLETE;return net_error;
}
/cronet/src/net/cert/cert_verify_proc.cc
https://chromium.googlesource.com/chromium/src/+/master/net/cert/cert_verify_proc.cc
int CertVerifyProc::Verify(X509Certificate* cert,const std::string& hostname,const std::string& ocsp_response,const std::string& sct_list,int flags,CertVerifyResult* verify_result,const NetLogWithSource& net_log) {CHECK(cert);CHECK(verify_result);net_log.BeginEvent(NetLogEventType::CERT_VERIFY_PROC, [&] {return CertVerifyParams(cert, hostname, ocsp_response, sct_list, flags,crl_set());});// CertVerifyProc's contract allows ::VerifyInternal() to wait on File I/O// (such as the Windows registry or smart cards on all platforms) or may re-// enter this code via extension hooks (such as smart card UI). To ensure// threads are not starved or deadlocked, the base::ScopedBlockingCall below// increments the thread pool capacity when this method takes too much time to// run.base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,base::BlockingType::MAY_BLOCK);verify_result->Reset();verify_result->verified_cert = cert;int rv = VerifyInternal(cert, hostname, ocsp_response, sct_list, flags,verify_result, net_log);CHECK(verify_result->verified_cert);if (rv == OK) {CHECK_EQ(verify_result->verified_cert->cert_buffers().size(),verify_result->public_key_hashes.size());}// Check for mismatched signature algorithms and unknown signature algorithms// in the chain. Also fills in the has_* booleans for the digest algorithms// present in the chain.if (!InspectSignatureAlgorithmsInChain(verify_result)) {verify_result->cert_status |= CERT_STATUS_INVALID;rv = MapCertStatusToNetError(verify_result->cert_status);}if (!cert->VerifyNameMatch(hostname)) {verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID;rv = MapCertStatusToNetError(verify_result->cert_status);}if (verify_result->ocsp_result.response_status ==bssl::OCSPVerifyResult::NOT_CHECKED) {// If VerifyInternal did not record the result of checking stapled OCSP,// do it now.BestEffortCheckOCSP(ocsp_response, *verify_result->verified_cert,&verify_result->ocsp_result);}// Check to see if the connection is being intercepted.for (const auto& hash : verify_result->public_key_hashes) {if (!crl_set()->IsKnownInterceptionKey(hash)) {continue;}if (verify_result->cert_status & CERT_STATUS_REVOKED) {// If the chain was revoked, and a known MITM was present, signal that// with a more meaningful error message.verify_result->cert_status |= CERT_STATUS_KNOWN_INTERCEPTION_BLOCKED;rv = MapCertStatusToNetError(verify_result->cert_status);} else {// Otherwise, simply signal informatively. Both statuses are not set// simultaneously.verify_result->cert_status |= CERT_STATUS_KNOWN_INTERCEPTION_DETECTED;}break;}std::vector<std::string> dns_names, ip_addrs;cert->GetSubjectAltName(&dns_names, &ip_addrs);if (HasNameConstraintsViolation(verify_result->public_key_hashes,cert->subject().common_name,dns_names,ip_addrs)) {verify_result->cert_status |= CERT_STATUS_NAME_CONSTRAINT_VIOLATION;rv = MapCertStatusToNetError(verify_result->cert_status);}// Check for weak keys in the entire verified chain.bool weak_key = ExaminePublicKeys(verify_result->verified_cert,verify_result->is_issued_by_known_root);if (weak_key) {verify_result->cert_status |= CERT_STATUS_WEAK_KEY;// Avoid replacing a more serious error, such as an OS/library failure,// by ensuring that if verification failed, it failed with a certificate// error.if (rv == OK || IsCertificateError(rv))rv = MapCertStatusToNetError(verify_result->cert_status);}if (verify_result->has_sha1)verify_result->cert_status |= CERT_STATUS_SHA1_SIGNATURE_PRESENT;// Flag certificates using weak signature algorithms.bool sha1_allowed = (flags & VERIFY_ENABLE_SHA1_LOCAL_ANCHORS) &&!verify_result->is_issued_by_known_root;if (!sha1_allowed && verify_result->has_sha1) {verify_result->cert_status |= CERT_STATUS_WEAK_SIGNATURE_ALGORITHM;// Avoid replacing a more serious error, such as an OS/library failure,// by ensuring that if verification failed, it failed with a certificate// error.if (rv == OK || IsCertificateError(rv))rv = MapCertStatusToNetError(verify_result->cert_status);}// Flag certificates using too long validity periods.if (verify_result->is_issued_by_known_root && HasTooLongValidity(*cert)) {verify_result->cert_status |= CERT_STATUS_VALIDITY_TOO_LONG;if (rv == OK)rv = MapCertStatusToNetError(verify_result->cert_status);}// Flag certificates from publicly-trusted CAs that are issued to intranet// hosts. These are not allowed per the CA/Browser Forum requirements.//// Validity period is checked first just for testing convenience; there's not// a strong security reason to let validity period vs non-unique names take// precedence.if (verify_result->is_issued_by_known_root && IsHostnameNonUnique(hostname)) {verify_result->cert_status |= CERT_STATUS_NON_UNIQUE_NAME;// On Cronet, CERT_STATUS_NON_UNIQUE_NAME is recorded as a warning but not// treated as an error, because consumers have tests that use certs with// non-unique names. See b/337196170 (Google-internal).
#if !BUILDFLAG(CRONET_BUILD)if (rv == OK) {rv = MapCertStatusToNetError(verify_result->cert_status);}
#endif // !BUILDFLAG(CRONET_BUILD)}// Record a histogram for per-verification usage of root certs.if (rv == OK) {RecordTrustAnchorHistogram(verify_result->public_key_hashes,verify_result->is_issued_by_known_root);}net_log.EndEvent(NetLogEventType::CERT_VERIFY_PROC,[&] { return verify_result->NetLogParams(rv); });return rv;
}
注意到 CertVerifyProc::Verify 这里有一堆校验的逻辑,其中的关键就是:
if (!cert->VerifyNameMatch(hostname)) {
verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID;
rv = MapCertStatusToNetError(verify_result->cert_status);
}
修改的话,比较简单,就是hostname是一个ip类型,就不做证书的校验即可。
const bool host_is_ip_address = HostIsIPAddressNoBrackets(hostname);
if (!host_is_ip_address && !cert->VerifyNameMatch(hostname)) {verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID;rv = MapCertStatusToNetError(verify_result->cert_status);
}
玩~
这个系列其他的文章:
- cronet的从编译到修改使用之 httpdns
- volley支持http3/(基于cronet发起网络请求)
- 配置nginx以支持http3
- cronet从编译到修改之: 支持IP直连