1 //! *S-Expressions* for communicating cryptographic primitives.
2 //!
3 //! *S-Expressions* as described in the internet draft [S-Expressions],
4 //! are a way to communicate cryptographic primitives like keys,
5 //! signatures, and ciphertexts between agents or implementations.
6 //!
7 //! [S-Expressions]: https://people.csail.mit.edu/rivest/Sexp.txt
8 
9 use std::convert::TryFrom;
10 use std::fmt;
11 use std::ops::Deref;
12 
13 #[cfg(test)]
14 use quickcheck::{Arbitrary, Gen};
15 
16 use sequoia_openpgp as openpgp;
17 use openpgp::crypto::{mpi, SessionKey};
18 use openpgp::crypto::mem::Protected;
19 
20 use openpgp::Error;
21 use openpgp::Result;
22 
23 mod parse;
24 mod serialize;
25 
26 /// An *S-Expression*.
27 ///
28 /// An *S-Expression* is either a string, or a list of *S-Expressions*.
29 #[derive(Clone, PartialEq, Eq)]
30 pub enum Sexp {
31     /// Just a string.
32     String(String_),
33     /// A list of *S-Expressions*.
34     List(Vec<Sexp>),
35 }
36 
37 impl fmt::Debug for Sexp {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result38     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39         match self {
40             Sexp::String(ref s) => s.fmt(f),
41             Sexp::List(ref l) => l.fmt(f),
42         }
43     }
44 }
45 
46 impl Sexp {
47     /// Completes the decryption of this S-Expression representing a
48     /// wrapped session key.
49     ///
50     /// Such an expression is returned from gpg-agent's `PKDECRYPT`
51     /// command.  `padding` must be set according to the status
52     /// messages sent.
finish_decryption<R>(&self, recipient: &openpgp::packet::Key< openpgp::packet::key::PublicParts, R>, ciphertext: &mpi::Ciphertext, padding: bool) -> Result<SessionKey> where R: openpgp::packet::key::KeyRole53     pub fn finish_decryption<R>(&self,
54                                 recipient: &openpgp::packet::Key<
55                                         openpgp::packet::key::PublicParts, R>,
56                                 ciphertext: &mpi::Ciphertext,
57                                 padding: bool)
58         -> Result<SessionKey>
59         where R: openpgp::packet::key::KeyRole
60     {
61         use openpgp::crypto::mpi::PublicKey;
62         let not_a_session_key = || -> anyhow::Error {
63             Error::MalformedMPI(
64                 format!("Not a session key: {:?}", self)).into()
65         };
66 
67         let value = self.get(b"value")?.ok_or_else(not_a_session_key)?
68             .into_iter().nth(0).ok_or_else(not_a_session_key)?;
69 
70         match value {
71             Sexp::String(ref s) => match recipient.mpis() {
72                 PublicKey::RSA { .. } | PublicKey::ElGamal { .. } if padding =>
73                 {
74                     // The session key is padded.  The format is
75                     // described in g10/pubkey-enc.c (note that we,
76                     // like GnuPG 2.2, only support the new encoding):
77                     //
78                     //   * Later versions encode the DEK like this:
79                     //   *
80                     //   *     0  2  RND(n bytes)  [...]
81                     //   *
82                     //   * (mpi_get_buffer already removed the leading zero).
83                     //   *
84                     //   * RND are non-zero random bytes.
85                     let mut s = &s[..];
86 
87                     // The leading 0 may or may not be swallowed along
88                     // the way due to MPI encoding.
89                     if s[0] == 0 {
90                         s = &s[1..];
91                     }
92 
93                     // Version.
94                     if s[0] != 2 {
95                         return Err(Error::MalformedMPI(
96                             format!("DEK encoding version {} not understood",
97                                     s[0])).into());
98                     }
99 
100                     // Skip non-zero bytes.
101                     while s.len() > 0 && s[0] > 0 {
102                         s = &s[1..];
103                     }
104 
105                     if s.len() == 0 {
106                         return Err(Error::MalformedMPI(
107                             "Invalid DEK encoding, no zero found".into())
108                                    .into());
109                     }
110 
111                     // Skip zero.
112                     s = &s[1..];
113 
114                     Ok(s.to_vec().into())
115                 },
116 
117                 PublicKey::RSA { .. } | PublicKey::ElGamal { .. } => {
118                     // The session key is not padded.  Currently, this
119                     // happens if the session key is decrypted using
120                     // scdaemon.
121                     assert!(! padding); // XXX: Don't assert that.
122                     Ok(s.to_vec().into())
123                 },
124 
125                 PublicKey::ECDH { curve, .. } => {
126                     // The shared point has been computed by the
127                     // remote agent.  The shared point is not padded.
128                     let s_: mpi::ProtectedMPI = s.to_vec().into();
129                     #[allow(non_snake_case)]
130                     let S: Protected = s_.decode_point(curve)?.0.into();
131                     // XXX: Erase shared point from s.
132 
133                     // Now finish the decryption.
134                     openpgp::crypto::ecdh::decrypt_unwrap(recipient, &S, ciphertext)
135                 },
136 
137                 _ =>
138                     Err(Error::InvalidArgument(
139                         format!("Don't know how to handle key {:?}", recipient))
140                         .into()),
141             }
142             Sexp::List(..) => Err(not_a_session_key()),
143         }
144     }
145 
146     /// Parses this s-expression to a signature.
147     ///
148     /// Such an expression is returned from gpg-agent's `PKSIGN`
149     /// command.
to_signature(&self) -> Result<mpi::Signature>150     pub fn to_signature(&self) -> Result<mpi::Signature> {
151         let not_a_signature = || -> anyhow::Error {
152             Error::MalformedMPI(
153                 format!("Not a signature: {:?}", self)).into()
154         };
155 
156         let sig = self.get(b"sig-val")?.ok_or_else(not_a_signature)?
157             .into_iter().nth(0).ok_or_else(not_a_signature)?;
158 
159         if let Some(param) = sig.get(b"eddsa")? {
160             let r = param.iter().find_map(|p| {
161                 p.get(b"r").ok().unwrap_or_default()
162                     .and_then(|l| l.get(0).and_then(Sexp::string).cloned())
163             }).ok_or_else(not_a_signature)?;
164             let s = param.iter().find_map(|p| {
165                 p.get(b"s").ok().unwrap_or_default()
166                     .and_then(|l| l.get(0).and_then(Sexp::string).cloned())
167             }).ok_or_else(not_a_signature)?;
168             Ok(mpi::Signature::EdDSA {
169                 r: mpi::MPI::new(&r),
170                 s: mpi::MPI::new(&s),
171             })
172         } else if let Some(param) = sig.get(b"ecdsa")? {
173             let r = param.iter().find_map(|p| {
174                 p.get(b"r").ok().unwrap_or_default()
175                     .and_then(|l| l.get(0).and_then(Sexp::string).cloned())
176             }).ok_or_else(not_a_signature)?;
177             let s = param.iter().find_map(|p| {
178                 p.get(b"s").ok().unwrap_or_default()
179                     .and_then(|l| l.get(0).and_then(Sexp::string).cloned())
180             }).ok_or_else(not_a_signature)?;
181             Ok(mpi::Signature::ECDSA {
182                 r: mpi::MPI::new(&r),
183                 s: mpi::MPI::new(&s),
184             })
185         } else if let Some(param) = sig.get(b"rsa")? {
186             let s = param.iter().find_map(|p| {
187                 p.get(b"s").ok().unwrap_or_default()
188                     .and_then(|l| l.get(0).and_then(Sexp::string).cloned())
189             }).ok_or_else(not_a_signature)?;
190             Ok(mpi::Signature::RSA {
191                 s: mpi::MPI::new(&s),
192             })
193         } else if let Some(param) = sig.get(b"dsa")? {
194             let r = param.iter().find_map(|p| {
195                 p.get(b"r").ok().unwrap_or_default()
196                     .and_then(|l| l.get(0).and_then(Sexp::string).cloned())
197             }).ok_or_else(not_a_signature)?;
198             let s = param.iter().find_map(|p| {
199                 p.get(b"s").ok().unwrap_or_default()
200                     .and_then(|l| l.get(0).and_then(Sexp::string).cloned())
201             }).ok_or_else(not_a_signature)?;
202             Ok(mpi::Signature::DSA {
203                 r: mpi::MPI::new(&r),
204                 s: mpi::MPI::new(&s),
205             })
206         } else {
207             Err(Error::MalformedMPI(
208                 format!("Unknown signature sexp: {:?}", self)).into())
209         }
210     }
211 
212     /// Casts this to a string.
string(&self) -> Option<&String_>213     pub fn string(&self) -> Option<&String_> {
214         match self {
215             Sexp::String(ref s) => Some(s),
216             _ => None,
217         }
218     }
219 
220     /// Casts this to a list.
list(&self) -> Option<&[Sexp]>221     pub fn list(&self) -> Option<&[Sexp]> {
222         match self {
223             Sexp::List(ref s) => Some(s.as_slice()),
224             _ => None,
225         }
226     }
227 
228     /// Given an alist, selects by key and returns the value.
get(&self, key: &[u8]) -> Result<Option<Vec<Sexp>>>229     fn get(&self, key: &[u8]) -> Result<Option<Vec<Sexp>>> {
230         match self {
231             Sexp::List(ref ll) => match ll.get(0) {
232                 Some(Sexp::String(ref tag)) =>
233                     if tag.deref() == key {
234                         Ok(Some(ll[1..].iter().cloned().collect()))
235                     } else {
236                         Ok(None)
237                     }
238                 _ =>
239                     Err(Error::InvalidArgument(
240                         format!("Malformed alist: {:?}", ll)).into()),
241             },
242             _ =>
243                 Err(Error::InvalidArgument(
244                     format!("Malformed alist: {:?}", self)).into()),
245         }
246     }
247 }
248 
249 impl TryFrom<&mpi::Ciphertext> for Sexp {
250     type Error = anyhow::Error;
251 
252     /// Constructs an S-Expression representing `ciphertext`.
253     ///
254     /// The resulting expression is suitable for gpg-agent's `INQUIRE
255     /// CIPHERTEXT` inquiry.
try_from(ciphertext: &mpi::Ciphertext) -> Result<Self>256     fn try_from(ciphertext: &mpi::Ciphertext) -> Result<Self> {
257         use openpgp::crypto::mpi::Ciphertext::*;
258         match ciphertext {
259             RSA { ref c } =>
260                 Ok(Sexp::List(vec![
261                     Sexp::String("enc-val".into()),
262                     Sexp::List(vec![
263                         Sexp::String("rsa".into()),
264                         Sexp::List(vec![
265                             Sexp::String("a".into()),
266                             Sexp::String(c.value().into())])])])),
267 
268             &ElGamal { ref e, ref c } =>
269                 Ok(Sexp::List(vec![
270                     Sexp::String("enc-val".into()),
271                     Sexp::List(vec![
272                         Sexp::String("elg".into()),
273                         Sexp::List(vec![
274                             Sexp::String("a".into()),
275                             Sexp::String(e.value().into())]),
276                         Sexp::List(vec![
277                             Sexp::String("b".into()),
278                             Sexp::String(c.value().into())])])])),
279 
280             &ECDH { ref e, ref key } =>
281                 Ok(Sexp::List(vec![
282                     Sexp::String("enc-val".into()),
283                     Sexp::List(vec![
284                         Sexp::String("ecdh".into()),
285                         Sexp::List(vec![
286                             Sexp::String("s".into()),
287                             Sexp::String(key.as_ref().into())]),
288                         Sexp::List(vec![
289                             Sexp::String("e".into()),
290                             Sexp::String(e.value().into())])])])),
291 
292             &Unknown { .. } =>
293                 Err(Error::InvalidArgument(
294                     format!("Don't know how to convert {:?}", ciphertext))
295                     .into()),
296 
297             __Nonexhaustive => unreachable!(),
298         }
299     }
300 }
301 
302 #[cfg(any(test, feature = "quickcheck"))]
303 impl Arbitrary for Sexp {
arbitrary<G: Gen>(g: &mut G) -> Self304     fn arbitrary<G: Gen>(g: &mut G) -> Self {
305         if f32::arbitrary(g) < 0.7 {
306             Sexp::String(String_::arbitrary(g))
307         } else {
308             let mut v = Vec::new();
309             for _ in 0..usize::arbitrary(g) % 3 {
310                 v.push(Sexp::arbitrary(g));
311             }
312             Sexp::List(v)
313         }
314     }
315 }
316 
317 /// A string.
318 ///
319 /// A string can optionally have a display hint.
320 #[derive(Clone, PartialEq, Eq)]
321 pub struct String_(Box<[u8]>, Option<Box<[u8]>>);
322 
323 impl fmt::Debug for String_ {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result324     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
325         fn bstring(f: &mut fmt::Formatter, buf: &[u8]) -> fmt::Result {
326             write!(f, "b\"")?;
327             for &b in buf {
328                 match b {
329                     0..=31 | 128..=255 =>
330                         write!(f, "\\x{:02x}", b)?,
331                     0x22 => // "
332                         write!(f, "\\\"")?,
333                     0x5c => // \
334                         write!(f, "\\\\")?,
335                     _ =>
336                         write!(f, "{}", b as char)?,
337                 }
338             }
339             write!(f, "\"")
340         }
341 
342         if let Some(hint) = self.display_hint() {
343             write!(f, "[")?;
344             bstring(f, hint)?;
345             write!(f, "]")?;
346         }
347         bstring(f, &self.0)
348     }
349 }
350 
351 impl String_ {
352     /// Constructs a new *Simple String*.
new<S>(s: S) -> Self where S: Into<Box<[u8]>>353     pub fn new<S>(s: S) -> Self
354         where S: Into<Box<[u8]>>
355     {
356         Self(s.into(), None)
357     }
358 
359     /// Constructs a new *String*.
with_display_hint<S, T>(s: S, display_hint: T) -> Self where S: Into<Box<[u8]>>, T: Into<Box<[u8]>>360     pub fn with_display_hint<S, T>(s: S, display_hint: T) -> Self
361         where S: Into<Box<[u8]>>, T: Into<Box<[u8]>>
362     {
363         Self(s.into(), Some(display_hint.into()))
364     }
365 
366     /// Gets a reference to this *String*'s display hint, if any.
display_hint(&self) -> Option<&[u8]>367     pub fn display_hint(&self) -> Option<&[u8]> {
368         self.1.as_ref().map(|b| b.as_ref())
369     }
370 }
371 
372 impl From<&str> for String_ {
from(b: &str) -> Self373     fn from(b: &str) -> Self {
374         Self::new(b.as_bytes().to_vec())
375     }
376 }
377 
378 impl From<&[u8]> for String_ {
from(b: &[u8]) -> Self379     fn from(b: &[u8]) -> Self {
380         Self::new(b.to_vec())
381     }
382 }
383 
384 impl Deref for String_ {
385     type Target = [u8];
386 
deref(&self) -> &Self::Target387     fn deref(&self) -> &Self::Target {
388         &self.0
389     }
390 }
391 
392 #[cfg(any(test, feature = "quickcheck"))]
393 impl Arbitrary for String_ {
arbitrary<G: Gen>(g: &mut G) -> Self394     fn arbitrary<G: Gen>(g: &mut G) -> Self {
395         if bool::arbitrary(g) {
396             Self::new(Vec::arbitrary(g).into_boxed_slice())
397         } else {
398             Self::with_display_hint(Vec::arbitrary(g).into_boxed_slice(),
399                                     Vec::arbitrary(g).into_boxed_slice())
400         }
401     }
402 }
403 
404 #[cfg(test)]
405 mod tests {
406     use super::*;
407     use openpgp::parse::Parse;
408     use openpgp::serialize::Serialize;
409 
410     quickcheck::quickcheck! {
411         fn roundtrip(s: Sexp) -> bool {
412             let mut buf = Vec::new();
413             s.serialize(&mut buf).unwrap();
414             let t = Sexp::from_bytes(&buf).unwrap();
415             assert_eq!(s, t);
416             true
417         }
418     }
419 
420     #[test]
to_signature()421     fn to_signature() {
422         use openpgp::crypto::mpi::Signature::*;
423         assert!(destructures_to!(DSA { .. } = Sexp::from_bytes(
424             crate::tests::file("sexp/dsa-signature.sexp")).unwrap()
425                       .to_signature().unwrap()));
426         assert!(destructures_to!(ECDSA { .. } = Sexp::from_bytes(
427             crate::tests::file("sexp/ecdsa-signature.sexp")).unwrap()
428                       .to_signature().unwrap()));
429         assert!(destructures_to!(EdDSA { .. } = Sexp::from_bytes(
430             crate::tests::file("sexp/eddsa-signature.sexp")).unwrap()
431                       .to_signature().unwrap()));
432         assert!(destructures_to!(RSA { .. } = Sexp::from_bytes(
433             crate::tests::file("sexp/rsa-signature.sexp")).unwrap()
434                       .to_signature().unwrap()));
435     }
436 }
437