1use bitflags::bitflags;
21use data_encoding::HEXLOWER;
22use std::{
23 fmt::{self, Display},
24 mem,
25};
26
27use crate::{nixbase32, nixhash::CAHash, store_path::StorePathRef};
28
29mod fingerprint;
30mod signature;
31mod signing_keys;
32mod verifying_keys;
33
34pub use fingerprint::fingerprint;
35pub use signature::{Error as SignatureError, Signature, SignatureRef};
36pub use signing_keys::parse_keypair;
37pub use signing_keys::{Error as SigningKeyError, SigningKey};
38pub use verifying_keys::{Error as VerifyingKeyError, VerifyingKey};
39
40#[derive(Debug)]
41pub struct NarInfo<'a> {
42 pub flags: Flags,
43 pub store_path: StorePathRef<'a>,
46 pub nar_hash: [u8; 32],
48 pub nar_size: u64,
50 pub references: Vec<StorePathRef<'a>>,
52 pub signatures: Vec<SignatureRef<'a>>,
55 pub ca: Option<CAHash>,
57 pub system: Option<&'a str>,
60 pub deriver: Option<StorePathRef<'a>>,
62 pub url: &'a str,
65 pub compression: Option<&'a str>,
72 pub file_hash: Option<[u8; 32]>,
74 pub file_size: Option<u64>,
76}
77
78bitflags! {
79 #[derive(Debug, Copy, Clone)]
81 pub struct Flags: u8 {
82 const UNKNOWN_FIELD = 1 << 0;
83 const COMPRESSION_DEFAULT = 1 << 1;
84 const REFERENCES_OUT_OF_ORDER = 1 << 2;
86 const NAR_HASH_HEX = 1 << 3;
87
88 const EXPLICIT_UNKNOWN_DERIVER = 1 << 4;
93
94 const REFERENCES_MISSING = 1 << 5;
96 }
97}
98
99const TAG_STOREPATH: &str = "StorePath";
100const TAG_URL: &str = "URL";
101const TAG_COMPRESSION: &str = "Compression";
102const TAG_FILEHASH: &str = "FileHash";
103const TAG_FILESIZE: &str = "FileSize";
104const TAG_NARHASH: &str = "NarHash";
105const TAG_NARSIZE: &str = "NarSize";
106const TAG_REFERENCES: &str = "References";
107const TAG_SYSTEM: &str = "System";
108const TAG_DERIVER: &str = "Deriver";
109const TAG_SIG: &str = "Sig";
110const TAG_CA: &str = "CA";
111
112impl<'a> NarInfo<'a> {
113 pub fn parse(input: &'a str) -> Result<Self, Error> {
114 let mut flags = Flags::empty();
115 let mut store_path = None;
116 let mut url = None;
117 let mut compression = None;
118 let mut file_hash = None;
119 let mut file_size = None;
120 let mut nar_hash = None;
121 let mut nar_size = None;
122 let mut references = None;
123 let mut system = None;
124 let mut deriver = None;
125 let mut signatures = vec![];
126 let mut ca = None;
127
128 for line in input.lines() {
129 let (tag, val) = line
130 .split_once(':')
131 .ok_or_else(|| Error::InvalidLine(line.to_string()))?;
132
133 let val = val
134 .strip_prefix(' ')
135 .ok_or_else(|| Error::InvalidLine(line.to_string()))?;
136
137 match tag {
138 TAG_STOREPATH => {
139 let val = val
140 .strip_prefix("/nix/store/")
141 .ok_or(Error::InvalidStorePath(
142 crate::store_path::Error::MissingStoreDir,
143 ))?;
144 let val = StorePathRef::from_bytes(val.as_bytes())
145 .map_err(Error::InvalidStorePath)?;
146
147 if store_path.replace(val).is_some() {
148 return Err(Error::DuplicateField(TAG_STOREPATH));
149 }
150 }
151 TAG_URL => {
152 if val.is_empty() {
153 return Err(Error::EmptyField(TAG_URL));
154 }
155
156 if url.replace(val).is_some() {
157 return Err(Error::DuplicateField(TAG_URL));
158 }
159 }
160 TAG_COMPRESSION => {
161 if val.is_empty() {
162 return Err(Error::EmptyField(TAG_COMPRESSION));
163 }
164
165 if compression.replace(val).is_some() {
166 return Err(Error::DuplicateField(TAG_COMPRESSION));
167 }
168 }
169 TAG_FILEHASH => {
170 let val = val
171 .strip_prefix("sha256:")
172 .ok_or(Error::MissingPrefixForHash(TAG_FILEHASH))?;
173 let val = nixbase32::decode_fixed::<32>(val)
174 .map_err(|e| Error::UnableToDecodeHash(TAG_FILEHASH, e))?;
175
176 if file_hash.replace(val).is_some() {
177 return Err(Error::DuplicateField(TAG_FILEHASH));
178 }
179 }
180 TAG_FILESIZE => {
181 let val = val
182 .parse::<u64>()
183 .map_err(|_| Error::UnableToParseSize(TAG_FILESIZE, val.to_string()))?;
184
185 if file_size.replace(val).is_some() {
186 return Err(Error::DuplicateField(TAG_FILESIZE));
187 }
188 }
189 TAG_NARHASH => {
190 let val = val
191 .strip_prefix("sha256:")
192 .ok_or(Error::MissingPrefixForHash(TAG_NARHASH))?;
193
194 let val = if val.len() != HEXLOWER.encode_len(32) {
195 nixbase32::decode_fixed::<32>(val)
196 } else {
197 flags |= Flags::NAR_HASH_HEX;
198
199 let val = val.as_bytes();
200 let mut buf = [0u8; 32];
201
202 HEXLOWER
203 .decode_mut(val, &mut buf)
204 .map_err(|e| e.error)
205 .map(|_| buf)
206 };
207
208 let val = val.map_err(|e| Error::UnableToDecodeHash(TAG_NARHASH, e))?;
209
210 if nar_hash.replace(val).is_some() {
211 return Err(Error::DuplicateField(TAG_NARHASH));
212 }
213 }
214 TAG_NARSIZE => {
215 let val = val
216 .parse::<u64>()
217 .map_err(|_| Error::UnableToParseSize(TAG_NARSIZE, val.to_string()))?;
218
219 if nar_size.replace(val).is_some() {
220 return Err(Error::DuplicateField(TAG_NARSIZE));
221 }
222 }
223 TAG_REFERENCES => {
224 let val: Vec<StorePathRef> = if !val.is_empty() {
225 let mut prev = "";
226 val.split(' ')
227 .enumerate()
228 .map(|(i, s)| {
229 if mem::replace(&mut prev, s) >= s {
231 flags |= Flags::REFERENCES_OUT_OF_ORDER;
232 }
233
234 StorePathRef::from_bytes(s.as_bytes())
235 .map_err(|err| Error::InvalidReference(i, err))
236 })
237 .collect::<Result<_, _>>()?
238 } else {
239 vec![]
240 };
241
242 if references.replace(val).is_some() {
243 return Err(Error::DuplicateField(TAG_REFERENCES));
244 }
245 }
246 TAG_SYSTEM => {
247 if val.is_empty() {
248 return Err(Error::EmptyField(TAG_SYSTEM));
249 }
250
251 if system.replace(val).is_some() {
252 return Err(Error::DuplicateField(TAG_SYSTEM));
253 }
254 }
255 TAG_DERIVER => {
256 match val.strip_suffix(".drv") {
257 Some(val) => {
258 let val = StorePathRef::from_bytes(val.as_bytes())
259 .map_err(Error::InvalidDeriverStorePath)?;
260
261 if deriver.replace(val).is_some() {
262 return Err(Error::DuplicateField(TAG_DERIVER));
263 }
264 }
265 None => {
266 if val == "unknown-deriver" {
267 flags |= Flags::EXPLICIT_UNKNOWN_DERIVER;
268 } else {
269 return Err(Error::InvalidDeriverStorePathMissingSuffix);
270 }
271 }
272 };
273 }
274 TAG_SIG => {
275 let val = SignatureRef::parse(val)
276 .map_err(|e| Error::UnableToParseSignature(signatures.len(), e))?;
277
278 signatures.push(val);
279 }
280 TAG_CA => {
281 let val = CAHash::from_nix_hex_str(val)
282 .ok_or_else(|| Error::UnableToParseCA(val.to_string()))?;
283
284 if ca.replace(val).is_some() {
285 return Err(Error::DuplicateField(TAG_CA));
286 }
287 }
288 _ => {
289 flags |= Flags::UNKNOWN_FIELD;
290 }
291 }
292 }
293
294 Ok(NarInfo {
295 store_path: store_path.ok_or(Error::MissingField("StorePath"))?,
296 nar_hash: nar_hash.ok_or(Error::MissingField("NarHash"))?,
297 nar_size: nar_size.ok_or(Error::MissingField("NarSize"))?,
298 references: match references {
299 Some(val) => val,
300 None => {
301 flags |= Flags::REFERENCES_MISSING;
302 vec![]
303 }
304 },
305 signatures,
306 ca,
307 system,
308 deriver,
309 url: url.ok_or(Error::MissingField("URL"))?,
310 compression: match compression {
311 Some("none") => None,
312 None => {
313 flags |= Flags::COMPRESSION_DEFAULT;
314 Some("bzip2")
315 }
316 _ => compression,
317 },
318 file_hash,
319 file_size,
320 flags,
321 })
322 }
323
324 pub fn fingerprint(&self) -> String {
327 fingerprint(
328 &self.store_path,
329 &self.nar_hash,
330 self.nar_size,
331 self.references.iter(),
332 )
333 }
334
335 pub fn add_signature<S>(&mut self, signer: &'a SigningKey<S>)
339 where
340 S: ed25519::signature::Signer<ed25519::Signature>,
341 {
342 let fp = self.fingerprint();
344
345 let sig = signer.sign(fp.as_bytes());
346
347 self.signatures.push(sig);
348 }
349}
350
351impl Display for NarInfo<'_> {
352 fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
353 writeln!(w, "StorePath: /nix/store/{}", self.store_path)?;
354 writeln!(w, "URL: {}", self.url)?;
355
356 if !self.flags.contains(Flags::COMPRESSION_DEFAULT) {
357 let compression = self.compression.unwrap_or("none");
358 writeln!(w, "Compression: {compression}")?;
359 };
360
361 if let Some(file_hash) = self.file_hash {
362 writeln!(w, "FileHash: sha256:{}", nixbase32::encode(&file_hash),)?;
363 }
364
365 if let Some(file_size) = self.file_size {
366 writeln!(w, "FileSize: {file_size}")?;
367 }
368
369 writeln!(w, "NarHash: sha256:{}", nixbase32::encode(&self.nar_hash),)?;
370 writeln!(w, "NarSize: {}", self.nar_size)?;
371
372 if !self.flags.contains(Flags::REFERENCES_MISSING) {
373 write!(w, "References:")?;
374 if self.references.is_empty() {
375 write!(w, " ")?;
376 } else {
377 for path in &self.references {
378 write!(w, " {path}")?;
379 }
380 }
381 writeln!(w)?;
382 }
383
384 if let Some(deriver) = &self.deriver {
385 writeln!(w, "Deriver: {deriver}.drv")?;
386 } else if self.flags.contains(Flags::EXPLICIT_UNKNOWN_DERIVER) {
387 writeln!(w, "Deriver: unknown-deriver")?;
388 }
389
390 if let Some(system) = self.system {
391 writeln!(w, "System: {system}")?;
392 }
393
394 for sig in &self.signatures {
395 writeln!(w, "Sig: {sig}")?;
396 }
397
398 if let Some(ca) = &self.ca {
399 writeln!(w, "CA: {}", ca.to_nix_nixbase32_string())?;
400 }
401
402 Ok(())
403 }
404}
405
406#[derive(thiserror::Error, Debug)]
407pub enum Error {
408 #[error("duplicate field: {0}")]
409 DuplicateField(&'static str),
410
411 #[error("missing field: {0}")]
412 MissingField(&'static str),
413
414 #[error("invalid line: {0}")]
415 InvalidLine(String),
416
417 #[error("invalid StorePath: {0}")]
418 InvalidStorePath(crate::store_path::Error),
419
420 #[error("field {0} may not be empty string")]
421 EmptyField(&'static str),
422
423 #[error("invalid {0}: {1}")]
424 UnableToParseSize(&'static str, String),
425
426 #[error("unable to parse #{0} reference: {1}")]
427 InvalidReference(usize, crate::store_path::Error),
428
429 #[error("invalid Deriver store path: {0}")]
430 InvalidDeriverStorePath(crate::store_path::Error),
431
432 #[error("invalid Deriver store path, must end with .drv")]
433 InvalidDeriverStorePathMissingSuffix,
434
435 #[error("missing prefix for {0}")]
436 MissingPrefixForHash(&'static str),
437
438 #[error("unable to decode {0}: {1}")]
439 UnableToDecodeHash(&'static str, data_encoding::DecodeError),
440
441 #[error("unable to parse signature #{0}: {1}")]
442 UnableToParseSignature(usize, SignatureError),
443
444 #[error("unable to parse CA field: {0}")]
445 UnableToParseCA(String),
446}
447
448#[cfg(test)]
449const DUMMY_KEYPAIR: &str = "cache.example.com-1:cCta2MEsRNuYCgWYyeRXLyfoFpKhQJKn8gLMeXWAb7vIpRKKo/3JoxJ24OYa3DxT2JVV38KjK/1ywHWuMe2JEw==";
450#[cfg(test)]
451const DUMMY_VERIFYING_KEY: &str =
452 "cache.example.com-1:yKUSiqP9yaMSduDmGtw8U9iVVd/Coyv9csB1rjHtiRM=";
453
454#[cfg(test)]
455mod test {
456 use hex_literal::hex;
457 use pretty_assertions::assert_eq;
458 use std::sync::LazyLock;
459 use std::{io, str};
460
461 use crate::{
462 nixhash::{CAHash, NixHash},
463 store_path::StorePathRef,
464 };
465
466 use super::{Flags, NarInfo};
467
468 static CASES: LazyLock<&'static [&'static str]> = LazyLock::new(|| {
469 let data = zstd::decode_all(io::Cursor::new(include_bytes!(
470 "../../testdata/narinfo.zst"
471 )))
472 .unwrap();
473 let data = str::from_utf8(Vec::leak(data)).unwrap();
474 Vec::leak(
475 data.split_inclusive("\n\n")
476 .map(|s| s.strip_suffix('\n').unwrap())
477 .collect::<Vec<_>>(),
478 )
479 });
480
481 #[test]
482 fn roundtrip() {
483 for &input in *CASES {
484 let parsed = NarInfo::parse(input).expect("should parse");
485 let output = format!("{parsed}");
486 assert_eq!(input, output, "should roundtrip");
487 }
488 }
489
490 #[test]
491 fn references_out_of_order() {
492 let parsed = NarInfo::parse(
493 r#"StorePath: /nix/store/xi429w4ddvb1r77978hm7jfb2jsn559r-gcc-3.4.6
494URL: nar/1hr09cgkyw1hcsfkv5qp5jlpmf2mqrkrqs3xj5zklq9c1h9544ff.nar.bz2
495Compression: bzip2
496FileHash: sha256:1hr09cgkyw1hcsfkv5qp5jlpmf2mqrkrqs3xj5zklq9c1h9544ff
497FileSize: 4006
498NarHash: sha256:0ik9mpqxpd9hv325hdblj2nawqj5w7951qdyy8ikxgwr6fq7m11c
499NarSize: 21264
500References: a8922c0h87iilxzzvwn2hmv8x210aqb9-glibc-2.7 7w2acjgalb0cm7b3bg8yswza4l7iil9y-binutils-2.18 mm631h09mj964hm9q04l5fd8vw12j1mm-bash-3.2-p39 nx2zs2qd6snfcpzw4a0jnh26z9m0yihz-gcc-3.4.6 xi429w4ddvb1r77978hm7jfb2jsn559r-gcc-3.4.6
501Deriver: 2dzpn70c1hawczwhg9aavqk18zp9zsva-gcc-3.4.6.drv
502Sig: cache.nixos.org-1:o1DTsjCz0PofLJ216P2RBuSulI8BAb6zHxWE4N+tzlcELk5Uk/GO2SCxWTRN5wJutLZZ+cHTMdWqOHF88KGQDg==
503"#).expect("should parse");
504
505 assert!(parsed.flags.contains(Flags::REFERENCES_OUT_OF_ORDER));
506 assert_eq!(
507 vec![
508 "a8922c0h87iilxzzvwn2hmv8x210aqb9-glibc-2.7",
509 "7w2acjgalb0cm7b3bg8yswza4l7iil9y-binutils-2.18",
510 "mm631h09mj964hm9q04l5fd8vw12j1mm-bash-3.2-p39",
511 "nx2zs2qd6snfcpzw4a0jnh26z9m0yihz-gcc-3.4.6",
512 "xi429w4ddvb1r77978hm7jfb2jsn559r-gcc-3.4.6"
513 ],
514 parsed
515 .references
516 .iter()
517 .map(StorePathRef::to_string)
518 .collect::<Vec<_>>(),
519 );
520 }
521
522 #[test]
523 fn ca_nar_hash_sha1() {
524 let parsed = NarInfo::parse(
525 r#"StorePath: /nix/store/k20pahypzvr49fy82cw5sx72hdfg3qcr-texlive-hyphenex-37354
526URL: nar/0i5biw0g01514llhfswxy6xfav8lxxdq1xg6ik7hgsqbpw0f06yi.nar.xz
527Compression: xz
528FileHash: sha256:0i5biw0g01514llhfswxy6xfav8lxxdq1xg6ik7hgsqbpw0f06yi
529FileSize: 7120
530NarHash: sha256:0h1bm4sj1cnfkxgyhvgi8df1qavnnv94sd0v09wcrm971602shfg
531NarSize: 22552
532References:
533Sig: cache.nixos.org-1:u01BybwQhyI5H1bW1EIWXssMDhDDIvXOG5uh8Qzgdyjz6U1qg6DHhMAvXZOUStIj6X5t4/ufFgR8i3fjf0bMAw==
534CA: fixed:r:sha1:1ak1ymbmsfx7z8kh09jzkr3a4dvkrfjw
535"#).expect("should parse");
536
537 assert_eq!(
538 parsed.ca,
539 Some(CAHash::Nar(NixHash::Sha1(hex!(
540 "5cba3c77236ae4f9650270a27fbad375551fa60a"
541 ))))
542 );
543 }
544
545 #[test]
546 fn compression_default() {
547 let input = r#"StorePath: /nix/store/a1jjalr4csx9hcga7fnm122aqabrjnch-digikam-2.6.0
550URL: nar/1fzimfnvq2k8b40n4g54abmncpx2ddckh6qlb77pgq6xiysyil69.nar.bz2
551FileHash: sha256:1fzimfnvq2k8b40n4g54abmncpx2ddckh6qlb77pgq6xiysyil69
552FileSize: 43503778
553NarHash: sha256:0zpbbwipqzr5p8mlpag9wrsp5hlaxkq7gax5jj0hg3vvdziypcw5
554NarSize: 100658640
555References: 0izkyk7bq2ag9393nvnhgm87p75cq09w-liblqr-1-0.4.1 1cslpgyb7vb30inj3210jv6agqv42jxz-qca-2.0.3 1sya3bwjxkzpkmwn67gfzp4gz4g62l36-libXrandr-1.3.1 26yxdaa9z0ma5sgw02i670rsqnl57crs-glib-2.30.3 27lnjh99236kmhbpc5747599zcymfzmg-qt-4.8.2 2v6x378vcfvyxilkvihs60zha54z2x2y-qjson-0.7.1 45hgr3fbnr45n795hn2x7hsymp0h2j2m-libjpeg-8c 4kw1b212s80ap2iyibxrimcqb5imhfj7-libkexiv2-4.7.4 7dvylm5crlc0sfafcc0n46mb5ch67q0j-glibc-2.13 a05cbh1awjbl1rbyb2ynyf4k42v5a9a7-boost-1.47.0 a1jjalr4csx9hcga7fnm122aqabrjnch-digikam-2.6.0 aav5ffg8wlnilgnvdb2jnrv2aam4zmmz-perl-5.14.2 ab0m9h30nsr13w48qriv0k350kmwx567-kdelibs-4.7.4 avffkd49cqvpwdkzry8bn69dkbw4cy29-lensfun-0.2.5 cy8rl8h4yp2j3h8987vkklg328q3wmjz-gcc-4.6.3 dmmh5ihyg1r2dm4azgsfj2kprj92czlg-libSM-1.2.0 fl56j5n4shfw9c0r6vs2i4f1h9zx5kac-soprano-2.7.6 g15cmvh15ggdjcwapskngv20q4yhix40-jasper-1.900.1 i04maxd0din6v92rnqcwl9yra0kl2vk5-marble-4.7.4 kqjjb3m26rdddwwwkk8v45821aps877k-libICE-1.0.7 lxz9r135wkndvi642z4bjgmvyypsgirb-libtiff-3.9.4 m9c8i0a6cl30lcqp654dqkbag3wjmd00-libX11-1.4.1 mpnj4k2ijrgyfkh48fg96nzcmklfh5pl-coreutils-8.15 nppljblap477s0893c151lyq7r7n5v1q-zlib-1.2.7 nw9mdbyp8kyn3v4vkdzq0gsnqbc4mnx3-expat-2.0.1 p1a0dn931mzdkvj6h5yzshbmgxba5r0z-libgphoto2-2.4.11 pvjj07xa1cfkad3gwk376nzdrgknbcqm-mesa-7.11.2 pzcxag98jqccp9ycbxknyh0w95pgnsk4-lcms-1.19 qfi5pgds33kg6vlnxsmj0hyl74vcmyiz-libpng-1.5.10 scm6bj86s3qh3s3x0b9ayjp6755p4q86-mysql-5.1.54 sd23qspcyg385va0lr35xgz3hvlqphg6-libkipi-4.7.4 svmbrhc6kzfzakv20a7zrfl6kbr5mfpq-kdepimlibs-4.7.4 v7kh3h7xfwjz4hgffg3gwrfzjff9bw9d-bash-4.2-p24 vi17f22064djgpk0w248da348q8gxkww-libkdcraw-4.7.4 wkjdzmj3z4dcbsc9f833zs6krdgg2krk-phonon-4.6.0 xf3i3awqi0035ixy2qyb6hk4c92r3vrn-opencv-2.4.2 y1vr0nz8i59x59501020nh2k1dw3bhwq-libusb-0.1.12 yf3hin2hb6i08n7zrk8g3acy54rhg9bp-libXext-1.2.0
556Deriver: la77dr44phk5m5jnl4dvk01cwpykyw9s-digikam-2.6.0.drv
557System: i686-linux
558Sig: cache.nixos.org-1:92fl0i5q7EyegCj5Yf4L0bENkWuVAtgveiRcTEEUH0P6HvCE1xFcPbz/0Pf6Np+K1LPzHK+s5RHOmVoxRsvsDg==
559"#;
560 let parsed = NarInfo::parse(input).expect("should parse");
561
562 assert!(parsed.flags.contains(Flags::COMPRESSION_DEFAULT));
563 assert_eq!(parsed.compression, Some("bzip2"));
564 assert_eq!(parsed.to_string(), input);
565 }
566
567 #[test]
568 fn compression_none() {
569 let input = r#"StorePath: /nix/store/a1jjalr4csx9hcga7fnm122aqabrjnch-digikam-2.6.0
572URL: nar/1fzimfnvq2k8b40n4g54abmncpx2ddckh6qlb77pgq6xiysyil69.nar.bz2
573Compression: none
574FileHash: sha256:0zpbbwipqzr5p8mlpag9wrsp5hlaxkq7gax5jj0hg3vvdziypcw5
575FileSize: 100658640
576NarHash: sha256:0zpbbwipqzr5p8mlpag9wrsp5hlaxkq7gax5jj0hg3vvdziypcw5
577NarSize: 100658640
578References: 0izkyk7bq2ag9393nvnhgm87p75cq09w-liblqr-1-0.4.1 1cslpgyb7vb30inj3210jv6agqv42jxz-qca-2.0.3 1sya3bwjxkzpkmwn67gfzp4gz4g62l36-libXrandr-1.3.1 26yxdaa9z0ma5sgw02i670rsqnl57crs-glib-2.30.3 27lnjh99236kmhbpc5747599zcymfzmg-qt-4.8.2 2v6x378vcfvyxilkvihs60zha54z2x2y-qjson-0.7.1 45hgr3fbnr45n795hn2x7hsymp0h2j2m-libjpeg-8c 4kw1b212s80ap2iyibxrimcqb5imhfj7-libkexiv2-4.7.4 7dvylm5crlc0sfafcc0n46mb5ch67q0j-glibc-2.13 a05cbh1awjbl1rbyb2ynyf4k42v5a9a7-boost-1.47.0 a1jjalr4csx9hcga7fnm122aqabrjnch-digikam-2.6.0 aav5ffg8wlnilgnvdb2jnrv2aam4zmmz-perl-5.14.2 ab0m9h30nsr13w48qriv0k350kmwx567-kdelibs-4.7.4 avffkd49cqvpwdkzry8bn69dkbw4cy29-lensfun-0.2.5 cy8rl8h4yp2j3h8987vkklg328q3wmjz-gcc-4.6.3 dmmh5ihyg1r2dm4azgsfj2kprj92czlg-libSM-1.2.0 fl56j5n4shfw9c0r6vs2i4f1h9zx5kac-soprano-2.7.6 g15cmvh15ggdjcwapskngv20q4yhix40-jasper-1.900.1 i04maxd0din6v92rnqcwl9yra0kl2vk5-marble-4.7.4 kqjjb3m26rdddwwwkk8v45821aps877k-libICE-1.0.7 lxz9r135wkndvi642z4bjgmvyypsgirb-libtiff-3.9.4 m9c8i0a6cl30lcqp654dqkbag3wjmd00-libX11-1.4.1 mpnj4k2ijrgyfkh48fg96nzcmklfh5pl-coreutils-8.15 nppljblap477s0893c151lyq7r7n5v1q-zlib-1.2.7 nw9mdbyp8kyn3v4vkdzq0gsnqbc4mnx3-expat-2.0.1 p1a0dn931mzdkvj6h5yzshbmgxba5r0z-libgphoto2-2.4.11 pvjj07xa1cfkad3gwk376nzdrgknbcqm-mesa-7.11.2 pzcxag98jqccp9ycbxknyh0w95pgnsk4-lcms-1.19 qfi5pgds33kg6vlnxsmj0hyl74vcmyiz-libpng-1.5.10 scm6bj86s3qh3s3x0b9ayjp6755p4q86-mysql-5.1.54 sd23qspcyg385va0lr35xgz3hvlqphg6-libkipi-4.7.4 svmbrhc6kzfzakv20a7zrfl6kbr5mfpq-kdepimlibs-4.7.4 v7kh3h7xfwjz4hgffg3gwrfzjff9bw9d-bash-4.2-p24 vi17f22064djgpk0w248da348q8gxkww-libkdcraw-4.7.4 wkjdzmj3z4dcbsc9f833zs6krdgg2krk-phonon-4.6.0 xf3i3awqi0035ixy2qyb6hk4c92r3vrn-opencv-2.4.2 y1vr0nz8i59x59501020nh2k1dw3bhwq-libusb-0.1.12 yf3hin2hb6i08n7zrk8g3acy54rhg9bp-libXext-1.2.0
579Deriver: la77dr44phk5m5jnl4dvk01cwpykyw9s-digikam-2.6.0.drv
580System: i686-linux
581Sig: cache.nixos.org-1:92fl0i5q7EyegCj5Yf4L0bENkWuVAtgveiRcTEEUH0P6HvCE1xFcPbz/0Pf6Np+K1LPzHK+s5RHOmVoxRsvsDg==
582"#;
583 let parsed = NarInfo::parse(input).expect("should parse");
584
585 assert!(!parsed.flags.contains(Flags::COMPRESSION_DEFAULT));
586 assert_eq!(parsed.compression, None);
587 assert_eq!(parsed.to_string(), input);
588 }
589
590 #[test]
591 fn explicit_unknown_deriver() {
592 let input = r#"StorePath: /nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432
595URL: nar/1094wph9z4nwlgvsd53abfz8i117ykiv5dwnq9nnhz846s7xqd7d.nar.xz
596Compression: xz
597FileHash: sha256:1094wph9z4nwlgvsd53abfz8i117ykiv5dwnq9nnhz846s7xqd7d
598FileSize: 114980
599NarHash: sha256:0lxjvvpr59c2mdram7ympy5ay741f180kv3349hvfc3f8nrmbqf6
600NarSize: 464152
601References: 7gx4kiv5m0i7d7qkixq2cwzbr10lvxwc-glibc-2.27
602Deriver: unknown-deriver
603Sig: cache.nixos.org-1:sn5s/RrqEI+YG6/PjwdbPjcAC7rcta7sJU4mFOawGvJBLsWkyLtBrT2EuFt/LJjWkTZ+ZWOI9NTtjo/woMdvAg==
604Sig: hydra.other.net-1:JXQ3Z/PXf0EZSFkFioa4FbyYpbbTbHlFBtZf4VqU0tuMTWzhMD7p9Q7acJjLn3jofOtilAAwRILKIfVuyrbjAA==
605"#;
606 let parsed = NarInfo::parse(input).expect("should parse");
607
608 assert!(parsed.flags.contains(Flags::EXPLICIT_UNKNOWN_DERIVER));
609 assert!(parsed.deriver.is_none());
610 assert_eq!(parsed.to_string(), input);
611 }
612
613 #[test]
614 fn nar_hash_hex() {
615 let parsed = NarInfo::parse(r#"StorePath: /nix/store/0vpqfxbkx0ffrnhbws6g9qwhmliksz7f-perl-HTTP-Cookies-6.01
616URL: nar/1rv1m9inydm1r4krw8hmwg1hs86d0nxddd1pbhihx7l7fycjvfk3.nar.xz
617Compression: xz
618FileHash: sha256:1rv1m9inydm1r4krw8hmwg1hs86d0nxddd1pbhihx7l7fycjvfk3
619FileSize: 19912
620NarHash: sha256:60adfd293a4d81ad7cd7e47263cbb3fc846309ef91b154a08ba672b558f94ff3
621NarSize: 45840
622References: 0vpqfxbkx0ffrnhbws6g9qwhmliksz7f-perl-HTTP-Cookies-6.01 9vrhbib2lxd9pjlg6fnl5b82gblidrcr-perl-HTTP-Message-6.06 wy20zslqxzxxfpzzk0rajh41d7a6mlnf-perl-HTTP-Date-6.02
623Deriver: fb4ihlq3psnsjq95mvvs49rwpplpc8zj-perl-HTTP-Cookies-6.01.drv
624Sig: cache.nixos.org-1:HhaiY36Uk3XV1JGe9d9xHnzAapqJXprU1YZZzSzxE97jCuO5RR7vlG2kF7MSC5thwRyxAtdghdSz3AqFi+QSCw==
625"#).expect("should parse");
626
627 assert!(parsed.flags.contains(Flags::NAR_HASH_HEX));
628 assert_eq!(
629 hex!("60adfd293a4d81ad7cd7e47263cbb3fc846309ef91b154a08ba672b558f94ff3"),
630 parsed.nar_hash,
631 );
632 }
633
634 #[test]
635 fn references_missing() {
636 let input = r#"StorePath: /nix/store/64s9zav4fk5qiba1jq0ipvyhnn57r7dq-cfg-if-1.0.0
639URL: nar/0lxxfhy5fmfz0sbnqkqjdf7gx9gsxrfzz49n19y8sr93inawhshh.nar?hash=64s9zav4fk5qiba1jq0ipvyhnn57r7dq
640Compression: none
641FileHash: sha256:0lxxfhy5fmfz0sbnqkqjdf7gx9gsxrfzz49n19y8sr93inawhshh
642FileSize: 24944
643NarHash: sha256:0lxxfhy5fmfz0sbnqkqjdf7gx9gsxrfzz49n19y8sr93inawhshh
644NarSize: 24944
645Deriver: s409kxiz6bx2g0da01gzvlnnjpl3i4h9-cfg-if-1.0.0.drv
646Sig: cache.nixos.org-1:WDvKIdxSnQ8p2w9SD0ffdibUSNMz6QQN6jpe+A8LLNHmZFsX+m8GZF0x9DN6PWV6k+OlnBT5UVbiWQYgXIsQAQ==
647"#;
648 let parsed = NarInfo::parse(input).expect("should parse");
649
650 assert!(parsed.flags.contains(Flags::REFERENCES_MISSING));
651 assert_eq!(parsed.references, vec![]);
652 assert_eq!(parsed.to_string(), input);
653 }
654
655 #[test]
659 fn sign() {
660 let mut narinfo = NarInfo::parse(
661 r#"StorePath: /nix/store/0vpqfxbkx0ffrnhbws6g9qwhmliksz7f-perl-HTTP-Cookies-6.01
662URL: nar/0i5biw0g01514llhfswxy6xfav8lxxdq1xg6ik7hgsqbpw0f06yi.nar.xz
663Compression: xz
664FileHash: sha256:0i5biw0g01514llhfswxy6xfav8lxxdq1xg6ik7hgsqbpw0f06yi
665FileSize: 7120
666NarHash: sha256:0h1bm4sj1cnfkxgyhvgi8df1qavnnv94sd0v09wcrm971602shfg
667NarSize: 22552
668References:
669CA: fixed:r:sha1:1ak1ymbmsfx7z8kh09jzkr3a4dvkrfjw
670"#,
671 )
672 .expect("should parse");
673
674 let fp = narinfo.fingerprint();
675
676 let (signing_key, _verifying_key) =
678 super::parse_keypair(super::DUMMY_KEYPAIR).expect("must succeed");
679
680 narinfo.add_signature(&signing_key);
682
683 let new_sig = narinfo.signatures.last().unwrap();
685 assert_eq!(signing_key.name(), *new_sig.name());
686
687 let verifying_key = super::VerifyingKey::parse(super::DUMMY_VERIFYING_KEY)
689 .expect("parsing dummy verifying key");
690
691 assert!(
692 verifying_key.verify(&fp, new_sig),
693 "expect signature to be valid"
694 );
695 }
696}