diff options
author | Florian Klink <flokli@flokli.de> | 2024-08-19T13·57+0300 |
---|---|---|
committer | clbot <clbot@tvl.fyi> | 2024-08-19T19·46+0000 |
commit | a259613c76a17f7a6719c18809e054605ef2cfa2 (patch) | |
tree | 379659675b04623a026e6f40af44c35b14a84d95 /tvix/nix-compat/src/narinfo/signature.rs | |
parent | 7612cb4c31fadd7ccaa7672cf551b9d21d7884b4 (diff) |
feat(nix-compat/narinfo/signature): generalize name field r/8540
Requiring `name` to be a `&str` means it'll get annoying to pass around `Signature`, but being able to pass them around in an owned fashion is kinda a requirement for a stronger typed `PathInfo` struct, where we want to have full ownership. Rework the `Signature` struct to become generic over the type of the `name` field. This means, it becomes possible to have owned versions of it. We don't want to impose `String` or `SmolStr` for example, but want to leave it up to the nix-compat user to decide. Provide a type alias for the existing `&str` variant (`SignatureRef`), and use it where we previously used the non-generic `Signature` one. Add some tests to ensure it's possible to *use* `Signature` with both `String` and `SmolStr` (but only pull in `smol_str` as dev dependency for the tests). Also, add some more docstrings, these were a bit sparse. Change-Id: I3f75691498c6bda9cd072d2d9dac83c4f6c57287 Reviewed-on: https://cl.tvl.fyi/c/depot/+/12253 Autosubmit: flokli <flokli@flokli.de> Tested-by: BuildkiteCI Reviewed-by: raitobezarius <tvl@lahfa.xyz>
Diffstat (limited to 'tvix/nix-compat/src/narinfo/signature.rs')
-rw-r--r-- | tvix/nix-compat/src/narinfo/signature.rs | 110 |
1 files changed, 88 insertions, 22 deletions
diff --git a/tvix/nix-compat/src/narinfo/signature.rs b/tvix/nix-compat/src/narinfo/signature.rs index e5567f44109a..33c49128c2d5 100644 --- a/tvix/nix-compat/src/narinfo/signature.rs +++ b/tvix/nix-compat/src/narinfo/signature.rs @@ -1,4 +1,7 @@ -use std::fmt::{self, Display}; +use std::{ + fmt::{self, Display}, + ops::Deref, +}; use data_encoding::BASE64; use serde::{Deserialize, Serialize}; @@ -6,17 +9,36 @@ use serde::{Deserialize, Serialize}; const SIGNATURE_LENGTH: usize = std::mem::size_of::<ed25519::SignatureBytes>(); #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Signature<'a> { - name: &'a str, +pub struct Signature<S> { + name: S, bytes: ed25519::SignatureBytes, } -impl<'a> Signature<'a> { - pub fn new(name: &'a str, bytes: ed25519::SignatureBytes) -> Self { +/// Type alias of a [Signature] using a `&str` as `name` field. +pub type SignatureRef<'a> = Signature<&'a str>; + +/// Represents the signatures that Nix emits. +/// It consists of a name (an identifier for a public key), and an ed25519 +/// signature (64 bytes). +/// It is generic over the string type that's used for the name, and there's +/// [SignatureRef] as a type alias for one containing &str. +impl<S> Signature<S> +where + S: Deref<Target = str>, +{ + /// Constructs a new [Signature] from a name and public key. + pub fn new(name: S, bytes: ed25519::SignatureBytes) -> Self { Self { name, bytes } } - pub fn parse(input: &'a str) -> Result<Self, Error> { + /// Parses a [Signature] from a string containing the name, a colon, and 64 + /// base64-encoded bytes (plus padding). + /// These strings are commonly seen in the `Signature:` field of a NARInfo + /// file. + pub fn parse<'a>(input: &'a str) -> Result<Self, Error> + where + S: From<&'a str>, + { let (name, bytes64) = input.split_once(':').ok_or(Error::MissingSeparator)?; if name.is_empty() @@ -40,13 +62,18 @@ impl<'a> Signature<'a> { Err(_) => return Err(Error::DecodeError(input.to_string())), } - Ok(Signature { name, bytes }) + Ok(Self { + name: name.into(), + bytes, + }) } - pub fn name(&self) -> &'a str { - self.name + /// Returns the name field of the signature. + pub fn name(&self) -> &S { + &self.name } + /// Returns the 64 bytes of signatures. pub fn bytes(&self) -> &ed25519::SignatureBytes { &self.bytes } @@ -57,9 +84,21 @@ impl<'a> Signature<'a> { verifying_key.verify_strict(fingerprint, &signature).is_ok() } + + /// Constructs a [SignatureRef] from this signature. + pub fn as_ref(&self) -> SignatureRef<'_> { + SignatureRef { + name: self.name.deref(), + bytes: self.bytes, + } + } } -impl<'de: 'a, 'a> Deserialize<'de> for Signature<'a> { +impl<'a, 'de, S> Deserialize<'de> for Signature<S> +where + S: Deref<Target = str> + From<&'a str>, + 'de: 'a, +{ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, @@ -71,10 +110,13 @@ impl<'de: 'a, 'a> Deserialize<'de> for Signature<'a> { } } -impl<'a> Serialize for Signature<'a> { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> +impl<S: Display> Serialize for Signature<S> +where + S: Deref<Target = str>, +{ + fn serialize<SR>(&self, serializer: SR) -> Result<SR::Ok, SR::Error> where - S: serde::Serializer, + SR: serde::Serializer, { let string: String = self.to_string(); @@ -82,6 +124,15 @@ impl<'a> Serialize for Signature<'a> { } } +impl<S> Display for Signature<S> +where + S: Display, +{ + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + write!(w, "{}:{}", self.name, BASE64.encode(&self.bytes)) + } +} + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Invalid name: {0}")] @@ -94,12 +145,6 @@ pub enum Error { DecodeError(String), } -impl Display for Signature<'_> { - fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { - write!(w, "{}:{}", self.name, BASE64.encode(&self.bytes)) - } -} - #[cfg(test)] mod test { use data_encoding::BASE64; @@ -144,7 +189,7 @@ mod test { #[case] fp: &str, #[case] expect_valid: bool, ) { - let sig = Signature::parse(sig_str).expect("must parse"); + let sig = Signature::<&str>::parse(sig_str).expect("must parse"); assert_eq!(expect_valid, sig.verify(fp.as_bytes(), verifying_key)); } @@ -159,7 +204,7 @@ mod test { "u01BybwQhyI5H1bW1EIWXssMDhDDIvXOG5uh8Qzgdyjz6U1qg6DHhMAvXZOUStIj6X5t4/ufFgR8i3fjf0bMAw==" )] fn parse_fail(#[case] input: &'static str) { - Signature::parse(input).expect_err("must fail"); + Signature::<&str>::parse(input).expect_err("must fail"); } #[test] @@ -178,8 +223,29 @@ mod test { let serialized = serde_json::to_string(&signature_actual).expect("must serialize"); assert_eq!(signature_str_json, &serialized); - let deserialized: Signature<'_> = + let deserialized: Signature<&str> = serde_json::from_str(signature_str_json).expect("must deserialize"); assert_eq!(&signature_actual, &deserialized); } + + /// Construct a [Signature], using different String types for the name field. + #[test] + fn signature_owned() { + let signature1 = Signature::<String>::parse("cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==").expect("must parse"); + let signature2 = Signature::<smol_str::SmolStr>::parse("cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==").expect("must parse"); + let signature3 = Signature::<&str>::parse("cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==").expect("must parse"); + + assert!( + signature1.verify(FINGERPRINT.as_bytes(), &PUB_CACHE_NIXOS_ORG_1), + "must verify" + ); + assert!( + signature2.verify(FINGERPRINT.as_bytes(), &PUB_CACHE_NIXOS_ORG_1), + "must verify" + ); + assert!( + signature3.verify(FINGERPRINT.as_bytes(), &PUB_CACHE_NIXOS_ORG_1), + "must verify" + ); + } } |