feat(nix-compat/narinfo): add PubKey
This represents a ed25519 public key and "name". These are normally passed in the `trusted-public-keys` Nix config option, and consist of a name and base64-encoded ed25519 pubkey, separated by a `:`. Change-Id: I9ab4b3e0e5821805ea6faf2499626630fc5a3f0a Reviewed-on: https://cl.tvl.fyi/c/depot/+/10150 Autosubmit: flokli <flokli@flokli.de> Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI
This commit is contained in:
parent
e7a86273b1
commit
6af67af76e
2 changed files with 119 additions and 0 deletions
|
@ -27,10 +27,12 @@ use std::{
|
|||
use crate::{nixbase32, nixhash::CAHash, store_path::StorePathRef};
|
||||
|
||||
mod fingerprint;
|
||||
mod public_keys;
|
||||
mod signature;
|
||||
|
||||
pub use fingerprint::fingerprint;
|
||||
|
||||
pub use public_keys::{Error as PubKeyError, PubKey};
|
||||
pub use signature::{Error as SignatureError, Signature};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
117
tvix/nix-compat/src/narinfo/public_keys.rs
Normal file
117
tvix/nix-compat/src/narinfo/public_keys.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
//! This module defines data structures and parsers for the public key format
|
||||
//! used inside Nix to verify signatures on .narinfo files.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use data_encoding::BASE64;
|
||||
use ed25519_dalek::{VerifyingKey, PUBLIC_KEY_LENGTH};
|
||||
|
||||
/// This represents a ed25519 public key and "name".
|
||||
/// These are normally passed in the `trusted-public-keys` Nix config option,
|
||||
/// and consist of a name and base64-encoded ed25519 pubkey, separated by a `:`.
|
||||
#[derive(Debug)]
|
||||
pub struct PubKey {
|
||||
name: String,
|
||||
verifying_key: VerifyingKey,
|
||||
}
|
||||
|
||||
impl PubKey {
|
||||
pub fn new(name: String, verifying_key: VerifyingKey) -> Self {
|
||||
Self {
|
||||
name,
|
||||
verifying_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(input: &str) -> Result<Self, Error> {
|
||||
let (name, bytes64) = input.split_once(':').ok_or(Error::MissingSeparator)?;
|
||||
|
||||
if name.is_empty()
|
||||
|| !name
|
||||
.chars()
|
||||
.all(|c| char::is_alphanumeric(c) || c == '-' || c == '.')
|
||||
{
|
||||
return Err(Error::InvalidName(name.to_string()));
|
||||
}
|
||||
|
||||
if bytes64.len() != BASE64.encode_len(PUBLIC_KEY_LENGTH) {
|
||||
return Err(Error::InvalidPubKeyLen(bytes64.len()));
|
||||
}
|
||||
|
||||
let mut buf = [0; PUBLIC_KEY_LENGTH + 1];
|
||||
let mut bytes = [0; PUBLIC_KEY_LENGTH];
|
||||
match BASE64.decode_mut(bytes64.as_bytes(), &mut buf) {
|
||||
Ok(PUBLIC_KEY_LENGTH) => {
|
||||
bytes.copy_from_slice(&buf[..PUBLIC_KEY_LENGTH]);
|
||||
}
|
||||
Ok(_) => unreachable!(),
|
||||
// keeping DecodePartial gets annoying lifetime-wise
|
||||
Err(_) => return Err(Error::DecodeError(input.to_string())),
|
||||
}
|
||||
|
||||
let verifying_key = VerifyingKey::from_bytes(&bytes).map_err(Error::InvalidVerifyingKey)?;
|
||||
|
||||
Ok(Self {
|
||||
name: name.to_string(),
|
||||
verifying_key,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Invalid name: {0}")]
|
||||
InvalidName(String),
|
||||
#[error("Missing separator")]
|
||||
MissingSeparator,
|
||||
#[error("Invalid pubkey len: {0}")]
|
||||
InvalidPubKeyLen(usize),
|
||||
#[error("VerifyingKey error: {0}")]
|
||||
InvalidVerifyingKey(ed25519_dalek::SignatureError),
|
||||
#[error("Unable to base64-decode pubkey: {0}")]
|
||||
DecodeError(String),
|
||||
}
|
||||
|
||||
impl Display for PubKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}:{}",
|
||||
self.name,
|
||||
BASE64.encode(self.verifying_key.as_bytes())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use data_encoding::BASE64;
|
||||
use ed25519_dalek::PUBLIC_KEY_LENGTH;
|
||||
use test_case::test_case;
|
||||
|
||||
use super::PubKey;
|
||||
|
||||
#[test_case("cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=", "cache.nixos.org-1", BASE64.decode(b"6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=").unwrap()[..].try_into().unwrap(); "cache.nixos.org")]
|
||||
#[test_case("cheesecake:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=", "cheesecake", BASE64.decode(b"6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=").unwrap()[..].try_into().unwrap(); "cache.nixos.org different name")]
|
||||
#[test_case("test1:tLAEn+EeaBUJYqEpTd2yeerr7Ic6+0vWe+aXL/vYUpE=", "test1", BASE64.decode(b"tLAEn+EeaBUJYqEpTd2yeerr7Ic6+0vWe+aXL/vYUpE=").unwrap()[..].try_into().unwrap(); "test-1")]
|
||||
fn parse(
|
||||
input: &'static str,
|
||||
exp_name: &'static str,
|
||||
exp_verifying_key_bytes: &[u8; PUBLIC_KEY_LENGTH],
|
||||
) {
|
||||
let pubkey = PubKey::parse(input).expect("must parse");
|
||||
assert_eq!(exp_name, pubkey.name());
|
||||
assert_eq!(exp_verifying_key_bytes, pubkey.verifying_key.as_bytes());
|
||||
}
|
||||
|
||||
#[test_case("6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="; "empty name")]
|
||||
#[test_case("cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY"; "missing padding")]
|
||||
#[test_case("cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDS"; "wrong length")]
|
||||
fn parse_fail(input: &'static str) {
|
||||
PubKey::parse(input).expect_err("must fail");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue