diff options
Diffstat (limited to 'net/src')
-rw-r--r-- | net/src/async.rs | 297 | ||||
-rw-r--r-- | net/src/lib.rs | 188 | ||||
-rw-r--r-- | net/src/wkd.rs | 77 |
3 files changed, 228 insertions, 334 deletions
diff --git a/net/src/async.rs b/net/src/async.rs deleted file mode 100644 index efce00bf..00000000 --- a/net/src/async.rs +++ /dev/null @@ -1,297 +0,0 @@ -//! 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::{ResponseFuture, HttpConnector}; -use hyper::header::{CONTENT_LENGTH, CONTENT_TYPE, HeaderValue}; -use hyper::{self, Client, Body, StatusCode, Request}; -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 url::Url; - -use crate::openpgp::TPK; -use crate::openpgp::parse::Parse; -use crate::openpgp::{KeyID, armor, serialize::Serialize}; -use sequoia_core::{Context, NetworkPolicy}; - -use crate::wkd as net_wkd; - -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: 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 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 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. - /// - /// 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) -> 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) - } - - /// 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) - -> 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) - -> 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()), - } - })) - } -} - -trait AClient { - fn do_get(&mut self, uri: Url) -> ResponseFuture; - fn do_request(&mut self, request: Request<Body>) -> ResponseFuture; -} - -impl AClient for Client<HttpConnector> { - fn do_get(&mut self, uri: Url) -> ResponseFuture { - self.get(url2uri(uri)) - } - fn do_request(&mut self, request: Request<Body>) -> ResponseFuture { - self.request(request) - } -} - -impl AClient for Client<HttpsConnector<HttpConnector>> { - fn do_get(&mut self, uri: Url) -> ResponseFuture { - self.get(url2uri(uri)) - } - fn do_request(&mut self, request: Request<Body>) -> ResponseFuture { - self.request(request) - } -} - -pub(crate) fn url2uri(uri: Url) -> hyper::Uri { - format!("{}", uri).parse().unwrap() -} - -pub mod wkd { - //! Asynchronously access Web Key Directories. - use super::*; - /// Retrieves the TPKs that contain userids with a given email address - /// from a Web Key Directory URL. - /// - /// This function is call by [net::wkd::get](../../wkd/fn.get.html). - /// - /// From [draft-koch]: - /// - /// ```text - /// There are two variants on how to form the request URI: The advanced - /// and the direct method. Implementations MUST first try the advanced - /// method. Only if the required sub-domain does not exist, they SHOULD - /// fall back to the direct method. - /// - /// [...] - /// - /// The HTTP GET method MUST return the binary representation of the - /// OpenPGP key for the given mail address. - /// - /// [...] - /// - /// Note that the key may be revoked or expired - it is up to the - /// client to handle such conditions. To ease distribution of revoked - /// keys, a server may return revoked keys in addition to a new key. - /// The keys are returned by a single request as concatenated key - /// blocks. - /// ``` - /// - /// [draft-koch]: https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service/#section-3.1 - - // XXX: Maybe the direct method should be tried on other errors too. - // https://mailarchive.ietf.org/arch/msg/openpgp/6TxZc2dQFLKXtS0Hzmrk963EteE - pub fn get<S: AsRef<str>>(email_address: S) - -> impl Future<Item=Vec<TPK>, Error=failure::Error> { - let email = email_address.as_ref().to_string(); - future::lazy(move || -> Result<_> { - // First, prepare URIs and client. - let wkd_url = net_wkd::Url::from(&email)?; - - // WKD must use TLS, so build a client for that. - let https = HttpsConnector::new(4)?; - let client = Client::builder().build::<_, hyper::Body>(https); - - Ok((email, client, wkd_url.to_uri(false)?, wkd_url.to_uri(true)?)) - }).and_then(|(email, client, advanced_uri, direct_uri)| { - // First, try the Advanced Method. - client.get(advanced_uri) - // Fall back to the Direct Method. - .or_else(move |_| { - client.get(direct_uri) - }) - .from_err() - .map(|res| (email, res)) - }).and_then(|(email, res)| { - // Join the response body. - res.into_body().concat2().from_err() - .map(|body| (email, body)) - }).and_then(|(email, body)| { - // And parse the response. - net_wkd::parse_body(&body, &email) - }) - } -} 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>; diff --git a/net/src/wkd.rs b/net/src/wkd.rs index 6d4613db..cdbf4e6c 100644 --- a/net/src/wkd.rs +++ b/net/src/wkd.rs @@ -18,21 +18,22 @@ extern crate tokio_core; use std::fmt; -use hyper::Uri; +use futures::{future, Future, Stream}; +use hyper::{Uri, Client}; +use hyper_tls::HttpsConnector; // Hash implements the traits for Sha1 // Sha1 is used to obtain a 20 bytes digest that after zbase32 encoding can // be used as file name use nettle::{ Hash, hash::insecure_do_not_use::Sha1, }; -use tokio_core::reactor::Core; use url; use crate::openpgp::TPK; use crate::openpgp::parse::Parse; use crate::openpgp::tpk::TPKParser; -use super::{Result, Error, r#async}; +use super::{Result, Error}; /// Stores the local_part and domain of an email address. @@ -194,23 +195,75 @@ pub(crate) fn parse_body<S: AsRef<str>>(body: &[u8], email_address: S) /// Retrieves the TPKs that contain userids with a given email address /// from a Web Key Directory URL. /// -/// This function calls the [async::wkd::get](../async/wkd/fn.get.html) -/// function. +/// This function is call by [net::wkd::get](../../wkd/fn.get.html). /// -/// # Example +/// From [draft-koch]: +/// +/// ```text +/// There are two variants on how to form the request URI: The advanced +/// and the direct method. Implementations MUST first try the advanced +/// method. Only if the required sub-domain does not exist, they SHOULD +/// fall back to the direct method. +/// +/// [...] +/// +/// The HTTP GET method MUST return the binary representation of the +/// OpenPGP key for the given mail address. /// +/// [...] +/// +/// Note that the key may be revoked or expired - it is up to the +/// client to handle such conditions. To ease distribution of revoked +/// keys, a server may return revoked keys in addition to a new key. +/// The keys are returned by a single request as concatenated key +/// blocks. /// ``` +/// +/// [draft-koch]: https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service/#section-3.1 +/// # Example +/// +/// ```no_run +/// extern crate tokio_core; +/// use tokio_core::reactor::Core; /// extern crate sequoia_net; /// use sequoia_net::wkd; /// /// let email_address = "foo@bar.baz"; -/// let tpks = wkd::get(&email_address); +/// let mut core = Core::new().unwrap(); +/// let tpks = core.run(wkd::get(&email_address)).unwrap(); /// ``` -// This function must have the same signature as async::wkd::get. -// XXX: Maybe implement WkdServer and AWkdClient. -pub fn get<S: AsRef<str>>(email_address: S) -> Result<Vec<TPK>> { - let mut core = Core::new()?; - core.run(r#async::wkd::get(&email_address)) + +// XXX: Maybe the direct method should be tried on other errors too. +// https://mailarchive.ietf.org/arch/msg/openpgp/6TxZc2dQFLKXtS0Hzmrk963EteE +pub fn get<S: AsRef<str>>(email_address: S) + -> impl Future<Item=Vec<TPK>, Error=failure::Error> { + let email = email_address.as_ref().to_string(); + future::lazy(move || -> Result<_> { + // First, prepare URIs and client. + let wkd_url = Url::from(&email)?; + + // WKD must use TLS, so build a client for that. + let https = HttpsConnector::new(4)?; + let client = Client::builder().build::<_, hyper::Body>(https); + + Ok((email, client, wkd_url.to_uri(false)?, wkd_url.to_uri(true)?)) + }).and_then(|(email, client, advanced_uri, direct_uri)| { + // First, try the Advanced Method. + client.get(advanced_uri) + // Fall back to the Direct Method. + .or_else(move |_| { + client.get(direct_uri) + }) + .from_err() + .map(|res| (email, res)) + }).and_then(|(email, res)| { + // Join the response body. + res.into_body().concat2().from_err() + .map(|body| (email, body)) + }).and_then(|(email, body)| { + // And parse the response. + parse_body(&body, &email) + }) } |