1mod base64;
107mod crypto;
108
109use std::path::Path;
110use std::{fmt, fs, io};
111
112use base64::{Base64, Decoder};
113
114use crate::crypto::blake2b::{Blake2b, BLAKE2B_OUTBYTES};
115use crate::crypto::ed25519;
116#[derive(Debug)]
117pub enum Error {
118 InvalidEncoding,
120 InvalidSignature,
122 IoError(io::Error),
124 UnexpectedAlgorithm,
126 UnexpectedKeyId,
128 UnsupportedAlgorithm,
130 UnsupportedLegacyMode,
132}
133
134impl fmt::Display for Error {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 match self {
137 Error::InvalidEncoding => write!(f, "Invalid encoding in minisign data"),
138 Error::InvalidSignature => write!(f, "The signature verification failed"),
139 Error::IoError(e) => write!(f, "I/O error: {}", e),
140 Error::UnexpectedAlgorithm => write!(f, "Unexpected signature algorithm"),
141 Error::UnexpectedKeyId => write!(
142 f,
143 "The signature was created with a different key than the one provided"
144 ),
145 Error::UnsupportedAlgorithm => write!(
146 f,
147 "This signature algorithm is not supported by this implementation"
148 ),
149 Error::UnsupportedLegacyMode => {
150 write!(f, "StreamVerifier only supports non-legacy mode signatures")
151 }
152 }
153 }
154}
155
156impl std::error::Error for Error {
157 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
160 match self {
161 Error::IoError(e) => Some(e),
162 _ => None,
163 }
164 }
165}
166
167impl From<base64::Error> for Error {
168 fn from(_e: base64::Error) -> Error {
169 Error::InvalidEncoding
172 }
173}
174
175impl From<io::Error> for Error {
176 fn from(e: io::Error) -> Error {
177 Error::IoError(e)
178 }
179}
180
181#[derive(Clone, Debug, Eq, PartialEq)]
190pub struct PublicKey {
191 untrusted_comment: Option<String>,
192 signature_algorithm: [u8; 2],
193 key_id: [u8; 8],
194 key: [u8; 32],
195}
196
197#[derive(Clone)]
204pub struct StreamVerifier<'a> {
205 public_key: &'a PublicKey,
206 signature: &'a Signature,
207 hasher: Blake2b,
208}
209
210#[derive(Clone)]
221pub struct Signature {
222 untrusted_comment: String,
223 key_id: [u8; 8],
224 signature: [u8; 64],
225 trusted_comment: String,
226 global_signature: [u8; 64],
227 is_prehashed: bool,
228}
229
230impl Signature {
231 pub fn decode(lines_str: &str) -> Result<Self, Error> {
233 let mut lines = lines_str.lines();
234 let untrusted_comment = lines.next().ok_or(Error::InvalidEncoding)?.to_string();
235 let bin1 = Base64::decode_to_vec(lines.next().ok_or(Error::InvalidEncoding)?)?;
236 if bin1.len() != 74 {
237 return Err(Error::InvalidEncoding);
238 }
239 let trusted_comment = lines.next().ok_or(Error::InvalidEncoding)?.to_string();
240 let bin2 = Base64::decode_to_vec(lines.next().ok_or(Error::InvalidEncoding)?)?;
241 if bin2.len() != 64 {
242 return Err(Error::InvalidEncoding);
243 }
244 if !trusted_comment.starts_with("trusted comment: ") {
245 return Err(Error::InvalidEncoding);
246 }
247 let mut signature_algorithm = [0u8; 2];
248 signature_algorithm.copy_from_slice(&bin1[0..2]);
249 let mut key_id = [0u8; 8];
250 key_id.copy_from_slice(&bin1[2..10]);
251 let mut signature = [0u8; 64];
252 signature.copy_from_slice(&bin1[10..74]);
253 let mut global_signature = [0u8; 64];
254 global_signature.copy_from_slice(&bin2);
255 let is_prehashed = match (signature_algorithm[0], signature_algorithm[1]) {
256 (0x45, 0x64) => false,
257 (0x45, 0x44) => true,
258 _ => return Err(Error::UnsupportedAlgorithm),
259 };
260 Ok(Signature {
261 untrusted_comment,
262 key_id,
263 signature,
264 trusted_comment,
265 global_signature,
266 is_prehashed,
267 })
268 }
269
270 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
272 let bin = fs::read_to_string(path)?;
273 Signature::decode(&bin)
274 }
275
276 pub fn trusted_comment(&self) -> &str {
278 &self.trusted_comment[17..]
279 }
280
281 pub fn untrusted_comment(&self) -> &str {
283 &self.untrusted_comment
284 }
285}
286
287impl PublicKey {
288 pub fn from_base64(public_key_b64: &str) -> Result<Self, Error> {
290 let bin = Base64::decode_to_vec(public_key_b64)?;
291 if bin.len() != 42 {
292 return Err(Error::InvalidEncoding);
293 }
294 let mut signature_algorithm = [0u8; 2];
295 signature_algorithm.copy_from_slice(&bin[0..2]);
296 match (signature_algorithm[0], signature_algorithm[1]) {
297 (0x45, 0x64) | (0x45, 0x44) => {}
298 _ => return Err(Error::UnsupportedAlgorithm),
299 };
300 let mut key_id = [0u8; 8];
301 key_id.copy_from_slice(&bin[2..10]);
302 let mut key = [0u8; 32];
303 key.copy_from_slice(&bin[10..42]);
304 Ok(PublicKey {
305 untrusted_comment: None,
306 signature_algorithm,
307 key_id,
308 key,
309 })
310 }
311
312 pub fn decode(lines_str: &str) -> Result<Self, Error> {
315 let mut lines = lines_str.lines();
316 let untrusted_comment = lines.next().ok_or(Error::InvalidEncoding)?;
317 let public_key_b64 = lines.next().ok_or(Error::InvalidEncoding)?;
318 let mut public_key = PublicKey::from_base64(public_key_b64)?;
319 public_key.untrusted_comment = Some(untrusted_comment.to_string());
320 Ok(public_key)
321 }
322
323 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
325 let bin = fs::read_to_string(path)?;
326 PublicKey::decode(&bin)
327 }
328
329 pub fn untrusted_comment(&self) -> Option<&str> {
331 self.untrusted_comment.as_deref()
332 }
333
334 fn verify_ed25519(&self, bin: &[u8], signature: &Signature) -> Result<(), Error> {
335 if !ed25519::verify(bin, &self.key, &signature.signature) {
336 return Err(Error::InvalidSignature);
337 }
338 let trusted_comment_bin = signature.trusted_comment().as_bytes();
339 let mut global = Vec::with_capacity(signature.signature.len() + trusted_comment_bin.len());
340 global.extend_from_slice(&signature.signature[..]);
341 global.extend_from_slice(trusted_comment_bin);
342 if !ed25519::verify(&global, &self.key, &signature.global_signature) {
343 return Err(Error::InvalidSignature);
344 }
345 Ok(())
346 }
347
348 pub fn verify(
352 &self,
353 bin: &[u8],
354 signature: &Signature,
355 allow_legacy: bool,
356 ) -> Result<(), Error> {
357 if self.key_id != signature.key_id {
358 return Err(Error::UnexpectedKeyId);
359 }
360 let mut h;
361 let bin = if signature.is_prehashed {
362 h = vec![0u8; BLAKE2B_OUTBYTES];
363 Blake2b::blake2b(&mut h, bin);
364 &h
365 } else if !allow_legacy {
366 return Err(Error::UnexpectedAlgorithm);
367 } else {
368 bin
369 };
370 self.verify_ed25519(bin, signature)
371 }
372
373 pub fn verify_stream<'a>(
375 &'a self,
376 signature: &'a Signature,
377 ) -> Result<StreamVerifier<'a>, Error> {
378 if self.key_id != signature.key_id {
379 return Err(Error::UnexpectedKeyId);
380 }
381 if !signature.is_prehashed {
382 return Err(Error::UnsupportedLegacyMode);
383 }
384 let hasher = Blake2b::new(BLAKE2B_OUTBYTES);
385 Ok(StreamVerifier {
386 public_key: self,
387 signature,
388 hasher,
389 })
390 }
391}
392
393impl StreamVerifier<'_> {
394 pub fn update(&mut self, buf: &[u8]) {
399 self.hasher.update(buf);
400 }
401
402 pub fn finalize(&mut self) -> Result<(), Error> {
409 let mut bin = vec![0u8; BLAKE2B_OUTBYTES];
410 self.hasher.finalize(&mut bin);
411 self.public_key.verify_ed25519(&bin, self.signature)
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418 #[test]
419 fn verify() {
420 let public_key =
421 PublicKey::from_base64("RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3")
422 .expect("Unable to decode the public key");
423 assert_eq!(public_key.untrusted_comment(), None);
424 let signature = Signature::decode(
425 "untrusted comment: signature from minisign secret key
426RWQf6LRCGA9i59SLOFxz6NxvASXDJeRtuZykwQepbDEGt87ig1BNpWaVWuNrm73YiIiJbq71Wi+dP9eKL8OC351vwIasSSbXxwA=
427trusted comment: timestamp:1555779966\tfile:test
428QtKMXWyYcwdpZAlPF7tE2ENJkRd1ujvKjlj1m9RtHTBnZPa5WKU5uWRs5GoP5M/VqE81QFuMKI5k/SfNQUaOAA==",
429 )
430 .expect("Unable to decode the signature");
431 assert_eq!(
432 signature.untrusted_comment(),
433 "untrusted comment: signature from minisign secret key"
434 );
435 assert_eq!(
436 signature.trusted_comment(),
437 "timestamp:1555779966\tfile:test"
438 );
439 let bin = b"test";
440 public_key
441 .verify(&bin[..], &signature, true)
442 .expect("Signature didn't verify");
443 let bin = b"Test";
444 match public_key.verify(&bin[..], &signature, true) {
445 Err(Error::InvalidSignature) => {}
446 _ => panic!("Invalid signature verified"),
447 };
448
449 let public_key2 = PublicKey::decode(
450 "untrusted comment: minisign public key E7620F1842B4E81F
451RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3",
452 )
453 .expect("Unable to decode the public key");
454 assert_eq!(
455 public_key2.untrusted_comment(),
456 Some("untrusted comment: minisign public key E7620F1842B4E81F")
457 );
458 match public_key2.verify(&bin[..], &signature, true) {
459 Err(Error::InvalidSignature) => {}
460 _ => panic!("Invalid signature verified"),
461 };
462 }
463
464 #[test]
465 fn verify_prehashed() {
466 let public_key =
467 PublicKey::from_base64("RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3")
468 .expect("Unable to decode the public key");
469 assert_eq!(public_key.untrusted_comment(), None);
470 let signature = Signature::decode(
471 "untrusted comment: signature from minisign secret key
472RUQf6LRCGA9i559r3g7V1qNyJDApGip8MfqcadIgT9CuhV3EMhHoN1mGTkUidF/\
473 z7SrlQgXdy8ofjb7bNJJylDOocrCo8KLzZwo=
474trusted comment: timestamp:1556193335\tfile:test
475y/rUw2y8/hOUYjZU71eHp/Wo1KZ40fGy2VJEDl34XMJM+TX48Ss/17u3IvIfbVR1FkZZSNCisQbuQY+bHwhEBg==",
476 )
477 .expect("Unable to decode the signature");
478 assert_eq!(
479 signature.untrusted_comment(),
480 "untrusted comment: signature from minisign secret key"
481 );
482 assert_eq!(
483 signature.trusted_comment(),
484 "timestamp:1556193335\tfile:test"
485 );
486 let bin = b"test";
487 public_key
488 .verify(&bin[..], &signature, false)
489 .expect("Signature didn't verify");
490 }
491
492 #[test]
493 fn verify_stream() {
494 let public_key =
495 PublicKey::from_base64("RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3")
496 .expect("Unable to decode the public key");
497 assert_eq!(public_key.untrusted_comment(), None);
498 let signature = Signature::decode(
499 "untrusted comment: signature from minisign secret key
500RUQf6LRCGA9i559r3g7V1qNyJDApGip8MfqcadIgT9CuhV3EMhHoN1mGTkUidF/\
501 z7SrlQgXdy8ofjb7bNJJylDOocrCo8KLzZwo=
502trusted comment: timestamp:1556193335\tfile:test
503y/rUw2y8/hOUYjZU71eHp/Wo1KZ40fGy2VJEDl34XMJM+TX48Ss/17u3IvIfbVR1FkZZSNCisQbuQY+bHwhEBg==",
504 )
505 .expect("Unable to decode the signature");
506 assert_eq!(
507 signature.untrusted_comment(),
508 "untrusted comment: signature from minisign secret key"
509 );
510 assert_eq!(
511 signature.trusted_comment(),
512 "timestamp:1556193335\tfile:test"
513 );
514 let mut stream_verifier = public_key
515 .verify_stream(&signature)
516 .expect("Can't extract StreamerVerifier");
517
518 let bin: &[u8] = b"te";
519 stream_verifier.update(bin);
520
521 let bin: &[u8] = b"st";
522 stream_verifier.update(bin);
523
524 stream_verifier.finalize().expect("Signature didn't verify");
525 }
526}