diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | ffi/src/error.rs | 5 | ||||
-rw-r--r-- | openpgp/build.rs | 6 | ||||
-rw-r--r-- | openpgp/src/lib.rs | 9 | ||||
-rw-r--r-- | openpgp/src/tpk/grammar.lalrpop | 257 | ||||
-rw-r--r-- | openpgp/src/tpk/lexer.rs | 147 | ||||
-rw-r--r-- | openpgp/src/tpk/mod.rs (renamed from openpgp/src/tpk.rs) | 1071 |
7 files changed, 1048 insertions, 448 deletions
@@ -4,3 +4,4 @@ Cargo.lock .gdb_history /openpgp/src/message/grammar.rs +/openpgp/src/tpk/grammar.rs diff --git a/ffi/src/error.rs b/ffi/src/error.rs index 6e40de34..647a7a6e 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -118,6 +118,9 @@ pub enum Status { /// Index out of range. IndexOutOfRange = -23, + + /// TPK not supported. + UnsupportedTPK = -24, } impl<'a> From<&'a failure::Error> for Status { @@ -171,6 +174,8 @@ impl<'a> From<&'a failure::Error> for Status { Status::MalformedTPK, &openpgp::Error::IndexOutOfRange => Status::IndexOutOfRange, + &openpgp::Error::UnsupportedTPK(_) => + Status::UnsupportedTPK, } } diff --git a/openpgp/build.rs b/openpgp/build.rs index b18f693b..76042853 100644 --- a/openpgp/build.rs +++ b/openpgp/build.rs @@ -2,8 +2,10 @@ extern crate lalrpop; // Rerun if any of these files change: #[allow(dead_code)] -const SOURCE: &'static str - = include_str!("src/message/grammar.lalrpop"); +const SOURCE: [ &'static str; 2 ] + = [ include_str!("src/message/grammar.lalrpop"), + include_str!("src/tpk/grammar.lalrpop"), + ]; fn main() { lalrpop::process_root().unwrap(); diff --git a/openpgp/src/lib.rs b/openpgp/src/lib.rs index b85e1c30..04ae2e67 100644 --- a/openpgp/src/lib.rs +++ b/openpgp/src/lib.rs @@ -212,6 +212,14 @@ pub enum Error { #[fail(display = "Malformed TPK: {}", _0)] MalformedTPK(String), + /// Unsupported TPK. + /// + /// This usually occurs, because the primary key is in an + /// unsupported format. In particular, Sequoia does not support + /// version 3 keys. + #[fail(display = "Unsupported TPK: {}", _0)] + UnsupportedTPK(String), + /// Index out of range. #[fail(display = "Index out of range")] IndexOutOfRange, @@ -643,6 +651,7 @@ pub struct TPK { userids: Vec<tpk::UserIDBinding>, user_attributes: Vec<tpk::UserAttributeBinding>, subkeys: Vec<tpk::SubkeyBinding>, + unknowns: Vec<tpk::UnknownBinding>, } /// An OpenPGP message. diff --git a/openpgp/src/tpk/grammar.lalrpop b/openpgp/src/tpk/grammar.lalrpop new file mode 100644 index 00000000..a89b3d9b --- /dev/null +++ b/openpgp/src/tpk/grammar.lalrpop @@ -0,0 +1,257 @@ +// -*- mode: Rust; -*- + +use Error; +use Signature; +use UserID; +use UserAttribute; +use Key; +use Unknown; +use Packet; +use TPK; + +use tpk::lexer; +use tpk::lexer::{Token, Component}; +use tpk::{SubkeyBinding, UserIDBinding, UserAttributeBinding, UnknownBinding}; + +use lalrpop_util::ParseError; + +grammar; + +// The parser is used in two ways: it can either be used to check +// whether a sequence of packets forms a TPK, or to build a TPK from a +// sequence of packets. In the former case, we only need the packet +// tags; in the latter case, we also need the packets. To handle both +// situations, the token includes the tag and an optional packet. +// When invoking the parser, it is essential, that either *all* tokens +// have no packet, or they all have a packet; mixing the two types of +// tokens will result in a crash. + +pub TPK: Option<TPK> = { + <p:Primary> <c:OptionalComponents> =>? { + match p { + Some((Packet::PublicKey(key), _sigs)) + | Some((Packet::SecretKey(key), _sigs)) => { + let c = c.unwrap(); + + let mut tpk = TPK { + primary: key, + subkeys: vec![], + userids: vec![], + user_attributes: vec![], + unknowns: vec![], + }; + + for c in c.into_iter() { + match c { + Component::SubkeyBinding(b) => + tpk.subkeys.push(b), + Component::UserIDBinding(b) => + tpk.userids.push(b), + Component::UserAttributeBinding(b) => + tpk.user_attributes.push(b), + Component::UnknownBinding(b) => + tpk.unknowns.push(b), + } + } + + Ok(Some(tpk)) + } + Some((Packet::Unknown(_unknown), _sigs)) => { + Err(ParseError::User { + error: Error::UnsupportedTPK( + "Unsupported primary key.".into()) + }) + } + None => { + // Just validating a message... + assert!(c.is_none() || c.unwrap().len() == 0); + Ok(None) + } + _ => unreachable!(), + } + } +}; + +Primary: Option<(Packet, Vec<Signature>)> = { + <pk:PrimaryKey> <sigs:OptionalSignatures> => { + if let Some(pk) = pk { + Some((pk, sigs.unwrap())) + } else { + // Just validating a message... + assert!(sigs.is_none() || sigs.unwrap().len() == 0); + None + } + } +} + +PrimaryKey: Option<Packet> = { + <t:PUBLIC_KEY> => t.into(), + <t:SECRET_KEY> => t.into(), +}; + +OptionalSignatures: Option<Vec<Signature>> = { + => Some(vec![]), + <sigs:OptionalSignatures> <sig:SIGNATURE> => { + match sig { + Token::Signature(Some(Packet::Signature(sig))) => { + assert!(sigs.is_some()); + let mut sigs = sigs.unwrap(); + + sigs.push(sig); + Some(sigs) + } + Token::Signature(Some(Packet::Unknown(_sig))) => { + // Ignore unsupported / bad signatures. + assert!(sigs.is_some()); + sigs + } + // Just validating a message... + Token::Signature(None) => return None, + _ => unreachable!(), + } + } +} + +OptionalComponents: Option<Vec<Component>> = { + => Some(vec![]), + <cs:OptionalComponents> <c:Component> => { + if let Some(c) = c { + let mut cs = cs.unwrap(); + cs.push(c); + Some(cs) + } else { + // Just validating a message... + None + } + }, +} + +Component: Option<Component> = { + <key:Subkey> <sigs:OptionalSignatures> => { + match key { + Some(key) => { + let sigs = sigs.unwrap(); + + Some(Component::SubkeyBinding(SubkeyBinding { + subkey: key, + selfsigs: vec![], + certifications: sigs, + })) + }, + // Just validating a message... + None => None, + } + }, + <u:UserID> <sigs:OptionalSignatures> => { + match u { + Some(u) => { + let sigs = sigs.unwrap(); + + Some(Component::UserIDBinding(UserIDBinding { + userid: u, + selfsigs: vec![], + certifications: sigs, + })) + }, + // Just validating a message... + None => None, + } + }, + <u:UserAttribute> <sigs:OptionalSignatures> => { + match u { + Some(u) => { + let sigs = sigs.unwrap(); + + Some(Component::UserAttributeBinding(UserAttributeBinding { + user_attribute: u, + selfsigs: vec![], + certifications: sigs, + })) + }, + // Just validating a message... + None => None, + } + }, + <u:Unknown> <sigs:OptionalSignatures> => { + match u { + Some(u) => { + let sigs = sigs.unwrap(); + + Some(Component::UnknownBinding(UnknownBinding { + unknown: u, + sigs: sigs, + })) + }, + // Just validating a message... + None => None, + } + }, +} + +Subkey: Option<Key> = { + <t:PUBLIC_SUBKEY> => { + match t.into() { + Some(Packet::PublicSubkey(key)) => Some(key), + // Just validating a message... + None => None, + _ => unreachable!(), + } + }, + <t:SECRET_SUBKEY> => { + match t.into() { + Some(Packet::SecretSubkey(key)) => Some(key), + // Just validating a message... + None => None, + _ => unreachable!(), + } + }, +} + +UserID: Option<UserID> = { + <t:USERID> => { + match t.into() { + Some(Packet::UserID(u)) => Some(u), + // Just validating a message... + None => None, + _ => unreachable!(), + } + }, +} + +UserAttribute: Option<UserAttribute> = { + <t:USER_ATTRIBUTE> => { + match t.into() { + Some(Packet::UserAttribute(u)) => Some(u), + // Just validating a message... + None => None, + _ => unreachable!(), + } + }, +} + +Unknown: Option<Unknown> = { + <t:UNKNOWN> => { + match t.into() { + Some(Packet::Unknown(u)) => Some(u), + // Just validating a message... + None => None, + _ => unreachable!(), + } + }, +} + +extern { + type Location = usize; + type Error = Error; + + enum lexer::Token { + PUBLIC_KEY => lexer::Token::PublicKey(_), + SECRET_KEY => lexer::Token::SecretKey(_), + PUBLIC_SUBKEY => lexer::Token::PublicSubkey(_), + SECRET_SUBKEY => lexer::Token::SecretSubkey(_), + USERID => lexer::Token::UserID(_), + USER_ATTRIBUTE => lexer::Token::UserAttribute(_), + SIGNATURE => lexer::Token::Signature(_), + UNKNOWN => lexer::Token::Unknown(_, _), + } +} diff --git a/openpgp/src/tpk/lexer.rs b/openpgp/src/tpk/lexer.rs new file mode 100644 index 00000000..aed42021 --- /dev/null +++ b/openpgp/src/tpk/lexer.rs @@ -0,0 +1,147 @@ +use std::fmt; + +use Error; +use Packet; +use packet::Tag; +use tpk::SubkeyBinding; +use tpk::UserIDBinding; +use tpk::UserAttributeBinding; +use tpk::UnknownBinding; + +// The type of the parser's input. +// +// The parser iterators over tuples consisting of the token's starting +// position, the token itself, and the token's ending position. +pub(crate) type LexerItem<Tok, Loc, Error> + = ::std::result::Result<(Loc, Tok, Loc), Error>; + +/// The components of an OpenPGP Message. +#[derive(Debug, Clone, PartialEq)] +pub enum Token { + /// A `PublicKey` packet. + PublicKey(Option<Packet>), + /// A `SecretKey` packet. + SecretKey(Option<Packet>), + + /// A `PublicSubkey` packet. + PublicSubkey(Option<Packet>), + /// A `SecretSubkey` packet. + SecretSubkey(Option<Packet>), + + /// A `UserID` packet. + UserID(Option<Packet>), + /// A `UserAttribute` packet. + UserAttribute(Option<Packet>), + + /// A `Signature` packet. + Signature(Option<Packet>), + + /// An `Unknown` packet. + Unknown(Tag, Option<Packet>), +} + +/// Internal data-structure used by the parser. +/// +/// Due to the way the parser code is generated, it must be marked as +/// public. But, since this module is not public, it will not +/// actually be exported to used of the library. +pub enum Component { + SubkeyBinding(SubkeyBinding), + UserIDBinding(UserIDBinding), + UserAttributeBinding(UserAttributeBinding), + UnknownBinding(UnknownBinding), +} + +impl<'a> From<&'a Token> for Tag { + fn from(token: &'a Token) -> Self { + match token { + &Token::PublicKey(_) => Tag::PublicKey, + &Token::SecretKey(_) => Tag::SecretKey, + &Token::PublicSubkey(_) => Tag::PublicSubkey, + &Token::SecretSubkey(_) => Tag::SecretSubkey, + &Token::UserID(_) => Tag::UserID, + &Token::UserAttribute(_) => Tag::UserAttribute, + &Token::Signature(_) => Tag::Signature, + &Token::Unknown(tag, _) => tag, + } + } +} + +impl From<Token> for Tag { + fn from(token: Token) -> Self { + (&token).into() + } +} + +impl From<Token> for Option<Packet> { + fn from(token: Token) -> Self { + match token { + Token::PublicKey(p @ Some(_)) => p, + Token::SecretKey(p @ Some(_)) => p, + Token::PublicSubkey(p @ Some(_)) => p, + Token::SecretSubkey(p @ Some(_)) => p, + Token::UserID(p @ Some(_)) => p, + Token::UserAttribute(p @ Some(_)) => p, + Token::Signature(p @ Some(_)) => p, + Token::Unknown(_, p @ Some(_)) => p, + + Token::PublicKey(None) + | Token::SecretKey(None) + | Token::PublicSubkey(None) + | Token::SecretSubkey(None) + | Token::UserID(None) + | Token::UserAttribute(None) + | Token::Signature(None) + | Token::Unknown(_, None) + => None, + } + } +} + +impl From<Packet> for Option<Token> { + fn from(p: Packet) -> Self { + match p.tag() { + Tag::PublicKey => Some(Token::PublicKey(Some(p))), + Tag::SecretKey => Some(Token::SecretKey(Some(p))), + Tag::PublicSubkey => Some(Token::PublicSubkey(Some(p))), + Tag::SecretSubkey => Some(Token::SecretSubkey(Some(p))), + Tag::UserID => Some(Token::UserID(Some(p))), + Tag::UserAttribute => Some(Token::UserAttribute(Some(p))), + Tag::Signature => Some(Token::Signature(Some(p))), + t @ Tag::Unknown(_) => Some(Token::Unknown(t, Some(p))), + _ => None, + } + } +} + +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&format!("{:?}", self)[..]) + } +} + +pub(crate) struct Lexer<'input> { + iter: Box<Iterator<Item=(usize, &'input Token)> + 'input>, +} + +impl<'input> Iterator for Lexer<'input> { + type Item = LexerItem<Token, usize, Error>; + + fn next(&mut self) -> Option<Self::Item> { + let n = self.iter.next().map(|(pos, tok)| (pos, tok.clone())); + if let Some((pos, tok)) = n { + Some(Ok((pos, tok, pos))) + } else { + None + } + } +} + +impl<'input> Lexer<'input> { + /// Uses a raw sequence of tokens as input to the parser. + pub(crate) fn from_tokens(raw: &'input [Token]) -> Self { + Lexer { + iter: Box::new(raw.iter().enumerate()) + } + } +} diff --git a/openpgp/src/tpk.rs b/openpgp/src/tpk/mod.rs index 7eee917e..f5d6745f 100644 --- a/openpgp/src/tpk.rs +++ b/openpgp/src/tpk/mod.rs @@ -9,6 +9,7 @@ use std::mem; use std::fmt; use std::vec; use time; +use failure; use { Error, @@ -18,6 +19,7 @@ use { Key, UserID, UserAttribute, + Unknown, Packet, PacketPile, TPK, @@ -28,6 +30,269 @@ use parse::{PacketParserResult, PacketParser}; use serialize::{Serialize, SerializeKey}; use constants::PublicKeyAlgorithm; +mod lexer; +mod grammar; + +use self::lexer::Lexer; +pub use self::lexer::Token; + +use lalrpop_util::ParseError; + +use self::grammar::TPKParser as TPKLowLevelParser; + +// Converts a ParseError<usize, Token, Error> to a +// ParseError<usize, Tag, Error>. +// +// Justification: a Token is a tuple containing a Tag and a Packet. +// This function essentially drops the Packet. Dropping the packet is +// necessary, because packets are not async, but Fail, which we want +// to convert ParseErrors to, is. Since we don't need the packet in +// general anyways, changing the Token to a Tag is a simple and +// sufficient fix. Unfortunately, this conversion is a bit ugly and +// will break if lalrpop ever extends ParseError. +fn parse_error_downcast(e: ParseError<usize, Token, Error>) + -> ParseError<usize, Tag, Error> +{ + match e { + ParseError::UnrecognizedToken { + token: Some((start, t, end)), + expected, + } => ParseError::UnrecognizedToken { + token: Some((start, t.into(), end)), + expected, + }, + ParseError::UnrecognizedToken { + token: None, + expected, + } => ParseError::UnrecognizedToken { + token: None, + expected, + }, + + ParseError::ExtraToken { + token: (start, t, end), + } => ParseError::ExtraToken { + token: (start, t.into(), end), + }, + + ParseError::InvalidToken { location } + => ParseError::InvalidToken { location }, + + ParseError::User { error } + => ParseError::User { error }, + } +} + +fn parse_error_to_openpgp_error(e: ParseError<usize, Tag, Error>) -> Error +{ + match e { + ParseError::User { error } => error, + e => Error::MalformedTPK(format!("{}", e)), + } +} + +/// Errors that TPKValidator::check may return. +#[derive(Debug, Clone)] +pub enum TPKParserError { + /// A parser error. + Parser(ParseError<usize, Tag, Error>), + /// An OpenPGP error. + OpenPGP(Error), +} + +impl From<TPKParserError> for failure::Error { + fn from(err: TPKParserError) -> Self { + match err { + TPKParserError::Parser(p) => p.into(), + TPKParserError::OpenPGP(p) => p.into(), + } + } +} + +/// Whether a packet sequence is a valid TPK. +#[derive(Debug)] +pub enum TPKValidity { + /// The packet sequence is a valid TPK. + TPK, + /// The packet sequence is a valid TPK prefix. + TPKPrefix, + /// The packet sequence is definitely not a TPK. + Error(failure::Error), +} + +impl TPKValidity { + /// Returns whether the packet sequence is a valid TPK. + /// + /// Note: a `TPKValidator` will only return this after + /// `TPKValidator::finish` has been called. + pub fn is_tpk(&self) -> bool { + if let TPKValidity::TPK = self { + true + } else { + false + } + } + + /// Returns whether the packet sequence is a valid TPK prefix. + /// + /// Note: a `TPKValidator` will only return this before + /// `TPKValidator::finish` has been called. + pub fn is_tpk_prefix(&self) -> bool { + if let TPKValidity::TPKPrefix = self { + true + } else { + false + } + } + + /// Returns whether the packet sequence is definitely not a valid + /// TPK. + pub fn is_err(&self) -> bool { + if let TPKValidity::Error(_) = self { + true + } else { + false + } + } +} + +/// Used to help validate that a packet sequence is a valid TPK. +#[derive(Debug)] +pub struct TPKValidator { + tokens: Vec<Token>, + finished: bool, + + // If we know that the packet sequence is invalid. + error: Option<TPKParserError>, +} + +impl Default for TPKValidator { + fn default() -> Self { + TPKValidator::new() + } +} + +impl TPKValidator { + /// Instantiates a new `TPKValidator`. + pub fn new() -> Self { + TPKValidator { + tokens: vec![], + finished: false, + error: None, + } + } + + /// Returns whether the packet sequence is a valid TPK. + /// + /// Note: a `TPKValidator` will only return this after + /// `TPKValidator::finish` has been called. + pub fn is_tpk(&self) -> bool { + self.check().is_tpk() + } + + /// Returns whether the packet sequence forms a valid TPK + /// prefix. + /// + /// Note: a `TPKValidator` will only return this before + /// `TPKValidator::finish` has been called. + pub fn is_tpk_prefix(&self) -> bool { + self.check().is_tpk_prefix() + } + + /// Returns whether the packet sequence is definitely not a valid + /// TPK. + pub fn is_err(&self) -> bool { + self.check().is_err() + } + + /// Add the token `token` to the token stream. + pub fn push_token(&mut self, token: Token) { + assert!(!self.finished); + + if self.error.is_some() { + return; + } + + self.tokens.push(token); + } + + /// Add a packet of type `tag` to the token stream. + pub fn push(&mut self, tag: Tag) { + let token = match tag { + Tag::PublicKey => Token::PublicKey(None), + Tag::SecretKey => Token::SecretKey(None), + Tag::PublicSubkey => Token::PublicSubkey(None), + Tag::SecretSubkey => Token::SecretSubkey(None), + Tag::UserID => Token::UserID(None), + Tag::UserAttribute => Token::UserAttribute(None), + Tag::Signature => Token::Signature(None), + _ => { + // Unknown token. + self.error = Some(TPKParserError::OpenPGP( + Error::MalformedMessage( + format!("Invalid OpenPGP message: unexpected packet: {:?}", + tag).into()))); + self.tokens.clear(); + return; + } + }; + + self.push_token(token) + } + + /// Note that the entire message has been seen. + /// + /// This function may only be called once. + /// + /// Once called, this function will no longer return + /// `TPKValidity::TPKPrefix`. + pub fn finish(&mut self) { + assert!(!self.finished); + self.finished = true; + } + + /// Returns whether the token stream corresponds to a valid + /// TPK. + /// + /// This returns a tri-state: if the packet sequence is a valid + /// TPK, it returns TPKValidity::TPK, if the packet sequence is + /// invalid, then it returns TPKValidity::Error. If the packet + /// sequence could be valid, then it returns + /// TPKValidity::TPKPrefix. + /// + /// Note: if TPKValidator::finish() *hasn't* been called, then + /// this function will only ever return either + /// TPKValidity::TPKPrefix or TPKValidity::Error. Once + /// TPKValidity::finish() has been called, then only + /// TPKValidity::TPK or TPKValidity::Bad will be called. + pub fn check(&self) -> TPKValidity { + if let Some(ref err) = self.error { + return TPKValidity::Error((*err).clone().into()); + } + + let r = TPKLowLevelParser::new().parse( + Lexer::from_tokens(&self.tokens[..])); + + if self.finished { + match r { + Ok(_) => TPKValidity::TPK, + Err(err) => + TPKValidity::Error( + TPKParserError::Parser(parse_error_downcast(err)).into()), + } + } else { + match r { + Ok(_) => TPKValidity::TPKPrefix, + Err(ParseError::UnrecognizedToken { token: None, .. }) => + TPKValidity::TPKPrefix, + Err(err) => + TPKValidity::Error( + TPKParserError::Parser(parse_error_downcast(err)).into()), + } + } + } +} + const TRACE : bool = false; /// A subkey and any associated signatures. @@ -214,6 +479,14 @@ impl UserAttributeBinding { } } +/// A User Attribute and any associated signatures. +#[derive(Debug, Clone, PartialEq)] +pub struct UnknownBinding { + unknown: Unknown, + + sigs: Vec<Signature>, +} + /// An iterator over all `Key`s (both the primary key and any subkeys) /// in a TPK. /// @@ -237,45 +510,6 @@ impl<'a> Iterator for KeyIter<'a> { } } -// We use a state machine to extract a TPK from a sequence of OpenPGP -// packets. These are the states. -enum TPKParserState { - Start, - TPK, - UserID(UserIDBinding), - UserAttribute(UserAttributeBinding), - Subkey(SubkeyBinding), - End, -} - -impl fmt::Debug for TPKParserState { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &TPKParserState::Start => f.debug_struct("Start").finish(), - &TPKParserState::TPK => f.debug_struct("TPK").finish(), - &TPKParserState::UserID(ref binding) => - f.debug_struct("UserID") - .field("userid", &binding.userid) - .field("self-sigs", &binding.selfsigs.len()) - .field("certifications", &binding.certifications.len()) - .finish(), - &TPKParserState::UserAttribute(ref binding) => - f.debug_struct("UserAttribute") - .field("userid", &binding.user_attribute) - .field("self-sigs", &binding.selfsigs.len()) - .field("certifications", &binding.certifications.len()) - .finish(), - &TPKParserState::Subkey(ref binding) => - f.debug_struct("Subkey") - .field("subkey", &binding.subkey) - .field("self-sigs", &binding.selfsigs.len()) - .field("certifications", &binding.certifications.len()) - .finish(), - &TPKParserState::End => f.debug_struct("End").finish(), - } - } -} - // A TPKParser can read packets from either an Iterator or a // PacketParser. Ideally, we would just take an iterator, but we // want to be able to handle errors, which iterators hide. @@ -323,11 +557,7 @@ enum PacketSource<'a, I: Iterator<Item=Packet>> { pub struct TPKParser<'a, I: Iterator<Item=Packet>> { source: PacketSource<'a, I>, - state: TPKParserState, - primary: Option<Key>, - userids: Vec<UserIDBinding>, - user_attributes: Vec<UserAttributeBinding>, - subkeys: Vec<SubkeyBinding>, + packets: Vec<Packet>, saw_error: bool, @@ -338,11 +56 |