Add: H3 support - incomplete

This commit is contained in:
DaZuo0122
2026-01-17 13:47:37 +08:00
parent 840ceec38f
commit ccd4a31d21
14 changed files with 1553 additions and 71 deletions

View File

@@ -35,6 +35,9 @@ pub struct TlsCertSummary {
pub not_before: String,
pub not_after: String,
pub san: Vec<String>,
pub signature_algorithm: Option<String>,
pub key_usage: Option<Vec<String>>,
pub extended_key_usage: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -45,6 +48,7 @@ pub struct TlsHandshakeReport {
pub alpn_negotiated: Option<String>,
pub tls_version: Option<String>,
pub cipher: Option<String>,
pub ocsp_stapled: Option<bool>,
pub cert_chain: Vec<TlsCertSummary>,
}
@@ -56,6 +60,7 @@ pub struct TlsVerifyReport {
pub alpn_negotiated: Option<String>,
pub tls_version: Option<String>,
pub cipher: Option<String>,
pub ocsp_stapled: Option<bool>,
pub verified: bool,
pub error: Option<String>,
}
@@ -64,6 +69,7 @@ pub struct TlsVerifyReport {
pub struct TlsCertReport {
pub target: String,
pub sni: Option<String>,
pub ocsp_stapled: Option<bool>,
pub cert_chain: Vec<TlsCertSummary>,
}
@@ -83,6 +89,8 @@ pub struct TlsOptions {
pub insecure: bool,
pub socks5: Option<String>,
pub prefer_ipv4: bool,
pub show_extensions: bool,
pub ocsp: bool,
}
pub async fn handshake(target: &str, options: TlsOptions) -> Result<TlsHandshakeReport, TlsError> {
@@ -120,7 +128,8 @@ pub async fn handshake(target: &str, options: TlsOptions) -> Result<TlsHandshake
cipher: session
.negotiated_cipher_suite()
.map(|suite| format!("{suite:?}")),
cert_chain: extract_cert_chain(session.peer_certificates())?,
ocsp_stapled: ocsp_status(session, options.ocsp),
cert_chain: extract_cert_chain(session.peer_certificates(), options.show_extensions)?,
})
}
@@ -160,6 +169,7 @@ pub async fn verify(target: &str, options: TlsOptions) -> Result<TlsVerifyReport
cipher: session
.negotiated_cipher_suite()
.map(|suite| format!("{suite:?}")),
ocsp_stapled: ocsp_status(session, options.ocsp),
verified: true,
error: None,
})
@@ -171,6 +181,7 @@ pub async fn verify(target: &str, options: TlsOptions) -> Result<TlsVerifyReport
alpn_negotiated: None,
tls_version: None,
cipher: None,
ocsp_stapled: None,
verified: false,
error: Some(err.to_string()),
}),
@@ -203,7 +214,8 @@ pub async fn certs(target: &str, options: TlsOptions) -> Result<TlsCertReport, T
Ok(TlsCertReport {
target: target.to_string(),
sni: options.sni,
cert_chain: extract_cert_chain(session.peer_certificates())?,
ocsp_stapled: ocsp_status(session, options.ocsp),
cert_chain: extract_cert_chain(session.peer_certificates(), options.show_extensions)?,
})
}
@@ -427,26 +439,41 @@ fn socks5_target_host(proxy: &str, host: &str) -> (String, bool) {
(host.to_string(), remote_dns)
}
fn extract_cert_chain(certs: Option<&[Certificate]>) -> Result<Vec<TlsCertSummary>, TlsError> {
fn extract_cert_chain(
certs: Option<&[Certificate]>,
show_extensions: bool,
) -> Result<Vec<TlsCertSummary>, TlsError> {
let mut results = Vec::new();
if let Some(certs) = certs {
for cert in certs {
let summary = parse_cert(&cert.0)?;
let summary = parse_cert(&cert.0, show_extensions)?;
results.push(summary);
}
}
Ok(results)
}
fn parse_cert(der: &[u8]) -> Result<TlsCertSummary, TlsError> {
fn parse_cert(der: &[u8], show_extensions: bool) -> Result<TlsCertSummary, TlsError> {
let (_, cert) =
X509Certificate::from_der(der).map_err(|err| TlsError::Parse(err.to_string()))?;
let (key_usage, extended_key_usage, signature_algorithm) = if show_extensions {
(
extract_key_usage(&cert),
extract_extended_key_usage(&cert),
Some(cert.signature_algorithm.algorithm.to_string()),
)
} else {
(None, None, None)
};
Ok(TlsCertSummary {
subject: cert.subject().to_string(),
issuer: cert.issuer().to_string(),
not_before: cert.validity().not_before.to_string(),
not_after: cert.validity().not_after.to_string(),
san: extract_san(&cert),
signature_algorithm,
key_usage,
extended_key_usage,
})
}
@@ -460,6 +487,85 @@ fn extract_san(cert: &X509Certificate<'_>) -> Vec<String> {
result
}
fn extract_key_usage(cert: &X509Certificate<'_>) -> Option<Vec<String>> {
let ext = cert.key_usage().ok()??;
let mut result = Vec::new();
if ext.value.digital_signature() {
result.push("digitalSignature".to_string());
}
if ext.value.non_repudiation() {
result.push("nonRepudiation".to_string());
}
if ext.value.key_encipherment() {
result.push("keyEncipherment".to_string());
}
if ext.value.data_encipherment() {
result.push("dataEncipherment".to_string());
}
if ext.value.key_agreement() {
result.push("keyAgreement".to_string());
}
if ext.value.key_cert_sign() {
result.push("keyCertSign".to_string());
}
if ext.value.crl_sign() {
result.push("cRLSign".to_string());
}
if ext.value.encipher_only() {
result.push("encipherOnly".to_string());
}
if ext.value.decipher_only() {
result.push("decipherOnly".to_string());
}
if result.is_empty() {
None
} else {
Some(result)
}
}
fn extract_extended_key_usage(cert: &X509Certificate<'_>) -> Option<Vec<String>> {
let ext = cert.extended_key_usage().ok()??;
let mut result = Vec::new();
if ext.value.any {
result.push("any".to_string());
}
if ext.value.server_auth {
result.push("serverAuth".to_string());
}
if ext.value.client_auth {
result.push("clientAuth".to_string());
}
if ext.value.code_signing {
result.push("codeSigning".to_string());
}
if ext.value.email_protection {
result.push("emailProtection".to_string());
}
if ext.value.time_stamping {
result.push("timeStamping".to_string());
}
if ext.value.ocsp_signing {
result.push("ocspSigning".to_string());
}
for oid in &ext.value.other {
result.push(oid.to_string());
}
if result.is_empty() {
None
} else {
Some(result)
}
}
fn ocsp_status(_session: &rustls::ClientConnection, enabled: bool) -> Option<bool> {
if enabled {
None
} else {
None
}
}
struct NoVerifier;
impl rustls::client::ServerCertVerifier for NoVerifier {