Эх сурвалжийг харах

Implemented certificate authentication in btmsg.

Matthew Carr 2 жил өмнө
parent
commit
b782c30ef7

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

@@ -732,6 +732,14 @@ impl Signature {
     pub fn as_mut_slice(&mut self) -> &mut [u8] {
         self.data.as_mut_slice()
     }
+
+    pub fn scheme(&self) -> Sign {
+        self.kind
+    }
+
+    pub fn take_data(self) -> Vec<u8> {
+        self.data
+    }
 }
 
 impl AsRef<[u8]> for Signature {
@@ -1483,6 +1491,14 @@ impl<S: Scheme> AsymKeyPair<S> {
         let private = AsymKey::<Private, _>::new(scheme, private_der)?;
         Ok(AsymKeyPair { public, private })
     }
+
+    pub fn public(&self) -> &AsymKey<Public, S> {
+        &self.public
+    }
+
+    pub fn private(&self) -> &AsymKey<Private, S> {
+        &self.private
+    }
 }
 
 // Note that only signing keys are associated with a Principal.
@@ -1598,8 +1614,12 @@ impl ConcreteCreds {
         self.writecap = Some(writecap)
     }
 
-    pub fn private_sign(&self) -> &AsymKey<Private, Sign> {
-        &self.sign.private
+    pub fn sign_pair(&self) -> &AsymKeyPair<Sign> {
+        &self.sign
+    }
+
+    pub fn encrypt_pair(&self) -> &AsymKeyPair<Encrypt> {
+        &self.encrypt
     }
 }
 

+ 37 - 24
crates/btlib/src/crypto/x509.rs

@@ -384,15 +384,38 @@ mod private {
         }
     }
 
+    impl Principal {
+        fn to_name(&self) -> Result<Name> {
+            let mut name = Name::default();
+            let string = self.to_string();
+            name.append_common_name_utf8_string(&string)
+                .map_err(|_| bterr!("failed to create Name for Principal"))?;
+            Ok(name)
+        }
+
+        fn from_name(name: &Name) -> Result<Self> {
+            let principal = name
+                .try_get_common_name()?
+                .to_string()?
+                .as_str()
+                .try_into()?;
+            Ok(principal)
+        }
+
+        pub fn to_name_der(&self) -> Result<Vec<u8>> {
+            let name = self.to_name()?;
+            let mut vec = Vec::new();
+            name.encode_ref().write_encoded(Mode::Der, &mut vec)?;
+            Ok(vec)
+        }
+    }
+
     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 = 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())
-                .map_err(|_| bterr!("failed to create issuer common name"))?;
+            let issuer = self.body.signing_key.principal().to_name()?;
             let expires = Utc
                 .timestamp_millis_opt(1000 * self.body.expires.to_unix())
                 .single()
@@ -403,11 +426,7 @@ mod private {
                 not_before: Time::UtcTime(UtcTime::now()),
                 not_after: Time::from(expires),
             };
-            let issued_to = self.body.issued_to.to_string();
-            let mut subject = Name::default();
-            subject
-                .append_common_name_utf8_string(&issued_to)
-                .map_err(|_| bterr!("failed to create subject common name"))?;
+            let subject = self.body.issued_to.to_name()?;
             let subject_public_key_info = subject_key.subject_public_key_info()?;
             let mut extensions = Extensions::default();
             let san = SubjectAltName::new(&self.body.path)?;
@@ -473,15 +492,13 @@ mod private {
         }
 
         pub fn from_cert_chain<B: AsRef<[u8]>>(
-            chain: &[B],
+            first: &B,
+            rest: &[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..])?;
+            let (next, signing_key) = if !rest.is_empty() {
+                let (writecap, signing_key) = Self::from_cert_chain(&rest[0], &rest[1..])?;
                 // Remove the extra writecap at the end.
-                let writecap = if chain.len() == 2 {
+                let writecap = if rest.len() == 1 {
                     None
                 } else {
                     Some(writecap)
@@ -491,7 +508,7 @@ mod private {
                 (None, None)
             };
 
-            let x509_cert = X509Certificate::from_der(der)?;
+            let x509_cert = X509Certificate::from_der(first)?;
             let cert: &Certificate = x509_cert.as_ref();
             if cert.signature.unused() > 0 {
                 return Err(bterr!("signature length is not divisible by 8"));
@@ -515,12 +532,7 @@ mod private {
                 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 issued_to = Principal::from_name(&cert.subject)?;
             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.
@@ -597,7 +609,8 @@ mod tests {
         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();
+        let (actual_wc, actual_key) =
+            Writecap::from_cert_chain(certs.first().unwrap(), &certs[1..]).unwrap();
 
         assert_eq!(expected_key, &actual_key);
         assert_eq!(expected_wc, &actual_wc);

+ 5 - 0
crates/btlib/src/lib.rs

@@ -1101,6 +1101,11 @@ impl Writecap {
     pub fn root_block_path(&self) -> BlockPath {
         BlockPath::new(self.root_principal(), vec![])
     }
+
+    /// Returns a reference to the path contained in this [Writecap].
+    pub fn path(&self) -> &BlockPath {
+        &self.body.path
+    }
 }
 
 /// Fragments are created from blocks using Erasure Encoding and stored with other nodes in the

+ 212 - 38
crates/btmsg/src/lib.rs

@@ -1,9 +1,12 @@
 //! Code which enables sending messages between processes in the blocktree system.
 use btlib::{
     bterr,
-    crypto::{rand_array, ConcreteCreds, CredsPriv, CredsPub},
+    crypto::{
+        rand_array, AsymKey, ConcreteCreds, CredsPriv, CredsPub, HashKind, Private, Scheme, Sign,
+        Signer, Verifier,
+    },
     error::BoxInIoErr,
-    BlockPath, Result,
+    BlockPath, Principal, Result, Writecap,
 };
 use btserde::{read_from, write_to};
 use bytes::{BufMut, BytesMut};
@@ -23,7 +26,12 @@ use lazy_static::lazy_static;
 use log::error;
 use quinn::{ClientConfig, Endpoint, SendStream, ServerConfig};
 use rustls::{
-    Certificate, ConfigBuilder, ConfigSide, PrivateKey, WantsCipherSuites, WantsVerifier,
+    client::ResolvesClientCert,
+    internal::msgs::base::PayloadU16,
+    server::ClientCertVerified,
+    sign::{CertifiedKey, SigningKey},
+    Certificate, ConfigBuilder, ConfigSide, PrivateKey, SignatureAlgorithm, SignatureScheme,
+    WantsCipherSuites, WantsVerifier,
 };
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use std::{
@@ -55,7 +63,7 @@ mod private {
 
     /// Returns a [Router] which can be used to make a [Receiver] for the given path and
     ///  [Sender] instances for any path.
-    pub fn router(addr: Arc<BlockAddr>, creds: &ConcreteCreds) -> Result<impl Router> {
+    pub fn router(addr: Arc<BlockAddr>, creds: Arc<ConcreteCreds>) -> Result<impl Router> {
         QuicRouter::new(addr, creds)
     }
 
@@ -91,20 +99,196 @@ mod private {
         for cert in chain {
             cert_chain.push(Certificate(cert))
         }
-        let key = PrivateKey(creds.private_sign().to_der()?);
+        let key = PrivateKey(creds.sign_pair().private().to_der()?);
         let server_config = common_config(rustls::ServerConfig::builder())?
-            .with_no_client_auth()
+            .with_client_cert_verifier(Arc::new(ClientCertVerifier))
             .with_single_cert(cert_chain, key)?;
         Ok(ServerConfig::with_crypto(Arc::new(server_config)))
     }
 
-    fn client_config() -> Result<ClientConfig> {
+    fn client_config(
+        server_path: Arc<BlockPath>,
+        creds: Arc<ConcreteCreds>,
+    ) -> Result<ClientConfig> {
         let client_config = common_config(rustls::ClientConfig::builder())?
-            .with_custom_certificate_verifier(CertVerifier::new())
-            .with_no_client_auth();
+            .with_custom_certificate_verifier(Arc::new(ServerCertVerifier::new(server_path)))
+            .with_client_cert_resolver(Arc::new(ClientCertResolver::new(creds)?));
         Ok(ClientConfig::new(Arc::new(client_config)))
     }
 
+    fn to_cert_err(err: btlib::Error) -> rustls::Error {
+        rustls::Error::InvalidCertificateData(err.to_string())
+    }
+
+    struct ServerCertVerifier {
+        peer_path: Arc<BlockPath>,
+    }
+
+    impl ServerCertVerifier {
+        fn new(path: Arc<BlockPath>) -> Self {
+            Self { peer_path: path }
+        }
+    }
+
+    impl rustls::client::ServerCertVerifier for ServerCertVerifier {
+        fn verify_server_cert(
+            &self,
+            end_entity: &Certificate,
+            intermediates: &[Certificate],
+            _server_name: &rustls::ServerName,
+            _scts: &mut dyn Iterator<Item = &[u8]>,
+            _ocsp_response: &[u8],
+            _now: std::time::SystemTime,
+        ) -> std::result::Result<rustls::client::ServerCertVerified, rustls::Error> {
+            let (writecap, ..) =
+                Writecap::from_cert_chain(end_entity, intermediates).map_err(to_cert_err)?;
+            writecap
+                .assert_valid_for(&self.peer_path)
+                .map_err(to_cert_err)?;
+            Ok(rustls::client::ServerCertVerified::assertion())
+        }
+    }
+
+    struct ClientCertVerifier;
+
+    impl rustls::server::ClientCertVerifier for ClientCertVerifier {
+        fn verify_client_cert(
+            &self,
+            end_entity: &Certificate,
+            intermediates: &[Certificate],
+            _now: std::time::SystemTime,
+        ) -> std::result::Result<rustls::server::ClientCertVerified, rustls::Error> {
+            let (writecap, ..) =
+                Writecap::from_cert_chain(end_entity, intermediates).map_err(to_cert_err)?;
+            writecap
+                .assert_valid_for(writecap.path())
+                .map_err(to_cert_err)?;
+            Ok(ClientCertVerified::assertion())
+        }
+
+        fn client_auth_root_subjects(&self) -> Option<rustls::DistinguishedNames> {
+            let der = match Principal::default().to_name_der() {
+                Ok(der) => der,
+                Err(err) => {
+                    error!("failed to create distinguished name from root principal: {err}");
+                    return None;
+                }
+            };
+            Some(vec![PayloadU16(der)])
+        }
+
+        fn verify_tls13_signature(
+            &self,
+            message: &[u8],
+            cert: &Certificate,
+            dss: &rustls::DigitallySignedStruct,
+        ) -> std::result::Result<rustls::client::HandshakeSignatureValid, rustls::Error> {
+            let (_, subject_key) = Writecap::from_cert_chain(cert, &[]).map_err(to_cert_err)?;
+            subject_key
+                .verify(std::iter::once(message), dss.signature())
+                .map_err(|_| rustls::Error::InvalidCertificateSignature)?;
+            Ok(rustls::client::HandshakeSignatureValid::assertion())
+        }
+    }
+
+    struct ClientCertResolver {
+        cert_key: Arc<CertifiedKey>,
+    }
+
+    impl ClientCertResolver {
+        fn new(creds: Arc<ConcreteCreds>) -> Result<Self> {
+            let writecap = creds.writecap().ok_or(btlib::BlockError::MissingWritecap)?;
+            let sign_pair = creds.sign_pair();
+            let chain = writecap.to_cert_chain(sign_pair.public())?;
+            let mut certs = Vec::with_capacity(chain.len());
+            for cert in chain {
+                certs.push(Certificate(cert))
+            }
+            let key = Arc::new(SignKey(creds));
+            let cert_key = Arc::new(CertifiedKey {
+                cert: certs,
+                key,
+                ocsp: None,
+                sct_list: None,
+            });
+            Ok(Self { cert_key })
+        }
+    }
+
+    impl ResolvesClientCert for ClientCertResolver {
+        fn resolve(
+            &self,
+            _acceptable_issuers: &[&[u8]],
+            _sigschemes: &[rustls::SignatureScheme],
+        ) -> Option<Arc<rustls::sign::CertifiedKey>> {
+            Some(self.cert_key.clone())
+        }
+
+        fn has_certs(&self) -> bool {
+            true
+        }
+    }
+
+    trait SignExt {
+        fn as_signature_scheme(&self) -> rustls::SignatureScheme;
+        fn as_signature_algorithm(&self) -> rustls::SignatureAlgorithm;
+    }
+
+    impl SignExt for Sign {
+        fn as_signature_scheme(&self) -> SignatureScheme {
+            match self {
+                Self::RsaSsaPss(scheme) => match scheme.hash_kind() {
+                    HashKind::Sha2_256 => SignatureScheme::RSA_PSS_SHA256,
+                    HashKind::Sha2_512 => SignatureScheme::RSA_PSS_SHA512,
+                },
+            }
+        }
+
+        fn as_signature_algorithm(&self) -> SignatureAlgorithm {
+            match self {
+                Self::RsaSsaPss(..) => SignatureAlgorithm::RSA,
+            }
+        }
+    }
+
+    struct SignKey(Arc<ConcreteCreds>);
+
+    impl SignKey {
+        fn key(&self) -> &AsymKey<Private, Sign> {
+            self.0.sign_pair().private()
+        }
+    }
+
+    impl SigningKey for SignKey {
+        fn choose_scheme(
+            &self,
+            offered: &[rustls::SignatureScheme],
+        ) -> Option<Box<dyn rustls::sign::Signer>> {
+            if offered.contains(&self.key().scheme().as_signature_scheme()) {
+                Some(Box::new(SignKey(self.0.clone())))
+            } else {
+                None
+            }
+        }
+
+        fn algorithm(&self) -> rustls::SignatureAlgorithm {
+            self.key().scheme().as_signature_algorithm()
+        }
+    }
+
+    impl rustls::sign::Signer for SignKey {
+        fn sign(&self, message: &[u8]) -> std::result::Result<Vec<u8>, rustls::Error> {
+            self.0
+                .sign(std::iter::once(message))
+                .map(|sig| sig.take_data())
+                .map_err(|err| rustls::Error::General(err.to_string()))
+        }
+
+        fn scheme(&self) -> rustls::SignatureScheme {
+            self.key().scheme().as_signature_scheme()
+        }
+    }
+
     /// An identifier for a block. Persistent blocks (files, directories, and servers) are
     /// identified by the `Inode` variant and transient blocks (processes) are identified by the
     /// PID variant.
@@ -470,15 +654,17 @@ mod private {
 
     struct QuicRouter {
         recv_addr: Arc<BlockAddr>,
+        creds: Arc<ConcreteCreds>,
         endpoint: Endpoint,
     }
 
     impl QuicRouter {
-        fn new(recv_addr: Arc<BlockAddr>, creds: &ConcreteCreds) -> Result<Self> {
+        fn new(recv_addr: Arc<BlockAddr>, creds: Arc<ConcreteCreds>) -> Result<Self> {
             let socket_addr = recv_addr.socket_addr();
-            let endpoint = Endpoint::server(server_config(creds)?, socket_addr)?;
+            let endpoint = Endpoint::server(server_config(creds.as_ref())?, socket_addr)?;
             Ok(Self {
                 endpoint,
+                creds,
                 recv_addr,
             })
         }
@@ -502,7 +688,9 @@ mod private {
         type SenderFut<'a> = Pin<Box<dyn 'a + Future<Output = Result<QuicSender>> + Send>>;
 
         fn sender(&self, addr: Arc<BlockAddr>) -> Self::SenderFut<'_> {
-            Box::pin(async { QuicSender::from_endpoint(self.endpoint.clone(), addr).await })
+            Box::pin(async {
+                QuicSender::from_endpoint(self.endpoint.clone(), addr, self.creds.clone()).await
+            })
         }
     }
 
@@ -587,38 +775,24 @@ mod private {
         }
     }
 
-    struct CertVerifier;
-
-    impl CertVerifier {
-        fn new() -> Arc<Self> {
-            Arc::new(Self)
-        }
-    }
-
-    impl rustls::client::ServerCertVerifier for CertVerifier {
-        fn verify_server_cert(
-            &self,
-            _end_entity: &Certificate,
-            _intermediates: &[Certificate],
-            _server_name: &rustls::ServerName,
-            _scts: &mut dyn Iterator<Item = &[u8]>,
-            _ocsp_response: &[u8],
-            _now: std::time::SystemTime,
-        ) -> std::result::Result<rustls::client::ServerCertVerified, rustls::Error> {
-            // TODO: Implement certificate verification.
-            Ok(rustls::client::ServerCertVerified::assertion())
-        }
-    }
-
     struct QuicSender {
         addr: Arc<BlockAddr>,
         sink: FramedWrite<SendStream, MsgEncoder>,
     }
 
     impl QuicSender {
-        async fn from_endpoint(endpoint: Endpoint, addr: Arc<BlockAddr>) -> Result<Self> {
+        async fn from_endpoint(
+            endpoint: Endpoint,
+            addr: Arc<BlockAddr>,
+            creds: Arc<ConcreteCreds>,
+        ) -> Result<Self> {
             let socket_addr = addr.socket_addr();
-            let connecting = endpoint.connect_with(client_config()?, socket_addr, "localhost")?;
+            let connecting = endpoint.connect_with(
+                client_config(addr.path.clone(), creds)?,
+                socket_addr,
+                // The ServerCertVerifier ensures we connect to the correct path.
+                "INDETERMINANT",
+            )?;
             let connection = connecting.await?;
             let send_stream = connection.open_uni().await?;
             let sink = FramedWrite::new(send_stream, MsgEncoder::new());
@@ -738,7 +912,7 @@ mod tests {
 
         async fn endpoint(&self, inode: u64) -> (impl Sender, impl Receiver<BodyOwned>) {
             let addr = Arc::new(block_addr([self.instance_num, inode].iter()));
-            let router = router(addr.clone(), &NODE_CREDS).unwrap();
+            let router = router(addr.clone(), Arc::new(NODE_CREDS.clone())).unwrap();
             let receiver = router.receiver::<BodyOwned>().await.unwrap();
             let sender = router.sender(addr).await.unwrap();
             (sender, receiver)