forked from lix-project/lix-installer
More substituter stuff compiles
This commit is contained in:
parent
6512b8d2cc
commit
4711dd7167
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue