diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2019-08-19 15:36:47 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2019-08-19 15:36:47 +0200 |
commit | 63637840a92f237b21369f2b8cb2c202d33613fc (patch) | |
tree | 0ee483ea20eae9bc63297ac8a2c75f4d56ccea4e /net/src/lib.rs | |
parent | bcd5ab8751c14f4ac48a2ea8b7d90163eb306b5f (diff) |
net: Drop the sync variant.
- The sync wrapper hide the async nature of the implementation, and
while this may seem convenient, it may cause subtle problems if it
is invoked from a different event loop.
- Furthermore, 'async' is a reserved keyword in the 2018 edition,
requiring awkward escaping.
- Fixes #307.
Diffstat (limited to 'net/src/lib.rs')
-rw-r--r-- | net/src/lib.rs | 188 |
1 files changed, 163 insertions, 25 deletions
diff --git a/net/src/lib.rs b/net/src/lib.rs index d1fb5df9..ad023d67 100644 --- a/net/src/lib.rs +++ b/net/src/lib.rs @@ -14,18 +14,21 @@ //! [SKS keyserver]: https://www.sks-keyservers.net/overview-of-pools.php#pool_hkps //! //! ```no_run +//! # extern crate tokio_core; //! # extern crate sequoia_openpgp as openpgp; //! # extern crate sequoia_core; //! # extern crate sequoia_net; //! # use openpgp::KeyID; //! # use sequoia_core::Context; //! # use sequoia_net::{KeyServer, Result}; +//! # use tokio_core::reactor::Core; //! # fn main() { f().unwrap(); } //! # fn f() -> Result<()> { +//! let mut core = Core::new().unwrap(); //! let ctx = Context::new()?; //! let mut ks = KeyServer::sks_pool(&ctx)?; //! let keyid = KeyID::from_hex("31855247603831FD").unwrap(); -//! println!("{:?}", ks.get(&keyid)); +//! println!("{:?}", core.run(ks.get(&keyid))); //! Ok(()) //! # } //! ``` @@ -51,43 +54,77 @@ extern crate percent_encoding; extern crate url; extern crate zbase32; +use futures::{future, Future, Stream}; use hyper::client::{ResponseFuture, HttpConnector}; -use hyper::{Client, Request, Body}; +use hyper::header::{CONTENT_LENGTH, CONTENT_TYPE, HeaderValue}; +use hyper::{Client, Body, StatusCode, Request}; use hyper_tls::HttpsConnector; -use native_tls::Certificate; +use native_tls::{Certificate, TlsConnector}; +use percent_encoding::{percent_encode, DEFAULT_ENCODE_SET}; use std::convert::From; -use tokio_core::reactor::Core; +use std::io::Cursor; use url::Url; -use crate::openpgp::KeyID; use crate::openpgp::TPK; -use sequoia_core::Context; +use crate::openpgp::parse::Parse; +use crate::openpgp::{KeyID, armor, serialize::Serialize}; +use sequoia_core::{Context, NetworkPolicy}; -pub mod r#async; -use crate::r#async::url2uri; pub mod wkd; +define_encode_set! { + /// Encoding used for submitting keys. + /// + /// The SKS keyserver as of version 1.1.6 is a bit picky with + /// respect to the encoding. + pub KEYSERVER_ENCODE_SET = [DEFAULT_ENCODE_SET] | {'-', '+', '/' } +} + /// For accessing keyservers using HKP. pub struct KeyServer { - core: Core, - ks: r#async::KeyServer, + client: Box<AClient>, + uri: Url, } +const DNS_WORKER: usize = 4; + impl KeyServer { /// Returns a handle for the given URI. pub fn new(ctx: &Context, uri: &str) -> Result<Self> { - let core = Core::new()?; - let ks = r#async::KeyServer::new(ctx, uri)?; - Ok(KeyServer{core: core, ks: ks}) + let uri: Url = uri.parse() + .or_else(|_| format!("hkps://{}", uri).parse())?; + + let client: Box<AClient> = match uri.scheme() { + "hkp" => Box::new(Client::new()), + "hkps" => { + Box::new(Client::builder() + .build(HttpsConnector::new(DNS_WORKER)?)) + }, + _ => return Err(Error::MalformedUri.into()), + }; + + Self::make(ctx, client, uri) } /// Returns a handle for the given URI. /// /// `cert` is used to authenticate the server. - pub fn with_cert(ctx: &Context, uri: &str, cert: Certificate) -> Result<Self> { - let core = Core::new()?; - let ks = r#async::KeyServer::with_cert(ctx, uri, cert)?; - Ok(KeyServer{core: core, ks: ks}) + pub fn with_cert(ctx: &Context, uri: &str, cert: Certificate) + -> Result<Self> { + let uri: Url = uri.parse()?; + + let client: Box<AClient> = { + let mut tls = TlsConnector::builder(); + tls.add_root_certificate(cert); + let tls = tls.build()?; + + let mut http = HttpConnector::new(DNS_WORKER); + http.enforce_http(false); + Box::new(Client::builder() + .build(HttpsConnector::from((http, tls)))) + }; + + Self::make(ctx, client, uri) } /// Returns a handle for the SKS keyserver pool. @@ -102,18 +139,115 @@ impl KeyServer { Self::with_cert(ctx, uri, cert) } + /// Common code for the above functions. + fn make(ctx: &Context, client: Box<AClient>, uri: Url) -> Result<Self> { + let s = uri.scheme(); + match s { + "hkp" => ctx.network_policy().assert(NetworkPolicy::Insecure), + "hkps" => ctx.network_policy().assert(NetworkPolicy::Encrypted), + _ => return Err(Error::MalformedUri.into()) + }?; + let uri = + format!("{}://{}:{}", + match s {"hkp" => "http", "hkps" => "https", + _ => unreachable!()}, + uri.host().ok_or(Error::MalformedUri)?, + match s { + "hkp" => uri.port().or(Some(11371)), + "hkps" => uri.port().or(Some(443)), + _ => unreachable!(), + }.unwrap()).parse()?; + + Ok(KeyServer{client: client, uri: uri}) + } + /// Retrieves the key with the given `keyid`. - pub fn get(&mut self, keyid: &KeyID) -> Result<TPK> { - self.core.run( - self.ks.get(keyid) - ) + pub fn get(&mut self, keyid: &KeyID) + -> Box<Future<Item=TPK, Error=failure::Error> + 'static> { + let uri = self.uri.join( + &format!("pks/lookup?op=get&options=mr&search=0x{}", + keyid.to_hex())); + if let Err(e) = uri { + // This shouldn't happen, but better safe than sorry. + return Box::new(future::err(Error::from(e).into())); + } + + Box::new(self.client.do_get(uri.unwrap()) + .from_err() + .and_then(|res| { + let status = res.status(); + res.into_body().concat2().from_err() + .and_then(move |body| match status { + StatusCode::OK => { + let c = Cursor::new(body.as_ref()); + let r = armor::Reader::new( + c, + armor::ReaderMode::Tolerant( + Some(armor::Kind::PublicKey))); + future::done(TPK::from_reader(r)) + }, + StatusCode::NOT_FOUND => + future::err(Error::NotFound.into()), + n => future::err(Error::HttpStatus(n).into()), + }) + })) } /// Sends the given key to the server. - pub fn send(&mut self, key: &TPK) -> Result<()> { - self.core.run( - self.ks.send(key) - ) + pub fn send(&mut self, key: &TPK) + -> Box<Future<Item=(), Error=failure::Error> + 'static> { + use crate::openpgp::armor::{Writer, Kind}; + + let uri = + match self.uri.join("pks/add") { + Err(e) => + // This shouldn't happen, but better safe than sorry. + return Box::new(future::err(Error::from(e).into())), + Ok(u) => u, + }; + + let mut armored_blob = vec![]; + { + let mut w = match Writer::new(&mut armored_blob, + Kind::PublicKey, &[]) { + Err(e) => return Box::new(future::err(e.into())), + Ok(w) => w, + }; + + if let Err(e) = key.serialize(&mut w) { + return Box::new(future::err(e)); + } + } + + // Prepare to send url-encoded data. + let mut post_data = b"keytext=".to_vec(); + post_data.extend_from_slice(percent_encode(&armored_blob, KEYSERVER_ENCODE_SET) + .collect::<String>().as_bytes()); + let length = post_data.len(); + + let mut request = match Request::post(url2uri(uri)) + .body(Body::from(post_data)) + { + Ok(r) => r, + Err(e) => return Box::new(future::err(Error::from(e).into())), + }; + request.headers_mut().insert( + CONTENT_TYPE, + HeaderValue::from_static("application/x-www-form-urlencoded")); + request.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::from_str(&format!("{}", length)) + .expect("cannot fail: only ASCII characters")); + + Box::new(self.client.do_request(request) + .from_err() + .and_then(|res| { + match res.status() { + StatusCode::OK => future::ok(()), + StatusCode::NOT_FOUND => future::err(Error::ProtocolViolation.into()), + n => future::err(Error::HttpStatus(n).into()), + } + })) } } @@ -140,6 +274,10 @@ impl AClient for Client<HttpsConnector<HttpConnector>> { } } +pub(crate) fn url2uri(uri: Url) -> hyper::Uri { + format!("{}", uri).parse().unwrap() +} + /// Results for sequoia-net. pub type Result<T> = ::std::result::Result<T, failure::Error>; |