More substituter stuff compiles

This commit is contained in:
Artemis Tosini 2024-07-08 22:51:47 +00:00
parent 6512b8d2cc
commit 4711dd7167
Signed by: artemist
GPG key ID: EE5227935FE3FF18

View file

@ -1,10 +1,11 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
io::BufRead,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use base64::Engine; use base64::Engine;
use ed25519_dalek::VerifyingKey; use ed25519_dalek::{SignatureError, VerifyingKey};
use reqwest::Url; use reqwest::Url;
use tracing::{span, Span}; use tracing::{span, Span};
@ -74,7 +75,7 @@ impl FetchAndUnpackNixSubstituter {
} }
Ok(Self { Ok(Self {
target: StorePath::from_full_path(target) target: StorePath::from_path(&target)
.ok_or_else(|| Self::error(SubstitutionError::InvalidStorePath))?, .ok_or_else(|| Self::error(SubstitutionError::InvalidStorePath))?,
trusted_keys: trusted_keys_parsed, trusted_keys: trusted_keys_parsed,
dest, dest,
@ -95,7 +96,7 @@ impl Action for FetchAndUnpackNixSubstituter {
fn tracing_synopsis(&self) -> String { fn tracing_synopsis(&self) -> String {
format!( format!(
"Fetch `{}` from substituters to `{}`", "Fetch `{}` from substituters to `{}`",
self.target.full_path.to_string_lossy(), self.target.full_path,
self.dest.display() self.dest.display()
) )
} }
@ -182,26 +183,47 @@ impl StorePath {
return None; return None;
} }
let remaining = let (_, full_name) = full_path.split_at(STORE_DIR.len());
let full_name = full_path.file_name()?.to_str()?.to_owned(); let (digest, name) = full_name.split_once('-')?;
if digest.len() != 32
|| digest.contains(|c: char| !c.is_ascii_lowercase() && !c.is_ascii_digit())
{
return None;
}
if name.contains(|c: char| {
!c.is_ascii_alphanumeric()
&& c != '+'
&& c != '-'
&& c != '.'
&& c != '_'
&& c != '?'
&& c != '='
}) {
return None;
}
let (digest, _) = full_name.split_once('-')?;
Some(Self { Some(Self {
full_path, full_path: full_path.to_string(),
digest: digest.to_owned(), digest: digest.to_string(),
full_name, full_name: full_name.to_string(),
}) })
} }
pub fn from_full_name(full_name: String) -> Option<Self> { pub fn from_path(path: &Path) -> Option<Self> {
let (digest, _) = full_name.split_once('-')?; Self::from_full_path(path.to_str()?)
}
Some(Self { pub fn from_full_name(full_name: &str) -> Option<Self> {
full_path: Path::new(STORE_DIR).join(&full_name), Self::from_full_path(
digest: digest.to_string(), &Path::new(STORE_DIR)
full_name, .join(&full_name)
}) .into_os_string()
.into_string()
.ok()?,
)
} }
} }
@ -241,8 +263,7 @@ impl serde::de::Visitor<'_> for StorePathVisitor {
where where
E: serde::de::Error, E: serde::de::Error,
{ {
StorePath::from_full_path(Path::new(value).to_owned()) StorePath::from_full_path(value).ok_or_else(|| E::custom("invalid store path"))
.ok_or_else(|| E::custom("invalid store path"))
} }
} }
@ -281,19 +302,73 @@ struct NarInfo {
pub nar_hash: String, pub nar_hash: String,
/// Size of the decompressed nar in bytes /// Size of the decompressed nar in bytes
pub nar_size: u64, pub nar_size: u64,
/// Other store paths referenced by /// Other store paths referenced by the nar
pub references: Vec<StorePath>, pub references: Vec<StorePath>,
/// Signature of the nar, used to sign other items
pub sig: (String, Vec<u8>),
} }
impl NarInfo { impl NarInfo {
fn parse(substituter_url: &Url, contents: &bytes::Bytes) -> Result<Self, SubstitutionError> { fn parse(substituter_url: &Url, contents: &bytes::Bytes) -> Result<Self, SubstitutionError> {
let mut store_path = None;
let mut url = None;
let mut compression = None;
let mut nar_hash = None;
let mut nar_size = None;
let mut references = None;
let mut sig = None;
for maybe_line in contents.lines() {
let line = maybe_line.map_err(SubstitutionError::UndecodableNarInfo)?;
let (tag, rest) = line
.split_once(':')
.ok_or_else(|| SubstitutionError::BadNarInfo)?;
let value = rest.trim_start_matches(' ');
match tag {
"StorePath" => store_path = StorePath::from_full_path(value),
"URL" => url = substituter_url.join(value).ok(),
"Compression" => compression = NarCompression::from_name(value),
"NarHash" => {
nar_hash = {
let (algorithm, digest) = value
.split_once(':')
.ok_or_else(|| SubstitutionError::BadNarInfo)?;
if algorithm == "sha256" {
Some(digest.to_string())
} else {
None
}
}
},
"NarSize" => nar_size = u64::from_str_radix(value, 10).ok(),
"References" => {
references = value
.split(' ')
.map(StorePath::from_full_name)
.collect::<Option<Vec<_>>>()
},
"Sig" => {
let (signer, base64_signature) = value
.split_once(':')
.ok_or_else(|| SubstitutionError::BadNarInfo)?;
let signature = base64::engine::general_purpose::STANDARD
.decode(base64_signature)
.map_err(|_| SubstitutionError::BadNarInfo)?;
sig = Some((signer.to_string(), signature))
},
_ => {},
}
}
Ok(Self { Ok(Self {
store_path: todo!(), store_path: store_path.ok_or_else(|| SubstitutionError::BadNarInfo)?,
url: todo!(), url: url.ok_or_else(|| SubstitutionError::BadNarInfo)?,
compression: todo!(), compression: compression.ok_or_else(|| SubstitutionError::BadNarInfo)?,
nar_hash: todo!(), nar_hash: nar_hash.ok_or_else(|| SubstitutionError::BadNarInfo)?,
nar_size: todo!(), nar_size: nar_size.ok_or_else(|| SubstitutionError::BadNarInfo)?,
references: todo!(), references: references.ok_or_else(|| SubstitutionError::BadNarInfo)?,
sig: sig.ok_or_else(|| SubstitutionError::BadNarInfo)?,
}) })
} }
@ -301,21 +376,35 @@ impl NarInfo {
&self, &self,
trusted_keys: &HashMap<String, VerifyingKey>, trusted_keys: &HashMap<String, VerifyingKey>,
) -> Result<(), SubstitutionError> { ) -> Result<(), SubstitutionError> {
let Some(key) = trusted_keys.get(&self.sig.0) else {
return Err(SubstitutionError::BadSignature);
};
// Fingerprint format not documented, but implemented in lix: // Fingerprint format not documented, but implemented in lix:
// https://git.lix.systems/lix-project/lix/src/commit/d461cc1d7b2f489c3886f147166ba5b5e0e37541/src/libstore/path-info.cc#L25 // https://git.lix.systems/lix-project/lix/src/commit/d461cc1d7b2f489c3886f147166ba5b5e0e37541/src/libstore/path-info.cc#L25
let fingerprint = format!( let fingerprint = format!(
"1;{};{};{};{}", "1;{};{};{};{}",
self.store_path.full_path.to_string_lossy(), self.store_path.full_path,
self.nar_hash, self.nar_hash,
self.nar_size, self.nar_size,
self.references self.references
.iter() .iter()
.map(|reference| reference.full_path) .map(|reference| reference.full_path.as_ref())
.collect::<Vec<String>>() .collect::<Vec<&str>>()
.join(",") .join(",")
); );
todo!() key.verify_strict(
fingerprint.as_bytes(),
&ed25519_dalek::Signature::from_bytes(
self.sig
.1
.as_slice()
.try_into()
.map_err(|_| SubstitutionError::BadSignature)?,
),
)
.map_err(|_| SubstitutionError::BadSignature)
} }
pub fn parse_and_verify( pub fn parse_and_verify(
@ -327,7 +416,7 @@ impl NarInfo {
let parsed = Self::parse(substituter_url, contents)?; let parsed = Self::parse(substituter_url, contents)?;
if &parsed.store_path != expected_store_path { if &parsed.store_path != expected_store_path {
return Err(SubstitutionError::BadNarInfo); return Err(SubstitutionError::BadSignature);
} }
parsed.verify(trusted_keys)?; parsed.verify(trusted_keys)?;
@ -347,8 +436,12 @@ pub enum SubstitutionError {
/// Normally an ed25519_dalek::SignatureError, /// Normally an ed25519_dalek::SignatureError,
/// but that comes with no extra information so no need to include it /// but that comes with no extra information so no need to include it
PublicKey, PublicKey,
#[error("Bad narinfo signature")] #[error("Undecodable narinfo")]
UndecodableNarInfo(#[source] std::io::Error),
#[error("Bad narinfo contents")]
BadNarInfo, BadNarInfo,
#[error("Bad narinfo signature")]
BadSignature,
#[error("Invalid nix store path")] #[error("Invalid nix store path")]
InvalidStorePath, InvalidStorePath,
} }