From 01a1221255fee361136fcc85b99c75c113d6c916 Mon Sep 17 00:00:00 2001 From: Wiktor Kwapisiewicz Date: Wed, 29 Dec 2021 15:32:08 +0100 Subject: pks: Return correct acceptable hashes for the remote signer. --- net/src/pks.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 21 deletions(-) (limited to 'net') diff --git a/net/src/pks.rs b/net/src/pks.rs index f0ce311a..271400f6 100644 --- a/net/src/pks.rs +++ b/net/src/pks.rs @@ -17,24 +17,62 @@ //! } //! ``` +use std::convert::{TryFrom, TryInto}; + use sequoia_openpgp as openpgp; use openpgp::packet::Key; use openpgp::packet::key::{PublicParts, UnspecifiedRole}; use openpgp::crypto::{Password, Decryptor, Signer, mpi, SessionKey, ecdh}; +use openpgp::types::HashAlgorithm; -use hyper::{Body, Client, Uri, client::HttpConnector, Request}; +use hyper::{Body, Client, Uri, client::HttpConnector, Request, HeaderMap, header::HeaderValue}; use hyper_tls::HttpsConnector; use super::Result; use url::Url; -/// Returns a capability URL for given key's capability. +/// Contains a description of the unlocked key. +struct KeyDescriptor { + /// URL of the endpoint that can be used to interact with the key. + url: Uri, + /// List of content types that this key accepts. This value is `None` if the key + /// did not indicate accepted content types. + accepted_types: Option>, +} + +impl TryFrom<&HeaderMap> for KeyDescriptor { + type Error = anyhow::Error; + + fn try_from(headers: &HeaderMap) -> Result { + if let Some(location) = headers.get("Location") { + let accepted_types = if let Some(accepted_types) = headers.get("Accept-Post") { + Some( + accepted_types + .to_str()? + .split(',') + .map(|typ| typ.trim().to_string()) + .collect::>(), + ) + } else { + None + }; + Ok(Self { + url: location.to_str()?.parse()?, + accepted_types, + }) + } else { + Err(anyhow::anyhow!("Key unlock did not return a Location header.")) + } + } +} + +/// Returns an unlocked key descriptor. /// -/// Unlocks a key using given password and on success returns a capability -/// URL that can be used for signing or decryption. -fn create_uri(store_uri: &str, key: &Key, - p: &Password, capability: &str) -> Result { +/// Unlocks a key using given password and on success returns a key descriptor +/// that can be used for signing or decryption. +fn create_descriptor(store_uri: &str, key: &Key, + p: &Password, capability: &str) -> Result { let mut url = Url::parse(store_uri)?; let auth = if !url.username().is_empty() { let credentials = format!("{}:{}", url.username(), url.password().unwrap_or_default()); @@ -65,14 +103,10 @@ fn create_uri(store_uri: &str, key: &Key, let response = rt.block_on(client.request(request))?; if !response.status().is_success() { - return Err(anyhow::anyhow!("PKS Key unlock failed.")); + return Err(anyhow::anyhow!("PKS Key unlock failed: {}", response.status())); } - if let Some(location) = response.headers().get("Location") { - Ok(location.to_str()?.parse()?) - } else { - Err(anyhow::anyhow!("Key unlock did not return a Location header.")) - } + response.headers().try_into() } /// Unlock a remote key for signing. @@ -100,8 +134,8 @@ fn create_uri(store_uri: &str, key: &Key, /// ``` pub fn unlock_signer(store_uri: impl AsRef, key: Key, p: &Password) -> Result> { - let capability = create_uri(store_uri.as_ref(), &key, p, "sign")?; - Ok(Box::new(PksClient::new(key, capability)?)) + let description = create_descriptor(store_uri.as_ref(), &key, p, "sign")?; + Ok(Box::new(PksClient::new(key, description)?)) } /// Unlock a remote key for decryption. @@ -129,21 +163,22 @@ pub fn unlock_signer(store_uri: impl AsRef, key: Key, key: Key, p: &Password) -> Result> { - let capability = create_uri(store_uri.as_ref(), &key, p, "decrypt")?; - Ok(Box::new(PksClient::new(key, capability)?)) + let description = create_descriptor(store_uri.as_ref(), &key, p, "decrypt")?; + Ok(Box::new(PksClient::new(key, description)?)) } struct PksClient { location: Uri, public: Key, client: hyper::client::Client>, + acceptable_hashes: Vec, rt: tokio::runtime::Runtime, } impl PksClient { fn new( public: Key, - location: Uri, + description: KeyDescriptor, ) -> Result { let client = Client::builder().build(HttpsConnector::new()); @@ -152,7 +187,17 @@ impl PksClient { .enable_time() .build()?; - Ok(Self { location, public, client, rt }) + let acceptable_types = description.accepted_types.unwrap_or_default(); + let mut acceptable_hashes = acceptable_types.iter().flat_map(|typ| match typ.as_ref() { + "application/vnd.pks.digest.sha1" => Some(HashAlgorithm::SHA1), + "application/vnd.pks.digest.sha256" => Some(HashAlgorithm::SHA256), + "application/vnd.pks.digest.sha384" => Some(HashAlgorithm::SHA384), + "application/vnd.pks.digest.sha512" => Some(HashAlgorithm::SHA512), + _ => None, + }).collect::>(); + acceptable_hashes.sort(); + + Ok(Self { location: description.url, public, client, rt, acceptable_hashes }) } fn make_request(&mut self, body: Vec, content_type: &str) -> Result> { @@ -167,8 +212,7 @@ impl PksClient { return Err(anyhow::anyhow!("PKS operation failed: {}", response.status())); } - let response = self.rt.block_on(hyper::body::to_bytes(response))?.to_vec(); - Ok(response) + Ok(self.rt.block_on(hyper::body::to_bytes(response))?.to_vec()) } } @@ -206,13 +250,16 @@ impl Signer for PksClient { &self.public } + fn acceptable_hashes(&self) -> &[HashAlgorithm] { + &self.acceptable_hashes + } + fn sign( &mut self, hash_algo: openpgp::types::HashAlgorithm, digest: &[u8], ) -> openpgp::Result { use openpgp::types::PublicKeyAlgorithm; - use openpgp::types::HashAlgorithm; let content_type = match hash_algo { HashAlgorithm::SHA1 => "application/vnd.pks.digest.sha1", -- cgit v1.2.3