use std::path::PathBuf;
use std::{sync::Arc, time::Duration};
use tokio::sync::Mutex;
use crate::protocol::DigestAlgorithm;
use crate::protocol::Signature;
use crate::{
error::{ClientError, ConnectionError},
nestls::Nestls,
protocol::{self, Request, Response, Role},
};
mod config;
mod inner;
mod proxy;
pub use config::{Config, Key};
pub use proxy::{ProxyClient, proxy};
#[derive(Clone, Debug)]
pub struct Client {
config: Arc<Config>,
keys: Arc<Mutex<Vec<Key>>>,
inner: Arc<Mutex<Option<inner::Client>>>,
}
impl Client {
pub fn new(config: Config) -> Result<Self, ClientError> {
let keys = config.keys.clone();
Ok(Self {
config: Arc::new(config),
keys: Arc::new(Mutex::new(keys)),
inner: Arc::new(Mutex::new(None)),
})
}
pub fn config(&self) -> &Config {
&self.config
}
async fn reconnecting_send(&self, request: Request) -> Result<protocol::Response, ClientError> {
loop {
let mut service_lock = self.inner.lock().await;
let response = if let Some(mut service) = service_lock.take() {
match tokio::time::timeout(
self.config.request_timeout,
service.send(request.clone()),
)
.await
{
Ok(Ok(response)) => {
*service_lock = Some(service);
Some(response)
}
Ok(Err(ClientError::Connection(ConnectionError::Io(error)))) => {
tracing::info!(
?error,
"An I/O error occurred while connecting; retrying..."
);
tokio::time::sleep(Duration::from_secs(3)).await;
None
}
Ok(Err(error)) => break Err(error),
Err(_timeout_err) => {
tracing::warn!(
"Timed out while attempting to send request; restarting connection..."
);
None
}
}
} else {
if let Some(client) = self.new_inner_client().await? {
*service_lock = Some(client);
} else {
tracing::warn!(
"Timed out while attempting to connect with the server; retrying..."
);
}
None
};
drop(service_lock);
if let Some(response) = response {
match tokio::time::timeout(self.config.request_timeout, response).await {
Ok(Ok(response)) => break Ok(response),
Ok(Err(_recv_error)) => {
tracing::warn!("Connection failed before server responded; retrying...");
}
Err(_elapsed) => tracing::warn!(
"Request timed out without a response; retrying on a new connection..."
),
};
self.inner.lock().await.take();
tokio::time::sleep(Duration::from_secs(3)).await;
}
}
}
async fn new_inner_client(&self) -> Result<Option<inner::Client>, ClientError> {
let tls_config = self.config.credentials.ssl_connector()?;
let bridge_ssl = tls_config
.configure()?
.into_ssl(&self.config.bridge_hostname)?;
let server_ssl = tls_config
.configure()?
.into_ssl(&self.config.server_hostname)?;
let conn = match tokio::time::timeout(
Duration::from_secs(15),
Nestls::builder(bridge_ssl, Role::Client).connect(
format!(
"{}:{}",
&self.config.bridge_hostname, self.config.bridge_port
),
server_ssl,
),
)
.await
{
Ok(conn) => conn,
Err(_elapsed) => {
tracing::warn!(
"Timed out while attempting to connect with the server; retrying..."
);
return Ok(None);
}
};
let conn = conn?;
let mut client = inner::Client::new(conn);
let keys = self.keys.lock().await.clone();
for key in keys {
let request = protocol::Request::Unlock {
key: key.key_name.clone(),
password: key.password(),
};
match tokio::time::timeout(self.config.request_timeout, client.send(request)).await {
Ok(Ok(pending_response)) => {
let response = match tokio::time::timeout(
self.config.request_timeout,
pending_response,
)
.await
{
Ok(Ok(response)) => response,
Ok(Err(_error)) => {
tracing::warn!(
"Connection failed before server responded; retrying..."
);
return Ok(None);
}
Err(_elapsed) => {
tracing::warn!(
"Request timed out without a response; retrying on a new connection..."
);
return Ok(None);
}
};
match response {
Response::Unlock {} => {
tracing::debug!(key = key.key_name, "Successfully unlocked key");
}
Response::Error { reason } => return Err(reason.into()),
_other => {
return Err(anyhow::anyhow!("Unexpected response from server").into());
}
};
}
Ok(Err(ClientError::Connection(ConnectionError::Io(error)))) => {
tracing::info!(
?error,
"An I/O error occurred while connecting; retrying..."
);
tokio::time::sleep(Duration::from_secs(3)).await;
return Ok(None);
}
Ok(Err(error)) => {
tracing::error!(
?error,
key = key.key_name,
"failed to unlock configured key"
);
return Err(error);
}
Err(_timeout_err) => {
tracing::warn!(
"Timed out while attempting to send request; restarting connection..."
);
return Ok(None);
}
}
}
Ok(Some(client))
}
pub async fn who_am_i(&self) -> Result<String, ClientError> {
let request = protocol::Request::WhoAmI {};
let response = self.reconnecting_send(request).await?;
match response {
Response::WhoAmI { user } => Ok(user),
Response::Error { reason } => Err(reason.into()),
_other => Err(anyhow::anyhow!("Unexpected response from server").into()),
}
}
pub async fn list_keys(&self) -> Result<Vec<protocol::Key>, ClientError> {
let request = protocol::Request::ListKeys {};
let response = self.reconnecting_send(request).await?;
match response {
Response::ListKeys { keys } => Ok(keys),
Response::Error { reason } => Err(reason.into()),
_other => Err(anyhow::anyhow!("Unexpected response from server").into()),
}
}
pub async fn is_unlocked(&self, key: String) -> bool {
self.keys.lock().await.iter().any(|k| k.key_name == key)
}
pub async fn unlock(&self, key: String, password: String) -> Result<(), ClientError> {
if self.keys.lock().await.iter().any(|k| k.key_name == key) {
return Ok(());
}
let request = protocol::Request::Unlock {
key: key.clone(),
password: password.clone(),
};
let response = self.reconnecting_send(request).await?;
match response {
Response::Unlock {} => {
let mut keys = self.keys.lock().await;
if !keys.iter().any(|k| k.key_name == key) {
keys.push(Key {
key_name: key,
passphrase_path: PathBuf::new(),
passphrase: password.into(),
});
}
Ok(())
}
Response::Error { reason } => Err(reason.into()),
_other => Err(anyhow::anyhow!("Unexpected response from server").into()),
}
}
pub async fn get_key(&self, key: String) -> Result<crate::protocol::Key, ClientError> {
let request = protocol::Request::GetKey { key };
let response = self.reconnecting_send(request).await?;
match response {
Response::GetKey { key } => Ok(key),
Response::Error { reason } => Err(reason.into()),
_other => Err(anyhow::anyhow!("Unexpected response from server").into()),
}
}
pub async fn sign(
&self,
key: String,
digest_algorithm: DigestAlgorithm,
digest: String,
) -> Result<Signature, ClientError> {
let request = protocol::Request::Sign {
key,
digest_algorithm,
digest,
};
let response = self.reconnecting_send(request).await?;
match response {
Response::Sign { signature } => Ok(signature),
Response::Error { reason } => Err(reason.into()),
_other => Err(anyhow::anyhow!("Unexpected response from server").into()),
}
}
pub async fn sign_all(
&self,
key: String,
digests: Vec<(DigestAlgorithm, String)>,
) -> Result<Vec<Signature>, ClientError> {
let request = protocol::Request::SignAll { key, digests };
let response = self.reconnecting_send(request).await?;
match response {
Response::SignPrehashed { signatures } => Ok(signatures),
Response::Error { reason } => Err(reason.into()),
_other => Err(anyhow::anyhow!("Unexpected response from server").into()),
}
}
pub async fn shutdown(self) {
if let Some(client) = self.inner.lock().await.take() {
client.shutdown().await;
}
}
}