use std::fmt::{self, Display};
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::encoding::Base64UrlBytes;
use crate::error::{Error, InvalidKeyError, ParseError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum EcCurve {
#[serde(rename = "P-256")]
P256,
#[serde(rename = "P-384")]
P384,
#[serde(rename = "P-521")]
P521,
#[serde(rename = "secp256k1")]
Secp256k1,
}
impl EcCurve {
pub fn coordinate_size(&self) -> usize {
match self {
EcCurve::P256 => 32,
EcCurve::P384 => 48,
EcCurve::P521 => 66, EcCurve::Secp256k1 => 32,
}
}
pub fn as_str(&self) -> &'static str {
match self {
EcCurve::P256 => "P-256",
EcCurve::P384 => "P-384",
EcCurve::P521 => "P-521",
EcCurve::Secp256k1 => "secp256k1",
}
}
}
impl FromStr for EcCurve {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"P-256" => Ok(EcCurve::P256),
"P-384" => Ok(EcCurve::P384),
"P-521" => Ok(EcCurve::P521),
"secp256k1" => Ok(EcCurve::Secp256k1),
_ => Err(Error::Parse(ParseError::UnknownCurve(s.to_string()))),
}
}
}
impl Display for EcCurve {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
#[non_exhaustive]
pub struct EcParams {
#[zeroize(skip)]
pub crv: EcCurve,
pub x: Base64UrlBytes,
pub y: Base64UrlBytes,
#[serde(skip_serializing_if = "Option::is_none")]
pub d: Option<Base64UrlBytes>,
}
impl EcParams {
#[must_use]
pub fn new_public(crv: EcCurve, x: Base64UrlBytes, y: Base64UrlBytes) -> Self {
Self { crv, x, y, d: None }
}
#[must_use]
pub fn new_private(
crv: EcCurve,
x: Base64UrlBytes,
y: Base64UrlBytes,
d: Base64UrlBytes,
) -> Self {
Self {
crv,
x,
y,
d: Some(d),
}
}
pub fn is_public_key_only(&self) -> bool {
self.d.is_none()
}
pub fn has_private_key(&self) -> bool {
self.d.is_some()
}
pub fn validate(&self) -> Result<()> {
let expected_size = self.crv.coordinate_size();
if self.x.len() != expected_size {
return Err(InvalidKeyError::InvalidKeySize {
expected: expected_size,
actual: self.x.len(),
context: "EC x coordinate",
}
.into());
}
if self.y.len() != expected_size {
return Err(InvalidKeyError::InvalidKeySize {
expected: expected_size,
actual: self.y.len(),
context: "EC y coordinate",
}
.into());
}
if let Some(ref d) = self.d
&& d.len() != expected_size
{
return Err(InvalidKeyError::InvalidKeySize {
expected: expected_size,
actual: d.len(),
context: "EC private key d",
}
.into());
}
Ok(())
}
#[must_use]
pub fn to_public(&self) -> Self {
Self {
crv: self.crv,
x: self.x.clone(),
y: self.y.clone(),
d: None,
}
}
#[must_use]
pub fn to_uncompressed_point(&self) -> Vec<u8> {
let mut point = Vec::with_capacity(1 + self.x.len() + self.y.len());
point.push(0x04); point.extend_from_slice(self.x.as_bytes());
point.extend_from_slice(self.y.as_bytes());
point
}
}
impl fmt::Debug for EcParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EcParams")
.field("crv", &self.crv)
.field("x", &format!("[{} bytes]", self.x.len()))
.field("y", &format!("[{} bytes]", self.y.len()))
.field("has_private_key", &self.has_private_key())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_curve_coordinate_sizes() {
assert_eq!(EcCurve::P256.coordinate_size(), 32);
assert_eq!(EcCurve::P384.coordinate_size(), 48);
assert_eq!(EcCurve::P521.coordinate_size(), 66);
assert_eq!(EcCurve::Secp256k1.coordinate_size(), 32);
}
#[test]
fn test_public_key_only() {
let params = EcParams::new_public(
EcCurve::P256,
Base64UrlBytes::new(vec![0; 32]),
Base64UrlBytes::new(vec![0; 32]),
);
assert!(params.is_public_key_only());
assert!(!params.has_private_key());
}
#[test]
fn test_validate_wrong_size() {
let params = EcParams::new_public(
EcCurve::P256,
Base64UrlBytes::new(vec![0; 31]), Base64UrlBytes::new(vec![0; 32]),
);
assert!(params.validate().is_err());
}
#[test]
fn test_uncompressed_point() {
let x = vec![1; 32];
let y = vec![2; 32];
let params = EcParams::new_public(
EcCurve::P256,
Base64UrlBytes::new(x.clone()),
Base64UrlBytes::new(y.clone()),
);
let point = params.to_uncompressed_point();
assert_eq!(point.len(), 65);
assert_eq!(point[0], 0x04);
assert_eq!(&point[1..33], &x);
assert_eq!(&point[33..], &y);
}
#[test]
fn test_curve_parsing() {
assert_eq!("P-256".parse::<EcCurve>().unwrap(), EcCurve::P256);
assert_eq!("P-384".parse::<EcCurve>().unwrap(), EcCurve::P384);
assert_eq!("P-521".parse::<EcCurve>().unwrap(), EcCurve::P521);
assert_eq!("secp256k1".parse::<EcCurve>().unwrap(), EcCurve::Secp256k1);
assert!("unknown".parse::<EcCurve>().is_err());
}
}