use std::sync::Arc;
use boatramp_core::cose::{LocalSigner, Signer, TokenAlg};
#[cfg(feature = "signer-aws")]
mod aws_kms;
#[cfg(feature = "signer-azure")]
mod azure;
#[cfg(feature = "signer-gcp")]
mod gcp;
#[cfg(feature = "signer-pkcs11")]
mod pkcs11;
#[cfg(any(
feature = "signer-vault",
feature = "signer-gcp",
feature = "signer-azure"
))]
mod rest;
#[cfg(feature = "signer-vault")]
mod vault;
#[derive(Debug, thiserror::Error)]
pub enum SignerError {
#[error("signer backend `{0}` is not enabled in this build")]
Unsupported(&'static str),
#[error("environment variable `{0}` is not set")]
MissingEnv(String),
#[error("unsupported algorithm for this backend: {0:?}")]
UnsupportedAlg(TokenAlg),
#[error("key: {0}")]
Key(String),
#[error("backend `{backend}`: {message}")]
Backend {
backend: &'static str,
message: String,
},
}
impl SignerError {
#[allow(dead_code)]
pub(crate) fn backend(backend: &'static str, message: impl std::fmt::Display) -> Self {
Self::Backend {
backend,
message: message.to_string(),
}
}
#[allow(dead_code)]
pub(crate) fn env(name: &str) -> Result<String, Self> {
std::env::var(name).map_err(|_| Self::MissingEnv(name.to_string()))
}
}
#[derive(Debug, Clone)]
pub enum SignerConfig {
Local {
private_key: String,
},
Vault {
address: String,
key: String,
token_env: String,
alg: TokenAlg,
},
AwsKms {
key_id: String,
region: Option<String>,
},
GcpKms {
key_version: String,
access_token_env: String,
},
AzureKv {
vault_url: String,
key: String,
key_version: String,
access_token_env: String,
},
Pkcs11 {
module: String,
token_label: String,
key_label: String,
pin_env: String,
alg: TokenAlg,
},
}
pub async fn build_signer(config: &SignerConfig) -> Result<Arc<dyn Signer>, SignerError> {
match config {
SignerConfig::Local { private_key } => Ok(Arc::new(
LocalSigner::from_private_hex(private_key)
.map_err(|e| SignerError::Key(e.to_string()))?,
)),
#[cfg(feature = "signer-vault")]
SignerConfig::Vault {
address,
key,
token_env,
alg,
} => Ok(Arc::new(
vault::VaultSigner::connect(address, key, token_env, *alg).await?,
)),
#[cfg(not(feature = "signer-vault"))]
SignerConfig::Vault { .. } => Err(SignerError::Unsupported("vault")),
#[cfg(feature = "signer-aws")]
SignerConfig::AwsKms { key_id, region } => Ok(Arc::new(
aws_kms::AwsKmsSigner::connect(key_id, region.as_deref()).await?,
)),
#[cfg(not(feature = "signer-aws"))]
SignerConfig::AwsKms { .. } => Err(SignerError::Unsupported("aws-kms")),
#[cfg(feature = "signer-gcp")]
SignerConfig::GcpKms {
key_version,
access_token_env,
} => Ok(Arc::new(
gcp::GcpKmsSigner::connect(key_version, access_token_env).await?,
)),
#[cfg(not(feature = "signer-gcp"))]
SignerConfig::GcpKms { .. } => Err(SignerError::Unsupported("gcp-kms")),
#[cfg(feature = "signer-azure")]
SignerConfig::AzureKv {
vault_url,
key,
key_version,
access_token_env,
} => Ok(Arc::new(
azure::AzureKvSigner::connect(vault_url, key, key_version, access_token_env).await?,
)),
#[cfg(not(feature = "signer-azure"))]
SignerConfig::AzureKv { .. } => Err(SignerError::Unsupported("azure-kv")),
#[cfg(feature = "signer-pkcs11")]
SignerConfig::Pkcs11 {
module,
token_label,
key_label,
pin_env,
alg,
} => Ok(Arc::new(pkcs11::Pkcs11Signer::connect(
module,
token_label,
key_label,
pin_env,
*alg,
)?)),
#[cfg(not(feature = "signer-pkcs11"))]
SignerConfig::Pkcs11 { .. } => Err(SignerError::Unsupported("pkcs11")),
}
}
#[cfg(any(
feature = "signer-gcp",
feature = "signer-azure",
feature = "signer-pkcs11"
))]
pub(crate) fn sha256(data: &[u8]) -> Vec<u8> {
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, data)
.as_ref()
.to_vec()
}
#[cfg(all(
test,
any(
feature = "signer-vault",
feature = "signer-gcp",
feature = "signer-azure",
feature = "signer-aws",
feature = "signer-pkcs11"
)
))]
pub(crate) async fn assert_signs_and_verifies(signer: &dyn Signer) {
use boatramp_core::authz::GrantedRole;
use boatramp_core::cose::{self, Claims};
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let claims = Claims {
roles: vec![GrantedRole::global("admin")],
kind: cose::KIND_ROLE.to_string(),
ttl_secs: Some(300),
now_unix: now,
};
let token = cose::mint(&claims, signer)
.await
.expect("mint through the external signer");
let verified =
cose::verify(&token, &signer.public_key(), now).expect("verify against the resolved key");
assert_eq!(verified.roles, vec![GrantedRole::global("admin")]);
assert_eq!(verified.kind, cose::KIND_ROLE);
}