Просмотр исходного кода

darkirc: Make server startup parsing fallible

x 1 месяц назад
Родитель
Сommit
0e7c15d121
1 измененных файлов с 157 добавлено и 34 удалено
  1. 157 34
      bin/darkirc/src/irc/server.rs

+ 157 - 34
bin/darkirc/src/irc/server.rs

@@ -16,7 +16,13 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
  */
 
 
-use std::{collections::HashMap, fs::File, io::BufReader, path::PathBuf, sync::Arc};
+use std::{
+    collections::HashMap,
+    fs::File,
+    io::{BufRead, BufReader},
+    path::PathBuf,
+    sync::Arc,
+};
 
 
 use darkfi::{
 use darkfi::{
     event_graph::Event,
     event_graph::Event,
@@ -26,7 +32,10 @@ use darkfi::{
 };
 };
 use darkfi_serial::{deserialize_async, serialize_async};
 use darkfi_serial::{deserialize_async, serialize_async};
 use futures_rustls::{
 use futures_rustls::{
-    rustls::{self, pki_types::PrivateKeyDer},
+    rustls::{
+        self,
+        pki_types::{CertificateDer, PrivateKeyDer},
+    },
     TlsAcceptor,
     TlsAcceptor,
 };
 };
 use sled_overlay::sled;
 use sled_overlay::sled;
@@ -125,6 +134,73 @@ pub(crate) async fn reserve_rln_message_id_in_store(
     Ok(RlnMessageReservation::Reserved { identity: updated, message_id })
     Ok(RlnMessageReservation::Reserved { identity: updated, message_id })
 }
 }
 
 
+fn parse_tls_secret<R>(reader: &mut R) -> Result<PrivateKeyDer<'static>>
+where
+    R: BufRead,
+{
+    let key = rustls_pemfile::pkcs8_private_keys(reader)
+        .next()
+        .ok_or(Error::ParseFailed("TLS key missing PKCS#8 private key"))?
+        .map_err(|_| Error::ParseFailed("TLS key contains invalid PKCS#8 private key"))?;
+
+    Ok(PrivateKeyDer::Pkcs8(key))
+}
+
+fn parse_tls_cert<R>(reader: &mut R) -> Result<CertificateDer<'static>>
+where
+    R: BufRead,
+{
+    rustls_pemfile::certs(reader)
+        .next()
+        .ok_or(Error::ParseFailed("TLS certificate missing"))?
+        .map_err(|_| Error::ParseFailed("TLS certificate contains invalid DER"))
+}
+
+fn tls_acceptor_from_pem<CR, KR>(
+    cert_reader: &mut CR,
+    secret_reader: &mut KR,
+) -> Result<TlsAcceptor>
+where
+    CR: BufRead,
+    KR: BufRead,
+{
+    let secret = parse_tls_secret(secret_reader)?;
+    let cert = parse_tls_cert(cert_reader)?;
+    let config = rustls::ServerConfig::builder()
+        .with_no_client_auth()
+        .with_single_cert(vec![cert], secret)
+        .map_err(|_| Error::ParseFailed("TLS certificate and key are invalid"))?;
+
+    Ok(TlsAcceptor::from(Arc::new(config)))
+}
+
+fn load_tls_acceptor(tls_cert: &str, tls_secret: &str) -> Result<TlsAcceptor> {
+    let f = File::open(expand_path(tls_secret)?)?;
+    let mut secret_reader = BufReader::new(f);
+
+    let f = File::open(expand_path(tls_cert)?)?;
+    let mut cert_reader = BufReader::new(f);
+
+    tls_acceptor_from_pem(&mut cert_reader, &mut secret_reader)
+}
+
+async fn load_default_rln_identity(sled_db: &sled::Db) -> Result<Option<RlnIdentity>> {
+    let default_db = sled_db.open_tree(ACCOUNTS_DEFAULT_TREE)?;
+    let Some(blob) = default_db.get(ACCOUNTS_KEY_RLN_IDENTITY)? else {
+        if default_db.is_empty() {
+            return Ok(None)
+        }
+
+        return Err(Error::ParseFailed("Default RLN account is missing identity record"))
+    };
+
+    let identity: RlnIdentity = deserialize_async(&blob)
+        .await
+        .map_err(|_| Error::ParseFailed("Default RLN account identity is corrupted"))?;
+
+    Ok(Some(identity))
+}
+
 /// IRC server instance
 /// IRC server instance
 pub struct IrcServer {
 pub struct IrcServer {
     /// DarkIrc instance
     /// DarkIrc instance
@@ -191,23 +267,12 @@ impl IrcServer {
                 // openssl genpkey -algorithm ED25519 > example.com.key
                 // openssl genpkey -algorithm ED25519 > example.com.key
                 // openssl req -new -out example.com.csr -key example.com.key
                 // openssl req -new -out example.com.csr -key example.com.key
                 // openssl x509 -req -in example.com.csr -signkey example.com.key -out example.com.crt
                 // openssl x509 -req -in example.com.csr -signkey example.com.key -out example.com.crt
-                let f = File::open(expand_path(tls_secret.as_ref().unwrap())?)?;
-                let mut reader = BufReader::new(f);
-                let secret = PrivateKeyDer::Pkcs8(
-                    rustls_pemfile::pkcs8_private_keys(&mut reader).next().unwrap().unwrap(),
-                );
-
-                let f = File::open(expand_path(tls_cert.as_ref().unwrap())?)?;
-                let mut reader = BufReader::new(f);
-                let cert = rustls_pemfile::certs(&mut reader).next().unwrap().unwrap();
-
-                let config = rustls::ServerConfig::builder()
-                    .with_no_client_auth()
-                    .with_single_cert(vec![cert], secret)
-                    .unwrap();
+                let (Some(tls_cert), Some(tls_secret)) = (tls_cert.as_ref(), tls_secret.as_ref())
+                else {
+                    return Err(Error::ParseFailed("TLS certificate and key are required"))
+                };
 
 
-                let acceptor = TlsAcceptor::from(Arc::new(config));
-                Some(acceptor)
+                Some(load_tls_acceptor(tls_cert, tls_secret)?)
             }
             }
             _ => None,
             _ => None,
         };
         };
@@ -216,15 +281,10 @@ impl IrcServer {
         let server_store = darkirc.sled.open_tree("server_store")?;
         let server_store = darkirc.sled.open_tree("server_store")?;
 
 
         // Set the default RLN account if any
         // Set the default RLN account if any
-        let default_db = darkirc.sled.open_tree(format!("{}default", ACCOUNTS_DB_PREFIX))?;
-        let rln_identity = if !default_db.is_empty() {
-            let default_accnt = default_db.get(ACCOUNTS_KEY_RLN_IDENTITY)?.unwrap();
-            let default_accnt = deserialize_async(&default_accnt).await.unwrap();
+        let rln_identity = load_default_rln_identity(&darkirc.sled).await?;
+        if rln_identity.is_some() {
             info!("Default RLN account set");
             info!("Default RLN account set");
-            Some(default_accnt)
-        } else {
-            None
-        };
+        }
 
 
         let self_ = Arc::new(Self {
         let self_ = Arc::new(Self {
             darkirc,
             darkirc,
@@ -305,13 +365,14 @@ impl IrcServer {
                 Ok((s, a)) => (s, a),
                 Ok((s, a)) => (s, a),
 
 
                 // As per usual accept(2) recommendations
                 // As per usual accept(2) recommendations
-                Err(e) if e.raw_os_error().is_some() => match e.raw_os_error().unwrap() {
-                    libc::EAGAIN | libc::ECONNABORTED | libc::EPROTO | libc::EINTR => continue,
-                    _ => {
-                        error!("[IRC SERVER] Failed accepting connection: {e}");
-                        return Err(e.into())
-                    }
-                },
+                Err(e)
+                    if matches!(
+                        e.raw_os_error(),
+                        Some(libc::EAGAIN | libc::ECONNABORTED | libc::EPROTO | libc::EINTR)
+                    ) =>
+                {
+                    continue
+                }
 
 
                 Err(e) => {
                 Err(e) => {
                     error!("[IRC SERVER] Failed accepting new connection: {e}");
                     error!("[IRC SERVER] Failed accepting new connection: {e}");
@@ -500,7 +561,9 @@ impl IrcServer {
 
 
 #[cfg(test)]
 #[cfg(test)]
 mod tests {
 mod tests {
-    use darkfi::event_graph::rln::epoch_of;
+    use std::io::Cursor;
+
+    use darkfi::{event_graph::rln::epoch_of, Error};
     use darkfi_sdk::pasta::pallas;
     use darkfi_sdk::pasta::pallas;
     use darkfi_serial::deserialize_async;
     use darkfi_serial::deserialize_async;
 
 
@@ -516,6 +579,66 @@ mod tests {
         }
         }
     }
     }
 
 
+    #[test]
+    fn tls_secret_parser_rejects_malformed_key() {
+        let mut reader = Cursor::new(b"not a private key".as_slice());
+
+        assert!(matches!(parse_tls_secret(&mut reader), Err(Error::ParseFailed(_))));
+    }
+
+    #[test]
+    fn tls_cert_parser_rejects_malformed_cert() {
+        let mut reader = Cursor::new(b"not a certificate".as_slice());
+
+        assert!(matches!(parse_tls_cert(&mut reader), Err(Error::ParseFailed(_))));
+    }
+
+    #[test]
+    fn load_default_rln_identity_returns_none_for_empty_tree() {
+        smol::block_on(async {
+            let sled_db = sled::Config::new().temporary(true).open().unwrap();
+
+            let identity = load_default_rln_identity(&sled_db).await.unwrap();
+
+            assert!(identity.is_none());
+        })
+    }
+
+    #[test]
+    fn load_default_rln_identity_rejects_missing_identity_record() {
+        smol::block_on(async {
+            let sled_db = sled::Config::new().temporary(true).open().unwrap();
+            let default = sled_db.open_tree(ACCOUNTS_DEFAULT_TREE).unwrap();
+            default.insert(b"other", b"value").unwrap();
+
+            let err = match load_default_rln_identity(&sled_db).await {
+                Ok(_) => panic!("expected missing identity record error"),
+                Err(e) => e,
+            };
+
+            assert!(matches!(
+                err,
+                Error::ParseFailed("Default RLN account is missing identity record")
+            ));
+        })
+    }
+
+    #[test]
+    fn load_default_rln_identity_rejects_corrupted_identity_record() {
+        smol::block_on(async {
+            let sled_db = sled::Config::new().temporary(true).open().unwrap();
+            let default = sled_db.open_tree(ACCOUNTS_DEFAULT_TREE).unwrap();
+            default.insert(ACCOUNTS_KEY_RLN_IDENTITY, b"not an identity").unwrap();
+
+            let err = match load_default_rln_identity(&sled_db).await {
+                Ok(_) => panic!("expected corrupted identity record error"),
+                Err(e) => e,
+            };
+
+            assert!(matches!(err, Error::ParseFailed("Default RLN account identity is corrupted")));
+        })
+    }
+
     #[test]
     #[test]
     fn rln_message_reservation_persists_default_and_account_counters() {
     fn rln_message_reservation_persists_default_and_account_counters() {
         smol::block_on(async {
         smol::block_on(async {