Skip to content

Commit 22ffd27

Browse files
alexclaude
authored andcommitted
Add valid_uri_names() method to Cert
Returns an iterator over URI names from the subject alternative names extension, following the same pattern as valid_dns_names(). refs #9 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d1eb8b0 commit 22ffd27

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

src/cert.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,23 @@ impl<'a> Cert<'a> {
173173
})
174174
}
175175

176+
/// Returns a list of valid URI names provided in the subject alternative names extension
177+
///
178+
/// This function returns URIs as strings without performing validation beyond checking that
179+
/// they are valid UTF-8.
180+
pub fn valid_uri_names(&self) -> impl Iterator<Item = &str> {
181+
NameIterator::new(self.subject_alt_name).filter_map(|result| {
182+
let presented_id = match result.ok()? {
183+
GeneralName::UniformResourceIdentifier(presented) => presented,
184+
_ => return None,
185+
};
186+
187+
// if the URI can be converted to a valid UTF-8 string, return it; otherwise,
188+
// keep going.
189+
core::str::from_utf8(presented_id.as_slice_less_safe()).ok()
190+
})
191+
}
192+
176193
/// Raw certificate serial number.
177194
///
178195
/// This is in big-endian byte order, in twos-complement encoding.

tests/integration.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,41 @@ fn no_subject_alt_names() {
345345
expect_cert_dns_names(include_bytes!("misc/no_subject_alternative_name.der"), [])
346346
}
347347

348+
#[test]
349+
fn list_uri_names() {
350+
expect_cert_uri_names(
351+
include_bytes!("misc/uri_san_ee.der"),
352+
[
353+
"https://example.com",
354+
"https://www.example.com/path",
355+
"spiffe://example.org/service",
356+
],
357+
);
358+
}
359+
360+
#[test]
361+
fn no_uri_names() {
362+
expect_cert_uri_names(include_bytes!("misc/no_subject_alternative_name.der"), [])
363+
}
364+
365+
#[test]
366+
fn mixed_san_types() {
367+
// The uri_san_ee.der certificate has both DNS and URI SANs
368+
let der = CertificateDer::from(&include_bytes!("misc/uri_san_ee.der")[..]);
369+
let cert = webpki::EndEntityCert::try_from(&der)
370+
.expect("should parse end entity certificate correctly");
371+
372+
// Verify it has the DNS name
373+
assert!(cert.valid_dns_names().eq(["example.com"]));
374+
375+
// Verify it has the URI names
376+
assert!(cert.valid_uri_names().eq([
377+
"https://example.com",
378+
"https://www.example.com/path",
379+
"spiffe://example.org/service",
380+
]));
381+
}
382+
348383
fn expect_cert_dns_names<'name>(
349384
cert_der: &[u8],
350385
expected_names: impl IntoIterator<Item = &'name str>,
@@ -356,6 +391,17 @@ fn expect_cert_dns_names<'name>(
356391
assert!(cert.valid_dns_names().eq(expected_names))
357392
}
358393

394+
fn expect_cert_uri_names<'name>(
395+
cert_der: &[u8],
396+
expected_uris: impl IntoIterator<Item = &'name str>,
397+
) {
398+
let der = CertificateDer::from(cert_der);
399+
let cert = webpki::EndEntityCert::try_from(&der)
400+
.expect("should parse end entity certificate correctly");
401+
402+
assert!(cert.valid_uri_names().eq(expected_uris))
403+
}
404+
359405
#[cfg(feature = "alloc")]
360406
#[test]
361407
fn cert_time_validity() {

tests/misc/uri_san_ee.der

901 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)