Procházet zdrojové kódy

Added code to the x509 module for converting certificates
back to Writecaps.

Matthew Carr před 2 roky
rodič
revize
bc22a6d7c2
3 změnil soubory, kde provedl 351 přidání a 89 odebrání
  1. 24 2
      crates/btlib/src/crypto.rs
  2. 323 85
      crates/btlib/src/crypto/x509.rs
  3. 4 2
      crates/btmsg/src/lib.rs

+ 24 - 2
crates/btlib/src/crypto.rs

@@ -710,12 +710,15 @@ pub struct Signature {
 }
 
 impl Signature {
+    pub fn new(kind: Sign, data: Vec<u8>) -> Self {
+        Self { kind, data }
+    }
+
     pub fn empty(kind: Sign) -> Signature {
         let data = vec![0; kind.key_len() as usize];
         Signature { kind, data }
     }
 
-    #[cfg(test)]
     pub fn copy_from(kind: Sign, from: &[u8]) -> Signature {
         let mut data = vec![0; kind.key_len() as usize];
         data.as_mut_slice().copy_from_slice(from);
@@ -969,6 +972,24 @@ impl KeyLen {
     const fn bits(self) -> u32 {
         8 * self as u32
     }
+
+    fn try_from_u32(value: u32) -> Result<Self> {
+        match value {
+            32 => Ok(Self::Bits256),
+            64 => Ok(Self::Bits512),
+            256 => Ok(Self::Bits2048),
+            384 => Ok(Self::Bits3072),
+            512 => Ok(Self::Bits4096),
+            _ => Err(bterr!("invalid KeyLen value: {value}")),
+        }
+    }
+}
+
+impl TryFrom<u32> for KeyLen {
+    type Error = crate::Error;
+    fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
+        Self::try_from_u32(value)
+    }
 }
 
 /// A Cryptographic Scheme. This is a common type for operations such as encrypting, decrypting,
@@ -2048,7 +2069,8 @@ pub enum WritecapAuthzErr {
 }
 
 impl Writecap {
-    /// Verifies that the given `Writecap` actually grants permission to write to the given `Path`.
+    /// Verifies that the given [Writecap] actually grants permission to write to the given
+    /// [BlockPath].
     pub fn assert_valid_for(&self, path: &BlockPath) -> Result<()> {
         let mut writecap = self;
         const CHAIN_LEN_LIMIT: usize = 256;

+ 323 - 85
crates/btlib/src/crypto/x509.rs

@@ -1,29 +1,43 @@
 //! Code for converting [Writecap]s to and from the X.509 certificate format.
+//! TODO: Improve the efficiency and quality of this code by moving to a different library for
+//! handling X.509 certificates.
 
 use crate::{
     bterr,
-    crypto::{AsymKeyPub, HashKind, RsaSsaPss, Sha2_256, Sha2_512, Sign},
-    Principaled, Result, Writecap,
+    crypto::{AsymKeyPub, HashKind, KeyLen, RsaSsaPss, Sha2_256, Sha2_512, Sign, Signature},
+    BlockPath, Epoch, Principal, Principaled, Result, Writecap, WritecapBody,
 };
 use bcder::{
-    decode::{BytesSource, Constructed},
+    decode::{BytesSource, Constructed, DecodeError, SliceSource},
     encode::{PrimitiveContent, Values},
-    BitString, Captured, Ia5String, Integer, Mode, OctetString, Oid, Tag,
+    BitString, Captured, Integer, Mode, OctetString, Oid, Tag, Utf8String,
 };
 use bytes::{BufMut, Bytes, BytesMut};
 use chrono::{offset::Utc, TimeZone};
+use std::ops::Deref;
 use x509_certificate::{
     asn1time::{Time, UtcTime},
     certificate::X509Certificate,
-    rfc3280::Name,
+    rfc3280::{AttributeValue, Name},
     rfc5280::{
         AlgorithmIdentifier, AlgorithmParameter, Certificate, CertificateSerialNumber, Extension,
         Extensions, SubjectPublicKeyInfo, TbsCertificate, Validity, Version,
     },
 };
+
 mod private {
     use super::*;
 
+    fn oid(slice: &'static [u8]) -> Oid {
+        Oid(Bytes::from(slice))
+    }
+
+    macro_rules! bit_string {
+        ($bytes:expr) => {
+            BitString::new(0, Bytes::from($bytes))
+        };
+    }
+
     impl Sha2_256 {
         // The DER encoding of the OID 2.16.840.1.101.3.4.2.1
         const OID: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01];
@@ -41,44 +55,30 @@ mod private {
                 HashKind::Sha2_512 => Sha2_512::OID,
             }
         }
+
+        fn from_oid(slice: &[u8]) -> Result<Self> {
+            if slice == Sha2_256::OID {
+                Ok(Self::Sha2_256)
+            } else if slice == Sha2_512::OID {
+                Ok(Self::Sha2_512)
+            } else {
+                Err(bterr!("unrecognized OID"))
+            }
+        }
     }
 
     /// The DER encoding of 1.2.840.113549.1.1.8 (id-mgf1 in RFC 4055)
     const MGF1_OID: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x08];
 
-    struct NullTermOid {
-        oid: Oid,
-    }
-
-    impl NullTermOid {
-        fn new(oid: Oid) -> Self {
-            Self { oid }
-        }
-
-        fn encode_ref(&self) -> impl Values + '_ {
-            self.encode_ref_as(Tag::SEQUENCE)
-        }
-
-        fn encode_ref_as(&self, tag: Tag) -> impl Values + '_ {
-            bcder::encode::sequence_as(
-                tag,
-                (
-                    self.oid.encode_ref(),
-                    bcder::encode::Constructed::new(Tag::NULL, bcder::encode::Nothing),
-                ),
-            )
-        }
-    }
-
-    struct AlgoId {
+    struct SingleParamAlgoId {
         algorithm: Oid,
-        parameters: NullTermOid,
+        parameters: Oid,
     }
 
-    impl AlgoId {
+    impl SingleParamAlgoId {
         fn new(hash_kind: HashKind) -> Self {
             let algorithm = Oid(Bytes::from(MGF1_OID));
-            let parameters = NullTermOid::new(Oid(Bytes::from(hash_kind.oid())));
+            let parameters = Oid(Bytes::from(hash_kind.oid()));
             Self {
                 algorithm,
                 parameters,
@@ -95,19 +95,30 @@ mod private {
                 (self.algorithm.encode_ref(), self.parameters.encode_ref()),
             )
         }
+
+        fn take_from<S: bcder::decode::Source>(
+            cons: &mut Constructed<'_, S>,
+        ) -> std::result::Result<Self, DecodeError<S::Error>> {
+            cons.take_sequence(|cons| {
+                Ok(Self {
+                    algorithm: Oid::take_from(cons)?,
+                    parameters: Oid::take_from(cons)?,
+                })
+            })
+        }
     }
 
     struct RsaSsaPssParams {
-        hash_algorithm: NullTermOid,
-        mask_gen_algorithm: AlgoId,
+        hash_algorithm: Oid,
+        mask_gen_algorithm: SingleParamAlgoId,
         salt_length: Integer,
         trailer_field: Option<Integer>,
     }
 
     impl RsaSsaPssParams {
         fn new(hash_kind: HashKind) -> Self {
-            let hash_algorithm = NullTermOid::new(Oid(Bytes::from(hash_kind.oid())));
-            let mask_gen_algorithm = AlgoId::new(hash_kind);
+            let hash_algorithm = Oid(Bytes::from(hash_kind.oid()));
+            let mask_gen_algorithm = SingleParamAlgoId::new(hash_kind);
             let salt_length = Integer::from(hash_kind.len() as u64);
             Self {
                 hash_algorithm,
@@ -135,6 +146,25 @@ mod private {
                 ),
             )
         }
+
+        fn take_from<S: bcder::decode::Source>(
+            cons: &mut Constructed<'_, S>,
+        ) -> std::result::Result<Option<Self>, DecodeError<S::Error>> {
+            let option = cons.take_opt_value_if(Tag::SEQUENCE, |content| {
+                let cons = content.as_constructed()?;
+                Ok(RsaSsaPssParams {
+                    hash_algorithm: cons.take_constructed_if(Tag::CTX_0, Oid::take_from)?,
+                    mask_gen_algorithm: cons
+                        .take_constructed_if(Tag::CTX_1, SingleParamAlgoId::take_from)?,
+                    salt_length: cons.take_constructed_if(Tag::CTX_2, Integer::take_from)?,
+                    trailer_field: cons.take_opt_constructed_if(Tag::CTX_3, Integer::take_from)?,
+                })
+            })?;
+            if option.is_none() {
+                cons.take_null()?;
+            }
+            Ok(option)
+        }
     }
 
     impl RsaSsaPss {
@@ -146,7 +176,7 @@ mod private {
         /// The OID 1.2.840.113549.1.1.1 (RSA-ES)
         const RSA_ES_OID: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01];
 
-        const fn oid(&self) -> &'static [u8] {
+        const fn oid() -> &'static [u8] {
             if Self::USE_PSS_OID {
                 Self::RSA_PSS_OID
             } else {
@@ -164,12 +194,33 @@ mod private {
                 Ok(None)
             }
         }
+
+        fn from_params(sig_octet_len: u32, params: Option<&[u8]>) -> Result<Self> {
+            let key_bits = KeyLen::try_from(sig_octet_len)?;
+            let hash_kind = match params {
+                Some(params) => {
+                    let source = SliceSource::new(params);
+                    let params =
+                        Constructed::decode(source, Mode::Der, RsaSsaPssParams::take_from)?;
+                    if let Some(params) = params {
+                        HashKind::from_oid(params.hash_algorithm.as_ref())?
+                    } else {
+                        HashKind::default()
+                    }
+                }
+                None => HashKind::default(),
+            };
+            Ok(Self {
+                key_bits,
+                hash_kind,
+            })
+        }
     }
 
     impl Sign {
         const fn oid(&self) -> &'static [u8] {
             match self {
-                Sign::RsaSsaPss(inner) => inner.oid(),
+                Sign::RsaSsaPss(..) => RsaSsaPss::oid(),
             }
         }
 
@@ -178,36 +229,43 @@ mod private {
                 Sign::RsaSsaPss(inner) => inner.params(),
             }
         }
-    }
 
-    fn scheme_to_algo_id(scheme: &Sign) -> Result<AlgorithmIdentifier> {
-        Ok(AlgorithmIdentifier {
-            algorithm: oid(scheme.oid()),
-            parameters: scheme.params()?,
-        })
-    }
+        fn from_der(sig_octet_len: u32, oid: &[u8], params: Option<&[u8]>) -> Result<Sign> {
+            if oid == RsaSsaPss::oid() {
+                Ok(Sign::RsaSsaPss(RsaSsaPss::from_params(
+                    sig_octet_len,
+                    params,
+                )?))
+            } else {
+                Err(bterr!("OID does not match a Sign variant"))
+            }
+        }
 
-    fn oid(slice: &'static [u8]) -> Oid {
-        Oid(Bytes::from(slice))
-    }
+        fn to_algo_id(self) -> Result<AlgorithmIdentifier> {
+            Ok(AlgorithmIdentifier {
+                algorithm: oid(self.oid()),
+                parameters: self.params()?,
+            })
+        }
 
-    macro_rules! bit_string {
-        ($bytes:expr) => {
-            BitString::new(0, Bytes::from($bytes))
-        };
+        fn from_algo_id(sig_octet_len: u32, algo_id: &AlgorithmIdentifier) -> Result<Sign> {
+            let params = algo_id.parameters.as_ref().map(|e| e.as_ref());
+            Sign::from_der(sig_octet_len, algo_id.algorithm.as_ref(), params)
+        }
     }
 
-    /// OID 2.5.29.17 (Subject Alternative Name)
-    const SAN_OID: &[u8] = &[0x55, 0x1D, 0x11];
-
     struct SubjectAltName {
-        issued_to: Ia5String,
+        block_path: Utf8String,
     }
 
     impl SubjectAltName {
-        fn new(issued_to: String) -> Result<Self> {
-            let issued_to = Ia5String::from_string(issued_to).map_err(|err| bterr!("{:?}", err))?;
-            Ok(Self { issued_to })
+        /// OID 2.5.29.17 (Subject Alternative Name)
+        const OID: &[u8] = &[0x55, 0x1D, 0x11];
+
+        fn new(block_path: &BlockPath) -> Result<Self> {
+            let block_path = Utf8String::from_string(block_path.to_string())
+                .map_err(|err| bterr!("{:?}", err))?;
+            Ok(Self { block_path })
         }
 
         fn encode(&self) -> impl Values + '_ {
@@ -215,20 +273,35 @@ mod private {
         }
 
         fn encode_as(&self, tag: Tag) -> impl Values + '_ {
-            bcder::encode::sequence_as(tag, self.issued_to.encode_ref())
+            bcder::encode::sequence_as(tag, self.block_path.encode_ref())
+        }
+
+        fn take_from<S: bcder::decode::Source>(
+            cons: &mut Constructed<'_, S>,
+        ) -> std::result::Result<Self, DecodeError<S::Error>> {
+            cons.take_sequence(|cons| {
+                Ok(Self {
+                    block_path: Utf8String::take_from(cons)?,
+                })
+            })
         }
 
         fn encode_der(&self) -> Result<Bytes> {
             let mut writer = BytesMut::new().writer();
-            self.encode().write_encoded(bcder::Mode::Der, &mut writer)?;
+            self.encode().write_encoded(Mode::Der, &mut writer)?;
             Ok(writer.into_inner().into())
         }
+
+        fn decode_der<B: AsRef<[u8]>>(bytes: B) -> Result<Self> {
+            let source = SliceSource::new(bytes.as_ref());
+            Constructed::decode(source, Mode::Der, Self::take_from).map_err(|err| err.into())
+        }
     }
 
     impl AsymKeyPub<Sign> {
         fn subject_public_key_info(&self) -> Result<SubjectPublicKeyInfo> {
             Ok(SubjectPublicKeyInfo {
-                algorithm: scheme_to_algo_id(&self.scheme)?,
+                algorithm: self.scheme.to_algo_id()?,
                 subject_public_key: self.to_bit_string()?,
             })
         }
@@ -240,6 +313,16 @@ mod private {
             Ok(spki.subject_public_key)
         }
 
+        fn from_subject_public_key_info(
+            sig_octet_len: u32,
+            spki: &SubjectPublicKeyInfo,
+        ) -> Result<Self> {
+            let scheme = Sign::from_algo_id(sig_octet_len, &spki.algorithm)?;
+            let mut der = Vec::new();
+            spki.encode_ref().write_encoded(Mode::Der, &mut der)?;
+            AsymKeyPub::new(scheme, der.as_slice())
+        }
+
         pub fn to_der(&self) -> Result<Vec<u8>> {
             let spki = self.subject_public_key_info()?;
             let mut vec = Vec::new();
@@ -248,19 +331,64 @@ mod private {
         }
     }
 
-    impl Writecap {
-        pub fn to_cert_chain(
-            &self,
-            subject_key: &AsymKeyPub<Sign>,
-        ) -> Result<Vec<X509Certificate>> {
-            let mut chain = match self.next.as_ref() {
-                Some(next) => next.as_ref().to_cert_chain(&self.body.signing_key)?,
-                None => Vec::new(),
-            };
+    trait NameExt {
+        fn try_get_common_name(&self) -> Result<&AttributeValue>;
+    }
+
+    impl NameExt for Name {
+        fn try_get_common_name(&self) -> Result<&AttributeValue> {
+            Ok(&self
+                .iter_common_name()
+                .next()
+                .ok_or_else(|| bterr!("no CommonName component in Name"))?
+                .value)
+        }
+    }
+
+    trait TryAsStr {
+        fn try_as_str(&self) -> Result<&str>;
+    }
+
+    impl<T: ?Sized + AsRef<[u8]>> TryAsStr for T {
+        fn try_as_str(&self) -> Result<&str> {
+            std::str::from_utf8(self.as_ref()).map_err(|err| err.into())
+        }
+    }
+
+    trait TimeExt {
+        fn try_to_epoch(&self) -> Result<Epoch>;
+    }
+
+    impl TimeExt for Time {
+        fn try_to_epoch(&self) -> Result<Epoch> {
+            match self {
+                Self::UtcTime(time) => Ok(Epoch::from_value(time.timestamp() as u64)),
+                Self::GeneralTime(..) => Err(bterr!("unsupported Time variant encountered")),
+            }
+        }
+    }
+
+    trait ExtensionsExt {
+        fn find_subject_alt_name(&self) -> Result<SubjectAltName>;
+    }
+
+    impl ExtensionsExt for Extensions {
+        fn find_subject_alt_name(&self) -> Result<SubjectAltName> {
+            let extensions: &[Extension] = self.deref();
+            for extension in extensions {
+                if extension.id.as_ref() == SubjectAltName::OID {
+                    return SubjectAltName::decode_der(extension.value.to_bytes());
+                }
+            }
+            Err(bterr!("SubjectAltName not found"))
+        }
+    }
 
+    impl Writecap {
+        pub fn to_cert(&self, subject_key: &AsymKeyPub<Sign>) -> Result<Vec<u8>> {
             let version = Some(Version::V3);
             let serial_number = CertificateSerialNumber::from(1);
-            let signature_algorithm = scheme_to_algo_id(&self.body.signing_key.scheme)?;
+            let signature_algorithm = self.body.signing_key.scheme.to_algo_id()?;
             let mut issuer = Name::default();
             issuer
                 .append_common_name_utf8_string(&self.body.signing_key.principal().to_string())
@@ -282,9 +410,9 @@ mod private {
                 .map_err(|_| bterr!("failed to create subject common name"))?;
             let subject_public_key_info = subject_key.subject_public_key_info()?;
             let mut extensions = Extensions::default();
-            let san = SubjectAltName::new(issued_to)?;
+            let san = SubjectAltName::new(&self.body.path)?;
             extensions.push(Extension {
-                id: oid(SAN_OID),
+                id: oid(SubjectAltName::OID),
                 critical: Some(false),
                 value: OctetString::new(san.encode_der()?),
             });
@@ -306,17 +434,119 @@ mod private {
                 signature_algorithm,
                 signature: bit_string!(self.signature.data.clone()),
             };
-            chain.push(cert.into());
+            let cert: X509Certificate = cert.into();
+            cert.encode_der().map_err(|err| err.into())
+        }
+
+        fn to_cert_chain_impl(&self, subject_key: &AsymKeyPub<Sign>) -> Result<Vec<Vec<u8>>> {
+            let mut chain = match self.next.as_ref() {
+                Some(next) => next.as_ref().to_cert_chain(&self.body.signing_key)?,
+                None => {
+                    // An extra cert is added to the end of the chain to contain the root
+                    // principal's signing key.
+                    let mut vec = Vec::with_capacity(2);
+                    let root_principal = self.body.signing_key.principal();
+                    let path = BlockPath::new(root_principal.clone(), vec![]);
+                    let writecap = Writecap {
+                        body: WritecapBody {
+                            issued_to: root_principal,
+                            expires: Epoch::now(),
+                            path,
+                            signing_key: self.body.signing_key.clone(),
+                        },
+                        signature: self.signature.clone(),
+                        next: None,
+                    };
+                    vec.push(writecap.to_cert(&self.body.signing_key)?);
+                    vec
+                }
+            };
+
+            chain.push(self.to_cert(subject_key)?);
             Ok(chain)
         }
+
+        pub fn to_cert_chain(&self, subject_key: &AsymKeyPub<Sign>) -> Result<Vec<Vec<u8>>> {
+            let mut chain = self.to_cert_chain_impl(subject_key)?;
+            chain.reverse();
+            Ok(chain)
+        }
+
+        pub fn from_cert_chain<B: AsRef<[u8]>>(
+            chain: &[B],
+        ) -> Result<(Writecap, AsymKeyPub<Sign>)> {
+            let der = chain
+                .first()
+                .ok_or_else(|| bterr!("at least one certificate must be supplied"))?;
+            let (next, signing_key) = if chain.len() > 1 {
+                let (writecap, signing_key) = Self::from_cert_chain(&chain[1..])?;
+                // Remove the extra writecap at the end.
+                let writecap = if chain.len() == 2 {
+                    None
+                } else {
+                    Some(writecap)
+                };
+                (writecap, Some(signing_key))
+            } else {
+                (None, None)
+            };
+
+            let x509_cert = X509Certificate::from_der(der)?;
+            let cert: &Certificate = x509_cert.as_ref();
+            if cert.signature.unused() > 0 {
+                return Err(bterr!("signature length is not divisible by 8"));
+            }
+            let extensions = cert
+                .tbs_certificate
+                .extensions
+                .as_ref()
+                .ok_or_else(|| bterr!("no extensions present"))?;
+            let san = extensions.find_subject_alt_name()?;
+            let path = BlockPath::try_from(san.block_path.into_bytes().try_as_str()?)
+                .map_err(|err| bterr!(err))?;
+            let sig_octet_len: u32 = cert.signature.octet_len().try_into()?;
+            let scheme = Sign::from_algo_id(
+                sig_octet_len,
+                &cert.tbs_certificate.subject_public_key_info.algorithm,
+            )?;
+            let signature = Signature::new(scheme, cert.signature.octet_bytes().into());
+            let cert = &cert.tbs_certificate;
+            let subject_key = AsymKeyPub::from_subject_public_key_info(
+                sig_octet_len,
+                &cert.subject_public_key_info,
+            )?;
+            let issued_to: Principal = cert
+                .subject
+                .try_get_common_name()?
+                .to_string()?
+                .as_str()
+                .try_into()?;
+            let expires = cert.validity.not_after.try_to_epoch()?;
+            // If signing_key is None, then we're at the last certificate in the chain, which is
+            // self-signed. So the subject_key is the same as the issuer's signing key.
+            let signing_key = signing_key.unwrap_or_else(|| subject_key.clone());
+            let writecap = Writecap {
+                body: WritecapBody {
+                    issued_to,
+                    signing_key,
+                    path,
+                    expires,
+                },
+                signature,
+                next: next.map(Box::new),
+            };
+            Ok((writecap, subject_key))
+        }
     }
 }
 
 #[cfg(test)]
 mod tests {
+    use super::*;
+
     use webpki::EndEntityCert;
 
-    use crate::test_helpers::node_creds;
+    use crate::{crypto::CredsPub, test_helpers::node_creds};
 
     #[allow(dead_code)]
     fn save_first_writecap_der_to_file() {
@@ -328,13 +558,7 @@ mod tests {
             .to_cert_chain(&node_creds.sign.public)
             .unwrap();
         let first = chain.first().unwrap();
-        let mut buf = Vec::new();
-
-        first.encode_der_to(&mut buf).unwrap();
-        std::fs::write("/tmp/cert.der", buf).unwrap();
-
-        let pem = first.encode_pem().unwrap();
-        std::fs::write("/tmp/cert.pem", &pem).unwrap();
+        std::fs::write("/tmp/cert.der", first).unwrap();
     }
 
     #[test]
@@ -359,10 +583,24 @@ mod tests {
             .unwrap()
             .to_cert_chain(&node_creds.sign.public)
             .unwrap();
-        let der = chain.first().unwrap().encode_der().unwrap();
+        let der = chain.first().unwrap();
 
         let result = EndEntityCert::try_from(der.as_slice());
 
         result.unwrap();
     }
+
+    #[test]
+    fn round_trip_writecap() {
+        let node_creds = node_creds();
+        let expected_key = node_creds.public_sign();
+        let expected_wc = node_creds.writecap.as_ref().unwrap();
+
+        let certs = expected_wc.to_cert_chain(expected_key).unwrap();
+        let (actual_wc, actual_key) = Writecap::from_cert_chain(certs.as_slice()).unwrap();
+
+        assert_eq!(expected_key, &actual_key);
+        assert_eq!(expected_wc, &actual_wc);
+        actual_wc.assert_valid_for(&expected_wc.body.path).unwrap();
+    }
 }

+ 4 - 2
crates/btmsg/src/lib.rs

@@ -20,7 +20,9 @@ use futures::{
 use lazy_static::lazy_static;
 use log::error;
 use quinn::{ClientConfig, Endpoint, SendStream, ServerConfig};
-use rustls::{Certificate, PrivateKey, ConfigBuilder, ConfigSide, WantsCipherSuites, WantsVerifier};
+use rustls::{
+    Certificate, ConfigBuilder, ConfigSide, PrivateKey, WantsCipherSuites, WantsVerifier,
+};
 use serde::{Deserialize, Serialize};
 use std::{
     collections::hash_map::DefaultHasher,
@@ -97,7 +99,7 @@ mod private {
         let chain = writecap.to_cert_chain(creds.public_sign())?;
         let mut cert_chain = Vec::with_capacity(chain.len());
         for cert in chain {
-            cert_chain.push(Certificate(cert.encode_der()?))
+            cert_chain.push(Certificate(cert))
         }
         let key = PrivateKey(creds.private_sign().to_der()?);
         let server_config = common_config(rustls::ServerConfig::builder())?