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", feature = "cnsa"))]
compile_error!(
"auths-crypto features `fips` and `cnsa` are mutually exclusive: enabling \
both silently links the FIPS provider (which accepts P-256 and \
ChaCha20-Poly1305) in place of the CNSA provider — a silent crypto-policy \
downgrade. Enable at most one of `fips`/`cnsa`."
);
#[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"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("unknown curve {got:?}: expected 'p256' or 'ed25519'")]
pub struct ParseCurveError {
pub got: String,
}
impl std::str::FromStr for CurveType {
type Err = ParseCurveError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"ed25519" => Ok(Self::Ed25519),
"p256" | "p-256" => Ok(Self::P256),
_ => Err(ParseCurveError { got: s.to_string() }),
}
}
}
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,
}
}
}
#[cfg(test)]
mod curve_type_tests {
use super::CurveType;
use std::str::FromStr;
#[test]
fn parses_canonical_and_aliased_spellings() {
assert_eq!(CurveType::from_str("ed25519").unwrap(), CurveType::Ed25519);
assert_eq!(CurveType::from_str("Ed25519").unwrap(), CurveType::Ed25519);
assert_eq!(CurveType::from_str("p256").unwrap(), CurveType::P256);
assert_eq!(CurveType::from_str("P-256").unwrap(), CurveType::P256);
}
#[test]
fn display_round_trips_through_from_str() {
for c in [CurveType::Ed25519, CurveType::P256] {
assert_eq!(CurveType::from_str(&c.to_string()).unwrap(), c);
}
}
#[test]
fn rejects_unknown_curve() {
let err = CurveType::from_str("secp256k1").unwrap_err();
assert_eq!(err.got, "secp256k1");
}
}