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