mod error;
use failure::Error;
use commands::*;
use connector::Connector;
#[cfg(feature = "reqwest-connector")]
use connector::ReqwestConnector;
use responses::*;
use securechannel::{Challenge, Channel, CommandMessage, ResponseCode, ResponseMessage, StaticKeys};
use serializers::deserialize;
use super::{Algorithm, Capabilities, Domains, ObjectId, ObjectLabel, ObjectType, SessionId};
pub use self::error::SessionError;
pub const PBKDF2_SALT: &[u8] = b"Yubico";
pub const PBKDF2_ITERATIONS: usize = 10_000;
const CONNECTOR_STATUS_OK: &str = "OK";
#[cfg(feature = "reqwest-connector")]
pub type Session = AbstractSession<ReqwestConnector>;
pub struct AbstractSession<C: Connector> {
id: SessionId,
channel: Channel,
connector: C,
#[allow(dead_code)]
static_keys: Option<StaticKeys>,
}
impl<C: Connector> AbstractSession<C> {
pub fn create(
connector_url: &str,
auth_key_id: ObjectId,
static_keys: StaticKeys,
reconnect: bool,
) -> Result<Self, Error> {
let connector = C::open(connector_url)?;
let status = connector.status()?;
if status.message != CONNECTOR_STATUS_OK {
fail!(
SessionError::CreateFailed,
"bad status response from {}: {}",
connector_url,
status.message
);
}
let host_challenge = Challenge::random();
Self::new(
connector,
&host_challenge,
auth_key_id,
static_keys,
reconnect,
)
}
pub fn create_from_password(
connector_url: &str,
auth_key_id: ObjectId,
password: &str,
reconnect: bool,
) -> Result<Self, Error> {
Self::create(
connector_url,
auth_key_id,
StaticKeys::derive_from_password(password.as_bytes(), PBKDF2_SALT, PBKDF2_ITERATIONS),
reconnect,
)
}
pub fn new(
connector: C,
host_challenge: &Challenge,
auth_key_id: ObjectId,
static_keys: StaticKeys,
reconnect: bool,
) -> Result<Self, Error> {
let command_message: CommandMessage = CreateSessionCommand {
auth_key_id,
host_challenge: *host_challenge,
}.into();
let response_message =
ResponseMessage::parse(connector.send_command(command_message.into())?)?;
if response_message.is_err() {
fail!(
SessionError::ResponseError,
"HSM error: {:?}",
response_message.code
);
}
if response_message.command().unwrap() != CommandType::CreateSession {
fail!(
SessionError::ProtocolError,
"command type mismatch: expected {:?}, got {:?}",
CommandType::CreateSession,
response_message.command().unwrap()
);
}
let session_id = response_message
.session_id
.ok_or_else(|| err!(SessionError::CreateFailed, "no session ID in response"))?;
let response: CreateSessionResponse = deserialize(response_message.data.as_ref())?;
let channel = Channel::new(
session_id,
&static_keys,
host_challenge,
&response.card_challenge,
);
if channel.card_cryptogram() != response.card_cryptogram {
fail!(SessionError::AuthFailed, "card cryptogram mismatch!");
}
let static_keys_option = if reconnect { Some(static_keys) } else { None };
let mut session = Self {
id: session_id,
channel,
connector,
static_keys: static_keys_option,
};
session.authenticate()?;
Ok(session)
}
pub fn blink(&mut self, num_seconds: u8) -> Result<BlinkResponse, Error> {
self.send_encrypted_command(BlinkCommand { num_seconds })
}
pub fn delete_object(
&mut self,
object_id: ObjectId,
object_type: ObjectType,
) -> Result<DeleteObjectResponse, Error> {
self.send_encrypted_command(DeleteObjectCommand {
object_id,
object_type,
})
}
pub fn echo<T>(&mut self, message: T) -> Result<EchoResponse, Error>
where
T: Into<Vec<u8>>,
{
self.send_encrypted_command(EchoCommand {
message: message.into(),
})
}
pub fn generate_asymmetric_key(
&mut self,
key_id: ObjectId,
label: ObjectLabel,
domains: Domains,
capabilities: Capabilities,
algorithm: Algorithm,
) -> Result<GenAsymmetricKeyResponse, Error> {
self.send_encrypted_command(GenAsymmetricKeyCommand {
key_id,
label,
domains,
capabilities,
algorithm,
})
}
pub fn get_object_info(
&mut self,
object_id: ObjectId,
object_type: ObjectType,
) -> Result<GetObjectInfoResponse, Error> {
self.send_encrypted_command(GetObjectInfoCommand {
object_id,
object_type,
})
}
pub fn get_pubkey(&mut self, key_id: ObjectId) -> Result<GetPubKeyResponse, Error> {
self.send_encrypted_command(GetPubKeyCommand { key_id })
}
pub fn id(&self) -> SessionId {
self.id
}
pub fn list_objects(&mut self) -> Result<ListObjectsResponse, Error> {
self.send_encrypted_command(ListObjectsCommand {})
}
pub fn sign_data_eddsa<T>(
&mut self,
key_id: ObjectId,
data: T,
) -> Result<SignDataEdDSAResponse, Error>
where
T: Into<Vec<u8>>,
{
self.send_encrypted_command(SignDataEdDSACommand {
key_id,
data: data.into(),
})
}
fn authenticate(&mut self) -> Result<(), Error> {
let command = self.channel.authenticate_session()?;
let response = self.send_command(command)?;
self.channel.finish_authenticate_session(&response)
}
fn send_command(&self, cmd: CommandMessage) -> Result<ResponseMessage, Error> {
let cmd_type = cmd.command_type;
let response_bytes = self.connector.send_command(cmd.into())?;
let response = ResponseMessage::parse(response_bytes)?;
if response.is_err() {
fail!(
SessionError::ResponseError,
"HSM error: {:?}",
response.code
);
}
if response.command().unwrap() != cmd_type {
fail!(
SessionError::ProtocolError,
"command type mismatch: expected {:?}, got {:?}",
cmd_type,
response.command().unwrap()
);
}
Ok(response)
}
fn send_encrypted_command<T: Command>(&mut self, command: T) -> Result<T::ResponseType, Error> {
let plaintext_cmd = command.into();
let encrypted_cmd = self.channel.encrypt_command(plaintext_cmd)?;
let encrypted_response = self.send_command(encrypted_cmd)?;
let response = self.channel.decrypt_response(encrypted_response)?;
if response.is_err() {
let description = match response.code {
ResponseCode::MemoryError => "HSM memory error (missing object?)".to_owned(),
other => format!("{:?}", other),
};
fail!(SessionError::ResponseError, description);
}
if response.command().unwrap() != T::COMMAND_TYPE {
fail!(
SessionError::ResponseError,
"command type mismatch: expected {:?}, got {:?}",
T::COMMAND_TYPE,
response.command().unwrap()
);
}
deserialize(response.data.as_ref())
}
}