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::{
|
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,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue