From c86ad83ae31d44aeab3317e5e05d6d63e428f0e0 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 12 Dec 2017 17:16:00 +0100 Subject: Split up Sequoia. - Split up into six crates: buffered-reader, openpgp, sequoia-core, sequoia-ffi, sequoia-net, and sequoia-store. - Adjust imports accordingly. --- net/Cargo.toml | 18 +++ net/src/lib.rs | 297 +++++++++++++++++++++++++++++++++++++++ net/src/sks-keyservers.netCA.der | Bin 0 -> 1423 bytes 3 files changed, 315 insertions(+) create mode 100644 net/Cargo.toml create mode 100644 net/src/lib.rs create mode 100644 net/src/sks-keyservers.netCA.der (limited to 'net') diff --git a/net/Cargo.toml b/net/Cargo.toml new file mode 100644 index 00000000..044171b4 --- /dev/null +++ b/net/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "sequoia-net" +version = "0.1.0" +authors = ["Justus Winter "] + +[dependencies] +openpgp = { path = "../openpgp" } +sequoia-core = { path = "../core" } +hyper = "0.11" +hyper-tls = "0.1.2" +libc = "0.2.33" +native-tls = "0.1.4" +nom = "3.2.0" +num = "0.1.40" +num-derive = "0.1.41" +percent-encoding = "1.0.1" +tokio-core = "0.1" +futures = "0.1" diff --git a/net/src/lib.rs b/net/src/lib.rs new file mode 100644 index 00000000..36b822c1 --- /dev/null +++ b/net/src/lib.rs @@ -0,0 +1,297 @@ +//! For accessing keys over the network. +//! +//! Currently, this module provides access to keyservers providing the [HKP] protocol. +//! +//! [HKP]: https://tools.ietf.org/html/draft-shaw-openpgp-hkp-00 +//! +//! # Example +//! +//! We provide a very reasonable default key server backed by +//! `hkps.pool.sks-keyservers.net`, the subset of the [SKS keyserver] +//! network that uses https to protect integrity and confidentiality +//! of the communication with the client: +//! +//! [SKS keyserver]: https://www.sks-keyservers.net/overview-of-pools.php#pool_hkps +//! +//! ```no_run +//! # extern crate openpgp; +//! # extern crate sequoia_core; +//! # extern crate sequoia_net; +//! # use openpgp::types::KeyId; +//! # use sequoia_core::Context; +//! # use sequoia_net::KeyServer; +//! # fn main() { +//! let ctx = Context::new("org.sequoia-pgp.example").unwrap(); +//! let mut ks = KeyServer::sks_pool(&ctx).unwrap(); +//! let keyid = KeyId::from_hex("31855247603831FD").unwrap(); +//! println!("{:?}", ks.get(&keyid)); +//! # } +//! ``` + +extern crate openpgp; +extern crate sequoia_core; + +extern crate futures; +extern crate hyper; +extern crate hyper_tls; +extern crate native_tls; +extern crate tokio_core; +#[macro_use] +extern crate percent_encoding; + +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 std::convert::From; +use std::io::{Cursor, Read}; +use std::io; + +use sequoia_core::Context; +use openpgp::tpk::{self, TPK}; +use openpgp::types::KeyId; +use openpgp::{Message, armor}; + +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, + uri: Uri, +} + +const DNS_WORKER: usize = 4; + +impl KeyServer { + /// Returns a handle for the given URI. + pub fn new(ctx: &Context, uri: &str) -> Result { + let core = Core::new()?; + let uri: Uri = uri.parse()?; + + let client: Box = 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), + }; + + Self::make(ctx, core, 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 { + let core = Core::new()?; + let uri: Uri = uri.parse()?; + + let client: Box = { + 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) + } + + /// 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 { + 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, core: Core, client: Box, uri: Uri) -> Result { + let uri = { + let s = uri.scheme().ok_or(Error::MalformedUri)?; + 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 { + let uri = format!("{}/pks/lookup?op=get&options=mr&search=0x{}", + self.uri, keyid.as_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> = 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), + n => Err(Error::from(n)), + } + Err(e) => Err(Error::HyperError(e)), + }; + + let m = Message::from_bytes(&key?)?; + TPK::from_message(m).map_err(|e| Error::KeysError(e)) + } + + /// Sends the given key to the server. + /// + /// XXX: This should take a &TPK, but TPKs cannot be serialized at + /// the moment. + pub fn send(&mut self, key: &[u8]) -> Result<()> { + let uri = format!("{}/pks/add", self.uri).parse()?; + + // Prepare to send url-encoded data. + let mut post_data = b"keytext=".to_vec(); + post_data.extend_from_slice(percent_encode(key, KEYSERVER_ENCODE_SET) + .collect::().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), + n => Err(Error::from(n)), + } + Err(e) => Err(Error::HyperError(e)), + } + } +} + +trait AClient { + fn do_get(&mut self, uri: Uri) -> FutureResponse; + fn do_request(&mut self, request: Request) -> FutureResponse; +} + +impl AClient for Client { + 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> { + fn do_get(&mut self, uri: Uri) -> FutureResponse { + self.get(uri) + } + fn do_request(&mut self, request: Request) -> FutureResponse { + self.request(request) + } +} + +type Result = ::std::result::Result; + +/// Errors returned from the network routines. +#[derive(Debug)] +pub enum Error { + /// A requested key was not found. + NotFound, + /// A given keyserver URI was malformed. + MalformedUri, + /// The server provided malformed data. + MalformedResponse, + /// A communication partner violated the protocol. + ProtocolViolation, + /// There was an error parsing the key. + KeysError(tpk::Error), + /// Encountered an unexpected low-level http status. + HttpStatus(hyper::StatusCode), + /// An `io::Error` occured. + IoError(io::Error), + /// A `hyper::error::UriError` occured. + UriError(hyper::error::UriError), + /// A `hyper::Error` occured. + HyperError(hyper::Error), + /// A `native_tls::Error` occured. + TlsError(native_tls::Error), +} + +impl From for Error { + fn from(e: tpk::Error) -> Self { + Error::KeysError(e) + } +} + +impl From for Error { + fn from(status: hyper::StatusCode) -> Self { + Error::HttpStatus(status) + } +} + +impl From for Error { + fn from(error: io::Error) -> Self { + Error::IoError(error) + } +} + +impl From for Error { + fn from(error: hyper::Error) -> Self { + Error::HyperError(error) + } +} + +impl From for Error { + fn from(error: hyper::error::UriError) -> Self { + Error::UriError(error) + } +} + +impl From for Error { + fn from(error: native_tls::Error) -> Self { + Error::TlsError(error) + } +} diff --git a/net/src/sks-keyservers.netCA.der b/net/src/sks-keyservers.netCA.der new file mode 100644 index 00000000..80ca132b Binary files /dev/null and b/net/src/sks-keyservers.netCA.der differ -- cgit v1.2.3