summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJustus Winter <justus@pep-project.org>2017-12-06 18:21:01 +0100
committerJustus Winter <justus@pep-project.org>2017-12-12 12:48:05 +0100
commitc1082dbc8461a27fe7e8967084a3f072a2880ed1 (patch)
tree7c5a6894aebf68c20fc7ecd24d19172a520032f5 /src
parent6f6a2e8fc95b7506f769f122b4b3e5982a52336c (diff)
Retrieve and send keys using the hkps? protocol.
- Use hyper for http, hyper-tls for https. - Provide an easy constructor for the hkps pool. - Add ffi glue.
Diffstat (limited to 'src')
-rw-r--r--src/ffi.rs119
-rw-r--r--src/lib.rs3
-rw-r--r--src/net/mod.rs278
-rw-r--r--src/net/sks-keyservers.netCA.derbin0 -> 1423 bytes
-rw-r--r--src/sequoia.h57
5 files changed, 455 insertions, 2 deletions
diff --git a/src/ffi.rs b/src/ffi.rs
index 1e1af9b3..f34ce4fd 100644
--- a/src/ffi.rs
+++ b/src/ffi.rs
@@ -1,5 +1,5 @@
extern crate libc;
-use self::libc::{uint8_t, uint64_t, c_char, size_t};
+extern crate native_tls;
use std::ffi::CStr;
use std::ptr;
@@ -7,8 +7,11 @@ use std::slice;
use std::str;
use keys::TPK;
-use openpgp;
+use net::KeyServer;
use openpgp::types::KeyId;
+use openpgp;
+use self::libc::{uint8_t, uint64_t, c_char, size_t};
+use self::native_tls::Certificate;
use super::{Config, Context};
/* sequoia::Context. */
@@ -187,3 +190,115 @@ pub extern "system" fn sq_tpk_free(tpk: *mut TPK) {
drop(Box::from_raw(tpk));
}
}
+
+/// Returns a handle for the given URI.
+///
+/// `uri` is a UTF-8 encoded value of a keyserver URI,
+/// e.g. `hkps://examle.org`.
+///
+/// Returns `NULL` on errors.
+#[no_mangle]
+pub extern "system" fn sq_keyserver_new(ctx: Option<&Context>,
+ uri: *const c_char) -> *mut KeyServer {
+ let uri = unsafe {
+ if uri.is_null() { None } else { Some(CStr::from_ptr(uri)) }
+ };
+
+ if ctx.is_none() || uri.is_none() {
+ return ptr::null_mut();
+ }
+ let ks = KeyServer::new(ctx.unwrap(), &uri.unwrap().to_string_lossy());
+
+ if let Ok(ks) = ks {
+ Box::into_raw(Box::new(ks))
+ } else {
+ ptr::null_mut()
+ }
+}
+
+/// Returns a handle for the given URI.
+///
+/// `uri` is a UTF-8 encoded value of a keyserver URI,
+/// e.g. `hkps://examle.org`. `cert` is a DER encoded certificate of
+/// size `len` used to authenticate the server.
+///
+/// Returns `NULL` on errors.
+pub extern "system" fn sq_keyserver_with_cert(ctx: Option<&Context>,
+ uri: *const c_char,
+ cert: *const uint8_t,
+ len: size_t) -> *mut KeyServer {
+ let uri = unsafe {
+ if uri.is_null() { None } else { Some(CStr::from_ptr(uri)) }
+ };
+
+ if ctx.is_none() || uri.is_none() || cert.is_null() {
+ return ptr::null_mut();
+ }
+
+ let cert = unsafe {
+ slice::from_raw_parts(cert, len as usize)
+ };
+
+ let cert = Certificate::from_der(cert);
+ if cert.is_err() {
+ return ptr::null_mut();
+ }
+
+ let ks = KeyServer::with_cert(ctx.unwrap(),
+ &uri.unwrap().to_string_lossy(),
+ cert.unwrap());
+
+ if let Ok(ks) = ks {
+ Box::into_raw(Box::new(ks))
+ } else {
+ ptr::null_mut()
+ }
+}
+
+/// 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.
+///
+/// Returns `NULL` on errors.
+#[no_mangle]
+pub extern "system" fn sq_keyserver_sks_pool(ctx: Option<&Context>) -> *mut KeyServer {
+ if ctx.is_none() {
+ return ptr::null_mut();
+ }
+
+ let ks = KeyServer::sks_pool(ctx.unwrap());
+
+ if let Ok(ks) = ks {
+ Box::into_raw(Box::new(ks))
+ } else {
+ ptr::null_mut()
+ }
+}
+
+/// Frees a keyserver object.
+#[no_mangle]
+pub extern "system" fn sq_keyserver_free(ks: *mut KeyServer) {
+ if ks.is_null() {
+ return
+ }
+ unsafe {
+ drop(Box::from_raw(ks));
+ }
+}
+
+/// Retrieves the key with the given `keyid`.
+///
+/// Returns `NULL` on errors.
+#[no_mangle]
+pub extern "system" fn sq_keyserver_get(ks: Option<&mut KeyServer>,
+ id: Option<&KeyId>) -> *mut TPK {
+ if ks.is_none() || id.is_none() {
+ return ptr::null_mut();
+ }
+
+ ks.unwrap().get(id.as_ref().unwrap())
+ .map(|id| Box::into_raw(Box::new(id)))
+ .unwrap_or(ptr::null_mut())
+}
diff --git a/src/lib.rs b/src/lib.rs
index 49132e82..c5821007 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -10,6 +10,9 @@ extern crate nom;
extern crate flate2;
extern crate bzip2;
+#[macro_use]
+extern crate percent_encoding;
+
pub mod openpgp;
pub mod keys;
pub mod store;
diff --git a/src/net/mod.rs b/src/net/mod.rs
index 3627141b..c1667ee8 100644
--- a/src/net/mod.rs
+++ b/src/net/mod.rs
@@ -1 +1,279 @@
//! 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
+//!
+//! ```
+//! # use sequoia::net;
+//! # use sequoia::openpgp::types::KeyId;
+//! let ctx = sequoia::Context::new("org.sequoia-pgp.example").finalize().unwrap();
+//! let mut ks = net::KeyServer::sks_pool(&ctx).unwrap();
+//! let keyid = KeyId::from_hex("31855247603831FD").unwrap();
+//! println!("{:?}", ks.get(&keyid));
+//! ```
+
+extern crate futures;
+extern crate hyper;
+extern crate hyper_tls;
+extern crate native_tls;
+extern crate tokio_core;
+
+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 super::Context;
+use super::armor;
+use super::keys::TPK;
+use super::openpgp::types::KeyId;
+use super::openpgp;
+
+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 = openpgp::Message::from_bytes(&key?)?;
+ TPK::from_message(m).ok_or(Error::MalformedResponse)
+ }
+
+ /// 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,
+ /// 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<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/src/net/sks-keyservers.netCA.der b/src/net/sks-keyservers.netCA.der
new file mode 100644
index 00000000..80ca132b
--- /dev/null
+++ b/src/net/sks-keyservers.netCA.der
Binary files differ
diff --git a/src/sequoia.h b/src/sequoia.h
index 1b7015b5..4755658a 100644
--- a/src/sequoia.h
+++ b/src/sequoia.h
@@ -93,4 +93,61 @@ struct sq_tpk *sq_tpk_from_bytes (const char *b, size_t len);
void sq_tpk_dump (const struct sq_tpk *tpk);
void sq_tpk_free (struct sq_tpk *tpk);
+
+/* sequoia::net. */
+
+/*/
+/// For accessing keyservers using HKP.
+/*/
+struct sq_keyserver;
+
+/*/
+/// Returns a handle for the given URI.
+///
+/// `uri` is a UTF-8 encoded value of a keyserver URI,
+/// e.g. `hkps://examle.org`.
+///
+/// Returns `NULL` on errors.
+/*/
+struct sq_keyserver *sq_keyserver_new (const struct sq_context *ctx,
+ const char *uri);
+
+/*/
+/// Returns a handle for the given URI.
+///
+/// `uri` is a UTF-8 encoded value of a keyserver URI,
+/// e.g. `hkps://examle.org`. `cert` is a DER encoded certificate of
+/// size `len` used to authenticate the server.
+///
+/// Returns `NULL` on errors.
+/*/
+struct sq_keyserver *sq_keyserver_with_cert (const struct sq_context *ctx,
+ const char *uri,
+ const uint8_t *cert,
+ size_t len);
+
+/*/
+/// 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.
+///
+/// Returns `NULL` on errors.
+/*/
+struct sq_keyserver *sq_keyserver_sks_pool (const struct sq_context *ctx);
+
+/*/
+/// Frees a keyserver object.
+/*/
+void sq_keyserver_free (struct sq_keyserver *ks);
+
+/*/
+/// Retrieves the key with the given `keyid`.
+///
+/// Returns `NULL` on errors.
+/*/
+struct sq_tpk *sq_keyserver_get (struct sq_keyserver *ks,
+ const struct sq_keyid *id);
+
#endif