diff --git a/packages/hurl/src/http/certificate.rs b/packages/hurl/src/http/certificate.rs index 1d901a1868..1a1fc2ce11 100644 --- a/packages/hurl/src/http/certificate.rs +++ b/packages/hurl/src/http/certificate.rs @@ -21,9 +21,13 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use super::easy_ext::CertInfo; +/// Represents an SSL/TLS certificate. +/// +/// Each attribute `subject`, `issuer` etc... is optional, so we can test invalid certificate, +/// (i.e. a certificate without serial number). #[derive(Clone, Debug, PartialEq, Eq)] pub struct Certificate { - pub subject: String, + pub subject: Option, pub issuer: String, pub start_date: DateTime, pub expire_date: DateTime, @@ -40,7 +44,7 @@ impl TryFrom for Certificate { /// - date format: "Jan 10 08:29:52 2023 GMT" vs "2023-01-10 08:29:52 GMT" fn try_from(cert_info: CertInfo) -> Result { let attributes = parse_attributes(&cert_info.data); - let subject = parse_subject(&attributes)?; + let subject = parse_subject(&attributes).ok(); let issuer = parse_issuer(&attributes)?; let start_date = parse_start_date(&attributes)?; let expire_date = parse_expire_date(&attributes)?; @@ -238,11 +242,11 @@ mod tests { let mut attributes = HashMap::new(); attributes.insert( "x509v3 subject alternative name".to_string(), - "DNS:localhost, IP address:127.0.0.1, IP adddress:0:0:0:0:0:0:0:1".to_string(), + "DNS:localhost, IP address:127.0.0.1, IP address:0:0:0:0:0:0:0:1".to_string(), ); assert_eq!( parse_subject_alt_name(&attributes).unwrap(), - "DNS:localhost, IP address:127.0.0.1, IP adddress:0:0:0:0:0:0:0:1".to_string() + "DNS:localhost, IP address:127.0.0.1, IP address:0:0:0:0:0:0:0:1".to_string() ); } @@ -258,14 +262,14 @@ mod tests { "Serial Number:1ee8b17f1b64d8d6b3de870103d2a4f533535ab0".to_string(), "Start date:Jan 10 08:29:52 2023 GMT".to_string(), "Expire date:Oct 30 08:29:52 2025 GMT".to_string(), - "x509v3 subject alternative name:DNS:localhost, IP address:127.0.0.1, IP adddress:0:0:0:0:0:0:0:1" + "x509v3 subject alternative name:DNS:localhost, IP address:127.0.0.1, IP address:0:0:0:0:0:0:0:1" .to_string(), ] }) .unwrap(), Certificate { - subject: "C = US, ST = Denial, L = Springfield, O = Dis, CN = localhost" - .to_string(), + subject: Some("C = US, ST = Denial, L = Springfield, O = Dis, CN = localhost" + .to_string()), issuer: "C = US, ST = Denial, L = Springfield, O = Dis, CN = localhost".to_string(), start_date: chrono::DateTime::parse_from_rfc2822("Tue, 10 Jan 2023 08:29:52 GMT") .unwrap() @@ -275,14 +279,14 @@ mod tests { .with_timezone(&Utc), serial_number: "1e:e8:b1:7f:1b:64:d8:d6:b3:de:87:01:03:d2:a4:f5:33:53:5a:b0" .to_string(), - subject_alt_name: Some("DNS:localhost, IP address:127.0.0.1, IP adddress:0:0:0:0:0:0:0:1".to_string()) + subject_alt_name: Some("DNS:localhost, IP address:127.0.0.1, IP address:0:0:0:0:0:0:0:1".to_string()) } ); assert_eq!( Certificate::try_from(CertInfo { data: vec![] }) .err() .unwrap(), - "missing Subject attribute in {}".to_string() + "missing Issuer attribute in {}".to_string() ); } } diff --git a/packages/hurl/src/json/result.rs b/packages/hurl/src/json/result.rs index 901c702a15..647dcc96d6 100644 --- a/packages/hurl/src/json/result.rs +++ b/packages/hurl/src/json/result.rs @@ -189,7 +189,8 @@ struct ResponseCookieJson { #[derive(Deserialize, Serialize)] struct CertificateJson { - subject: String, + #[serde(skip_serializing_if = "Option::is_none")] + subject: Option, issuer: String, start_date: String, expire_date: String, diff --git a/packages/hurl/src/report/html/run.rs b/packages/hurl/src/report/html/run.rs index 4816b6d590..d0b99ec60e 100644 --- a/packages/hurl/src/report/html/run.rs +++ b/packages/hurl/src/report/html/run.rs @@ -133,13 +133,15 @@ fn get_call_html( if let Some(certificate) = &call.response.certificate { let start_date = certificate.start_date.to_string(); let end_date = certificate.expire_date.to_string(); - let mut values = vec![ - ("Subject", certificate.subject.as_str()), - ("Issuer", certificate.issuer.as_str()), - ("Start Date", start_date.as_str()), - ("Expire Date", end_date.as_str()), - ("Serial Number", certificate.serial_number.as_str()), - ]; + let mut values = vec![]; + + if let Some(subject) = certificate.subject.as_ref() { + values.push(("Subject", subject.as_str())); + } + values.push(("Issuer", certificate.issuer.as_str())); + values.push(("Start Date", start_date.as_str())); + values.push(("Expire Date", end_date.as_str())); + values.push(("Serial Number", certificate.serial_number.as_str())); if let Some(subject_alt_name) = certificate.subject_alt_name.as_ref() { values.push(("Subject Alt Name", subject_alt_name.as_str())); } diff --git a/packages/hurl/src/runner/query.rs b/packages/hurl/src/runner/query.rs index 859d628c62..b05add4223 100644 --- a/packages/hurl/src/runner/query.rs +++ b/packages/hurl/src/runner/query.rs @@ -384,7 +384,10 @@ fn eval_query_certificate( ) -> QueryResult { if let Some(certificate) = &response.certificate { let value = match certificate_attribute { - CertificateAttributeName::Subject => Value::String(certificate.subject.clone()), + CertificateAttributeName::Subject => match certificate.subject.as_ref() { + Some(s) => Value::String(s.clone()), + None => return Ok(None), + }, CertificateAttributeName::Issuer => Value::String(certificate.issuer.clone()), CertificateAttributeName::StartDate => Value::Date(certificate.start_date), CertificateAttributeName::ExpireDate => Value::Date(certificate.expire_date), @@ -1486,7 +1489,7 @@ pub mod tests { eval_query_certificate( &Response { certificate: Some(http::Certificate { - subject: "A=B, C=D".to_string(), + subject: Some("A=B, C=D".to_string()), issuer: String::new(), start_date: Default::default(), expire_date: Default::default(),