Rust实现Http3请求-支持自定义证书检验

561 阅读4分钟

1. HTTP3 简介

HTTP3,也被称为HTTP over QUIC,它基于QUIC(Quick UDP Internet Connections)协议,而不是传统的TCP协议。QUIC是由Google开发的一种基于UDP的多路复用传输协议,旨在减少网络延迟并提高数据传输的效率。

2. 相对于HTTP2的主要优点

2.1 真正解决TCP的队头阻塞问题

  在HTTP1.x中的流水线技术虽然允许发送多个请求而不必等待响应,但响应仍然需要按照请求的顺序返回,这就可能导致队头阻塞问题:如果一个响应被延迟,那么其后面的所有响应也都会被阻塞。

  HTTP2通过多路复用技术解决了这一问题。它允许在单个TCP连接上同时传输多个请求和响应,每个请求或响应被分解成独立的帧,并通过流的方式进行传输。流是独立的、双向的序列,它们在连接中共存,可以各自承载双向的消息交换。多路复用提高了连接效率,减少了延迟。

  尽管HTTP2解决了应用层的队头阻塞问题,但在传输层(TCP层)仍然存在队头阻塞问题。TCP协议要求数据严格按照序号顺序传输,如果前面的数据包丢失或延迟,那么后面的数据包即使已经到达接收端,也会被阻塞等待前面的数据包重传。这种队头阻塞问题是由TCP协议本身的特性决定的,HTTP2作为应用层协议,无法直接解决传输层的队头阻塞问题。

  为了解决TCP的队头阻塞问题,HTTP3采用了基于UDP的QUIC协议。QUIC协议在UDP的基础上实现了可靠传输、拥塞控制等功能。由于UDP是无连接的协议,不存在TCP那样的队头阻塞问题,因此HTTP/3能够更好地利用网络资源,提高通信效率。

2.2 减少连接建立时间

QUIC协议在初次连接时仅需一次RTT(Round-Trip Time),重用现有连接则无需再执行握手,进一步降低了延迟。 HTTP3的握手过程更加快速,因为它结合了TLS加密和连接建立的过程,减少了往返次数,加快了连接建立的速度。

3. 代码示例

3.1 实现HTTP3请求

下面的代码使用了reqwest实现HTTP3请求,并且支持自定义证书校验

use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified};
use rustls::crypto::{ring, CryptoProvider};
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
use rustls::{ClientConfig, DigitallySignedStruct, Error, SignatureScheme};
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
static ALPN: &[u8] = b"h3";

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    use http::Version;
    use reqwest::{Client, IntoUrl, Response};
    CryptoProvider::install_default(ring::default_provider()).expect("install provider failed");

    let root_store = rustls::RootCertStore {
        roots: webpki_roots::TLS_SERVER_ROOTS.into(),
    };

    let mut client_config = ClientConfig::builder()
        .with_root_certificates(root_store.clone())
        .with_no_client_auth();
    client_config.alpn_protocols = vec![ALPN.into()];
    client_config
        .dangerous()
        .set_certificate_verifier(Arc::new(NoCertVerifier {}));

    let client = Client::builder()
        .http3_prior_knowledge()
        .use_preconfigured_tls(client_config)
        .build()?;

    // Some simple CLI args requirements...
    let url = match std::env::args().nth(1) {
        Some(url) => url,
        None => {
            println!("No CLI URL provided, using default.");
            "https://hyper.rs".into()
        }
    };

    eprintln!("Fetching {url:?}...");

    let res = client
        .get(url.as_str())
        .version(Version::HTTP_3)
        .send()
        .await?;

    eprintln!("Response: {:?} {}", res.version(), res.status());
    eprintln!("Headers: {:#?}\n", res.headers());
    let body = res.text().await?;
    println!("{body}");
    Ok(())
}

注意reqwest库要enable http3 feature

reqwest = {version = "0.12.7", default-features = false, features = ["rustls-tls", "http3"]}

另外http3 feature目前还不稳定,编译时加上reqwest_unstable,

RUSTFLAGS='--cfg reqwest_unstable' cargo run

3.2 自定义证书解析

本例中reqwest使用rustls-tls feature,即使用rustls作为tls,此时自定义证书校验代码如下

pub mod custom_verify {
    use std::fmt::{Debug, Formatter};
    use std::sync::Arc;
    use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified};
    use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider};
    use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
    use rustls::{DigitallySignedStruct, Error, RootCertStore, SignatureScheme};
    use webpki::Error as PkiError;
    use crate::asn1_parse;

    pub(crate) struct CustomCertVerifier {
        roots: Arc<RootCertStore>,
        provider: CryptoProvider,
    }
    impl CustomCertVerifier {
        pub fn new(roots: Arc<RootCertStore>, provider: CryptoProvider) -> Self {
            CustomCertVerifier { roots, provider }
        }
    }
    impl Debug for CustomCertVerifier {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            f.write_str("verifier cert")
        }
    }

    impl rustls::client::danger::ServerCertVerifier for CustomCertVerifier {
        fn verify_server_cert(
            &self,
            end_entity: &CertificateDer<'_>,
            intermediates: &[CertificateDer<'_>],
            server_name: &ServerName<'_>,
            ocsp_response: &[u8],
            now: UnixTime,
        ) -> Result<ServerCertVerified, Error> {
            let end_cert = webpki::EndEntityCert::try_from(end_entity).unwrap();
            let issuer = end_cert.issuer();
            let issuer_str = asn1_parse::asn1_parse(issuer);
            println!("issuer  {issuer_str:?} ");

            let dns_name: Vec<&str> = end_cert.valid_dns_names().collect();
            println!("dns name {:?}", dns_name);
            match server_name {
                ServerName::IpAddress(ip) => {
                    // todo ip check
                }
                ServerName::DnsName(dnsName) => {
                    // todo dns name
                }
                _ => {}
            }

            match end_cert.verify_for_usage(
                &self.provider.signature_verification_algorithms.all,
                &self.roots.roots,
                intermediates,
                now,
                webpki::KeyUsage::server_auth(),
                None,
                None,
            ) {
                Err(e) => match e {
                    PkiError::BadDerTime => {
                        // todo
                    }
                    PkiError::CertNotValidYet => {
                        // todo
                    }
                    _ => {
                        // todo
                    }
                },
                _ => {}
            }

            return match end_cert.verify_is_valid_for_subject_name(server_name) {
                Ok(_) => Ok(ServerCertVerified::assertion()),
                Err(e) => Err(Error::General(e.to_string())),
            };
        }
        fn verify_tls12_signature(
            &self,
            message: &[u8],
            cert: &CertificateDer<'_>,
            dss: &DigitallySignedStruct,
        ) -> Result<HandshakeSignatureValid, Error> {
            verify_tls12_signature(
                message,
                cert,
                dss,
                &self.provider.signature_verification_algorithms,
            )
        }
        fn verify_tls13_signature(
            &self,
            message: &[u8],
            cert: &CertificateDer<'_>,
            dss: &DigitallySignedStruct,
        ) -> Result<HandshakeSignatureValid, Error> {
            verify_tls13_signature(
                message,
                cert,
                dss,
                &self.provider.signature_verification_algorithms,
            )
        }
        fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
            self.provider
                .signature_verification_algorithms
                .supported_schemes()
        }
    }
}

证书颁发者asn1格式数据解析

use simple_asn1::{from_der, ASN1Block};

pub fn asn1_parse(data: &[u8]) -> Vec<String> {
    let mut result = vec![];
    match from_der(&data) {
        Ok(blocks) => decode_asn1(&blocks, &mut result),
        Err(err) => {
            println!("Error decoding ASN.1 data: {:?}", err);
        }
    }
    result
}

fn decode_asn1(asn1_blocks: &Vec<ASN1Block>, result: &mut Vec<String>) {
    for b in asn1_blocks {
        match b {
            ASN1Block::Boolean(_, _) => {}
            ASN1Block::Integer(_, _) => {}
            ASN1Block::BitString(_, _, _) => {}
            ASN1Block::OctetString(_, _) => {}
            ASN1Block::Null(_) => {}
            ASN1Block::ObjectIdentifier(_, _) => {}
            ASN1Block::UTF8String(_, _) => {
                println!("ASN1Block UTF8String")
            }
            ASN1Block::PrintableString(_, data) => result.push(data.clone()),
            ASN1Block::TeletexString(_, data) => result.push(data.clone()),
            ASN1Block::IA5String(_, _) => {}
            ASN1Block::UTCTime(_, _) => {}
            ASN1Block::GeneralizedTime(_, _) => {}
            ASN1Block::UniversalString(_, _) => {}
            ASN1Block::BMPString(_, _) => {}
            ASN1Block::Sequence(size, seq_blocks) => decode_asn1(seq_blocks, result),
            ASN1Block::Set(size, set_datas) => decode_asn1(set_datas, result),
            ASN1Block::Explicit(_, _, _, _) => {}
            ASN1Block::Unknown(_, _, _, _, _) => {}
        }
    }
}