cronet从编译到修改之: 支持IP直连

使用cronet,发起请求的时候,如果是ip的形式,会有报错:ERR_CERT_COMMON_NAME_INVALID,完整信息如下:

txt 复制代码
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

c++ 复制代码
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 state
      next_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

c++ 复制代码
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 这里有一堆校验的逻辑,其中的关键就是:

c++ 复制代码
if (!cert->VerifyNameMatch(hostname)) {
verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID;
rv = MapCertStatusToNetError(verify_result->cert_status);
}

修改的话,比较简单,就是hostname是一个ip类型,就不做证书的校验即可。

c++ 复制代码
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);
}

玩~


这个系列其他的文章:

  1. cronet的从编译到修改使用之 httpdns
  2. volley支持http3/(基于cronet发起网络请求)
  3. 配置nginx以支持http3
  4. cronet从编译到修改之: 支持IP直连
相关推荐
2503_924806852 小时前
动态IP的适用业务场景
网络·网络协议·tcp/ip
2401_841495642 小时前
【机器学习】朴素贝叶斯法
人工智能·python·数学·算法·机器学习·概率论·朴素贝叶斯法
听潮阁2 小时前
Python 旅游数据分析平台【源码请评论区留言】
python·数据分析·旅游
付玉祥2 小时前
第 2 章 变量与基本数据类型
python·llm
扑克中的黑桃A3 小时前
Python快速入门专业版(八):字符串基础:创建、拼接与切片(10+实用代码案例)
python
时间醉酒3 小时前
逻辑回归(四):从原理到实战-训练,评估与应用指南
人工智能·python·算法·机器学习·逻辑回归
康一夏3 小时前
git fatal:Server aborted the SSL handshake
git·网络协议·ssl
weixin_446260853 小时前
提升开发效率的RPC系统!
网络·网络协议·rpc
计算机毕设残哥3 小时前
紧跟大数据技术趋势:食物口味分析系统Spark SQL+HDFS最新架构实现
大数据·hadoop·python·sql·hdfs·架构·spark