bootstrap: verify signatures
This commit is contained in:
		@ -11,10 +11,10 @@ use crate::bootstrap::config::Config;
 | 
			
		||||
use crate::{dkl, utils};
 | 
			
		||||
 | 
			
		||||
pub async fn bootstrap(cfg: Config) {
 | 
			
		||||
    let verifier = retry(async || Verifier::from_config(&cfg)).await;
 | 
			
		||||
    let bs = cfg.bootstrap;
 | 
			
		||||
 | 
			
		||||
    retry_or_ignore(async || {
 | 
			
		||||
        fs::create_dir_all("/boostrap").await?;
 | 
			
		||||
        mount(Some(&bs.dev), "/bootstrap", "ext4", None).await;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    })
 | 
			
		||||
@ -33,12 +33,12 @@ pub async fn bootstrap(cfg: Config) {
 | 
			
		||||
    .await;
 | 
			
		||||
 | 
			
		||||
    let sys_cfg: dkl::Config = retry(async || {
 | 
			
		||||
        let sys_cfg_bytes = seed_config(base_dir, &bs.seed).await?;
 | 
			
		||||
        let sys_cfg_bytes = seed_config(base_dir, &bs.seed, &verifier).await?;
 | 
			
		||||
        Ok(serde_yaml::from_slice(&sys_cfg_bytes)?)
 | 
			
		||||
    })
 | 
			
		||||
    .await;
 | 
			
		||||
 | 
			
		||||
    mount_system(&sys_cfg, base_dir).await;
 | 
			
		||||
    mount_system(&sys_cfg, base_dir, &verifier).await;
 | 
			
		||||
 | 
			
		||||
    retry_or_ignore(async || {
 | 
			
		||||
        let path = "/etc/resolv.conf";
 | 
			
		||||
@ -68,7 +68,67 @@ pub async fn bootstrap(cfg: Config) {
 | 
			
		||||
    exec("chroot", &["/system", "update-ca-certificates"]).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn seed_config(base_dir: &str, seed_url: &Option<String>) -> Result<Vec<u8>> {
 | 
			
		||||
struct Verifier {
 | 
			
		||||
    pubkey: Option<Vec<u8>>,
 | 
			
		||||
}
 | 
			
		||||
impl Verifier {
 | 
			
		||||
    fn from_config(cfg: &Config) -> Result<Self> {
 | 
			
		||||
        let Some(ref pubkey) = cfg.signer_public_key else {
 | 
			
		||||
            return Ok(Self { pubkey: None });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        use base64::{prelude::BASE64_STANDARD, Engine};
 | 
			
		||||
        let pubkey = BASE64_STANDARD.decode(pubkey)?;
 | 
			
		||||
        let pubkey = Some(pubkey);
 | 
			
		||||
 | 
			
		||||
        return Ok(Self { pubkey });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn verify_path(&self, path: &str) -> Result<()> {
 | 
			
		||||
        let Some(ref pubkey) = self.pubkey else {
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        info!("verifying {path}");
 | 
			
		||||
 | 
			
		||||
        let mut pubkey = std::io::Cursor::new(pubkey);
 | 
			
		||||
 | 
			
		||||
        let sig = format!("{path}.sig");
 | 
			
		||||
 | 
			
		||||
        use std::process::Stdio;
 | 
			
		||||
        use tokio::process::Command;
 | 
			
		||||
 | 
			
		||||
        let mut openssl = Command::new("openssl")
 | 
			
		||||
            .stdin(Stdio::piped())
 | 
			
		||||
            .args(&[
 | 
			
		||||
                "dgst",
 | 
			
		||||
                "-sha512",
 | 
			
		||||
                "-verify",
 | 
			
		||||
                "/dev/stdin",
 | 
			
		||||
                "-signature",
 | 
			
		||||
                &sig,
 | 
			
		||||
                path,
 | 
			
		||||
            ])
 | 
			
		||||
            .spawn()?;
 | 
			
		||||
 | 
			
		||||
        tokio::io::copy(&mut pubkey, openssl.stdin.as_mut().unwrap()).await?;
 | 
			
		||||
 | 
			
		||||
        let status = openssl.wait().await?;
 | 
			
		||||
        if status.success() {
 | 
			
		||||
            Ok(())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(format_err!(
 | 
			
		||||
                "signature verification failed for {path}: {status}"
 | 
			
		||||
            ))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn seed_config(
 | 
			
		||||
    base_dir: &str,
 | 
			
		||||
    seed_url: &Option<String>,
 | 
			
		||||
    verifier: &Verifier,
 | 
			
		||||
) -> Result<Vec<u8>> {
 | 
			
		||||
    let cfg_path = &format!("{base_dir}/config.yaml");
 | 
			
		||||
 | 
			
		||||
    if fs::try_exists(cfg_path).await? {
 | 
			
		||||
@ -92,6 +152,8 @@ async fn seed_config(base_dir: &str, seed_url: &Option<String>) -> Result<Vec<u8
 | 
			
		||||
        return Err(format_err!("{cfg_path} does not exist after seeding"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    verifier.verify_path(&cfg_path).await?;
 | 
			
		||||
 | 
			
		||||
    Ok(fs::read(cfg_path).await?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -107,7 +169,7 @@ async fn fetch_bootstrap(seed_url: &str, output_file: &str) -> Result<()> {
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn mount_system(cfg: &dkl::Config, bs_dir: &str) {
 | 
			
		||||
async fn mount_system(cfg: &dkl::Config, bs_dir: &str, verifier: &Verifier) {
 | 
			
		||||
    let mem_dir = "/mem";
 | 
			
		||||
    mount(None, mem_dir, "tmpfs", Some("size=512m")).await;
 | 
			
		||||
 | 
			
		||||
@ -116,14 +178,17 @@ async fn mount_system(cfg: &dkl::Config, bs_dir: &str) {
 | 
			
		||||
 | 
			
		||||
    for layer in &cfg.layers {
 | 
			
		||||
        let src = if layer == "modules" {
 | 
			
		||||
            "/modules.sqfs"
 | 
			
		||||
            "/modules.sqfs".to_string()
 | 
			
		||||
        } else {
 | 
			
		||||
            &format!("{bs_dir}/{layer}.fs")
 | 
			
		||||
            let p = format!("{bs_dir}/{layer}.fs");
 | 
			
		||||
            retry(async || verifier.verify_path(&p).await).await;
 | 
			
		||||
            p
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let tgt = &format!("{mem_dir}/{layer}.fs");
 | 
			
		||||
        retry(async || {
 | 
			
		||||
            info!("copying layer {layer} from {src}");
 | 
			
		||||
            fs::copy(src, tgt).await?;
 | 
			
		||||
            fs::copy(&src, tgt).await?;
 | 
			
		||||
            Ok(())
 | 
			
		||||
        })
 | 
			
		||||
        .await;
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user