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