diff --git a/src/name.rs b/src/name.rs
index 040a8133..afa50279 100644
--- a/src/name.rs
+++ b/src/name.rs
@@ -13,7 +13,7 @@
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 mod dns_name;
-pub use dns_name::{DnsNameRef, InvalidDnsNameError};
+pub use dns_name::{DnsNameRef, GeneralDnsNameRef, InvalidDnsNameError, WildcardDnsNameRef};
 
 /// Requires the `alloc` feature.
 #[cfg(feature = "alloc")]
@@ -23,3 +23,6 @@ mod ip_address;
 
 mod verify;
 pub(super) use verify::{check_name_constraints, verify_cert_dns_name};
+
+#[cfg(feature = "alloc")]
+pub(super) use verify::list_cert_dns_names;
diff --git a/src/name/dns_name.rs b/src/name/dns_name.rs
index 1b850fa0..11fe2b72 100644
--- a/src/name/dns_name.rs
+++ b/src/name/dns_name.rs
@@ -24,7 +24,7 @@ use alloc::string::String;
 /// allowed.
 ///
 /// `DnsName` stores a copy of the input it was constructed from in a `String`
-/// and so it is only available when the `std` default feature is enabled.
+/// and so it is only available when the `alloc` default feature is enabled.
 ///
 /// `Eq`, `PartialEq`, etc. are not implemented because name comparison
 /// frequently should be done case-insensitively and/or with other caveats that
@@ -147,6 +147,131 @@ impl<'a> From<DnsNameRef<'a>> for &'a str {
     }
 }
 
+/// A DNS Name suitable for use in the TLS Server Name Indication (SNI)
+/// extension and/or for use as the reference hostname for which to verify a
+/// certificate.
+pub enum GeneralDnsNameRef<'name> {
+    /// a valid DNS name
+    DnsName(DnsNameRef<'name>),
+    /// a DNS name containing a wildcard
+    Wildcard(WildcardDnsNameRef<'name>),
+}
+
+impl<'a> From<GeneralDnsNameRef<'a>> for &'a str {
+    fn from(d: GeneralDnsNameRef<'a>) -> Self {
+        match d {
+            GeneralDnsNameRef::DnsName(name) => name.into(),
+            GeneralDnsNameRef::Wildcard(name) => name.into(),
+        }
+    }
+}
+
+/// A reference to a DNS Name suitable for use in the TLS Server Name Indication
+/// (SNI) extension and/or for use as the reference hostname for which to verify
+/// a certificate. Compared to `DnsName`, this one will store domain names containing
+/// a wildcard.
+///
+/// A `WildcardDnsName` is guaranteed to be syntactically valid. The validity rules are
+/// specified in [RFC 5280 Section 7.2], except that underscores are also
+/// allowed, and following [RFC 6125].
+///
+/// `WildcardDnsName` stores a copy of the input it was constructed from in a `String`
+/// and so it is only available when the `alloc` default feature is enabled.
+///
+/// `Eq`, `PartialEq`, etc. are not implemented because name comparison
+/// frequently should be done case-insensitively and/or with other caveats that
+/// depend on the specific circumstances in which the comparison is done.
+///
+/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
+/// [RFC 6125]: https://tools.ietf.org/html/rfc6125
+#[cfg(feature = "alloc")]
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub struct WildcardDnsName(String);
+
+#[cfg(feature = "alloc")]
+impl WildcardDnsName {
+    /// Returns a `WildcardDnsNameRef` that refers to this `WildcardDnsName`.
+    pub fn as_ref(&self) -> WildcardDnsNameRef {
+        WildcardDnsNameRef(self.0.as_bytes())
+    }
+}
+
+#[cfg(feature = "alloc")]
+impl AsRef<str> for WildcardDnsName {
+    fn as_ref(&self) -> &str {
+        self.0.as_ref()
+    }
+}
+
+// Deprecated
+#[cfg(feature = "alloc")]
+impl From<WildcardDnsNameRef<'_>> for WildcardDnsName {
+    fn from(dns_name: WildcardDnsNameRef) -> Self {
+        dns_name.to_owned()
+    }
+}
+
+/// A reference to a DNS Name suitable for use in the TLS Server Name Indication
+/// (SNI) extension and/or for use as the reference hostname for which to verify
+/// a certificate.
+///
+/// A `WildcardDnsNameRef` is guaranteed to be syntactically valid. The validity rules
+/// are specified in [RFC 5280 Section 7.2], except that underscores are also
+/// allowed.
+///
+/// `Eq`, `PartialEq`, etc. are not implemented because name comparison
+/// frequently should be done case-insensitively and/or with other caveats that
+/// depend on the specific circumstances in which the comparison is done.
+///
+/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
+#[derive(Clone, Copy)]
+pub struct WildcardDnsNameRef<'a>(&'a [u8]);
+
+impl<'a> WildcardDnsNameRef<'a> {
+    /// Constructs a `WildcardDnsNameRef` from the given input if the input is a
+    /// syntactically-valid DNS name.
+    pub fn try_from_ascii(dns_name: &'a [u8]) -> Result<Self, InvalidDnsNameError> {
+        if !is_valid_wildcard_dns_id(untrusted::Input::from(dns_name)) {
+            return Err(InvalidDnsNameError);
+        }
+
+        Ok(Self(dns_name))
+    }
+
+    /// Constructs a `WildcardDnsNameRef` from the given input if the input is a
+    /// syntactically-valid DNS name.
+    pub fn try_from_ascii_str(dns_name: &'a str) -> Result<Self, InvalidDnsNameError> {
+        Self::try_from_ascii(dns_name.as_bytes())
+    }
+
+    /// Constructs a `WildcardDnsName` from this `WildcardDnsNameRef`
+    #[cfg(feature = "alloc")]
+    pub fn to_owned(&self) -> WildcardDnsName {
+        // WildcardDnsNameRef is already guaranteed to be valid ASCII, which is a
+        // subset of UTF-8.
+        let s: &str = self.clone().into();
+        WildcardDnsName(s.to_ascii_lowercase())
+    }
+}
+
+#[cfg(feature = "alloc")]
+impl core::fmt::Debug for WildcardDnsNameRef<'_> {
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
+        let lowercase = self.clone().to_owned();
+        f.debug_tuple("WildcardDnsNameRef")
+            .field(&lowercase.0)
+            .finish()
+    }
+}
+
+impl<'a> From<WildcardDnsNameRef<'a>> for &'a str {
+    fn from(WildcardDnsNameRef(d): WildcardDnsNameRef<'a>) -> Self {
+        // The unwrap won't fail because DnsNameRefs are guaranteed to be ASCII
+        // and ASCII is a subset of UTF-8.
+        core::str::from_utf8(d).unwrap()
+    }
+}
+
 pub(super) fn presented_id_matches_reference_id(
     presented_dns_id: untrusted::Input,
     reference_dns_id: untrusted::Input,
@@ -577,6 +702,10 @@ fn is_valid_dns_id(
     true
 }
 
+fn is_valid_wildcard_dns_id(hostname: untrusted::Input) -> bool {
+    is_valid_dns_id(hostname, IDRole::ReferenceID, AllowWildcards::Yes)
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/src/name/verify.rs b/src/name/verify.rs
index 721797eb..1c7bac35 100644
--- a/src/name/verify.rs
+++ b/src/name/verify.rs
@@ -20,6 +20,11 @@ use crate::{
     cert::{Cert, EndEntityOrCA},
     der, Error,
 };
+#[cfg(feature = "alloc")]
+use {
+    alloc::vec::Vec,
+    dns_name::{GeneralDnsNameRef, WildcardDnsNameRef},
+};
 
 pub fn verify_cert_dns_name(
     cert: &crate::EndEntityCert,
@@ -245,11 +250,11 @@ enum NameIteration {
     Stop(Result<(), Error>),
 }
 
-fn iterate_names(
-    subject: untrusted::Input,
-    subject_alt_name: Option<untrusted::Input>,
+fn iterate_names<'names>(
+    subject: untrusted::Input<'names>,
+    subject_alt_name: Option<untrusted::Input<'names>>,
     result_if_never_stopped_early: Result<(), Error>,
-    f: &dyn Fn(GeneralName) -> NameIteration,
+    f: &dyn Fn(GeneralName<'names>) -> NameIteration,
 ) -> Result<(), Error> {
     match subject_alt_name {
         Some(subject_alt_name) => {
@@ -279,6 +284,33 @@ fn iterate_names(
     }
 }
 
+#[cfg(feature = "alloc")]
+pub fn list_cert_dns_names<'names>(
+    cert: &crate::EndEntityCert<'names>,
+) -> Result<Vec<GeneralDnsNameRef<'names>>, Error> {
+    let cert = &cert.inner;
+    let names = core::cell::RefCell::new(Vec::new());
+
+    iterate_names(cert.subject, cert.subject_alt_name, Ok(()), &|name| {
+        match name {
+            GeneralName::DnsName(presented_id) => {
+                match DnsNameRef::try_from_ascii(presented_id.as_slice_less_safe())
+                    .map(GeneralDnsNameRef::DnsName)
+                    .or_else(|_| {
+                        WildcardDnsNameRef::try_from_ascii(presented_id.as_slice_less_safe())
+                            .map(GeneralDnsNameRef::Wildcard)
+                    }) {
+                    Ok(name) => names.borrow_mut().push(name),
+                    Err(_) => { /* keep going */ }
+                };
+            }
+            _ => (),
+        }
+        NameIteration::KeepGoing
+    })
+    .map(|_| names.into_inner())
+}
+
 // It is *not* valid to derive `Eq`, `PartialEq, etc. for this type. In
 // particular, for the types of `GeneralName`s that we don't understand, we
 // don't even store the value. Also, the meaning of a `GeneralName` in a name
diff --git a/src/webpki.rs b/src/webpki.rs
index c9b48aaa..1a2481b3 100644
--- a/src/webpki.rs
+++ b/src/webpki.rs
@@ -50,7 +50,7 @@ pub mod trust_anchor_util;
 mod verify_cert;
 
 pub use error::Error;
-pub use name::{DnsNameRef, InvalidDnsNameError};
+pub use name::{DnsNameRef, GeneralDnsNameRef, InvalidDnsNameError, WildcardDnsNameRef};
 
 #[cfg(feature = "alloc")]
 pub use name::DnsName;
@@ -248,6 +248,19 @@ impl<'a> EndEntityCert<'a> {
             untrusted::Input::from(signature),
         )
     }
+
+    /// Returns a list of the DNS names provided in the subject alternative names extension
+    ///
+    /// This function must not be used to implement custom DNS name verification.
+    /// Verification functions are already provided as `verify_is_valid_for_dns_name`
+    /// and `verify_is_valid_for_at_least_one_dns_name`.
+    ///
+    /// Requires the `alloc` default feature; i.e. this isn't available in
+    /// `#![no_std]` configurations.
+    #[cfg(feature = "alloc")]
+    pub fn dns_names(&self) -> Result<Vec<GeneralDnsNameRef<'a>>, Error> {
+        name::list_cert_dns_names(&self)
+    }
 }
 
 /// A trust anchor (a.k.a. root CA).
diff --git a/tests/integration.rs b/tests/integration.rs
index 0f39f3cc..9df55610 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -90,3 +90,117 @@ fn read_root_with_neg_serial() {
 fn time_constructor() {
     let _ = webpki::Time::try_from(std::time::SystemTime::now()).unwrap();
 }
+
+#[cfg(feature = "alloc")]
+#[test]
+pub fn list_netflix_names() {
+    let ee = include_bytes!("netflix/ee.der");
+
+    expect_cert_dns_names(
+        ee,
+        &[
+            "account.netflix.com",
+            "ca.netflix.com",
+            "netflix.ca",
+            "netflix.com",
+            "signup.netflix.com",
+            "www.netflix.ca",
+            "www1.netflix.com",
+            "www2.netflix.com",
+            "www3.netflix.com",
+            "develop-stage.netflix.com",
+            "release-stage.netflix.com",
+            "www.netflix.com",
+        ],
+    );
+}
+
+#[cfg(feature = "alloc")]
+#[test]
+pub fn invalid_subject_alt_names() {
+    // same as netflix ee certificate, but with the last name in the list
+    // changed to 'www.netflix:com'
+    let data = include_bytes!("misc/invalid_subject_alternative_name.der");
+
+    expect_cert_dns_names(
+        data,
+        &[
+            "account.netflix.com",
+            "ca.netflix.com",
+            "netflix.ca",
+            "netflix.com",
+            "signup.netflix.com",
+            "www.netflix.ca",
+            "www1.netflix.com",
+            "www2.netflix.com",
+            "www3.netflix.com",
+            "develop-stage.netflix.com",
+            "release-stage.netflix.com",
+            // NOT 'www.netflix:com'
+        ],
+    );
+}
+
+#[cfg(feature = "alloc")]
+#[test]
+pub fn wildcard_subject_alternative_names() {
+    // same as netflix ee certificate, but with the last name in the list
+    // changed to 'ww*.netflix:com'
+    let data = include_bytes!("misc/dns_names_and_wildcards.der");
+
+    expect_cert_dns_names(
+        data,
+        &[
+            "account.netflix.com",
+            "*.netflix.com",
+            "netflix.ca",
+            "netflix.com",
+            "signup.netflix.com",
+            "www.netflix.ca",
+            "www1.netflix.com",
+            "www2.netflix.com",
+            "www3.netflix.com",
+            "develop-stage.netflix.com",
+            "release-stage.netflix.com",
+            "www.netflix.com",
+        ],
+    );
+}
+
+#[cfg(feature = "alloc")]
+fn expect_cert_dns_names(data: &[u8], expected_names: &[&str]) {
+    use std::collections::HashSet;
+
+    let cert = webpki::EndEntityCert::try_from(data)
+        .expect("should parse end entity certificate correctly");
+
+    let expected_names: HashSet<_> = expected_names.iter().cloned().collect();
+
+    let mut actual_names = cert
+        .dns_names()
+        .expect("should get all DNS names correctly for end entity cert");
+
+    // Ensure that converting the list to a set doesn't throw away
+    // any duplicates that aren't supposed to be there
+    assert_eq!(actual_names.len(), expected_names.len());
+
+    let actual_names: std::collections::HashSet<&str> =
+        actual_names.drain(..).map(|name| name.into()).collect();
+
+    assert_eq!(actual_names, expected_names);
+}
+
+#[cfg(feature = "alloc")]
+#[test]
+pub fn no_subject_alt_names() {
+    let data = include_bytes!("misc/no_subject_alternative_name.der");
+
+    let cert = webpki::EndEntityCert::try_from(&data[..])
+        .expect("should parse end entity certificate correctly");
+
+    let names = cert
+        .dns_names()
+        .expect("we should get a result even without subjectAltNames");
+
+    assert!(names.is_empty());
+}
diff --git a/tests/misc/dns_names_and_wildcards.der b/tests/misc/dns_names_and_wildcards.der
new file mode 100644
index 00000000..c41e2d61
Binary files /dev/null and b/tests/misc/dns_names_and_wildcards.der differ
diff --git a/tests/misc/invalid_subject_alternative_name.der b/tests/misc/invalid_subject_alternative_name.der
new file mode 100644
index 00000000..f81ad7b7
Binary files /dev/null and b/tests/misc/invalid_subject_alternative_name.der differ
diff --git a/tests/misc/no_subject_alternative_name.der b/tests/misc/no_subject_alternative_name.der
new file mode 100644
index 00000000..8292e358
Binary files /dev/null and b/tests/misc/no_subject_alternative_name.der differ