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直连
相关推荐
Pluchon1 小时前
硅基计划6.0 陆 JavaEE Http&Https协议
网络协议·tcp/ip·http·网络安全·https·udp·java-ee
是店小二呀1 小时前
仓颉三方库开发实战:Simple HTTP Server 实现详解
网络·网络协议·http
洗紫1 小时前
Python中的条件语句怎么使用?
python
北京耐用通信1 小时前
从‘卡壳’到‘丝滑’:耐达讯自动化PROFIBUS光纤模块如何让RFID读写器实现‘零延迟’物流追踪?”
网络·人工智能·科技·物联网·网络协议·自动化
南汐汐月1 小时前
重生归来,我要成功 Python 高手--day35 深度学习 Pytorch
pytorch·python·深度学习
java1234_小锋1 小时前
[免费]基于Python的深度学习豆瓣电影数据可视化+情感分析推荐系统(Flask+Vue+LSTM+scrapy)【论文+源码+SQL脚本】
python·信息可视化·flask·电影数据可视化
交换机路由器测试之路2 小时前
交换机路由器基础(四)--TCPIP四层模型及常见协议技术
网络·网络协议·路由器·交换机·tcp/ip模型
老蒋新思维2 小时前
借刘润之智,在 IP+AI 时代构筑战略 “增长方舟”|创客匠人
大数据·网络·人工智能·网络协议·tcp/ip·创客匠人·知识变现
多多*2 小时前
一个有 IP 的服务端监听了某个端口,那么他的 TCP 最大链接数是多少
java·开发语言·网络·网络协议·tcp/ip·缓存·mybatis
taxunjishu2 小时前
Modbus RTU 转 Modbus TCP:借助数据采集提升罗克韦尔PLC工艺参数反馈实时性案例
人工智能·物联网·tcp/ip·工业物联网·工业自动化·总线协议