sop/lib.rs
1//! A Rust implementation of the Stateless OpenPGP Interface.
2//!
3//! This crate defines an interface that is the Rust equivalent of the
4//! [draft 08] of the Stateless OpenPGP Command Line Interface. Note
5//! that you need an concrete implementation of this interface (such
6//! as [`sequoia-sop`]) in order to use it.
7//!
8//! [draft 08]: https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-08.html
9//! [`sequoia-sop`]: https://docs.rs/sequoia-sop
10//!
11//! # Examples
12//!
13//! Given a reference to a [`SOP`] implementation, which is the main
14//! entry point for every SOP operation, generate keys, extract certs,
15//! sign, verify, encrypt, and decrypt:
16//!
17//! ```rust
18//! # use sop::*; use std::io::Cursor;
19//! # fn sop_examples<'s, S: SOP<'s> + 's>(sop: &'s S) -> Result<()> {
20//! let alice_sec = sop.generate_key()?
21//! .userid("Alice Lovelace <alice@openpgp.example>")
22//! .generate()?;
23//! let alice_pgp = sop.extract_cert()?
24//! .keys(&alice_sec)?;
25//!
26//! let bob_sec = sop.generate_key()?
27//! .userid("Bob Babbage <bob@openpgp.example>")
28//! .generate()?;
29//! let bob_pgp = sop.extract_cert()?
30//! .keys(&bob_sec)?;
31//!
32//! let statement = b"Hello World :)";
33//! let mut data = Cursor::new(&statement);
34//! let (_micalg, signature) = sop.sign()?
35//! .mode(ops::SignAs::Text)
36//! .keys(&alice_sec)?
37//! .data(&mut data)?;
38//!
39//! let verifications = sop.verify()?
40//! .certs(&alice_pgp)?
41//! .signatures(&signature)?
42//! .data(&mut Cursor::new(&statement))?;
43//! assert_eq!(verifications.len(), 1);
44//!
45//! let mut statement_cur = Cursor::new(&statement);
46//! let (_session_key, ciphertext) = sop.encrypt()?
47//! .sign_with_keys(&alice_sec)?
48//! .with_certs(&bob_pgp)?
49//! .plaintext(&mut statement_cur)?
50//! .to_vec()?;
51//!
52//! let mut ciphertext_cur = Cursor::new(&ciphertext);
53//! let (_, plaintext) = sop.decrypt()?
54//! .with_keys(&bob_sec)?
55//! .ciphertext(&mut ciphertext_cur)?
56//! .to_vec()?;
57//! assert_eq!(&plaintext, statement);
58//! # Ok(()) }
59//! ```
60//!
61//! The above snippet is the equivalent of the following SOP command
62//! line example from the SOP spec:
63//!
64//! ```sh
65//! $ sop generate-key "Alice Lovelace <alice@openpgp.example>" > alice.sec
66//! $ sop extract-cert < alice.sec > alice.pgp
67//!
68//! $ sop sign --as=text alice.sec < statement.txt > statement.txt.asc
69//! $ sop verify announcement.txt.asc alice.pgp < announcement.txt
70//!
71//! $ sop encrypt --sign-with=alice.sec bob.pgp < msg.eml > encrypted.asc
72//! $ sop decrypt alice.sec < ciphertext.asc > cleartext.out
73//! ```
74
75use std::{
76 fmt,
77 io,
78};
79
80pub mod ops;
81use ops::*;
82pub mod errors;
83use errors::*;
84pub mod plumbing;
85
86#[cfg(any(feature = "cli", feature = "cliv"))]
87pub mod cli;
88
89/// Loads objects like certs and keys.
90pub trait Load<'s, S: SOP<'s>> {
91 /// Loads objects like certs and keys from stdin.
92 fn from_stdin(sop: &'s S) -> Result<Self>
93 where
94 Self: Sized,
95 {
96 Self::from_reader(sop, &mut io::stdin(), Some("/dev/stdin".into()))
97 }
98
99 /// Loads objects like certs and keys from the given file.
100 fn from_file<P>(sop: &'s S, path: P) -> Result<Self>
101 where
102 Self: Sized,
103 P: AsRef<std::path::Path>,
104 {
105 let path = path.as_ref();
106 Self::from_reader(sop, &mut std::fs::File::open(path)?,
107 Some(path.display().to_string()))
108 }
109
110 /// Loads objects like certs and keys from the given reader.
111 fn from_reader(sop: &'s S, source: &mut (dyn io::Read + Send + Sync),
112 source_name: Option<String>)
113 -> Result<Self>
114 where Self: Sized;
115
116 /// Loads objects like certs and keys from the given byte slice.
117 fn from_bytes(sop: &'s S, source: &[u8]) -> Result<Self>
118 where
119 Self: Sized,
120 {
121 Self::from_reader(sop, &mut io::Cursor::new(source), None)
122 }
123
124 /// Returns the source name, if any.
125 fn source_name(&self) -> Option<&str>;
126}
127
128/// Saves objects like certs and keys.
129pub trait Save {
130 /// Writes objects like certs and keys to stdout.
131 fn to_stdout(&self, armored: bool) -> Result<()> {
132 self.to_writer(armored, &mut io::stdout())
133 }
134
135 /// Saves objects like certs and keys, writing them to the given writer.
136 fn to_writer(&self, armored: bool, sink: &mut (dyn io::Write + Send + Sync))
137 -> Result<()>;
138
139 /// Saves objects like certs and keys, writing them to a vector.
140 fn to_vec(&self, armored: bool) -> Result<Vec<u8>> {
141 let mut sink = vec![];
142 self.to_writer(armored, &mut sink)?;
143 Ok(sink)
144 }
145}
146
147/// Main entry point to the Stateless OpenPGP Interface.
148pub trait SOP<'s>: Sized {
149 /// Secret keys.
150 type Keys: Load<'s, Self> + Save + plumbing::SopRef<'s, Self>;
151
152 /// Public keys.
153 type Certs: Load<'s, Self> + Save + plumbing::SopRef<'s, Self>;
154
155 /// Signatures.
156 type Sigs: Load<'s, Self> + Save + plumbing::SopRef<'s, Self>;
157
158 /// Controls debugging.
159 ///
160 /// If enabled, implementations may emit debugging information in
161 /// any way they see fit, most commonly by printing to stderr.
162 fn debug(&mut self, enable: bool) {
163 let _ = enable;
164 }
165
166 /// Gets SOP version information.
167 ///
168 /// The default implementation returns the version of the spec
169 /// that this framework supports. This should be fine for most
170 /// implementations. However, implementations may chose to
171 /// override this function to return a more nuanced response.
172 fn spec_version(&'s self) -> &'static str {
173 "~draft-dkg-openpgp-stateless-cli-11"
174 }
175
176 /// Completeness of the sopv subset
177 ///
178 /// If all the features of the sopv subset are implemented, an
179 /// implementation should return Ok("1.0")
180 fn sopv_version(&'s self) -> Result<&'static str> {
181 Err(Error::UnsupportedOption)
182 }
183
184 /// Gets version information.
185 ///
186 /// # Examples
187 ///
188 /// ```rust
189 /// # use sop::*; use std::io::Cursor;
190 /// # fn sop_examples<'s, S: SOP<'s> + 's>(sop: &'s S) -> Result<()> {
191 /// // Prints the name of the SOP implementation.
192 /// println!("{}", sop.version()?.frontend()?);
193 ///
194 /// // Prints the name of the underlying OpenPGP implementation.
195 /// println!("{}", sop.version()?.backend()?);
196 ///
197 /// // Prints extended version information.
198 /// println!("{}", sop.version()?.extended()?);
199 /// # Ok(()) }
200 /// ```
201 fn version(&self) -> Result<Box<dyn Version>>;
202
203 /// Generates a Secret Key.
204 ///
205 /// Customize the operation using the builder [`GenerateKey`].
206 ///
207 /// # Examples
208 ///
209 /// ```rust
210 /// # use sop::*; use std::io::Cursor;
211 /// # fn sop_examples<'s, S: SOP<'s> + 's>(sop: &'s S) -> Result<()> {
212 /// let alice_sec = sop.generate_key()?
213 /// .userid("Alice Lovelace <alice@openpgp.example>")
214 /// .generate()?;
215 /// # Ok(()) }
216 /// ```
217 fn generate_key(&'s self)
218 -> Result<Box<dyn GenerateKey<Self, Self::Keys> + 's>>;
219
220 /// Updates a key's password.
221 ///
222 /// Customize the operation using the builder [`ChangeKeyPassword`].
223 ///
224 /// # Examples
225 ///
226 /// ```rust
227 /// # use sop::*; use std::io::Cursor; use std::fs::File;
228 /// # fn sop_examples<'s, S, Certs, Keys>(sop: &'s S) -> Result<()>
229 /// # where
230 /// # S: SOP<'s, Certs = Certs, Keys = Keys>,
231 /// # Certs: Load<'s, S> + Save,
232 /// # Keys: Load<'s, S> + Save,
233 /// # {
234 /// let alice_secret =
235 /// Keys::from_file(sop, "alice.secret")?;
236 ///
237 /// let alice_updated_secret = sop.change_key_password()?
238 /// .old_key_password(Password::new_unchecked(b"hunter2".to_vec()))?
239 /// .new_key_password(Password::new(b"jaeger2".to_vec())?)?
240 /// .keys(&alice_secret)?;
241 /// # Ok(()) }
242 /// ```
243 fn change_key_password(&'s self)
244 -> Result<Box<dyn ChangeKeyPassword<Self, Self::Keys> + 's>>;
245
246 /// Creates a Revocation Certificate.
247 ///
248 /// Customize the operation using the builder [`RevokeKey`].
249 ///
250 /// # Examples
251 ///
252 /// ```rust
253 /// # use sop::*; use std::io::Cursor; use std::fs::File;
254 /// # fn sop_examples<'s, S, Certs, Keys>(sop: &'s S) -> Result<()>
255 /// # where
256 /// # S: SOP<'s, Certs = Certs, Keys = Keys>,
257 /// # Certs: Load<'s, S> + Save,
258 /// # Keys: Load<'s, S> + Save,
259 /// # {
260 /// let alice_secret =
261 /// Keys::from_file(sop, "alice.secret")?;
262 ///
263 /// let alice_revoked = sop.revoke_key()?
264 /// .with_key_password(Password::new_unchecked(b"hunter2".to_vec()))?
265 /// .keys(&alice_secret)?;
266 /// # Ok(()) }
267 /// ```
268 fn revoke_key(&'s self)
269 -> Result<Box<dyn RevokeKey<Self, Self::Certs, Self::Keys> + 's>>;
270
271 /// Extracts a Certificate from a Secret Key.
272 ///
273 /// Customize the operation using the builder [`ExtractCert`].
274 ///
275 /// # Examples
276 ///
277 /// ```rust
278 /// # use sop::*; use std::io::Cursor; use std::fs::File;
279 /// # fn sop_examples<'s, S, Certs, Keys>(sop: &'s S) -> Result<()>
280 /// # where
281 /// # S: SOP<'s, Certs = Certs, Keys = Keys>,
282 /// # Certs: Load<'s, S> + Save,
283 /// # Keys: Load<'s, S> + Save,
284 /// # {
285 /// let alice_secret =
286 /// Keys::from_file(sop, "alice.secret")?;
287 ///
288 /// let alice_public = sop.extract_cert()?
289 /// .keys(&alice_secret)?;
290 /// # Ok(()) }
291 /// ```
292 fn extract_cert(&'s self) -> Result<Box<
293 dyn ExtractCert<Self, Self::Certs, Self::Keys> + 's>>;
294
295 /// Keeps a Secret Key Up-To-Date.
296 ///
297 /// This update will "fix" everything that the implementation
298 /// knows how to fix to bring each Transferable Secret Key up to
299 /// reasonable modern practice. Each Transferable Secret Key
300 /// output must be capable of signing, and (unless --signing-only
301 /// is provided) capable of decryption. The primary key of each
302 /// Transferable Secret Key will not be changed in any way that
303 /// affects its fingerprint.
304 ///
305 /// One important aspect of sop update-key is how it handles
306 /// advertisement of support for various OpenPGP capabilities
307 /// (algorithms, mechanisms, etc). All capabilities that the
308 /// implementation knows it does not support, or knows to be weak
309 /// and/or deprecated MUST be removed from the output Transferable
310 /// Secret Keys. This includes unknown/deprecated flags in the
311 /// Features subpacket, and any unknown/deprecated algorithm IDs
312 /// in algorithm preferences subpackets. For example, an
313 /// implementation compliant with [RFC9580] will never emit a
314 /// Transferable Secret Key with a Preferred Hash Preferences
315 /// subpacket that explicitly indicates support for MD5,
316 /// RIPEMD160, or SHA1.
317 fn update_key(&'s self) -> Result<Box<
318 dyn UpdateKey<Self, Self::Certs, Self::Keys> + 's>>;
319
320 /// Merge OpenPGP Certificates.
321 ///
322 /// This can be used, for example, to absorb a third-party
323 /// certification into a certificate, or to update a certificate's
324 /// feature advertisements without losing local annotations.
325 fn merge_certs(&'s self)
326 -> Result<Box<dyn MergeCerts<Self, Self::Certs> + 's>>;
327
328 /// Certify OpenPGP Certificate User IDs.
329 ///
330 /// With each Transferable Secret Key provided, add a third-party
331 /// certification to the provided certificates, and
332 /// emit the updated OpenPGP certificates.
333 fn certify_userid(&'s self) -> Result<Box<
334 dyn CertifyUserID<Self, Self::Certs, Self::Keys> + 's>>;
335
336 /// Validate a User ID in an OpenPGP Certificate.
337 ///
338 /// Given a set of authority OpenPGP certificates, succeed if and
339 /// only if all provided OpenPGP certificates are correctly bound
340 /// by at least one valid signature from one authority to the User
341 /// ID or email address in question.
342 fn validate_userid(&'s self) -> Result<Box<
343 dyn ValidateUserID<Self, Self::Certs> + 's>>;
344
345 /// Creates Detached Signatures.
346 ///
347 /// Customize the operation using the builder [`Sign`].
348 ///
349 /// # Examples
350 ///
351 /// ```rust
352 /// # use sop::*; use std::io::Cursor; use std::fs::File;
353 /// # fn sop_examples<'s, S, Certs, Keys>(sop: &'s S) -> Result<()>
354 /// # where
355 /// # S: SOP<'s, Certs = Certs, Keys = Keys>,
356 /// # Certs: Load<'s, S> + Save,
357 /// # Keys: Load<'s, S> + Save,
358 /// # {
359 /// let alice_secret =
360 /// Keys::from_file(sop, "alice.secret")?;
361 ///
362 /// let (_micalg, sig) = sop.sign()?
363 /// .keys(&alice_secret)?
364 /// .data(&mut Cursor::new(&b"Hello World :)"))?;
365 /// # Ok(()) }
366 /// ```
367 fn sign(&'s self)
368 -> Result<Box<dyn Sign<Self, Self::Keys, Self::Sigs> + 's>>;
369
370 /// Verifies Detached Signatures.
371 ///
372 /// Customize the operation using the builder [`Verify`].
373 ///
374 /// # Examples
375 ///
376 /// ```rust
377 /// # use sop::*; use std::io::Cursor; use std::fs::File;
378 /// # fn sop_examples<'s, S, Certs, Keys, Sigs>(sop: &'s S) -> Result<()>
379 /// # where
380 /// # S: SOP<'s, Certs = Certs, Keys = Keys, Sigs = Sigs>,
381 /// # Certs: Load<'s, S> + Save,
382 /// # Keys: Load<'s, S> + Save,
383 /// # Sigs: Load<'s, S> + Save,
384 /// # {
385 /// let alice_public =
386 /// Certs::from_file(sop, "alice.public")?;
387 /// let sig =
388 /// Sigs::from_file(sop, "data.asc")?;
389 ///
390 /// let verifications = sop.verify()?
391 /// .certs(&alice_public)?
392 /// .signatures(&sig)?
393 /// .data(&mut Cursor::new(&b"Hello World :)"))?;
394 /// let valid_signatures = ! verifications.is_empty();
395 /// # Ok(()) }
396 /// ```
397 fn verify(&'s self)
398 -> Result<Box<dyn Verify<Self, Self::Certs, Self::Sigs> + 's>>;
399
400 /// Encrypts a Message.
401 ///
402 /// Customize the operation using the builder [`Encrypt`].
403 ///
404 /// # Examples
405 ///
406 /// Encrypts a message for Bob, and signs it using Alice's key.
407 ///
408 /// ```rust
409 /// # use sop::*; use std::io::Cursor; use std::fs::File;
410 /// # fn sop_examples<'s, S, Certs, Keys>(sop: &'s S) -> Result<()>
411 /// # where
412 /// # S: SOP<'s, Certs = Certs, Keys = Keys>,
413 /// # Certs: Load<'s, S> + Save,
414 /// # Keys: Load<'s, S> + Save,
415 /// # {
416 /// let alice_secret =
417 /// Keys::from_file(sop, "alice.secret")?;
418 /// let bob_public =
419 /// Certs::from_file(sop, "bob.public")?;
420 ///
421 /// let (_session_key, ciphertext) = sop.encrypt()?
422 /// .sign_with_keys(&alice_secret)?
423 /// .with_certs(&bob_public)?
424 /// .plaintext(&mut Cursor::new(&b"Hello World :)"))?
425 /// .to_vec()?;
426 /// # Ok(()) }
427 /// ```
428 fn encrypt(&'s self)
429 -> Result<Box<dyn Encrypt<Self, Self::Certs, Self::Keys> + 's>>;
430
431 /// Decrypts a Message.
432 ///
433 /// Customize the operation using the builder [`Decrypt`].
434 ///
435 /// # Examples
436 ///
437 /// Decrypts a message encrypted for Bob, and verifies Alice's
438 /// signature on it.
439 ///
440 /// ```rust
441 /// # use sop::*; use std::io::Cursor; use std::fs::File;
442 /// # fn sop_examples<'s, S, Certs, Keys>(sop: &'s S) -> Result<()>
443 /// # where
444 /// # S: SOP<'s, Certs = Certs, Keys = Keys>,
445 /// # Certs: Load<'s, S> + Save,
446 /// # Keys: Load<'s, S> + Save,
447 /// # {
448 /// let alice_public =
449 /// Certs::from_file(sop, "alice.public")?;
450 /// let bob_secret =
451 /// Keys::from_file(sop, "bob.secret")?;
452 ///
453 /// let ((_session_key, verifications), plaintext) = sop.decrypt()?
454 /// .verify_with_certs(&alice_public)?
455 /// .with_keys(&bob_secret)?
456 /// .ciphertext(&mut File::open("ciphertext.pgp")?)?
457 /// .to_vec()?;
458 /// let valid_signatures = ! verifications.is_empty();
459 /// # Ok(()) }
460 /// ```
461 fn decrypt(&'s self)
462 -> Result<Box<dyn Decrypt<Self, Self::Certs, Self::Keys> + 's>>;
463
464 /// Converts binary OpenPGP data to ASCII.
465 ///
466 /// By default, SOP operations emit ASCII-Armored data. But,
467 /// occasionally it can be useful to explicitly armor data.
468 ///
469 /// Customize the operation using the builder [`Armor`].
470 ///
471 /// # Examples
472 ///
473 /// ```rust
474 /// # use sop::*; use std::io::Cursor; use std::fs::File;
475 /// # fn sop_examples<'s, S: SOP<'s> + 's>(sop: &'s S) -> Result<()> {
476 /// let (_, alice_secret_asc) = sop.armor()?
477 /// .data(&mut File::open("alice.secret.bin")?)?
478 /// .to_vec()?;
479 /// assert!(alice_secret_asc.starts_with(b"-----BEGIN PGP PRIVATE KEY BLOCK-----"));
480 /// # Ok(()) }
481 /// ```
482 fn armor(&'s self) -> Result<Box<dyn Armor + 's>>;
483
484 /// Converts ASCII OpenPGP data to binary.
485 ///
486 /// By default, SOP operations emit ASCII-Armored data, but this
487 /// behavior can be changed at export time. Nevertheless,
488 /// occasionally it can be useful to explicitly dearmor data.
489 ///
490 /// Customize the operation using the builder [`Dearmor`].
491 ///
492 /// # Examples
493 ///
494 /// ```rust
495 /// # use sop::*; use std::io::Cursor; use std::fs::File;
496 /// # fn sop_examples<'s, S: SOP<'s> + 's>(sop: &'s S) -> Result<()> {
497 /// let (_, alice_secret_bin) = sop.dearmor()?
498 /// .data(&mut File::open("alice.secret.asc")?)?
499 /// .to_vec()?;
500 /// assert!(! alice_secret_bin.starts_with(b"-----BEGIN PGP PRIVATE KEY BLOCK-----"));
501 /// # Ok(()) }
502 /// ```
503 fn dearmor(&'s self) -> Result<Box<dyn Dearmor + 's>>;
504
505 /// Splits Signatures from an Inline-Signed Message.
506 ///
507 /// Note: The signatures are not verified, this merely transforms
508 /// an inline-signed message into a detached signature, which in
509 /// turn can be verified using [`SOP::verify`].
510 ///
511 /// Customize the operation using the builder [`InlineDetach`].
512 ///
513 /// # Examples
514 ///
515 /// ```rust
516 /// # use sop::*; use std::io::Cursor; use std::fs::File;
517 /// # fn sop_examples<'s, S: SOP<'s> + 's>(sop: &'s S) -> Result<()> {
518 /// let (signatures, data) = sop.inline_detach()?
519 /// .message(&mut File::open("inline-signed.pgp")?)?
520 /// .to_vec()?;
521 /// # Ok(()) }
522 /// ```
523 fn inline_detach(&'s self)
524 -> Result<Box<dyn InlineDetach<Self::Sigs> + 's>>;
525
526 /// Verifies an Inline-Signed Message.
527 ///
528 /// Customize the operation using the builder [`InlineVerify`].
529 ///
530 /// # Examples
531 ///
532 /// ```rust
533 /// # use sop::*; use std::io::Cursor; use std::fs::File;
534 /// # fn sop_examples<'s, S, Certs, Keys>(sop: &'s S) -> Result<()>
535 /// # where
536 /// # S: SOP<'s, Certs = Certs, Keys = Keys>,
537 /// # Certs: Load<'s, S> + Save,
538 /// # Keys: Load<'s, S> + Save,
539 /// # {
540 /// let alice_public =
541 /// Certs::from_file(sop, "alice.public")?;
542 ///
543 /// let (verifications, data) = sop.inline_verify()?
544 /// .certs(&alice_public)?
545 /// .message(&mut File::open("inline-signed.pgp")?)?
546 /// .to_vec()?;
547 /// let valid_signatures = ! verifications.is_empty();
548 /// # Ok(()) }
549 /// ```
550 fn inline_verify(&'s self)
551 -> Result<Box<dyn InlineVerify<Self, Self::Certs> + 's>>;
552
553 /// Creates an Inline-Signed Message.
554 ///
555 /// Customize the operation using the builder [`InlineSign`].
556 ///
557 /// # Examples
558 ///
559 /// ```rust
560 /// # use sop::*; use std::io::Cursor; use std::fs::File;
561 /// # fn sop_examples<'s, S, Certs, Keys>(sop: &'s S) -> Result<()>
562 /// # where
563 /// # S: SOP<'s, Certs = Certs, Keys = Keys>,
564 /// # Certs: Load<'s, S> + Save,
565 /// # Keys: Load<'s, S> + Save,
566 /// # {
567 /// let alice_secret =
568 /// Keys::from_file(sop, "alice.secret")?;
569 ///
570 /// let (inline_signed_asc) = sop.inline_sign()?
571 /// .keys(&alice_secret)?
572 /// .data(&mut Cursor::new(&b"Hello World :)"))?
573 /// .to_vec()?;
574 /// # Ok(()) }
575 /// ```
576 fn inline_sign(&'s self)
577 -> Result<Box<dyn InlineSign<Self, Self::Keys> + 's>>;
578}
579
580/// A password.
581///
582/// See [Passwords are Human-Readable] in the SOP spec.
583///
584/// [Passwords are Human-Readable]: https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-08.html#name-passwords-are-human-readabl
585pub struct Password(Box<[u8]>);
586
587impl Password {
588 /// Returns a `Password` that is guaranteed to be human-readable.
589 ///
590 /// Use this function when you get a password from the user to
591 /// generate an artifact with (see [Generating Material with
592 /// Human-Readable Passwords]).
593 ///
594 /// [Generating Material with Human-Readable Passwords]: https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-08.html#name-generating-material-with-hu
595 pub fn new(password: Vec<u8>) -> Result<Password> {
596 // Securely erase the password.
597 fn securely_erase(mut p: Vec<u8>) {
598 unsafe {
599 memsec::memzero(p.as_mut_ptr(), p.len());
600 }
601 }
602
603 let mut s = String::from_utf8(password)
604 .map_err(|e| {
605 securely_erase(e.into_bytes());
606 Error::PasswordNotHumanReadable
607 })?;
608
609 // Check for leading whitespace.
610 if s.trim_start().len() != s.len() {
611 securely_erase(s.into_bytes());
612 return Err(Error::PasswordNotHumanReadable);
613 }
614
615 // Trim trailing whitespace.
616 s.truncate(s.trim_end().len());
617
618 // Check for odd whitespace.
619 if s.chars().any(|c| c.is_whitespace() && c != ' ') {
620 securely_erase(s.into_bytes());
621 return Err(Error::PasswordNotHumanReadable);
622 }
623
624 // XXX: Check that the password is in Unicode Normal Form C,
625 // but I don't think that is possible with Rust's stdlib.
626
627 Ok(Password(s.into_bytes().into()))
628 }
629
630 /// Returns a `Password` without further checking.
631 ///
632 /// Use this function when you get a password from the user that
633 /// is used when consuming an artifact (see [Consuming
634 /// Password-protected Material]).
635 ///
636 /// [Consuming Password-protected Material]: https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-08.html#name-consuming-password-protecte
637 pub fn new_unchecked(password: Vec<u8>) -> Password {
638 Password(password.into())
639 }
640}
641
642impl plumbing::PasswordsAreHumanReadable for Password {
643 fn normalized(&self) -> &[u8] {
644 // First, let's hope it is UTF-8.
645 if let Ok(p) = std::str::from_utf8(&self.0) {
646 p.trim_end().as_bytes()
647 } else {
648 // As a best effort for now, drop ASCII-whitespace from
649 // the end.
650 let mut p = &self.0[..];
651 while ! p.is_empty() && p[p.len() - 1].is_ascii_whitespace() {
652 p = &p[..p.len() - 1];
653 }
654 p
655 }
656 }
657
658 fn variants(&self) -> Box<dyn Iterator<Item = &[u8]> + '_> {
659 Box::new(std::iter::once(self.normalized())
660 .filter_map(move |normalized| {
661 if normalized.len() < self.0.len() {
662 Some(normalized)
663 } else {
664 None
665 }
666 })
667 .chain(std::iter::once(&self.0[..])))
668 }
669}
670
671impl Drop for Password {
672 fn drop(&mut self) {
673 unsafe {
674 memsec::memzero(self.0.as_mut_ptr(), self.0.len());
675 }
676 }
677}
678
679/// A session key.
680pub struct SessionKey {
681 algorithm: u8,
682 key: Box<[u8]>,
683}
684
685impl SessionKey {
686 /// Creates a new session key object.
687 pub fn new<A, K>(algorithm: A, key: K) -> Result<Self>
688 where A: Into<u8>,
689 K: AsRef<[u8]>,
690 {
691 // XXX: Maybe sanity check key lengths.
692 Ok(SessionKey {
693 algorithm: algorithm.into(),
694 key: key.as_ref().to_vec().into(),
695 })
696 }
697
698 /// Returns the symmetric algorithm octet.
699 pub fn algorithm(&self) -> u8 {
700 self.algorithm
701 }
702
703 /// Returns the session key.
704 pub fn key(&self) -> &[u8] {
705 &self.key
706 }
707}
708
709impl fmt::Display for SessionKey {
710 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
711 write!(f, "{}:", self.algorithm)?;
712 for b in &self.key[..] {
713 write!(f, "{:02X}", b)?
714 }
715 Ok(())
716 }
717}
718
719impl Drop for SessionKey {
720 fn drop(&mut self) {
721 unsafe {
722 memsec::memzero(self.key.as_mut_ptr(), self.key.len());
723 }
724 }
725}
726
727impl std::str::FromStr for SessionKey {
728 type Err = ParseError;
729 fn from_str(sk: &str) -> ParseResult<Self> {
730 // The SOP format is:
731 //
732 // <decimal-cipher-octet> ":" <hex-session-key>
733 //
734 // We most likely will change the first field, so we split
735 // from the end of the string using `rsplit`, which puts the
736 // last segment first. This is rather unexpected. Reverse
737 // it.
738 let fields = sk.rsplit(':').rev().collect::<Vec<_>>();
739
740 if fields.len() != 2 {
741 return Err(ParseError(format!(
742 "Expected two colon-separated fields, got {:?}",
743 fields)));
744 }
745
746 let algo: u8 = fields[0].parse().map_err(
747 |e| ParseError(format!("Failed to parse algorithm: {}", e)))?;
748 let sk = from_hex(&fields[1], true)?;
749 Self::new(algo, sk).map_err(
750 |e| ParseError(format!("Bad session key: {}", e)))
751 }
752}
753
754/// A helpful function for converting a hexadecimal string to binary.
755/// This function skips whitespace if `pretty` is set.
756fn from_hex(hex: &str, pretty: bool) -> ParseResult<Vec<u8>> {
757 const BAD: u8 = 255u8;
758 const X: u8 = 'x' as u8;
759
760 let mut nibbles = hex.chars().filter_map(|x| {
761 match x {
762 '0' => Some(0u8),
763 '1' => Some(1u8),
764 '2' => Some(2u8),
765 '3' => Some(3u8),
766 '4' => Some(4u8),
767 '5' => Some(5u8),
768 '6' => Some(6u8),
769 '7' => Some(7u8),
770 '8' => Some(8u8),
771 '9' => Some(9u8),
772 'a' | 'A' => Some(10u8),
773 'b' | 'B' => Some(11u8),
774 'c' | 'C' => Some(12u8),
775 'd' | 'D' => Some(13u8),
776 'e' | 'E' => Some(14u8),
777 'f' | 'F' => Some(15u8),
778 'x' | 'X' if pretty => Some(X),
779 _ if pretty && x.is_whitespace() => None,
780 _ => Some(BAD),
781 }
782 }).collect::<Vec<u8>>();
783
784 if pretty && nibbles.len() >= 2 && nibbles[0] == 0 && nibbles[1] == X {
785 // Drop '0x' prefix.
786 nibbles.remove(0);
787 nibbles.remove(0);
788 }
789
790 if nibbles.iter().any(|&b| b == BAD || b == X) {
791 // Not a hex character.
792 return Err(ParseError("Invalid characters".into()));
793 }
794
795 // We need an even number of nibbles.
796 if nibbles.len() % 2 != 0 {
797 return Err(ParseError("Odd number of nibbles".into()));
798 }
799
800 let bytes = nibbles.chunks(2).map(|nibbles| {
801 (nibbles[0] << 4) | nibbles[1]
802 }).collect::<Vec<u8>>();
803
804 Ok(bytes)
805}
806
807/// Result specialization.
808pub type Result<T> = std::result::Result<T, Error>;
809
810/// Convenience alias.
811type ParseResult<T> = std::result::Result<T, ParseError>;
812
813#[cfg(test)]
814mod tests {
815 use super::*;
816
817 #[test]
818 fn session_key_roundtrip() -> Result<()> {
819 for algo in &[9, 13] {
820 let sk = SessionKey::new(
821 *algo,
822 &[0xE1, 0x48, 0x97, 0x81, 0xAA, 0x22, 0xE1, 0xBF,
823 0x6E, 0x3E, 0x61, 0x74, 0x8C, 0x8D, 0x3F, 0x35,
824 0x50, 0x7C, 0x80, 0x9E, 0x95, 0x64, 0x86, 0x87,
825 0xC7, 0xE4, 0xB9, 0xAF, 0x86, 0x17, 0xD3, 0xAE])?;
826 let sk_s = sk.to_string();
827 let sk_p: SessionKey = sk_s.parse().unwrap();
828 assert_eq!(sk.algorithm(), sk_p.algorithm());
829 assert_eq!(sk.key(), sk_p.key());
830 }
831 Ok(())
832 }
833
834 #[test]
835 fn sign_as_roundtrip() -> Result<()> {
836 use SignAs::*;
837 for a in &[Text, Binary] {
838 let s = a.to_string();
839 let b: SignAs = s.parse().unwrap();
840 assert_eq!(a, &b);
841 }
842 Ok(())
843 }
844
845 #[test]
846 fn encrypt_as_roundtrip() -> Result<()> {
847 use EncryptAs::*;
848 for a in &[Text, Binary] {
849 let s = a.to_string();
850 let b: EncryptAs = s.parse().unwrap();
851 assert_eq!(a, &b);
852 }
853 Ok(())
854 }
855
856 #[test]
857 fn armor_label_roundtrip() -> Result<()> {
858 use ArmorLabel::*;
859 for a in &[Auto, Sig, Key, Cert, Message] {
860 let s = a.to_string();
861 let b: ArmorLabel = s.parse().unwrap();
862 assert_eq!(a, &b);
863 }
864 Ok(())
865 }
866}