use openssl::nid::Nid;
use sequoia_openpgp::cert::CipherSuite;
use serde::{Deserialize, Serialize};
use tokio::{
io::{AsyncRead, AsyncReadExt},
task::JoinError,
};
use tokio_openssl::SslStream;
use tracing::instrument;
use uuid::Uuid;
use zerocopy::{
Immutable, IntoBytes, KnownLayout, TryFromBytes,
byteorder::network_endian::{U32, U64, U128},
};
use crate::error::ConnectionError;
pub const MAGIC: U64 = U64::from_bytes([83, 73, 71, 85, 76, 68, 82, 89]);
pub const PROTOCOL_VERSION: U32 = U32::new(2);
#[derive(IntoBytes, Immutable, KnownLayout, TryFromBytes, Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
#[non_exhaustive]
pub enum Role {
Client = 0,
Server = 1,
}
impl std::fmt::Display for Role {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Role::Client => write!(f, "client"),
Role::Server => write!(f, "server"),
}
}
}
#[derive(IntoBytes, Immutable, KnownLayout, TryFromBytes, Debug, Clone)]
pub(crate) struct ProtocolHeader {
pub(crate) magic: U64,
pub(crate) version: U32,
pub(crate) role: Role,
}
#[derive(IntoBytes, Immutable, KnownLayout, TryFromBytes, Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum BridgeStatus {
Ok = 0,
UnsupportedVersion = 1,
InvalidRole = 2,
MissingCommonName = 3,
MissingMagic = 4,
}
impl std::fmt::Display for BridgeStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BridgeStatus::Ok => write!(f, "OK"),
BridgeStatus::UnsupportedVersion => {
write!(f, "The requested protocol version is not supported")
}
BridgeStatus::InvalidRole => write!(
f,
"The requested role is invalid for the bridge address and port"
),
BridgeStatus::MissingCommonName => {
write!(f, "The client certificate does not contain a CommonName")
}
BridgeStatus::MissingMagic => {
write!(f, "The connection didn't start with the magic number")
}
}
}
}
#[derive(IntoBytes, Immutable, KnownLayout, TryFromBytes, Debug, Clone)]
pub(crate) struct ProtocolAck {
pub(crate) session_id: U128,
status: BridgeStatus,
}
impl ProtocolAck {
pub fn new(status: BridgeStatus) -> Self {
Self {
session_id: U128::new(Uuid::now_v7().as_u128()),
status,
}
}
#[instrument(level = "trace", skip_all, err)]
pub(crate) async fn check<C: AsyncRead + Unpin>(conn: &mut C) -> Result<Uuid, ConnectionError> {
let mut ack_buf = [0_u8; std::mem::size_of::<Self>()];
conn.read_exact(&mut ack_buf).await?;
let ack = Self::try_ref_from_bytes(&ack_buf)?;
let session_id = Uuid::from_u128(ack.session_id.get());
tracing::debug!(?session_id, status=?ack.status, "Bridge acknowledgement received");
match ack.status {
BridgeStatus::Ok => Ok(session_id),
BridgeStatus::MissingCommonName => Err(Error::MissingCommonName.into()),
other => Err(Error::Bridge(other.to_string()).into()),
}
}
}
impl ProtocolHeader {
pub(crate) fn new(role: Role) -> Self {
Self {
magic: MAGIC,
version: PROTOCOL_VERSION,
role,
}
}
pub(crate) fn check(&self, expected_role: Role) -> BridgeStatus {
if self.magic != MAGIC {
BridgeStatus::MissingMagic
} else if self.version != PROTOCOL_VERSION {
BridgeStatus::UnsupportedVersion
} else if self.role != expected_role {
BridgeStatus::InvalidRole
} else {
BridgeStatus::Ok
}
}
}
impl From<Role> for ProtocolHeader {
fn from(role: Role) -> Self {
ProtocolHeader::new(role)
}
}
pub(crate) fn peer_common_name<S>(stream: &SslStream<S>) -> Result<String, Error> {
stream
.ssl()
.peer_certificate()
.and_then(|cert| {
cert.subject_name()
.entries_by_nid(Nid::COMMONNAME)
.next()
.and_then(|entry| entry.data().to_string().ok())
})
.ok_or(Error::MissingCommonName)
}
#[derive(Debug, thiserror::Error, PartialEq)]
#[non_exhaustive]
pub enum Error {
#[error("The peer's certificate does not include a Common Name")]
MissingCommonName,
#[error("The frame was invalid: {0}")]
Framing(String),
#[error("The bridge rejected the protocol header: {0}")]
Bridge(String),
}
#[derive(IntoBytes, Immutable, KnownLayout, TryFromBytes, Debug, Clone)]
pub(crate) struct Frame {
pub(crate) json_size: U64,
}
impl Frame {
pub fn new(json_size: u64) -> Self {
Self {
json_size: U64::new(json_size),
}
}
pub fn empty() -> Self {
Self {
json_size: U64::new(0),
}
}
pub fn is_empty(&self) -> bool {
self.json_size.get() == 0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) struct OuterRequest {
pub(crate) session_id: Uuid,
pub(crate) request_id: u64,
pub(crate) request: Request,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) struct OuterResponse {
pub(crate) session_id: Uuid,
pub(crate) request_id: u64,
pub(crate) response: Response,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum Request {
WhoAmI {},
ListKeys {},
Unlock {
key: String,
password: String,
},
Sign {
key: String,
digest_algorithm: DigestAlgorithm,
digest: String,
},
SignAll {
key: String,
digests: Vec<(DigestAlgorithm, String)>,
},
GetKey {
key: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum Response {
WhoAmI {
user: String,
},
ListKeys {
keys: Vec<Key>,
},
Unlock {},
GetKey {
key: Key,
},
Sign {
signature: Signature,
},
SignPrehashed {
signatures: Vec<Signature>,
},
Error {
reason: ServerError,
},
Unsupported,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Signature {
pub signature: SignaturePayload,
pub digest: DigestAlgorithm,
pub hash: String,
}
impl Signature {
pub fn value(&self) -> &[u8] {
self.signature.as_ref()
}
pub fn pkcs11_value(&self) -> Option<Vec<u8>> {
match &self.signature {
SignaturePayload::RSA(pkcs1_15_sig) => Some(pkcs1_15_sig.clone()),
SignaturePayload::P256(ecdsa_sig) => {
let ecdsa_sig = openssl::ecdsa::EcdsaSig::from_der(ecdsa_sig)
.inspect_err(|error| {
tracing::error!(?error, "Failed to parse DER-encoded ECDSASignature");
})
.ok()?;
let r = ecdsa_sig
.r()
.to_vec_padded(32)
.inspect_err(|error| {
tracing::error!(?error, "Failed to pad ECDSA r value");
})
.ok()?;
let s = ecdsa_sig
.s()
.to_vec_padded(32)
.inspect_err(|error| {
tracing::error!(?error, "Failed to pad ECDSA s value");
})
.ok()?;
let mut r_and_s = Vec::with_capacity(64);
r_and_s.extend_from_slice(&r);
r_and_s.extend_from_slice(&s);
Some(r_and_s)
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum SignaturePayload {
#[serde(with = "base64")]
RSA(Vec<u8>),
#[serde(with = "base64")]
P256(Vec<u8>),
}
impl std::ops::Deref for SignaturePayload {
type Target = [u8];
fn deref(&self) -> &Self::Target {
match self {
SignaturePayload::RSA(value) | SignaturePayload::P256(value) => value,
}
}
}
mod base64 {
use serde::{Deserialize, Serialize};
use serde::{Deserializer, Serializer};
pub fn serialize<S: Serializer>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
String::serialize(&openssl::base64::encode_block(value), serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
openssl::base64::decode_block(&String::deserialize(deserializer)?)
.map_err(|error| serde::de::Error::custom(format!("invalid base64: {error:?}")))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Certificate {
pub certificate: String,
pub certificate_type: CertificateType,
pub fingerprint: String,
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum CertificateType {
Pgp,
X509,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Key {
pub name: String,
pub key_algorithm: KeyAlgorithm,
pub handle: String,
pub public_key: String,
pub certificates: Vec<Certificate>,
}
impl Key {
pub fn x509_certificates(&self) -> Vec<Certificate> {
self.certificates
.iter()
.filter(|c| matches!(c.certificate_type, CertificateType::X509))
.cloned()
.collect()
}
pub fn openpgp_certificates(&self) -> Vec<Certificate> {
self.certificates
.iter()
.filter(|c| matches!(c.certificate_type, CertificateType::Pgp,))
.cloned()
.collect()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
#[non_exhaustive]
pub enum KeyAlgorithm {
Rsa2K,
#[default]
Rsa4K,
P256,
}
impl KeyAlgorithm {
pub fn as_str(&self) -> &str {
match self {
KeyAlgorithm::Rsa2K => "rsa2k",
KeyAlgorithm::Rsa4K => "rsa4k",
KeyAlgorithm::P256 => "P256",
}
}
}
impl std::fmt::Display for KeyAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
KeyAlgorithm::Rsa2K => "RSA 2048",
KeyAlgorithm::Rsa4K => "RSA 4096",
KeyAlgorithm::P256 => "NIST P-256",
};
write!(f, "{s}")
}
}
impl From<KeyAlgorithm> for CipherSuite {
fn from(value: KeyAlgorithm) -> Self {
match value {
KeyAlgorithm::Rsa2K => CipherSuite::RSA2k,
KeyAlgorithm::Rsa4K => CipherSuite::RSA4k,
KeyAlgorithm::P256 => CipherSuite::P256,
}
}
}
impl TryFrom<&str> for KeyAlgorithm {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"rsa2k" => Ok(Self::Rsa2K),
"rsa4k" => Ok(Self::Rsa4K),
"P256" => Ok(Self::P256),
_ => Err(anyhow::anyhow!("Unknown key type '{value}'!")),
}
}
}
impl From<String> for KeyAlgorithm {
fn from(value: String) -> Self {
let msg = "The database contains key types the application is unaware \
of; this is either an application bug, or the database migration level does not match \
the application";
Self::try_from(value.as_str()).expect(msg)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum DigestAlgorithm {
Sha256,
Sha512,
Sha3_256,
Sha3_512,
}
impl DigestAlgorithm {
pub fn size(self) -> usize {
let algorithm: openssl::hash::MessageDigest = self.into();
algorithm.size()
}
}
impl std::fmt::Display for DigestAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
DigestAlgorithm::Sha256 => "sha256",
DigestAlgorithm::Sha512 => "sha512",
DigestAlgorithm::Sha3_256 => "sha3-256",
DigestAlgorithm::Sha3_512 => "sha3-512",
};
write!(f, "{name}")
}
}
impl From<DigestAlgorithm> for openssl::hash::MessageDigest {
fn from(value: DigestAlgorithm) -> Self {
match value {
DigestAlgorithm::Sha256 => openssl::hash::MessageDigest::sha256(),
DigestAlgorithm::Sha512 => openssl::hash::MessageDigest::sha512(),
DigestAlgorithm::Sha3_256 => openssl::hash::MessageDigest::sha3_256(),
DigestAlgorithm::Sha3_512 => openssl::hash::MessageDigest::sha3_512(),
}
}
}
impl From<DigestAlgorithm> for &'static openssl::md::MdRef {
fn from(value: DigestAlgorithm) -> Self {
match value {
DigestAlgorithm::Sha256 => openssl::md::Md::sha256(),
DigestAlgorithm::Sha512 => openssl::md::Md::sha512(),
DigestAlgorithm::Sha3_256 => openssl::md::Md::sha3_256(),
DigestAlgorithm::Sha3_512 => openssl::md::Md::sha3_512(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ServerError {
#[error("The user '{0}' does not exist in the database")]
NoSuchUser(String),
#[error("The requested operation requires administrator privileges")]
RequiresAdmin,
#[error("An internal server error occurred; notify an administrator to check the logs")]
Internal,
}
#[cfg(feature = "server")]
impl From<sqlx::Error> for ServerError {
fn from(error: sqlx::Error) -> Self {
tracing::error!(?error, "A database error occurred");
Self::Internal
}
}
impl From<std::io::Error> for ServerError {
fn from(error: std::io::Error) -> Self {
tracing::error!(?error, "An IO error occurred");
Self::Internal
}
}
impl From<anyhow::Error> for ServerError {
fn from(error: anyhow::Error) -> Self {
tracing::error!(?error, "An error occurred");
Self::Internal
}
}
impl From<JoinError> for ServerError {
fn from(error: JoinError) -> Self {
tracing::error!(?error, "tokio task failed to join");
Self::Internal
}
}