#![allow(clippy::too_many_arguments)]
#[macro_use]
mod error;
pub use self::error::{Error, ErrorKind};
use crate::{
asymmetric::{self, commands::*, PublicKey},
attestation::{self, commands::*},
audit::{commands::*, *},
authentication::{self, commands::*, Credentials},
capability::Capability,
command::{self, Command},
connector::Connector,
device::{self, commands::*, StorageInfo},
domain::Domain,
ecdsa::{self, commands::*},
ed25519::{self, commands::*},
hmac::{self, commands::*},
object::{self, commands::*, generate},
opaque::{self, commands::*},
otp::{self, commands::*},
serialization::{deserialize, serialize},
session::{self, Session},
template::{commands::*, Template},
uuid,
wrap::{self, commands::*},
};
use std::{
sync::{Arc, Mutex},
time::{Duration, Instant},
};
#[cfg(feature = "passwords")]
use std::{thread, time::SystemTime};
#[cfg(feature = "yolocrypto")]
use {
crate::{
algorithm::Algorithm,
ecdh::{self, commands::*},
rsa::{self, pkcs1::commands::*, pss::commands::*},
ssh::{self, commands::*},
},
sha2::{Digest, Sha256},
};
#[derive(Clone)]
pub struct Client {
connector: Connector,
session: Arc<Mutex<Option<Session>>>,
credentials: Option<Credentials>,
}
impl Client {
pub fn open(
connector: Connector,
credentials: Credentials,
reconnect: bool,
) -> Result<Self, Error> {
let mut client = Self::create(connector, credentials)?;
client.connect()?;
if !reconnect {
client.credentials = None;
}
Ok(client)
}
pub fn create(connector: Connector, credentials: Credentials) -> Result<Self, Error> {
let client = Self {
connector,
session: Arc::new(Mutex::new(None)),
credentials: Some(credentials),
};
Ok(client)
}
pub fn connector(&self) -> &Connector {
&self.connector
}
pub fn connect(&self) -> Result<(), Error> {
self.session()?;
Ok(())
}
pub fn session(&self) -> Result<session::Guard<'_>, Error> {
let mut session_mutex_guard = self.session.lock().unwrap();
if let Some(session) = session_mutex_guard.as_ref() {
if session.is_open() {
return Ok(session::Guard::new(session_mutex_guard));
}
}
let session = Session::open(
self.connector.clone(),
self.credentials.as_ref().ok_or_else(|| {
format_err!(
ErrorKind::AuthenticationError,
"session reconnection disabled"
)
})?,
session::Timeout::default(),
)?;
*session_mutex_guard = Some(session);
Ok(session::Guard::new(session_mutex_guard))
}
pub fn ping(&self) -> Result<Duration, Error> {
let t = Instant::now();
let uuid = uuid::new_v4().to_hyphenated().to_string();
let response = self.echo(uuid.as_bytes())?;
ensure!(
uuid.as_bytes() == response.as_slice(),
ErrorKind::ResponseError,
"expected {}, got {}",
uuid,
String::from_utf8_lossy(&response)
);
Ok(Instant::now().duration_since(t))
}
fn send_command<T: Command>(&self, command: T) -> Result<T::ResponseType, Error> {
match self.session()?.send_command(&command) {
Ok(response) => Ok(response),
Err(e) => {
if e.kind() == session::ErrorKind::CommandLimitExceeded {
Ok(self.session()?.send_command(&command)?)
} else {
Err(e.into())
}
}
}
}
pub fn blink_device(&self, num_seconds: u8) -> Result<(), Error> {
self.send_command(BlinkDeviceCommand { num_seconds })?;
Ok(())
}
pub fn delete_object(
&self,
object_id: object::Id,
object_type: object::Type,
) -> Result<(), Error> {
self.send_command(DeleteObjectCommand {
object_id,
object_type,
})?;
Ok(())
}
#[cfg(feature = "yolocrypto")]
pub fn derive_ecdh(
&self,
key_id: object::Id,
public_key: ecdh::UncompressedPoint,
) -> Result<ecdh::UncompressedPoint, Error> {
Ok(self
.send_command(DeriveEcdhCommand { key_id, public_key })?
.into())
}
pub fn device_info(&self) -> Result<device::Info, Error> {
Ok(self.send_command(DeviceInfoCommand {})?.into())
}
pub fn echo<M>(&self, msg: M) -> Result<Vec<u8>, Error>
where
M: Into<Vec<u8>>,
{
Ok(self
.send_command(EchoCommand {
message: msg.into(),
})?
.0)
}
pub fn export_wrapped(
&self,
wrap_key_id: object::Id,
object_type: object::Type,
object_id: object::Id,
) -> Result<wrap::Message, Error> {
Ok(self
.send_command(ExportWrappedCommand {
wrap_key_id,
object_type,
object_id,
})?
.0)
}
pub fn generate_asymmetric_key(
&self,
key_id: object::Id,
label: object::Label,
domains: Domain,
capabilities: Capability,
algorithm: asymmetric::Algorithm,
) -> Result<object::Id, Error> {
Ok(self
.send_command(GenAsymmetricKeyCommand(generate::Params {
key_id,
label,
domains,
capabilities,
algorithm: algorithm.into(),
}))?
.key_id)
}
pub fn generate_hmac_key(
&self,
key_id: object::Id,
label: object::Label,
domains: Domain,
capabilities: Capability,
algorithm: hmac::Algorithm,
) -> Result<object::Id, Error> {
Ok(self
.send_command(GenHmacKeyCommand(generate::Params {
key_id,
label,
domains,
capabilities,
algorithm: algorithm.into(),
}))?
.key_id)
}
pub fn generate_wrap_key(
&self,
key_id: object::Id,
label: object::Label,
domains: Domain,
capabilities: Capability,
delegated_capabilities: Capability,
algorithm: wrap::Algorithm,
) -> Result<object::Id, Error> {
Ok(self
.send_command(GenWrapKeyCommand {
params: generate::Params {
key_id,
label,
domains,
capabilities,
algorithm: algorithm.into(),
},
delegated_capabilities,
})?
.key_id)
}
pub fn get_log_entries(&self) -> Result<LogEntries, Error> {
Ok(self.send_command(GetLogEntriesCommand {})?)
}
pub fn get_object_info(
&self,
object_id: object::Id,
object_type: object::Type,
) -> Result<object::Info, Error> {
Ok(self
.send_command(GetObjectInfoCommand(object::Handle::new(
object_id,
object_type,
)))?
.0)
}
pub fn get_opaque(&self, object_id: object::Id) -> Result<Vec<u8>, Error> {
Ok(self.send_command(GetOpaqueCommand { object_id })?.0)
}
pub fn get_command_audit_option(&self, command: command::Code) -> Result<AuditOption, Error> {
let command_audit_options = self.get_commands_audit_options()?;
Ok(command_audit_options
.iter()
.find(|opt| opt.command_type() == command)
.map(AuditCommand::audit_option)
.unwrap_or(AuditOption::Off))
}
pub fn get_commands_audit_options(&self) -> Result<Vec<AuditCommand>, Error> {
let response = self.send_command(GetOptionCommand {
tag: AuditTag::Command,
})?;
Ok(deserialize(&response.0)?)
}
pub fn get_force_audit_option(&self) -> Result<AuditOption, Error> {
let response = self.send_command(GetOptionCommand {
tag: AuditTag::Force,
})?;
ensure!(
response.0.len() == 1,
ErrorKind::ProtocolError,
"expected 1-byte response, got {}",
response.0.len()
);
AuditOption::from_u8(response.0[0]).map_err(|e| format_err!(ErrorKind::ProtocolError, e))
}
pub fn get_pseudo_random(&self, bytes: usize) -> Result<Vec<u8>, Error> {
ensure!(
bytes <= MAX_RAND_BYTES,
ErrorKind::ProtocolError,
"requested number of bytes too large: {} (max: {})",
bytes,
MAX_RAND_BYTES
);
Ok(self
.send_command(GetPseudoRandomCommand {
bytes: bytes as u16,
})?
.bytes)
}
pub fn get_public_key(&self, key_id: object::Id) -> Result<PublicKey, Error> {
Ok(self.send_command(GetPublicKeyCommand { key_id })?.into())
}
pub fn get_storage_info(&self) -> Result<StorageInfo, Error> {
Ok(self.send_command(GetStorageInfoCommand {})?.into())
}
pub fn get_template(&self, object_id: object::Id) -> Result<Vec<u8>, Error> {
Ok(self.send_command(GetTemplateCommand { object_id })?.0)
}
pub fn import_wrapped<M>(
&self,
wrap_key_id: object::Id,
wrap_message: M,
) -> Result<object::Handle, Error>
where
M: Into<wrap::Message>,
{
let wrap::Message { nonce, ciphertext } = wrap_message.into();
let response = self.send_command(ImportWrappedCommand {
wrap_key_id,
nonce,
ciphertext,
})?;
Ok(object::Handle::new(
response.object_id,
response.object_type,
))
}
pub fn list_objects(&self, filters: &[object::Filter]) -> Result<Vec<object::Entry>, Error> {
let mut filter_bytes = vec![];
for filter in filters {
filter.serialize(&mut filter_bytes)?;
}
Ok(self.send_command(ListObjectsCommand(filter_bytes))?.0)
}
pub fn put_asymmetric_key<K>(
&self,
key_id: object::Id,
label: object::Label,
domains: Domain,
capabilities: Capability,
algorithm: asymmetric::Algorithm,
key_bytes: K,
) -> Result<object::Id, Error>
where
K: Into<Vec<u8>>,
{
let data = key_bytes.into();
if data.len() != algorithm.key_len() {
fail!(
ErrorKind::ProtocolError,
"invalid key length for {:?}: {} (expected {})",
algorithm,
data.len(),
algorithm.key_len()
);
}
Ok(self
.send_command(PutAsymmetricKeyCommand {
params: object::put::Params {
id: key_id,
label,
domains,
capabilities,
algorithm: algorithm.into(),
},
data,
})?
.key_id)
}
pub fn put_authentication_key<K>(
&self,
key_id: object::Id,
label: object::Label,
domains: Domain,
capabilities: Capability,
delegated_capabilities: Capability,
algorithm: authentication::Algorithm,
authentication_key: K,
) -> Result<object::Id, Error>
where
K: Into<authentication::Key>,
{
Ok(self
.send_command(PutAuthenticationKeyCommand {
params: object::put::Params {
id: key_id,
label,
domains,
capabilities,
algorithm: algorithm.into(),
},
delegated_capabilities,
authentication_key: authentication_key.into(),
})?
.key_id)
}
pub fn put_hmac_key<K>(
&self,
key_id: object::Id,
label: object::Label,
domains: Domain,
capabilities: Capability,
algorithm: hmac::Algorithm,
key_bytes: K,
) -> Result<object::Id, Error>
where
K: Into<Vec<u8>>,
{
let hmac_key = key_bytes.into();
if hmac_key.len() < HMAC_MIN_KEY_SIZE || hmac_key.len() > algorithm.max_key_len() {
fail!(
ErrorKind::ProtocolError,
"invalid key length for {:?}: {} (min {}, max {})",
algorithm,
hmac_key.len(),
HMAC_MIN_KEY_SIZE,
algorithm.max_key_len()
);
}
Ok(self
.send_command(PutHmacKeyCommand {
params: object::put::Params {
id: key_id,
label,
domains,
capabilities,
algorithm: algorithm.into(),
},
hmac_key,
})?
.key_id)
}
pub fn put_opaque<B>(
&self,
object_id: object::Id,
label: object::Label,
domains: Domain,
capabilities: Capability,
algorithm: opaque::Algorithm,
opaque_data: B,
) -> Result<object::Id, Error>
where
B: Into<Vec<u8>>,
{
Ok(self
.send_command(PutOpaqueCommand {
params: object::put::Params {
id: object_id,
label,
domains,
capabilities,
algorithm: algorithm.into(),
},
data: opaque_data.into(),
})?
.object_id)
}
pub fn put_otp_aead_key<K>(
&self,
key_id: object::Id,
label: object::Label,
domains: Domain,
capabilities: Capability,
algorithm: otp::Algorithm,
key_bytes: K,
) -> Result<object::Id, Error>
where
K: Into<Vec<u8>>,
{
let data = key_bytes.into();
if data.len() != algorithm.key_len() {
fail!(
ErrorKind::ProtocolError,
"invalid key length for {:?}: {} (expected {})",
algorithm,
data.len(),
algorithm.key_len()
);
}
Ok(self
.send_command(PutOTPAEADKeyCommand {
params: object::put::Params {
id: key_id,
label,
domains,
capabilities,
algorithm: algorithm.into(),
},
data,
})?
.key_id)
}
pub fn put_wrap_key<K>(
&self,
key_id: object::Id,
label: object::Label,
domains: Domain,
capabilities: Capability,
delegated_capabilities: Capability,
algorithm: wrap::Algorithm,
key_bytes: K,
) -> Result<object::Id, Error>
where
K: Into<Vec<u8>>,
{
let data = key_bytes.into();
if data.len() != algorithm.key_len() {
fail!(
ErrorKind::ProtocolError,
"invalid key length for {:?}: {} (expected {})",
algorithm,
data.len(),
algorithm.key_len()
);
}
Ok(self
.send_command(PutWrapKeyCommand {
params: object::put::Params {
id: key_id,
label,
domains,
capabilities,
algorithm: algorithm.into(),
},
delegated_capabilities,
data,
})?
.key_id)
}
pub fn put_template<T>(
&self,
object_id: object::Id,
label: object::Label,
domains: Domain,
capabilities: Capability,
template: T,
) -> Result<object::Id, Error>
where
T: Into<Template>,
{
let template: Template = template.into();
Ok(self
.send_command(PutTemplateCommand {
params: object::put::Params {
id: object_id,
label,
domains,
capabilities,
algorithm: template.algorithm().into(),
},
data: template.as_ref().into(),
})?
.object_id)
}
pub fn reset_device(&self) -> Result<(), Error> {
let mut session = self.session()?;
if let Err(e) = session.send_command(&ResetDeviceCommand {}) {
debug!("error sending reset command: {}", e);
}
session.abort();
Ok(())
}
#[cfg(feature = "passwords")]
pub fn reset_device_and_reconnect(&mut self, timeout: Duration) -> Result<(), Error> {
const DEVICE_RESET_WAIT_MS: u64 = 1000;
const DEVICE_POLL_INTERVAL_MS: u64 = 200;
warn!("factory resetting HSM device! all data will be lost!");
thread::sleep(Duration::from_millis(DEVICE_RESET_WAIT_MS));
self.reset_device()?;
self.credentials = Some(Credentials::default());
let deadline = SystemTime::now() + timeout;
info!("waiting for device reset to complete");
thread::sleep(Duration::from_millis(DEVICE_RESET_WAIT_MS));
loop {
match self.connect() {
Ok(_) => {
debug!("successfully reconnected to HSM after reset!");
return Ok(());
}
Err(e) => {
if SystemTime::now() >= deadline {
fail!(
ErrorKind::CreateFailed,
"timed out after {} seconds connecting to HSM after reset: {}",
timeout.as_secs(),
e
)
} else {
debug!("error reconnecting to HSM: {}", e);
thread::sleep(Duration::from_millis(DEVICE_POLL_INTERVAL_MS))
}
}
}
}
}
pub fn set_command_audit_option(
&self,
command: command::Code,
audit_option: AuditOption,
) -> Result<(), Error> {
self.send_command(SetOptionCommand {
tag: AuditTag::Command,
length: 2,
value: serialize(&AuditCommand(command, audit_option))?,
})?;
Ok(())
}
pub fn set_force_audit_option(&self, option: AuditOption) -> Result<(), Error> {
self.send_command(SetOptionCommand {
tag: AuditTag::Force,
length: 1,
value: vec![option.to_u8()],
})?;
Ok(())
}
pub fn set_log_index(&self, log_index: u16) -> Result<(), Error> {
self.send_command(SetLogIndexCommand { log_index })?;
Ok(())
}
pub fn sign_attestation_certificate(
&self,
key_id: object::Id,
attestation_key_id: Option<object::Id>,
) -> Result<attestation::Certificate, Error> {
Ok(self.send_command(SignAttestationCertificateCommand {
key_id,
attestation_key_id: attestation_key_id.unwrap_or(0),
})?)
}
pub fn sign_ecdsa<T>(&self, key_id: object::Id, digest: T) -> Result<ecdsa::Signature, Error>
where
T: Into<Vec<u8>>,
{
Ok(self
.send_command(SignEcdsaCommand {
key_id,
digest: digest.into(),
})?
.into())
}
pub fn sign_ed25519<T>(&self, key_id: object::Id, data: T) -> Result<ed25519::Signature, Error>
where
T: Into<Vec<u8>>,
{
self.send_command(SignEddsaCommand {
key_id,
data: data.into(),
})?
.signature()
}
pub fn sign_hmac<M>(&self, key_id: object::Id, msg: M) -> Result<hmac::Tag, Error>
where
M: Into<Vec<u8>>,
{
Ok(self
.send_command(SignHmacCommand {
key_id,
data: msg.into(),
})?
.into())
}
#[cfg(feature = "yolocrypto")]
pub fn sign_rsa_pkcs1v15_sha256(
&self,
key_id: object::Id,
data: &[u8],
) -> Result<rsa::pkcs1::Signature, Error> {
Ok(self
.send_command(SignPkcs1Command {
key_id,
digest: Sha256::digest(data).as_slice().into(),
})?
.into())
}
#[cfg(feature = "yolocrypto")]
pub fn sign_rsa_pss_sha256(
&self,
key_id: object::Id,
data: &[u8],
) -> Result<rsa::pss::Signature, Error> {
ensure!(
data.len() > rsa::pss::MAX_MESSAGE_SIZE,
ErrorKind::ProtocolError,
"message too large to be signed (max: {})",
rsa::pss::MAX_MESSAGE_SIZE
);
let mut hasher = Sha256::default();
let length = data.len() as u16;
hasher.input(&length.to_be_bytes());
hasher.input(data);
let digest = hasher.result();
Ok(self
.send_command(SignPssCommand {
key_id,
mgf1_hash_alg: rsa::mgf::Algorithm::Sha256,
salt_len: digest.as_slice().len() as u16,
digest: digest.as_slice().into(),
})?
.into())
}
#[cfg(feature = "yolocrypto")]
pub fn sign_ssh_certificate<A>(
&self,
key_id: object::Id,
template_id: object::Id,
algorithm: A,
timestamp: u32,
signature: [u8; 32],
request: Vec<u8>,
) -> Result<ssh::Certificate, Error>
where
A: Into<Algorithm>,
{
Ok(self
.send_command(SignSshCertificateCommand {
key_id,
template_id,
algorithm: algorithm.into(),
timestamp,
signature,
request,
})?
.into())
}
pub fn unwrap_data<M>(&self, wrap_key_id: object::Id, wrap_message: M) -> Result<Vec<u8>, Error>
where
M: Into<wrap::Message>,
{
let wrap::Message { nonce, ciphertext } = wrap_message.into();
Ok(self
.send_command(UnwrapDataCommand {
wrap_key_id,
nonce,
ciphertext,
})?
.0)
}
pub fn verify_hmac<M, T>(&self, key_id: object::Id, msg: M, tag: T) -> Result<(), Error>
where
M: Into<Vec<u8>>,
T: Into<hmac::Tag>,
{
let result = self.send_command(VerifyHmacCommand {
key_id,
tag: tag.into(),
data: msg.into(),
})?;
if result.0 == 1 {
Ok(())
} else {
Err(format_err!(
ErrorKind::ResponseError,
"HMAC verification failure"
))
}
}
pub fn wrap_data(
&self,
wrap_key_id: object::Id,
plaintext: Vec<u8>,
) -> Result<wrap::Message, Error> {
Ok(self
.send_command(WrapDataCommand {
wrap_key_id,
plaintext,
})?
.0)
}
}