uniauth 2.1.1

Easy-to-use abstraction over authentication
Documentation
//! Enums that contain types from all supported signature algorithms

use crate::error::*;

use ed25519_dalek::{
	ed25519::signature::Signature as _,
	Keypair as Ed25519Keypair,
	PublicKey as Ed25519Pubkey,
	SecretKey as Ed25519Seckey,
	Signature as Ed25519Signature,
	Signer as _,
	Verifier as _
};
use pqcrypto_dilithium::dilithium3::{
	detached_sign,
	keypair,
	verify_detached_signature,
	DetachedSignature as Dilithium3Signature,
	PublicKey as Dilithium3Pubkey,
	SecretKey as Dilithium3Seckey
};
use pqcrypto_traits::sign::*;
use rand_core::OsRng;

/// A signature for a challenge created by `uniauth::make_challenge`.
/// Does not include the challenge message itself.
pub enum AnySignature {
	Ed25519(Ed25519Signature),
	Dilithium3(Box<Dilithium3Signature>)
}

impl AnySignature {
	/// Read a signature of some algorithm from bytes
	/// If it is invalid or uses an unsupported algorithm is used it will return an `Error`.
	pub fn new(name: &str, bytes: &[u8]) -> Result<Self> {
		Ok(match name {
			"ed25519" => Self::Ed25519(Ed25519Signature::from_bytes(bytes)
				.map_err(|_| Error::InvalidSignature)?),
			"dilithium3" => Self::Dilithium3(Box::new(Dilithium3Signature::from_bytes(bytes)
				.map_err(|_| Error::InvalidSignature)?)),
			_ => return Err(Error::UnsupportedAlgorithm)
		})
	}

	/// Get the algorithm name of this signature, suitable for wire protocol.
	pub fn name(&self) -> &'static str {
		match self {
			Self::Ed25519(_) => "ed25519",
			Self::Dilithium3(_) => "dilithium3"
		}
	}

	/// Get the underlying bytes for this signature
	pub fn to_bytes(&self) -> Vec<u8> {
		match self {
			Self::Ed25519(sig) => sig.as_bytes().to_vec(),
			Self::Dilithium3(sig) => sig.as_bytes().to_vec()
		}
	}
}

/// A public key for verifying signatures against.
/// If there are multiple, try each one until it works
pub enum AnyPubkey {
	Ed25519(Ed25519Pubkey),
	Dilithium3(Box<Dilithium3Pubkey>)
}

impl AnyPubkey {
	/// Read a public key of some algorithm from bytes
	/// If it is invalid or uses an unsupported algorithm is used it will return an `Error`.
	pub fn new(name: &str, bytes: &[u8]) -> Result<Self> {
		Ok(match name {
			"ed25519" => Self::Ed25519(Ed25519Pubkey::from_bytes(bytes)
				.map_err(|_| Error::InvalidData)?),
			"dilithium3" => Self::Dilithium3(Box::new(Dilithium3Pubkey::from_bytes(bytes)
				.map_err(|_| Error::InvalidData)?)),
			_ => return Err(Error::UnsupportedAlgorithm)
		})
	}

	/// Get the algorithm name of this signature, suitable for wire protocol.
	pub fn name(&self) -> &'static str {
		match self {
			Self::Ed25519(_) => "ed25519",
			Self::Dilithium3(_) => "dilithium3"
		}
	}

	/// Get the underlying bytes for this key
	pub fn as_bytes(&self) -> &[u8] {
		match self {
			Self::Ed25519(pubkey) => pubkey.as_bytes(),
			Self::Dilithium3(pubkey) => pubkey.as_bytes()
		}
	}

	/// Verify a signature using this public key.
	/// If the signature is of a different type, returns `Error::UnsupportedAlgorithm`.
	/// If the signature could not be verified using this key, returns `Error::InvalidSignature`.
	pub fn verify(&self, challenge: &[u8], sig: &AnySignature) -> Result<()> {
		match self {
			Self::Ed25519(pubkey) => match sig {
				AnySignature::Ed25519(sig) => pubkey.verify(challenge, sig)
					.map_err(|_| Error::InvalidSignature),
				_ => Err(Error::UnsupportedAlgorithm)
			},
			Self::Dilithium3(pubkey) => match sig {
				AnySignature::Dilithium3(sig) => verify_detached_signature(sig, challenge, pubkey)
					.map_err(|_| Error::InvalidSignature),
				_ => Err(Error::UnsupportedAlgorithm)
			}
		}
	}
}

/// A full keypair for signing challenges with
pub enum AnyKeypair {
	Ed25519(Box<Ed25519Keypair>),
	Dilithium3(Box<Dilithium3Pubkey>, Box<Dilithium3Seckey>)
}

impl AnyKeypair {
	/// Create a keypair of some algorithm from its halves' bytes
	/// If they are invalid or it uses an unsupported algorithm is used it will return an `Error`.
	pub fn new(name: &str, public: &[u8], secret: &[u8]) -> Result<Self> {
		Ok(match name {
			"ed25519" => Self::Ed25519(Box::new(Ed25519Keypair {
				public: Ed25519Pubkey::from_bytes(public)
					.map_err(|_| Error::InvalidData)?,
				secret: Ed25519Seckey::from_bytes(secret)
					.map_err(|_| Error::InvalidData)?
			})),
			"dilithium3" => Self::Dilithium3(
				Box::new(Dilithium3Pubkey::from_bytes(public)
					.map_err(|_| Error::InvalidData)?),
				Box::new(Dilithium3Seckey::from_bytes(secret)
					.map_err(|_| Error::InvalidData)?)
			),
			_ => return Err(Error::UnsupportedAlgorithm)
		})
	}

	/// Generate a new ed25519 keypair.
	/// Should only ever be run by the daemon, never by a client (or server).
	pub fn generate_ed25519() -> Self {
		Self::Ed25519(Box::new(Ed25519Keypair::generate(&mut OsRng)))
	}

	/// Generate a new dilithium3 keypair.
	/// Should only ever be run by the daemon, never by a client (or server).
	pub fn generate_dilithium3() -> Self {
		let (pk, sk) = keypair();
		Self::Dilithium3(Box::new(pk), Box::new(sk))
	}

	/// Get the algorithm name of this keypair, suitable for storage.
	pub fn name(&self) -> &'static str {
		match self {
			Self::Ed25519(_) => "ed25519",
			Self::Dilithium3(_, _) => "dilithium3"
		}
	}

	/// Get the underlying bytes for the public half
	pub fn public_bytes(&self) -> &[u8] {
		match self {
			Self::Ed25519(keypair) => keypair.public.as_bytes(),
			Self::Dilithium3(pk, _) => pk.as_bytes()
		}
	}

	/// Get the underlying bytes for the secret half
	pub fn secret_bytes(&self) -> &[u8] {
		match self {
			Self::Ed25519(keypair) => keypair.secret.as_bytes(),
			Self::Dilithium3(_, sk) => sk.as_bytes()
		}
	}

	/// Sign a challenge created by `uniauth::make_challenge`.
	/// Done by the client's daemon only.
	pub fn sign(&self, challenge: &[u8]) -> AnySignature {
		match self {
			Self::Ed25519(keypair) => AnySignature::Ed25519(keypair.sign(challenge)),
			Self::Dilithium3(_, sk) => AnySignature::Dilithium3(Box::new(detached_sign(challenge, sk)))
		}
	}
}