diff --git a/src/name.rs b/src/name.rs
index 60703978..d88926d4 100644
--- a/src/name.rs
+++ b/src/name.rs
@@ -16,126 +16,216 @@ use crate::{
     cert::{Cert, EndEntityOrCA},
     der, Error,
 };
+use core::fmt::Write as _;
 
-/// 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 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 `DnsName` is guaranteed to be syntactically valid. The validity rules are
-/// specified in [RFC 5280 Section 7.2], except that underscores are also
+/// A `DnsName` is guaranteed to be syntactically valid. The validity rules
+/// are specified in [RFC 5280 Section 7.2], except that underscores are also
 /// 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.
+/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
+pub struct DnsName<B>(B)
+where
+    B: AsRef<[u8]>;
+
+/// A borrowed `DnsName`.
 ///
-/// `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.
+/// This is an alias for `DnsName<&'a [u8]>`.
+pub type DnsNameRef<'a> = DnsName<&'a [u8]>;
+
+/// An owned `DnsName`
 ///
-/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
+/// This is an alias for `DnsName<Box<[u8]>>`.
 #[cfg(feature = "std")]
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
-pub struct DnsName(String);
+pub type DnsNameBox = DnsName<Box<[u8]>>;
 
-#[cfg(feature = "std")]
-impl DnsName {
-    /// Returns a `DnsNameRef` that refers to this `DnsName`.
-    pub fn as_ref(&self) -> DnsNameRef {
-        DnsNameRef(self.0.as_bytes())
+/// An error indicating that a `DnsName` could not built because the input
+/// is not a syntactically-valid DNS Name.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct InvalidDnsNameError;
+
+impl core::fmt::Display for InvalidDnsNameError {
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+        write!(f, "{:?}", self)
     }
 }
 
 #[cfg(feature = "std")]
-impl AsRef<str> for DnsName {
-    fn as_ref(&self) -> &str {
-        self.0.as_ref()
-    }
+impl ::std::error::Error for InvalidDnsNameError {}
+
+pub trait DnsNameInput: AsRef<[u8]> + Sized {
+    type Storage: AsRef<[u8]>;
+    fn into_storage(self) -> Self::Storage;
 }
 
-// Deprecated
 #[cfg(feature = "std")]
-impl From<DnsNameRef<'_>> for DnsName {
-    fn from(dns_name: DnsNameRef) -> Self {
-        dns_name.to_owned()
+impl DnsNameInput for String {
+    type Storage = Box<[u8]>;
+    fn into_storage(self) -> Self::Storage {
+        self.into_boxed_str().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.
-///
-/// A `DnsNameRef` 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 DnsNameRef<'a>(&'a [u8]);
+#[cfg(feature = "std")]
+impl DnsNameInput for Box<[u8]> {
+    type Storage = Box<[u8]>;
+    fn into_storage(self) -> Self::Storage {
+        self
+    }
+}
 
-/// An error indicating that a `DnsNameRef` could not built because the input
-/// is not a syntactically-valid DNS Name.
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub struct InvalidDnsNameError;
+#[cfg(feature = "std")]
+impl DnsNameInput for Vec<u8> {
+    type Storage = Box<[u8]>;
+    fn into_storage(self) -> Self::Storage {
+        self.into()
+    }
+}
 
-impl core::fmt::Display for InvalidDnsNameError {
-    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
-        write!(f, "{:?}", self)
+impl<'a> DnsNameInput for &'a str {
+    type Storage = &'a [u8];
+    fn into_storage(self) -> Self::Storage {
+        self.as_ref()
     }
 }
 
-#[cfg(feature = "std")]
-impl ::std::error::Error for InvalidDnsNameError {}
+impl<'a> DnsNameInput for &'a [u8] {
+    type Storage = &'a [u8];
+    fn into_storage(self) -> Self::Storage {
+        self.as_ref()
+    }
+}
 
-impl<'a> DnsNameRef<'a> {
-    /// Constructs a `DnsNameRef` from the given input if the input is a
+impl<B> DnsName<B>
+where
+    B: AsRef<[u8]>,
+{
+    /// Constructs a `DnsName` 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_reference_dns_id(untrusted::Input::from(dns_name)) {
+    pub fn try_from_punycode(
+        input: impl DnsNameInput<Storage = B>,
+    ) -> Result<Self, InvalidDnsNameError> {
+        if !is_valid_reference_dns_id(untrusted::Input::from(input.as_ref())) {
             return Err(InvalidDnsNameError);
         }
 
-        Ok(Self(dns_name))
+        Ok(Self(input.into_storage()))
     }
+}
 
-    /// Constructs a `DnsNameRef` 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())
+impl<B> DnsName<B>
+where
+    B: AsRef<[u8]>,
+{
+    /// Borrows any `DnsName` as a `DnsName<&[u8]>`.
+    ///
+    /// Use `DnsName<&[u8]>` when you don't *need* to be generic over the
+    /// underlying representation to reduce monomorphization overhead.
+    #[inline]
+    pub fn borrow(&self) -> DnsName<&[u8]> {
+        DnsName(self.0.as_ref())
     }
 
-    /// Constructs a `DnsName` from this `DnsNameRef`
+    /// TODO:
     #[cfg(feature = "std")]
-    pub fn to_owned(&self) -> DnsName {
-        // DnsNameRef is already guaranteed to be valid ASCII, which is a
-        // subset of UTF-8.
-        let s: &str = self.clone().into();
-        DnsName(s.to_ascii_lowercase())
+    pub fn into_owned(self) -> DnsName<Box<[u8]>> {
+        DnsName(Box::from(self.0.as_ref()))
+    }
+
+    /// Returns an iterator of the punycode characters of the DNS name, as
+    /// bytes.
+    ///
+    /// The iterator implements many specialized iterator traits, such as
+    /// `ExactSizeIterator` and `DoubleEndedIterator`.
+    ///
+    /// ```
+    /// # #[cfg(feature = "std")]
+    /// use std::{iter::FromIterator, string::String};
+    ///
+    /// # #[cfg(feature = "std")]
+    /// fn string_from_dns_name<B>(dns_name: webpki::DnsName<B>) -> String where B: AsRef<[u8]> {
+    ///     String::from_iter(dns_name.punycode_lowercase_bytes().map(char::from))
+    /// }
+    /// ```
+    #[inline]
+    pub fn punycode_lowercase_bytes<'b>(
+        &self,
+    ) -> core::iter::Map<core::slice::Iter<u8>, fn(&u8) -> u8>
+    where
+        B: 'b,
+    {
+        // The unwrap won't fail because DnsNames are guaranteed to be ASCII
+        // and ASCII is a subset of UTF-8.
+        self.0.as_ref().iter().map(u8::to_ascii_lowercase)
     }
 }
 
-#[cfg(feature = "std")]
-impl core::fmt::Debug for DnsNameRef<'_> {
-    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
-        let lowercase = self.clone().to_owned();
-        f.debug_tuple("DnsNameRef").field(&lowercase.0).finish()
+impl<B> Clone for DnsName<B>
+where
+    B: AsRef<[u8]>,
+    B: Clone,
+{
+    fn clone(&self) -> Self {
+        Self(self.0.clone())
     }
 }
 
-impl<'a> From<DnsNameRef<'a>> for &'a str {
-    fn from(DnsNameRef(d): DnsNameRef<'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()
+impl<B> Copy for DnsName<B> where B: AsRef<[u8]> + Copy {}
+
+impl<B> core::fmt::Debug for DnsName<B>
+where
+    B: AsRef<[u8]>,
+{
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+        f.write_str("DnsName(\"")?;
+        // None of the characters in a `DnsName` need to be escaped.
+        self.punycode_lowercase_bytes()
+            .try_for_each(|b| f.write_char(b.into()))?;
+        f.write_str("\")")
+    }
+}
+
+impl<B> core::fmt::Display for DnsName<B>
+where
+    B: AsRef<[u8]>,
+{
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        self.punycode_lowercase_bytes()
+            .try_for_each(|b| f.write_char(b.into()))
+    }
+}
+
+impl<B> core::hash::Hash for DnsName<B>
+where
+    B: AsRef<[u8]>,
+{
+    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
+        // This is modeled after the implementation of `Hash` for `[T]`.
+        let lowercase = self.punycode_lowercase_bytes();
+        state.write_usize(lowercase.len());
+        lowercase.for_each(|b| state.write_u8(b));
     }
 }
 
-pub fn verify_cert_dns_name(
+impl<B1, B2> PartialEq<DnsName<B1>> for DnsName<B2>
+where
+    B1: AsRef<[u8]>,
+    B2: AsRef<[u8]>,
+{
+    #[inline]
+    fn eq(&self, other: &DnsName<B1>) -> bool {
+        self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref())
+    }
+}
+
+impl<B> Eq for DnsName<B> where B: AsRef<[u8]> {}
+
+pub(crate) fn verify_cert_dns_name(
     cert: &super::EndEntityCert,
-    DnsNameRef(dns_name): DnsNameRef,
+    DnsName(dns_name): DnsName<&[u8]>,
 ) -> Result<(), Error> {
     let cert = &cert.inner;
     let dns_name = untrusted::Input::from(dns_name);
diff --git a/src/webpki.rs b/src/webpki.rs
index 7076de8b..2675058d 100644
--- a/src/webpki.rs
+++ b/src/webpki.rs
@@ -41,10 +41,10 @@ pub mod trust_anchor_util;
 mod verify_cert;
 
 pub use error::Error;
-pub use name::{DnsNameRef, InvalidDnsNameError};
+pub use name::{DnsName, DnsNameRef, InvalidDnsNameError};
 
 #[cfg(feature = "std")]
-pub use name::DnsName;
+pub use name::DnsNameBox;
 
 pub use signed_data::{
     SignatureAlgorithm, ECDSA_P256_SHA256, ECDSA_P256_SHA384, ECDSA_P384_SHA256, ECDSA_P384_SHA384,
@@ -167,7 +167,7 @@ impl<'a> EndEntityCert<'a> {
     }
 
     /// Verifies that the certificate is valid for the given DNS host name.
-    pub fn verify_is_valid_for_dns_name(&self, dns_name: DnsNameRef) -> Result<(), Error> {
+    pub fn verify_is_valid_for_dns_name(&self, dns_name: DnsName<&[u8]>) -> Result<(), Error> {
         name::verify_cert_dns_name(&self, dns_name)
     }
 
@@ -181,15 +181,16 @@ impl<'a> EndEntityCert<'a> {
     /// Requires the `std` default feature; i.e. this isn't available in
     /// `#![no_std]` configurations.
     #[cfg(feature = "std")]
-    pub fn verify_is_valid_for_at_least_one_dns_name<'names, Names>(
+    pub fn verify_is_valid_for_at_least_one_dns_name<'names, B, Names>(
         &self,
         dns_names: Names,
-    ) -> Result<Vec<DnsNameRef<'names>>, Error>
+    ) -> Result<Vec<DnsName<B>>, Error>
     where
-        Names: Iterator<Item = DnsNameRef<'names>>,
+        B: AsRef<[u8]>,
+        Names: Iterator<Item = DnsName<B>>,
     {
-        let result: Vec<DnsNameRef<'names>> = dns_names
-            .filter(|n| self.verify_is_valid_for_dns_name(*n).is_ok())
+        let result: Vec<_> = dns_names
+            .filter(|n| self.verify_is_valid_for_dns_name(n.borrow()).is_ok())
             .collect();
         if result.is_empty() {
             return Err(Error::CertNotValidForName);
diff --git a/tests/dns_name_tests.rs b/tests/dns_name_tests.rs
index b3a3adc4..636bfb00 100644
--- a/tests/dns_name_tests.rs
+++ b/tests/dns_name_tests.rs
@@ -1,5 +1,7 @@
 // Copyright 2014-2017 Brian Smith.
 
+use webpki::{DnsName, DnsNameRef};
+
 // (name, is_valid)
 static DNS_NAME_VALIDITY: &[(&[u8], bool)] = &[
     (b"a", true),
@@ -399,10 +401,102 @@ fn dns_name_ref_try_from_ascii_test() {
         .chain(IP_ADDRESS_DNS_VALIDITY.iter())
     {
         assert_eq!(
-            webpki::DnsNameRef::try_from_ascii(s).is_ok(),
+            DnsName::<&[u8]>::try_from_punycode(s).is_ok(),
             is_valid,
-            "DnsNameRef::try_from_ascii_str failed for \"{:?}\"",
+            "DnsName::try_from_punycode failed for \"{:?}\"",
             s
         );
     }
 }
+
+const DNS_NAME_LOWERCASE_TEST_CASES: &[(&str, &str)] = &[
+    // (expected_lowercase, input)
+    ("abc", "abc"),
+    ("abc", "Abc"),
+    ("abc", "aBc"),
+    ("abc", "abC"),
+    ("abc1", "abC1"),
+    ("abc.def", "abC.Def"),
+];
+
+#[test]
+fn test_dns_name_ascii_lowercase_chars() {
+    for (expected_lowercase, input) in DNS_NAME_LOWERCASE_TEST_CASES {
+        let dns_name: DnsNameRef = DnsName::try_from_punycode(*input).unwrap();
+        let actual_lowercase = dns_name.punycode_lowercase_bytes();
+
+        assert_eq!(expected_lowercase.len(), actual_lowercase.len());
+        assert!(expected_lowercase
+            .chars()
+            .zip(actual_lowercase)
+            .all(|(a, b)| a == b.into()));
+    }
+}
+
+// XXX: This shouldn't require the "std" feature.
+#[cfg(feature = "std")]
+#[test]
+fn test_dns_name_fmt() {
+    for (expected_lowercase, input) in DNS_NAME_LOWERCASE_TEST_CASES {
+        let dns_name: DnsNameRef = DnsName::try_from_punycode(*input).unwrap();
+
+        // Test `Display` implementation.
+        assert_eq!(*expected_lowercase, format!("{}", dns_name));
+
+        // Test `Debug` implementation.
+        let debug_formatted = format!("{:?}", &dns_name);
+        assert_eq!(
+            String::from("DnsName(\"") + *expected_lowercase + "\")",
+            debug_formatted
+        );
+    }
+}
+
+// XXX: We need more test cases.
+#[test]
+fn test_dns_name_eq_different_len() {
+    #[rustfmt::skip]
+    const NOT_EQUAL: &[(&str, &str)] = &[
+        ("a", "aa"),
+        ("aa", "a"),
+        ("aaa", "a"),
+        ("a", "aaa")
+    ];
+
+    for (a, b) in NOT_EQUAL {
+        let a: DnsNameRef = DnsName::try_from_punycode(*a).unwrap();
+        let b: DnsNameRef = DnsName::try_from_punycode(*b).unwrap();
+        assert_ne!(a, b)
+    }
+}
+
+/// XXX: We need more test cases.
+#[test]
+fn test_dns_name_eq_case() {
+    for (expected_lowercase, input) in DNS_NAME_LOWERCASE_TEST_CASES {
+        let a: DnsNameRef = DnsName::try_from_punycode(*expected_lowercase).unwrap();
+        let b: DnsNameRef = DnsName::try_from_punycode(*input).unwrap();
+        assert_eq!(a, b);
+    }
+}
+
+/// XXX: We need more test cases.
+#[cfg(feature = "std")]
+#[test]
+fn test_dns_name_eq_various_types() {
+    use webpki::DnsNameBox;
+
+    for (expected_lowercase, input) in DNS_NAME_LOWERCASE_TEST_CASES {
+        let a: DnsNameRef = DnsName::try_from_punycode(*expected_lowercase).unwrap();
+        let b: DnsNameBox = DnsName::try_from_punycode(*input).unwrap().into_owned();
+        assert_eq!(a, b);
+    }
+
+    for (expected_lowercase, input) in DNS_NAME_LOWERCASE_TEST_CASES {
+        let a: DnsNameBox = DnsName::try_from_punycode(*expected_lowercase)
+            .unwrap()
+            .into_owned();
+        let b: DnsNameRef = DnsName::try_from_punycode(*input).unwrap();
+        assert_eq!(a, b);
+    }
+}