summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorJustus Winter <justus@pep-project.org>2017-12-12 17:16:00 +0100
committerJustus Winter <justus@pep-project.org>2017-12-13 13:55:20 +0100
commitc86ad83ae31d44aeab3317e5e05d6d63e428f0e0 (patch)
tree12d1aac77f256e733104e98faf9143152eacd573 /net
parentc0cab61441df7a0334f817c2cc4817a0910e1193 (diff)
Split up Sequoia.
- Split up into six crates: buffered-reader, openpgp, sequoia-core, sequoia-ffi, sequoia-net, and sequoia-store. - Adjust imports accordingly.
Diffstat (limited to 'net')
-rw-r--r--net/Cargo.toml18
-rw-r--r--net/src/lib.rs297
-rw-r--r--net/src/sks-keyservers.netCA.derbin0 -> 1423 bytes
3 files changed, 315 insertions, 0 deletions
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 <justus@pep-project.org>"]
+
+[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<AClient>,
+ uri: Uri,
+}
+
+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),
+ };
+
+ 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<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)
+ }
+
+ /// 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, core: Core, client: Box<AClient>, uri: Uri) -> Result<Self> {
+ 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<TPK> {
+ 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<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),
+ 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::<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),
+ 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<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)
+ }
+}
+
+type Result<T> = ::std::result::Result<T, Error>;
+
+/// 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<tpk::Error> for Error {
+ fn from(e: tpk::Error) -> Self {
+ Error::KeysError(e)
+ }
+}
+
+impl From<hyper::StatusCode> for Error {
+ fn from(status: hyper::StatusCode) -> Self {
+ Error::HttpStatus(status)
+ }
+}
+
+impl From<io::Error> for Error {
+ fn from(error: io::Error) -> Self {
+ Error::IoError(error)
+ }
+}
+
+impl From<hyper::Error> for Error {
+ fn from(error: hyper::Error) -> Self {
+ Error::HyperError(error)
+ }
+}
+
+impl From<hyper::error::UriError> for Error {
+ fn from(error: hyper::error::UriError) -> Self {
+ Error::UriError(error)
+ }
+}
+
+impl From<native_tls::Error> 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
--- /dev/null
+++ b/net/src/sks-keyservers.netCA.der
Binary files differ