use async_trait::async_trait;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum CryptoError {
#[error("Invalid signature")]
InvalidSignature,
#[error("Invalid public key length: expected {expected}, got {actual}")]
InvalidKeyLength { expected: usize, actual: usize },
#[error("Invalid private key: {0}")]
InvalidPrivateKey(String),
#[error("Crypto operation failed: {0}")]
OperationFailed(String),
#[error("Operation not supported on current compilation target")]
UnsupportedTarget,
}
impl crate::AuthsErrorInfo for CryptoError {
fn error_code(&self) -> &'static str {
match self {
Self::InvalidSignature => "AUTHS-E1001",
Self::InvalidKeyLength { .. } => "AUTHS-E1002",
Self::InvalidPrivateKey(_) => "AUTHS-E1003",
Self::OperationFailed(_) => "AUTHS-E1004",
Self::UnsupportedTarget => "AUTHS-E1005",
}
}
fn suggestion(&self) -> Option<&'static str> {
match self {
Self::InvalidSignature => Some("The signature does not match the data or public key"),
Self::InvalidKeyLength { .. } => Some(
"Ensure the key length matches the declared curve (32 bytes Ed25519, 33 bytes P-256 compressed SEC1)",
),
Self::UnsupportedTarget => {
Some("This operation is not available on the current platform")
}
_ => None,
}
}
}
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct SecureSeed([u8; 32]);
impl SecureSeed {
pub fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
impl std::fmt::Debug for SecureSeed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("SecureSeed([REDACTED])")
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait CryptoProvider: Send + Sync {
async fn verify_ed25519(
&self,
pubkey: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), CryptoError>;
async fn verify_p256(
&self,
_pubkey: &[u8],
_message: &[u8],
_signature: &[u8],
) -> Result<(), CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn sign_ed25519(&self, seed: &SecureSeed, message: &[u8])
-> Result<Vec<u8>, CryptoError>;
async fn generate_ed25519_keypair(&self) -> Result<(SecureSeed, [u8; 32]), CryptoError>;
async fn ed25519_public_key_from_seed(
&self,
seed: &SecureSeed,
) -> Result<[u8; 32], CryptoError>;
async fn sign_p256(&self, _seed: &SecureSeed, _message: &[u8]) -> Result<Vec<u8>, CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn generate_p256_keypair(&self) -> Result<(SecureSeed, Vec<u8>), CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn p256_public_key_from_seed(&self, _seed: &SecureSeed) -> Result<Vec<u8>, CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn aead_encrypt(
&self,
_key: &[u8; 32],
_nonce: &[u8; 12],
_aad: &[u8],
_plaintext: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn aead_decrypt(
&self,
_key: &[u8; 32],
_nonce: &[u8; 12],
_aad: &[u8],
_ciphertext: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn hkdf_sha256_expand(
&self,
_ikm: &[u8],
_salt: &[u8],
_info: &[u8],
_out_len: usize,
) -> Result<Vec<u8>, CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn hkdf_sha384_expand(
&self,
_ikm: &[u8],
_salt: &[u8],
_info: &[u8],
_out_len: usize,
) -> Result<Vec<u8>, CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn hmac_sha256_compute(&self, _key: &[u8], _msg: &[u8]) -> Result<[u8; 32], CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn hmac_sha256_verify(
&self,
_key: &[u8],
_msg: &[u8],
_tag: &[u8],
) -> Result<(), CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn hmac_sha384_compute(&self, _key: &[u8], _msg: &[u8]) -> Result<[u8; 48], CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn hmac_sha384_verify(
&self,
_key: &[u8],
_msg: &[u8],
_tag: &[u8],
) -> Result<(), CryptoError> {
Err(CryptoError::UnsupportedTarget)
}
async fn sign_typed(
&self,
seed: &crate::key_ops::TypedSeed,
message: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let secure = SecureSeed::new(*seed.as_bytes());
match seed.curve() {
CurveType::Ed25519 => self.sign_ed25519(&secure, message).await,
CurveType::P256 => self.sign_p256(&secure, message).await,
}
}
async fn verify_typed(
&self,
curve: CurveType,
pubkey: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), CryptoError> {
match curve {
CurveType::Ed25519 => self.verify_ed25519(pubkey, message, signature).await,
CurveType::P256 => self.verify_p256(pubkey, message, signature).await,
}
}
async fn generate_typed_keypair(
&self,
curve: CurveType,
) -> Result<(crate::key_ops::TypedSeed, Vec<u8>), CryptoError> {
match curve {
CurveType::Ed25519 => {
let (seed, pk) = self.generate_ed25519_keypair().await?;
Ok((
crate::key_ops::TypedSeed::Ed25519(*seed.as_bytes()),
pk.to_vec(),
))
}
CurveType::P256 => {
let (seed, pk) = self.generate_p256_keypair().await?;
Ok((crate::key_ops::TypedSeed::P256(*seed.as_bytes()), pk))
}
}
}
async fn typed_public_key_from_seed(
&self,
seed: &crate::key_ops::TypedSeed,
) -> Result<Vec<u8>, CryptoError> {
let secure = SecureSeed::new(*seed.as_bytes());
match seed.curve() {
CurveType::Ed25519 => {
let pk = self.ed25519_public_key_from_seed(&secure).await?;
Ok(pk.to_vec())
}
CurveType::P256 => self.p256_public_key_from_seed(&secure).await,
}
}
}
#[cfg(all(feature = "fips", target_arch = "wasm32"))]
compile_error!(
"auths-crypto feature `fips` is incompatible with `target_arch = wasm32`. \
Build the verifier with default features (RustCrypto + ring)."
);
#[cfg(all(feature = "fips", not(target_arch = "wasm32")))]
pub fn default_provider() -> &'static dyn CryptoProvider {
&crate::aws_lc_provider::AwsLcProvider
}
#[cfg(all(feature = "cnsa", not(feature = "fips"), not(target_arch = "wasm32")))]
pub fn default_provider() -> &'static dyn CryptoProvider {
&crate::cnsa_provider::CnsaProvider
}
#[cfg(all(
feature = "native",
not(feature = "fips"),
not(feature = "cnsa"),
not(target_arch = "wasm32")
))]
pub fn default_provider() -> &'static dyn CryptoProvider {
&crate::ring_provider::RingCryptoProvider
}
#[derive(Debug, thiserror::Error)]
pub enum SeedDecodeError {
#[error("invalid hex encoding: {0}")]
InvalidHex(hex::FromHexError),
#[error("expected {expected} bytes, got {got}")]
WrongLength {
expected: usize,
got: usize,
},
}
pub fn decode_seed_hex(hex_str: &str) -> Result<SecureSeed, SeedDecodeError> {
let bytes = hex::decode(hex_str).map_err(SeedDecodeError::InvalidHex)?;
let arr: [u8; 32] = bytes
.try_into()
.map_err(|v: Vec<u8>| SeedDecodeError::WrongLength {
expected: 32,
got: v.len(),
})?;
Ok(SecureSeed::new(arr))
}
pub const ED25519_PUBLIC_KEY_LEN: usize = 32;
pub const ED25519_SIGNATURE_LEN: usize = 64;
pub const P256_PUBLIC_KEY_LEN: usize = 33;
pub const P256_SIGNATURE_LEN: usize = 64;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "lowercase")]
pub enum CurveType {
Ed25519,
#[default]
P256,
}
impl std::fmt::Display for CurveType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ed25519 => f.write_str("ed25519"),
Self::P256 => f.write_str("p256"),
}
}
}
impl CurveType {
pub fn public_key_len(&self) -> usize {
match self {
Self::Ed25519 => ED25519_PUBLIC_KEY_LEN,
Self::P256 => P256_PUBLIC_KEY_LEN,
}
}
pub fn signature_len(&self) -> usize {
match self {
Self::Ed25519 => ED25519_SIGNATURE_LEN,
Self::P256 => P256_SIGNATURE_LEN,
}
}
}