diff --git a/Cargo.toml b/Cargo.toml index 797c7b2a..5910ecad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,3 +74,7 @@ rustdoc-args = ["--cfg", "docsrs"] [[example]] name = "connector_http_server" required-features = ["http-server", "usb"] + +[[example]] +name = "mockhsm" +required-features = ["http-server", "mockhsm"] diff --git a/examples/mockhsm.rs b/examples/mockhsm.rs new file mode 100644 index 00000000..9b1c5db7 --- /dev/null +++ b/examples/mockhsm.rs @@ -0,0 +1,30 @@ +//! `yubihsm-connector` compatible HTTP server example. +//! +//! This exposes an HTTP server which provides an API that is compatible with +//! the `yubihsm-connector` executable which comes with the YubiHSM SDK. +//! +//! It allows utilities like `yubihsm-shell` or other things written with +//! `libyubihsm` to function in tandem with a Rust application +//! communicating directly with the YubiHSM2 over USB. + +fn main() { + println!("using mockhsm"); + let connector = yubihsm::Connector::mockhsm(); + + // http://127.0.0.1:12345 + let http_config = yubihsm::connector::HttpConfig::default(); + + println!( + "starting server at http://{}:{}", + &http_config.addr, http_config.port + ); + + let server = yubihsm::connector::http::Server::new(&http_config, connector).unwrap(); + + println!("server started! connect by running:\n"); + println!(" $ yubihsm-shell"); + println!(" yubihsm> connect"); + println!(" yubihsm> session open 1 "); + + server.run().unwrap(); +} diff --git a/src/audit/commands/set_option.rs b/src/audit/commands/set_option.rs index d466d337..9be438ec 100644 --- a/src/audit/commands/set_option.rs +++ b/src/audit/commands/set_option.rs @@ -3,11 +3,7 @@ //! For more information, see: //! -use crate::{ - audit::*, - command::{self, Command}, - response::Response, -}; +use crate::{audit::*, command::Command, response::Response}; use serde::{Deserialize, Serialize}; /// Request parameters for `command::put_option` diff --git a/src/connector/http/client/connection.rs b/src/connector/http/client/connection.rs index 32622dab..798a12f3 100644 --- a/src/connector/http/client/connection.rs +++ b/src/connector/http/client/connection.rs @@ -5,10 +5,8 @@ use std::{ io::Write, net::{TcpStream, ToSocketAddrs}, ops::DerefMut, - string::String, sync::Mutex, time::Duration, - vec::Vec, }; use super::{error::Error, path::PathBuf, request, response, HTTP_VERSION, USER_AGENT}; diff --git a/src/connector/http/client/error.rs b/src/connector/http/client/error.rs index 44a3169d..34e737f7 100644 --- a/src/connector/http/client/error.rs +++ b/src/connector/http/client/error.rs @@ -3,10 +3,7 @@ #![allow(unused_macros)] use std::{fmt, num::ParseIntError, str::Utf8Error}; -use std::{ - io, - string::{FromUtf8Error, String, ToString}, -}; +use std::{io, string::FromUtf8Error}; /// Error type #[derive(Debug)] diff --git a/src/connector/http/client/response/reader.rs b/src/connector/http/client/response/reader.rs index 44b0d3c9..02b79f39 100644 --- a/src/connector/http/client/response/reader.rs +++ b/src/connector/http/client/response/reader.rs @@ -4,7 +4,7 @@ use super::Body; use crate::connector::http::client::Error; -use std::{io::Read, str, vec::Vec}; +use std::{io::Read, str}; const TRANSFER_ENCODING_HEADER: &str = "Transfer-Encoding: "; const HEADER_DELIMITER: &[u8] = b"\r\n\r\n"; diff --git a/src/connector/http/server.rs b/src/connector/http/server.rs index e84b9f83..843a2bb5 100644 --- a/src/connector/http/server.rs +++ b/src/connector/http/server.rs @@ -18,7 +18,7 @@ use crate::{ }, uuid, }; -use std::{io, process, time::Instant}; +use std::{fmt::Write, io, process, time::Instant}; use tiny_http as http; /// `yubihsm-connector` compatible HTTP server @@ -107,11 +107,10 @@ impl Server { ("port", &self.port.to_string()), ]; - let body = status - .iter() - .map(|(k, v)| [*k, *v].join("\n")) - .collect::>() - .join("\n"); + let body = status.iter().fold(String::new(), |mut body, (k, v)| { + let _ = writeln!(body, "{k}={v}"); + body + }); Ok(http::Response::from_string(body)) } diff --git a/src/mockhsm/command.rs b/src/mockhsm/command.rs index 23088d0c..70f2590a 100644 --- a/src/mockhsm/command.rs +++ b/src/mockhsm/command.rs @@ -117,6 +117,7 @@ pub(crate) fn session_message( Code::SetLogIndex => SetLogIndexResponse {}.serialize(), Code::SignEcdsa => sign_ecdsa(state, &command.data), Code::SignEddsa => sign_eddsa(state, &command.data), + Code::SignPss => sign_rsa_pss(state, &command.data), Code::GetStorageInfo => get_storage_info(), Code::VerifyHmac => verify_hmac(state, &command.data), unsupported => panic!("unsupported command type: {unsupported:?}"), @@ -156,7 +157,7 @@ fn delete_object(state: &mut State, cmd_data: &[u8]) -> response::Message { } /// Generate a mock device information report -fn device_info() -> response::Message { +pub(crate) fn device_info() -> response::Message { let info = device::Info { major_version: 2, minor_version: 0, @@ -685,6 +686,45 @@ fn sign_eddsa(state: &State, cmd_data: &[u8]) -> response::Message { } } +/// Sign a message using the RSA PSS signature algorithm +#[cfg(feature = "untested")] +fn sign_rsa_pss(state: &State, cmd_data: &[u8]) -> response::Message { + use crate::rsa::pss::{commands::*, Signature}; + use ::rsa::pss::SigningKey; + use ::signature::{hazmat::RandomizedPrehashSigner, SignatureEncoding}; + + let command: SignPssCommand = + deserialize(cmd_data).unwrap_or_else(|e| panic!("error parsing Code::SignRSA: {e:?}")); + + if let Some(obj) = state + .objects + .get(command.key_id, object::Type::AsymmetricKey) + { + if let Payload::RsaKey(signing_key) = &obj.payload { + let signing_key = SigningKey::::new_with_salt_len( + signing_key.clone(), + command.salt_len.into(), + ); + let signature = signing_key + .sign_prehash_with_rng(&mut OsRng, command.digest.as_ref()) + .expect("RSA PSS signature failed") + .to_vec(); + SignPssResponse(Signature(signature)).serialize() + } else { + debug!("not an RSA key: {:?}", obj.algorithm()); + device::ErrorKind::InvalidCommand.into() + } + } else { + debug!("no such object ID: {:?}", command.key_id); + device::ErrorKind::ObjectNotFound.into() + } +} + +#[cfg(not(feature = "untested"))] +fn sign_rsa_pss(_: &State, _: &[u8]) -> response::Message { + unimplemented!("RSASSA-PSS support disabled (use --features untested)") +} + /// Compute the HMAC tag for the given data fn sign_hmac(state: &State, cmd_data: &[u8]) -> response::Message { let command: SignHmacCommand = diff --git a/src/mockhsm/connection.rs b/src/mockhsm/connection.rs index b0f8fd56..ba38580d 100644 --- a/src/mockhsm/connection.rs +++ b/src/mockhsm/connection.rs @@ -34,6 +34,7 @@ impl Connection for MockConnection { Code::CreateSession => command::create_session(&mut state, &command), Code::AuthenticateSession => command::authenticate_session(&mut state, &command), Code::SessionMessage => command::session_message(&mut state, command), + Code::DeviceInfo => Ok(command::device_info().into()), unsupported => fail!(ConnectionFailed, "unsupported command: {:?}", unsupported), } .map(Message::from) diff --git a/src/rsa/pss/commands.rs b/src/rsa/pss/commands.rs index 53d28a2a..2376784e 100644 --- a/src/rsa/pss/commands.rs +++ b/src/rsa/pss/commands.rs @@ -30,7 +30,7 @@ impl Command for SignPssCommand { /// RSASSA-PSS signatures (ASN.1 DER encoded) #[derive(Serialize, Deserialize, Debug)] -pub struct SignPssResponse(rsa::pss::Signature); +pub struct SignPssResponse(pub(crate) rsa::pss::Signature); impl Response for SignPssResponse { const COMMAND_CODE: command::Code = command::Code::SignPss; diff --git a/src/serialization/ser.rs b/src/serialization/ser.rs index c4d28eb2..a6eff305 100644 --- a/src/serialization/ser.rs +++ b/src/serialization/ser.rs @@ -97,9 +97,9 @@ impl<'a, W: Write> serde::Serializer for &'a mut Serializer { unimplemented!(); } - fn serialize_some(self, _v: &T) -> Result<(), Error> + fn serialize_some(self, _v: &T) -> Result<(), Error> where - T: serde::Serialize, + T: serde::Serialize + ?Sized, { unimplemented!(); } @@ -195,9 +195,9 @@ impl<'a, W: Write> SerializeSeq for SerializeHelper<'a, W> { type Error = Error; #[inline] - fn serialize_element(&mut self, value: &T) -> Result<(), Error> + fn serialize_element(&mut self, value: &T) -> Result<(), Error> where - T: serde::Serialize, + T: serde::Serialize + ?Sized, { value.serialize(&mut *self.ser) } @@ -213,9 +213,9 @@ impl<'a, W: Write> SerializeTuple for SerializeHelper<'a, W> { type Error = Error; #[inline] - fn serialize_element(&mut self, value: &T) -> Result<(), Error> + fn serialize_element(&mut self, value: &T) -> Result<(), Error> where - T: serde::Serialize, + T: serde::Serialize + ?Sized, { value.serialize(&mut *self.ser) } @@ -231,9 +231,9 @@ impl<'a, W: Write> SerializeTupleStruct for SerializeHelper<'a, W> { type Error = Error; #[inline] - fn serialize_field(&mut self, value: &T) -> Result<(), Error> + fn serialize_field(&mut self, value: &T) -> Result<(), Error> where - T: serde::Serialize, + T: serde::Serialize + ?Sized, { value.serialize(&mut *self.ser) } @@ -249,9 +249,9 @@ impl<'a, W: Write> SerializeTupleVariant for SerializeHelper<'a, W> { type Error = Error; #[inline] - fn serialize_field(&mut self, value: &T) -> Result<(), Error> + fn serialize_field(&mut self, value: &T) -> Result<(), Error> where - T: serde::Serialize, + T: serde::Serialize + ?Sized, { value.serialize(&mut *self.ser) } @@ -267,17 +267,17 @@ impl<'a, W: Write> SerializeMap for SerializeHelper<'a, W> { type Error = Error; #[inline] - fn serialize_key(&mut self, value: &K) -> Result<(), Error> + fn serialize_key(&mut self, value: &K) -> Result<(), Error> where - K: serde::Serialize, + K: serde::Serialize + ?Sized, { value.serialize(&mut *self.ser) } #[inline] - fn serialize_value(&mut self, value: &V) -> Result<(), Error> + fn serialize_value(&mut self, value: &V) -> Result<(), Error> where - V: serde::Serialize, + V: serde::Serialize + ?Sized, { value.serialize(&mut *self.ser) } @@ -293,9 +293,9 @@ impl<'a, W: Write> SerializeStruct for SerializeHelper<'a, W> { type Error = Error; #[inline] - fn serialize_field(&mut self, _key: &'static str, value: &T) -> Result<(), Error> + fn serialize_field(&mut self, _key: &'static str, value: &T) -> Result<(), Error> where - T: serde::Serialize, + T: serde::Serialize + ?Sized, { value.serialize(&mut *self.ser) } @@ -311,9 +311,9 @@ impl<'a, W: Write> SerializeStructVariant for SerializeHelper<'a, W> { type Error = Error; #[inline] - fn serialize_field(&mut self, _key: &'static str, value: &T) -> Result<(), Error> + fn serialize_field(&mut self, _key: &'static str, value: &T) -> Result<(), Error> where - T: serde::Serialize, + T: serde::Serialize + ?Sized, { value.serialize(&mut *self.ser) } diff --git a/src/session/securechannel.rs b/src/session/securechannel.rs index 05c2e146..62414ebe 100644 --- a/src/session/securechannel.rs +++ b/src/session/securechannel.rs @@ -628,7 +628,6 @@ fn compute_icv(cipher: &Aes128, counter: u32) -> GenericArray { #[cfg(all(test, feature = "mockhsm"))] mod tests { use super::*; - use crate::authentication; const PASSWORD: &[u8] = b"password"; const HOST_CHALLENGE: &[u8] = &[0u8; 8]; diff --git a/src/session/securechannel/challenge.rs b/src/session/securechannel/challenge.rs index dc03c4ee..dbecfd7e 100644 --- a/src/session/securechannel/challenge.rs +++ b/src/session/securechannel/challenge.rs @@ -31,7 +31,7 @@ impl Challenge { } /// Borrow the challenge value as a slice - #[cfg_attr(clippy, allow(clippy::trivially_copy_pass_by_ref))] + #[allow(clippy::trivially_copy_pass_by_ref)] pub fn as_slice(&self) -> &[u8] { &self.0 } diff --git a/tests/command/decrypt_oaep.rs b/tests/command/decrypt_oaep.rs index d279565f..e23d97be 100644 --- a/tests/command/decrypt_oaep.rs +++ b/tests/command/decrypt_oaep.rs @@ -1,6 +1,5 @@ use crate::{generate_asymmetric_key, TEST_KEY_ID}; -use rand_core; -use sha2::{self, Digest}; +use sha2::Digest; use yubihsm::{asymmetric, Capability}; /// Test RSA OAEP decryption