1mod base64;
28mod crypto;
29
30use std::path::Path;
31use std::{fmt, fs, io};
32
33use base64::{Base64, Decoder};
34
35use crate::crypto::blake2b::{Blake2b, BLAKE2B_OUTBYTES};
36use crate::crypto::ed25519;
37#[derive(Debug)]
38pub enum Error {
39 InvalidEncoding,
40 InvalidSignature,
41 IoError(io::Error),
42 UnexpectedAlgorithm,
43 UnexpectedKeyId,
44 UnsupportedAlgorithm,
45 UnsupportedLegacyMode,
46}
47
48impl fmt::Display for Error {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(f, "{:?}", self)
51 }
52}
53
54impl std::error::Error for Error {
55 fn description(&self) -> &str {
56 match self {
57 Error::InvalidEncoding => "Invalid encoding",
58 Error::InvalidSignature => "Invalid signature",
59 Error::IoError(_) => "I/O error",
60 Error::UnexpectedAlgorithm => "Unexpected algorithm",
61 Error::UnexpectedKeyId => "Unexpected key identifier",
62 Error::UnsupportedAlgorithm => "Unsupported algorithm",
63 Error::UnsupportedLegacyMode => {
64 "Unsupported operration - StreamVerifier only supports non-legacy mode"
65 }
66 }
67 }
68
69 fn cause(&self) -> Option<&dyn std::error::Error> {
70 match self {
71 Error::IoError(e) => Some(e),
72 _ => None,
73 }
74 }
75}
76
77impl From<base64::Error> for Error {
78 fn from(_e: base64::Error) -> Error {
79 Error::InvalidEncoding
80 }
81}
82
83impl From<io::Error> for Error {
84 fn from(e: io::Error) -> Error {
85 Error::IoError(e)
86 }
87}
88
89#[derive(Clone, Debug, Eq, PartialEq)]
91pub struct PublicKey {
92 untrusted_comment: Option<String>,
93 signature_algorithm: [u8; 2],
94 key_id: [u8; 8],
95 key: [u8; 32],
96}
97
98#[derive(Clone)]
101pub struct StreamVerifier<'a> {
102 public_key: &'a PublicKey,
103 signature: &'a Signature,
104 hasher: Blake2b,
105}
106
107#[derive(Clone)]
109pub struct Signature {
110 untrusted_comment: String,
111 key_id: [u8; 8],
112 signature: [u8; 64],
113 trusted_comment: String,
114 global_signature: [u8; 64],
115 is_prehashed: bool,
116}
117
118impl Signature {
119 pub fn decode(lines_str: &str) -> Result<Self, Error> {
121 let mut lines = lines_str.lines();
122 let untrusted_comment = lines.next().ok_or(Error::InvalidEncoding)?.to_string();
123 let bin1 = Base64::decode_to_vec(lines.next().ok_or(Error::InvalidEncoding)?)?;
124 if bin1.len() != 74 {
125 return Err(Error::InvalidEncoding);
126 }
127 let trusted_comment = lines.next().ok_or(Error::InvalidEncoding)?.to_string();
128 let bin2 = Base64::decode_to_vec(lines.next().ok_or(Error::InvalidEncoding)?)?;
129 if bin2.len() != 64 {
130 return Err(Error::InvalidEncoding);
131 }
132 if !trusted_comment.starts_with("trusted comment: ") {
133 return Err(Error::InvalidEncoding);
134 }
135 let mut signature_algorithm = [0u8; 2];
136 signature_algorithm.copy_from_slice(&bin1[0..2]);
137 let mut key_id = [0u8; 8];
138 key_id.copy_from_slice(&bin1[2..10]);
139 let mut signature = [0u8; 64];
140 signature.copy_from_slice(&bin1[10..74]);
141 let mut global_signature = [0u8; 64];
142 global_signature.copy_from_slice(&bin2);
143 let is_prehashed = match (signature_algorithm[0], signature_algorithm[1]) {
144 (0x45, 0x64) => false,
145 (0x45, 0x44) => true,
146 _ => return Err(Error::UnsupportedAlgorithm),
147 };
148 Ok(Signature {
149 untrusted_comment,
150 key_id,
151 signature,
152 trusted_comment,
153 global_signature,
154 is_prehashed,
155 })
156 }
157
158 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
160 let bin = fs::read_to_string(path)?;
161 Signature::decode(&bin)
162 }
163
164 pub fn trusted_comment(&self) -> &str {
166 &self.trusted_comment[17..]
167 }
168
169 pub fn untrusted_comment(&self) -> &str {
171 &self.untrusted_comment
172 }
173}
174
175impl<'a> PublicKey {
176 pub fn from_base64(public_key_b64: &str) -> Result<Self, Error> {
178 let bin = Base64::decode_to_vec(public_key_b64)?;
179 if bin.len() != 42 {
180 return Err(Error::InvalidEncoding);
181 }
182 let mut signature_algorithm = [0u8; 2];
183 signature_algorithm.copy_from_slice(&bin[0..2]);
184 match (signature_algorithm[0], signature_algorithm[1]) {
185 (0x45, 0x64) | (0x45, 0x44) => {}
186 _ => return Err(Error::UnsupportedAlgorithm),
187 };
188 let mut key_id = [0u8; 8];
189 key_id.copy_from_slice(&bin[2..10]);
190 let mut key = [0u8; 32];
191 key.copy_from_slice(&bin[10..42]);
192 Ok(PublicKey {
193 untrusted_comment: None,
194 signature_algorithm,
195 key_id,
196 key,
197 })
198 }
199
200 pub fn decode(lines_str: &str) -> Result<Self, Error> {
203 let mut lines = lines_str.lines();
204 let untrusted_comment = lines.next().ok_or(Error::InvalidEncoding)?;
205 let public_key_b64 = lines.next().ok_or(Error::InvalidEncoding)?;
206 let mut public_key = PublicKey::from_base64(public_key_b64)?;
207 public_key.untrusted_comment = Some(untrusted_comment.to_string());
208 Ok(public_key)
209 }
210
211 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
213 let bin = fs::read_to_string(path)?;
214 PublicKey::decode(&bin)
215 }
216
217 pub fn untrusted_comment(&self) -> Option<&str> {
219 self.untrusted_comment.as_deref()
220 }
221
222 fn verify_ed25519(&self, bin: &[u8], signature: &Signature) -> Result<(), Error> {
223 if !ed25519::verify(bin, &self.key, &signature.signature) {
224 return Err(Error::InvalidSignature);
225 }
226 let trusted_comment_bin = signature.trusted_comment().as_bytes();
227 let mut global = Vec::with_capacity(signature.signature.len() + trusted_comment_bin.len());
228 global.extend_from_slice(&signature.signature[..]);
229 global.extend_from_slice(trusted_comment_bin);
230 if !ed25519::verify(&global, &self.key, &signature.global_signature) {
231 return Err(Error::InvalidSignature);
232 }
233 Ok(())
234 }
235
236 pub fn verify(
240 &self,
241 bin: &[u8],
242 signature: &Signature,
243 allow_legacy: bool,
244 ) -> Result<(), Error> {
245 if self.key_id != signature.key_id {
246 return Err(Error::UnexpectedKeyId);
247 }
248 let mut h;
249 let bin = if signature.is_prehashed {
250 h = vec![0u8; BLAKE2B_OUTBYTES];
251 Blake2b::blake2b(&mut h, bin);
252 &h
253 } else if !allow_legacy {
254 return Err(Error::UnexpectedAlgorithm);
255 } else {
256 bin
257 };
258 self.verify_ed25519(bin, signature)
259 }
260
261 pub fn verify_stream(&'a self, signature: &'a Signature) -> Result<StreamVerifier, Error> {
263 if self.key_id != signature.key_id {
264 return Err(Error::UnexpectedKeyId);
265 }
266 if !signature.is_prehashed {
267 return Err(Error::UnsupportedLegacyMode);
268 }
269 let hasher = Blake2b::new(BLAKE2B_OUTBYTES);
270 Ok(StreamVerifier {
271 public_key: self,
272 signature,
273 hasher,
274 })
275 }
276}
277
278impl<'a> StreamVerifier<'a> {
279 pub fn update(&mut self, buf: &[u8]) {
280 self.hasher.update(buf);
281 }
282
283 pub fn finalize(&mut self) -> Result<(), Error> {
284 let mut bin = vec![0u8; BLAKE2B_OUTBYTES];
285 self.hasher.finalize(&mut bin);
286 self.public_key.verify_ed25519(&bin, self.signature)
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 #[test]
294 fn verify() {
295 let public_key =
296 PublicKey::from_base64("RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3")
297 .expect("Unable to decode the public key");
298 assert_eq!(public_key.untrusted_comment(), None);
299 let signature = Signature::decode(
300 "untrusted comment: signature from minisign secret key
301RWQf6LRCGA9i59SLOFxz6NxvASXDJeRtuZykwQepbDEGt87ig1BNpWaVWuNrm73YiIiJbq71Wi+dP9eKL8OC351vwIasSSbXxwA=
302trusted comment: timestamp:1555779966\tfile:test
303QtKMXWyYcwdpZAlPF7tE2ENJkRd1ujvKjlj1m9RtHTBnZPa5WKU5uWRs5GoP5M/VqE81QFuMKI5k/SfNQUaOAA==",
304 )
305 .expect("Unable to decode the signature");
306 assert_eq!(
307 signature.untrusted_comment(),
308 "untrusted comment: signature from minisign secret key"
309 );
310 assert_eq!(
311 signature.trusted_comment(),
312 "timestamp:1555779966\tfile:test"
313 );
314 let bin = b"test";
315 public_key
316 .verify(&bin[..], &signature, true)
317 .expect("Signature didn't verify");
318 let bin = b"Test";
319 match public_key.verify(&bin[..], &signature, true) {
320 Err(Error::InvalidSignature) => {}
321 _ => panic!("Invalid signature verified"),
322 };
323
324 let public_key2 = PublicKey::decode(
325 "untrusted comment: minisign public key E7620F1842B4E81F
326RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3",
327 )
328 .expect("Unable to decode the public key");
329 assert_eq!(
330 public_key2.untrusted_comment(),
331 Some("untrusted comment: minisign public key E7620F1842B4E81F")
332 );
333 match public_key2.verify(&bin[..], &signature, true) {
334 Err(Error::InvalidSignature) => {}
335 _ => panic!("Invalid signature verified"),
336 };
337 }
338
339 #[test]
340 fn verify_prehashed() {
341 let public_key =
342 PublicKey::from_base64("RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3")
343 .expect("Unable to decode the public key");
344 assert_eq!(public_key.untrusted_comment(), None);
345 let signature = Signature::decode(
346 "untrusted comment: signature from minisign secret key
347RUQf6LRCGA9i559r3g7V1qNyJDApGip8MfqcadIgT9CuhV3EMhHoN1mGTkUidF/\
348 z7SrlQgXdy8ofjb7bNJJylDOocrCo8KLzZwo=
349trusted comment: timestamp:1556193335\tfile:test
350y/rUw2y8/hOUYjZU71eHp/Wo1KZ40fGy2VJEDl34XMJM+TX48Ss/17u3IvIfbVR1FkZZSNCisQbuQY+bHwhEBg==",
351 )
352 .expect("Unable to decode the signature");
353 assert_eq!(
354 signature.untrusted_comment(),
355 "untrusted comment: signature from minisign secret key"
356 );
357 assert_eq!(
358 signature.trusted_comment(),
359 "timestamp:1556193335\tfile:test"
360 );
361 let bin = b"test";
362 public_key
363 .verify(&bin[..], &signature, false)
364 .expect("Signature didn't verify");
365 }
366
367 #[test]
368 fn verify_stream() {
369 let public_key =
370 PublicKey::from_base64("RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3")
371 .expect("Unable to decode the public key");
372 assert_eq!(public_key.untrusted_comment(), None);
373 let signature = Signature::decode(
374 "untrusted comment: signature from minisign secret key
375RUQf6LRCGA9i559r3g7V1qNyJDApGip8MfqcadIgT9CuhV3EMhHoN1mGTkUidF/\
376 z7SrlQgXdy8ofjb7bNJJylDOocrCo8KLzZwo=
377trusted comment: timestamp:1556193335\tfile:test
378y/rUw2y8/hOUYjZU71eHp/Wo1KZ40fGy2VJEDl34XMJM+TX48Ss/17u3IvIfbVR1FkZZSNCisQbuQY+bHwhEBg==",
379 )
380 .expect("Unable to decode the signature");
381 assert_eq!(
382 signature.untrusted_comment(),
383 "untrusted comment: signature from minisign secret key"
384 );
385 assert_eq!(
386 signature.trusted_comment(),
387 "timestamp:1556193335\tfile:test"
388 );
389 let mut stream_verifier = public_key
390 .verify_stream(&signature)
391 .expect("Can't extract StreamerVerifier");
392
393 let bin: &[u8] = b"te";
394 stream_verifier.update(bin);
395
396 let bin: &[u8] = b"st";
397 stream_verifier.update(bin);
398
399 stream_verifier.finalize().expect("Signature didn't verify");
400 }
401}