use subtle::ConstantTimeEq;
use super::{SessionError, SessionErrorKind::*};
use adapter::Adapter;
use command::{create_session::create_session, Command, CommandType};
use credentials::Credentials;
use error::HsmErrorKind;
use securechannel::{Challenge, CommandMessage, ResponseMessage, SecureChannel, SessionId};
use serialization::deserialize;
pub(super) struct Connection<A: Adapter> {
adapter: A,
secure_channel: Option<SecureChannel>,
}
impl<A: Adapter> Connection<A> {
pub(super) fn open(
config: &A::Config,
credentials: &Credentials,
) -> Result<Self, SessionError> {
let adapter = A::open(config)?;
if let Err(e) = adapter.healthcheck() {
fail!(CreateFailed, e);
}
let host_challenge = Challenge::random();
let (session_id, session_response) =
create_session(&adapter, credentials.auth_key_id, host_challenge)?;
let channel = SecureChannel::new(
session_id,
&credentials.auth_key,
host_challenge,
session_response.card_challenge,
);
if channel
.card_cryptogram()
.ct_eq(&session_response.card_cryptogram)
.unwrap_u8()
!= 1
{
fail!(
AuthFail,
"(session: {}) card cryptogram mismatch!",
channel.id().to_u8()
);
}
let mut connection = Connection {
adapter,
secure_channel: Some(channel),
};
connection.authenticate(credentials)?;
Ok(connection)
}
#[inline]
pub(super) fn id(&self) -> Option<SessionId> {
self.secure_channel.as_ref().map(|c| c.id())
}
pub(super) fn adapter(&self) -> &A {
&self.adapter
}
pub(super) fn send_command<T: Command>(
&mut self,
command: T,
) -> Result<T::ResponseType, SessionError> {
let plaintext_cmd: CommandMessage = command.into();
let cmd_type = plaintext_cmd.command_type;
let encrypted_cmd = self.secure_channel()?.encrypt_command(plaintext_cmd)?;
let uuid = encrypted_cmd.uuid;
session_debug!(self, "uuid={} cmd={:?}", uuid, T::COMMAND_TYPE);
let encrypted_response = self.send_message(encrypted_cmd)?;
let response = self
.secure_channel()?
.decrypt_response(encrypted_response)
.map_err(|e| {
self.secure_channel = None;
e
})?;
if response.is_err() {
if let Some(kind) = HsmErrorKind::from_response_message(&response) {
session_debug!(self, "uuid={} failed={:?} error={:?}", uuid, cmd_type, kind);
return Err(kind.into());
} else {
session_debug!(self, "uuid={} failed={:?} error=unknown", uuid, cmd_type);
fail!(ResponseError, "{:?} failed: HSM error", cmd_type);
}
}
if response.command() != Some(T::COMMAND_TYPE) {
fail!(
ResponseError,
"bad command type in response: {:?} (expected {:?})",
response.command(),
T::COMMAND_TYPE,
);
}
deserialize(response.data.as_ref()).map_err(|e| e.into())
}
fn send_message(&mut self, cmd: CommandMessage) -> Result<ResponseMessage, SessionError> {
let cmd_type = cmd.command_type;
let uuid = cmd.uuid;
session_debug!(self, "uuid={} command={:?}", &uuid, cmd_type);
let response = match self.adapter.send_message(uuid, cmd.into()) {
Ok(response_bytes) => ResponseMessage::parse(response_bytes)?,
Err(e) => {
self.secure_channel = None;
return Err(e.into());
}
};
if response.is_err() || response.command() != Some(cmd_type) {
session_error!(self, "uuid={} error={:?}", &uuid, response.code);
fail!(
ResponseError,
"HSM error (session: {})",
self.id().unwrap().to_u8(),
);
}
Ok(response)
}
fn authenticate(&mut self, credentials: &Credentials) -> Result<(), SessionError> {
session_debug!(
self,
"command={:?} key={}",
CommandType::AuthSession,
credentials.auth_key_id
);
let command = self.secure_channel()?.authenticate_session()?;
let response = self.send_message(command)?;
if let Err(e) = self
.secure_channel()?
.finish_authenticate_session(&response)
{
session_error!(
self,
"failed={:?} key={} err={:?}",
CommandType::AuthSession,
credentials.auth_key_id,
e.to_string()
);
return Err(e.into());
}
session_debug!(self, "auth=OK key={}", credentials.auth_key_id);
Ok(())
}
fn secure_channel(&mut self) -> Result<&mut SecureChannel, SessionError> {
self.secure_channel
.as_mut()
.ok_or_else(|| err!(ClosedSessionError, "session is already closed"))
}
}