extern crate tiny_http;
use std::collections::HashMap;
use std::io::Cursor;
use ed25519_dalek::Keypair as Ed25519Keypair;
use failure::Error;
use rand::OsRng;
use sha2::Sha512;
use self::tiny_http::{Method, Request, Server, StatusCode};
use self::tiny_http::Response as HttpResponse;
use {Algorithm, Capabilities, Domains, ObjectId, ObjectLabel, ObjectOrigin, ObjectType,
SequenceId, SessionId};
use commands::*;
use session::{PBKDF2_ITERATIONS, PBKDF2_SALT};
use responses::*;
use securechannel::{Challenge, Channel, CommandMessage, CommandType, ResponseMessage, StaticKeys};
use serializers::deserialize;
const DEFAULT_AUTH_KEY_ID: ObjectId = 1;
const DEFAULT_PASSWORD: &str = "password";
pub struct MockHSM {
addr: String,
server: Server,
static_keys: StaticKeys,
sessions: HashMap<SessionId, Channel>,
objects: Objects,
}
#[derive(Default)]
pub struct Objects {
ed25519_keys: HashMap<ObjectId, Object<Ed25519Keypair>>,
}
impl Objects {
pub fn new() -> Self {
Objects {
ed25519_keys: HashMap::new(),
}
}
}
struct Object<T> {
value: T,
object_type: ObjectType,
algorithm: Algorithm,
capabilities: Capabilities,
delegated_capabilities: Capabilities,
domains: Domains,
length: u16,
sequence: SequenceId,
origin: ObjectOrigin,
label: ObjectLabel,
}
impl Object<Ed25519Keypair> {
pub fn new(label: ObjectLabel, capabilities: Capabilities, domains: Domains) -> Self {
let mut cspring = OsRng::new().unwrap();
Self {
value: Ed25519Keypair::generate::<Sha512>(&mut cspring),
object_type: ObjectType::Asymmetric,
algorithm: Algorithm::EC_ED25519,
capabilities,
delegated_capabilities: Capabilities::default(),
domains,
length: 24,
sequence: 1,
origin: ObjectOrigin::Generated,
label,
}
}
}
impl MockHSM {
pub fn new(addr: &str) -> Result<Self, Error> {
let server = Server::http(addr)
.or_else(|e| Err(format_err!("error creating MockHSM server: {:?}", e)))?;
Ok(Self {
addr: addr.to_owned(),
server,
static_keys: StaticKeys::derive_from_password(
DEFAULT_PASSWORD.as_bytes(),
PBKDF2_SALT,
PBKDF2_ITERATIONS,
),
sessions: HashMap::new(),
objects: Objects::new(),
})
}
pub fn run(&mut self, num_requests: usize) {
for _ in 0..num_requests {
let mut request = self.server.recv().unwrap();
let response = match *request.method() {
Method::Get => match request.url() {
"/connector/status" => Some(self.status()),
_ => None,
},
Method::Post => match request.url() {
"/connector/api" => Some(self.api(&mut request)),
_ => None,
},
_ => None,
}.unwrap_or_else(|| {
HttpResponse::new(
StatusCode::from(404),
vec![],
Cursor::new(vec![]),
None,
None,
)
});
request.respond(response).unwrap();
}
}
fn status(&self) -> HttpResponse<Cursor<Vec<u8>>> {
let mut addr_parts = self.addr.split(':');
HttpResponse::from_string(
[
"status=OK",
"serial=*",
"version=1.0.1",
"pid=12345",
&format!("address={}", addr_parts.next().unwrap()),
&format!("port={}", addr_parts.next().unwrap()),
].join("\n"),
)
}
fn api(&mut self, request: &mut Request) -> HttpResponse<Cursor<Vec<u8>>> {
let mut body = Vec::new();
request
.as_reader()
.read_to_end(&mut body)
.expect("HTTP request read error");
let command = CommandMessage::parse(body).unwrap();
match command.command_type {
CommandType::CreateSession => self.create_session(&command),
CommandType::AuthSession => self.authenticate_session(&command),
CommandType::SessionMessage => self.session_message(command),
unsupported => panic!("unsupported command type: {:?}", unsupported),
}
}
fn create_session(&mut self, cmd_message: &CommandMessage) -> HttpResponse<Cursor<Vec<u8>>> {
let cmd: CreateSessionCommand = deserialize(cmd_message.data.as_ref())
.unwrap_or_else(|e| panic!("error parsing CreateSession command data: {:?}", e));
assert_eq!(
cmd.auth_key_id, DEFAULT_AUTH_KEY_ID,
"unexpected auth key ID: {}",
cmd.auth_key_id
);
let card_challenge = Challenge::random();
let session_id = self.sessions
.keys()
.max()
.map(|id| id.succ().expect("session count exceeded"))
.unwrap_or_else(|| SessionId::new(0).unwrap());
let channel = Channel::new(
session_id,
&self.static_keys,
&cmd.host_challenge,
&card_challenge,
);
let card_cryptogram = channel.card_cryptogram();
assert!(self.sessions.insert(session_id, channel).is_none());
let mut response = CreateSessionResponse {
card_challenge,
card_cryptogram,
}.serialize();
response.session_id = Some(session_id);
let response_bytes: Vec<u8> = response.into();
HttpResponse::from_data(response_bytes)
}
fn authenticate_session(&mut self, command: &CommandMessage) -> HttpResponse<Cursor<Vec<u8>>> {
let session_id = command
.session_id
.unwrap_or_else(|| panic!("no session ID in command: {:?}", command.command_type));
let response_bytes: Vec<u8> = self.channel(&session_id)
.verify_authenticate_session(command)
.unwrap()
.into();
HttpResponse::from_data(response_bytes)
}
fn session_message(
&mut self,
encrypted_command: CommandMessage,
) -> HttpResponse<Cursor<Vec<u8>>> {
let session_id = encrypted_command.session_id.unwrap_or_else(|| {
panic!(
"no session ID in command: {:?}",
encrypted_command.command_type
)
});
let command = self.channel(&session_id)
.decrypt_command(encrypted_command)
.unwrap();
let response = match command.command_type {
CommandType::DeleteObject => self.delete_object(&command.data),
CommandType::Echo => self.echo(&command.data),
CommandType::GenAsymmetricKey => self.gen_asymmetric_key(&command.data),
CommandType::GetObjectInfo => self.get_object_info(&command.data),
CommandType::GetPubKey => self.get_pubkey(&command.data),
CommandType::ListObjects => self.list_objects(&command.data),
CommandType::SignDataEdDSA => self.sign_data_eddsa(&command.data),
unsupported => panic!("unsupported command type: {:?}", unsupported),
};
let encrypted_response: Vec<u8> = self.channel(&session_id)
.encrypt_response(response)
.unwrap()
.into();
HttpResponse::from_data(encrypted_response)
}
fn delete_object(&mut self, cmd_data: &[u8]) -> ResponseMessage {
let command: DeleteObjectCommand = deserialize(cmd_data)
.unwrap_or_else(|e| panic!("error parsing CommandType::DeleteObject: {:?}", e));
match command.object_type {
ObjectType::Asymmetric => match self.objects.ed25519_keys.remove(&command.object_id) {
Some(_) => DeleteObjectResponse {}.serialize(),
None => {
ResponseMessage::error(&format!("no such object ID: {:?}", command.object_id))
}
},
_ => panic!("MockHSM only supports delete_object for ObjectType::Asymmetric"),
}
}
fn echo(&self, cmd_data: &[u8]) -> ResponseMessage {
EchoResponse {
message: cmd_data.into(),
}.serialize()
}
fn gen_asymmetric_key(&mut self, cmd_data: &[u8]) -> ResponseMessage {
let command: GenAsymmetricKeyCommand = deserialize(cmd_data)
.unwrap_or_else(|e| panic!("error parsing CommandType::GetObjectInfo: {:?}", e));
match command.algorithm {
Algorithm::EC_ED25519 => {
let key = Object::new(command.label, command.capabilities, command.domains);
assert!(
self.objects
.ed25519_keys
.insert(command.key_id, key)
.is_none()
);
}
other => panic!("unsupported algorithm: {:?}", other),
}
GenAsymmetricKeyResponse {
key_id: command.key_id,
}.serialize()
}
fn get_object_info(&self, cmd_data: &[u8]) -> ResponseMessage {
let command: GetObjectInfoCommand = deserialize(cmd_data)
.unwrap_or_else(|e| panic!("error parsing CommandType::GetObjectInfo: {:?}", e));
if command.object_type != ObjectType::Asymmetric {
panic!("MockHSM only supports ObjectType::Asymmetric for now");
}
match self.objects.ed25519_keys.get(&command.object_id) {
Some(key) => GetObjectInfoResponse {
capabilities: key.capabilities,
id: command.object_id,
length: key.length,
domains: key.domains,
object_type: key.object_type,
algorithm: key.algorithm,
sequence: key.sequence,
origin: key.origin,
label: key.label.clone(),
delegated_capabilities: key.delegated_capabilities,
}.serialize(),
None => ResponseMessage::error(&format!("no such object ID: {:?}", command.object_id)),
}
}
fn get_pubkey(&self, cmd_data: &[u8]) -> ResponseMessage {
let command: GetPubKeyCommand = deserialize(cmd_data)
.unwrap_or_else(|e| panic!("error parsing CommandType::GetPubKey: {:?}", e));
match self.objects.ed25519_keys.get(&command.key_id) {
Some(key) => GetPubKeyResponse {
algorithm: Algorithm::EC_ED25519,
data: Vec::from(key.value.public.as_bytes().as_ref()),
}.serialize(),
None => ResponseMessage::error(&format!("no such object ID: {:?}", command.key_id)),
}
}
fn list_objects(&self, cmd_data: &[u8]) -> ResponseMessage {
let _command: ListObjectsCommand = deserialize(cmd_data)
.unwrap_or_else(|e| panic!("error parsing CommandType::ListObjects: {:?}", e));
let list_entries = self.objects
.ed25519_keys
.iter()
.map(|(object_id, object)| ListObjectsEntry {
id: *object_id,
object_type: object.object_type,
sequence: object.sequence,
})
.collect();
ListObjectsResponse {
objects: list_entries,
}.serialize()
}
fn sign_data_eddsa(&self, cmd_data: &[u8]) -> ResponseMessage {
let command: SignDataEdDSACommand = deserialize(cmd_data)
.unwrap_or_else(|e| panic!("error parsing CommandType::SignDataEdDSA: {:?}", e));
match self.objects.ed25519_keys.get(&command.key_id) {
Some(key) => {
let signature = key.value.sign::<Sha512>(command.data.as_ref()).to_bytes();
SignDataEdDSAResponse {
signature: Vec::from(signature.as_ref()),
}.serialize()
}
None => ResponseMessage::error(&format!("no such object ID: {:?}", command.key_id)),
}
}
fn channel(&mut self, id: &SessionId) -> &mut Channel {
self.sessions
.get_mut(id)
.unwrap_or_else(|| panic!("invalid session ID: {:?}", id))
}
}