summaryrefslogtreecommitdiffstats
path: root/net/src/lib.rs
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2019-08-19 15:36:47 +0200
committerJustus Winter <justus@sequoia-pgp.org>2019-08-19 15:36:47 +0200
commit63637840a92f237b21369f2b8cb2c202d33613fc (patch)
tree0ee483ea20eae9bc63297ac8a2c75f4d56ccea4e /net/src/lib.rs
parentbcd5ab8751c14f4ac48a2ea8b7d90163eb306b5f (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.rs188
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>;