diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2018-02-16 14:45:15 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2018-02-16 14:45:15 +0100 |
commit | a90ea0b5a90a996b8b7086fc1f06a3ac2990b98f (patch) | |
tree | f747db3be758a5b6b592815e8413a393c0a0dc7e /net | |
parent | ddb4f6ff456f2152afecd190c44e1a4ff6cacb2f (diff) |
net: Add an asynchronous interface.
Diffstat (limited to 'net')
-rw-r--r-- | net/src/async.rs | 204 | ||||
-rw-r--r-- | net/src/lib.rs | 165 |
2 files changed, 235 insertions, 134 deletions
diff --git a/net/src/async.rs b/net/src/async.rs new file mode 100644 index 00000000..e467e83a --- /dev/null +++ b/net/src/async.rs @@ -0,0 +1,204 @@ +//! Asynchronously access keyservers. +//! +//! This module exposes the same interface, but for use within an +//! asynchronous framework. + +use failure; +use futures::{future, Future, Stream}; +use hyper::client::{FutureResponse, HttpConnector}; +use hyper::header::{ContentLength, ContentType}; +use hyper::{Client, Uri, StatusCode, Request, Method}; +use hyper_tls::HttpsConnector; +use native_tls::{Certificate, TlsConnector}; +use percent_encoding::{percent_encode, DEFAULT_ENCODE_SET}; +use std::convert::From; +use std::io::Cursor; +use tokio_core::reactor::Handle; + +use openpgp::tpk::TPK; +use openpgp::{KeyID, armor}; +use sequoia_core::{Context, NetworkPolicy}; + +use super::{Error, Result}; + +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 { + client: Box<AClient>, + uri: Uri, +} + +const DNS_WORKER: usize = 4; + +impl KeyServer { + /// Returns a handle for the given URI. + pub fn new(ctx: &Context, uri: &str, handle: &Handle) -> Result<Self> { + let uri: Uri = uri.parse()?; + + let client: Box<AClient> = match uri.scheme() { + Some("hkp") => Box::new(Client::new(handle)), + Some("hkps") => { + Box::new(Client::configure() + .connector(HttpsConnector::new(DNS_WORKER, handle)?) + .build(handle)) + }, + _ => 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, + handle: &Handle) -> Result<Self> { + let uri: Uri = uri.parse()?; + + let client: Box<AClient> = { + let mut ssl = TlsConnector::builder()?; + ssl.add_root_certificate(cert)?; + let ssl = ssl.build()?; + + let mut http = HttpConnector::new(DNS_WORKER, handle); + http.enforce_http(false); + Box::new(Client::configure() + .connector(HttpsConnector::from((http, ssl))) + .build(handle)) + }; + + Self::make(ctx, client, uri) + } + + /// Returns a handle for the SKS keyserver pool. + /// + /// The pool `hkps://hkps.pool.sks-keyservers.net` provides HKP + /// services over https. It is authenticated using a certificate + /// included in this library. It is a good default choice. + pub fn sks_pool(ctx: &Context, handle: &Handle) -> Result<Self> { + let uri = "hkps://hkps.pool.sks-keyservers.net"; + let cert = Certificate::from_der( + include_bytes!("sks-keyservers.netCA.der")).unwrap(); + Self::with_cert(ctx, uri, cert, handle) + } + + /// Common code for the above functions. + fn make(ctx: &Context, client: Box<AClient>, uri: Uri) -> Result<Self> { + let s = uri.scheme().ok_or(Error::MalformedUri)?; + match s { + "hkp" => ctx.network_policy().assert(NetworkPolicy::Insecure), + "hkps" => ctx.network_policy().assert(NetworkPolicy::Encrypted), + _ => unreachable!() + }?; + 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) + -> Box<Future<Item=TPK, Error=failure::Error> + 'static> { + let uri = format!("{}/pks/lookup?op=get&options=mr&search=0x{}", + self.uri, keyid.to_hex()).parse(); + 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.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::Kind::PublicKey); + future::done(TPK::from_reader(r)) + }, + StatusCode::NotFound => 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) + -> Box<Future<Item=(), Error=failure::Error> + 'static> { + use openpgp::armor::{Writer, Kind}; + + let uri = + match format!("{}/pks/add", self.uri).parse() { + 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 = Writer::new(&mut armored_blob, Kind::PublicKey); + 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 mut request = Request::new(Method::Post, uri); + request.headers_mut().set(ContentType::form_url_encoded()); + request.headers_mut().set(ContentLength(post_data.len() as u64)); + request.set_body(post_data); + + Box::new(self.client.do_request(request) + .from_err() + .and_then(|res| { + match res.status() { + StatusCode::Ok => future::ok(()), + StatusCode::NotFound => future::err(Error::ProtocolViolation.into()), + n => future::err(Error::HttpStatus(n).into()), + } + })) + } +} + +trait AClient { + fn do_get(&mut self, uri: Uri) -> FutureResponse; + fn do_request(&mut self, request: Request) -> FutureResponse; +} + +impl AClient for Client<HttpConnector> { + fn do_get(&mut self, uri: Uri) -> FutureResponse { + self.get(uri) + } + fn do_request(&mut self, request: Request) -> FutureResponse { + self.request(request) + } +} + +impl AClient for Client<HttpsConnector<HttpConnector>> { + fn do_get(&mut self, uri: Uri) -> FutureResponse { + self.get(uri) + } + fn do_request(&mut self, request: Request) -> FutureResponse { + self.request(request) + } +} diff --git a/net/src/lib.rs b/net/src/lib.rs index 52a43a60..9161df53 100644 --- a/net/src/lib.rs +++ b/net/src/lib.rs @@ -46,58 +46,32 @@ extern crate percent_encoding; extern crate capnp_rpc; -use percent_encoding::{percent_encode, DEFAULT_ENCODE_SET}; -use self::futures::{Future, Stream}; -use self::hyper::client::{FutureResponse, HttpConnector}; -use self::hyper::header::{ContentLength, ContentType}; -use self::hyper::{Client, Uri, StatusCode, Request, Method}; -use self::hyper_tls::HttpsConnector; -use self::native_tls::{Certificate, TlsConnector}; -use self::tokio_core::reactor::Core; +use hyper::client::{FutureResponse, HttpConnector}; +use hyper::{Client, Uri, Request}; +use hyper_tls::HttpsConnector; +use native_tls::Certificate; use std::convert::From; -use std::io::{Cursor, Read}; +use tokio_core::reactor::Core; -use sequoia_core::{Context, NetworkPolicy}; +use openpgp::KeyID; use openpgp::tpk::TPK; -use openpgp::{Message, KeyID, armor}; +use sequoia_core::Context; +pub mod async; pub mod ipc; -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, - client: Box<AClient>, - uri: Uri, + ks: async::KeyServer, } -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 uri: Uri = uri.parse()?; - - let client: Box<AClient> = match uri.scheme() { - Some("hkp") => Box::new(Client::new(&core.handle())), - Some("hkps") => { - Box::new(Client::configure() - .connector(HttpsConnector::new(DNS_WORKER, - &core.handle())?) - .build(&core.handle())) - }, - _ => return Err(Error::MalformedUri.into()), - }; - - Self::make(ctx, core, client, uri) + let ks = async::KeyServer::new(ctx, uri, &core.handle())?; + Ok(KeyServer{core: core, ks: ks}) } /// Returns a handle for the given URI. @@ -105,21 +79,8 @@ impl KeyServer { /// `cert` is used to authenticate the server. pub fn with_cert(ctx: &Context, uri: &str, cert: Certificate) -> Result<Self> { let core = Core::new()?; - let uri: Uri = uri.parse()?; - - let client: Box<AClient> = { - let mut ssl = TlsConnector::builder()?; - ssl.add_root_certificate(cert)?; - let ssl = ssl.build()?; - - let mut http = HttpConnector::new(DNS_WORKER, &core.handle()); - http.enforce_http(false); - Box::new(Client::configure() - .connector(HttpsConnector::from((http, ssl))) - .build(&core.handle())) - }; - - Self::make(ctx, core, client, uri) + let ks = async::KeyServer::with_cert(ctx, uri, cert, &core.handle())?; + Ok(KeyServer{core: core, ks: ks}) } /// Returns a handle for the SKS keyserver pool. @@ -134,94 +95,18 @@ impl KeyServer { Self::with_cert(ctx, uri, cert) } - /// Common code for the above functions. - fn make(ctx: &Context, core: Core, client: Box<AClient>, uri: Uri) -> Result<Self> { - let s = uri.scheme().ok_or(Error::MalformedUri)?; - match s { - "hkp" => ctx.network_policy().assert(NetworkPolicy::Insecure), - "hkps" => ctx.network_policy().assert(NetworkPolicy::Encrypted), - _ => unreachable!() - }?; - 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{core: core, client: client, uri: uri}) - } - /// Retrieves the key with the given `keyid`. pub fn get(&mut self, keyid: &KeyID) -> Result<TPK> { - let uri = format!("{}/pks/lookup?op=get&options=mr&search=0x{}", - self.uri, keyid.to_hex()).parse()?; - let result = self.core.run( - self.client.do_get(uri).and_then(|res| { - let status = res.status(); - res.body().concat2().and_then(move |body| Ok((status, body))) - })); - - let key: Result<::std::vec::Vec<u8>> = match result { - Ok((status, body)) => - match status { - StatusCode::Ok => { - let mut c = Cursor::new(body.as_ref()); - let mut r = armor::Reader::new(&mut c, armor::Kind::PublicKey); - let mut key = Vec::new(); - r.read_to_end(&mut key)?; - Ok(key) - }, - StatusCode::NotFound => Err(Error::NotFound.into()), - n => Err(Error::HttpStatus(n).into()), - } - Err(e) => Err(Error::HyperError(e).into()), - }; - - let m = Message::from_bytes(&key?)?; - TPK::from_message(m) + self.core.run( + self.ks.get(keyid) + ) } /// Sends the given key to the server. pub fn send(&mut self, key: &TPK) -> Result<()> { - use openpgp::armor::{Writer, Kind}; - - let uri = format!("{}/pks/add", self.uri).parse()?; - let mut armored_blob = vec![]; - { - let mut w = Writer::new(&mut armored_blob, Kind::PublicKey); - key.serialize(&mut w)?; - } - - // 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 mut request = Request::new(Method::Post, uri); - request.headers_mut().set(ContentType::form_url_encoded()); - request.headers_mut().set(ContentLength(post_data.len() as u64)); - request.set_body(post_data); - - let result = - self.core.run( - self.client.do_request(request).and_then(|res| { - let status = res.status(); - res.body().concat2().and_then(move |body| Ok((status, body))) - })); - - match result { - Ok((status, _body)) => - match status { - StatusCode::Ok => Ok(()), - StatusCode::NotFound => Err(Error::ProtocolViolation.into()), - n => Err(Error::HttpStatus(n).into()), - } - Err(e) => Err(Error::HyperError(e).into()), - } + self.core.run( + self.ks.send(key) + ) } } @@ -279,3 +164,15 @@ pub enum Error { #[fail(display = "TLS Error")] TlsError(native_tls::Error), } + +impl From<hyper::Error> for Error { + fn from(e: hyper::Error) -> Error { + Error::HyperError(e) + } +} + +impl From<hyper::error::UriError> for Error { + fn from(e: hyper::error::UriError) -> Error { + Error::UriError(e) + } +} |