diff options
author | Justus Winter <justus@pep-project.org> | 2017-12-06 18:21:01 +0100 |
---|---|---|
committer | Justus Winter <justus@pep-project.org> | 2017-12-12 12:48:05 +0100 |
commit | c1082dbc8461a27fe7e8967084a3f072a2880ed1 (patch) | |
tree | 7c5a6894aebf68c20fc7ecd24d19172a520032f5 /src | |
parent | 6f6a2e8fc95b7506f769f122b4b3e5982a52336c (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.rs | 119 | ||||
-rw-r--r-- | src/lib.rs | 3 | ||||
-rw-r--r-- | src/net/mod.rs | 278 | ||||
-rw-r--r-- | src/net/sks-keyservers.netCA.der | bin | 0 -> 1423 bytes | |||
-rw-r--r-- | src/sequoia.h | 57 |
5 files changed, 455 insertions, 2 deletions
@@ -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()) +} @@ -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 Binary files differnew file mode 100644 index 00000000..80ca132b --- /dev/null +++ b/src/net/sks-keyservers.netCA.der 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 |