diff --git a/botan/src/ec_group.rs b/botan/src/ec_group.rs index fcf13fc..7df2cd1 100644 --- a/botan/src/ec_group.rs +++ b/botan/src/ec_group.rs @@ -4,6 +4,9 @@ use botan_sys::*; #[cfg(botan_ffi_20250506)] use crate::OID; +#[cfg(botan_ffi_20260506)] +use crate::EcPoint; + #[cfg(botan_ffi_20250506)] #[derive(Debug)] /// An elliptic curve group @@ -21,6 +24,15 @@ botan_impl_drop!(EcGroup, botan_ec_group_destroy); #[cfg(botan_ffi_20250506)] impl EcGroup { + pub(crate) fn handle(&self) -> botan_ec_group_t { + self.obj + } + + #[allow(dead_code)] + pub(crate) fn from_handle(obj: botan_ec_group_t) -> Self { + Self { obj } + } + /// Does this build configuration support application specific groups pub fn supports_application_specific_groups() -> Result { let mut result = 0; @@ -111,41 +123,53 @@ impl EcGroup { }) } - /// Return the groups parameter p + /// Return the group's parameter p pub fn p(&self) -> Result { MPI::from_handle(botan_init!(botan_ec_group_get_p, self.obj)?) } - /// Return the groups parameter a + /// Return the group's parameter a pub fn a(&self) -> Result { MPI::from_handle(botan_init!(botan_ec_group_get_a, self.obj)?) } - /// Return the groups parameter b + /// Return the group's parameter b pub fn b(&self) -> Result { MPI::from_handle(botan_init!(botan_ec_group_get_b, self.obj)?) } - /// Return the groups order + /// Return the group's order pub fn order(&self) -> Result { MPI::from_handle(botan_init!(botan_ec_group_get_order, self.obj)?) } - /// Return the groups generator x coordinate + /// Return the group's generator x coordinate pub fn g_x(&self) -> Result { MPI::from_handle(botan_init!(botan_ec_group_get_g_x, self.obj)?) } - /// Return the groups generator y coordinate + /// Return the group's generator y coordinate pub fn g_y(&self) -> Result { MPI::from_handle(botan_init!(botan_ec_group_get_g_y, self.obj)?) } - /// Return the groups object identifier + /// Return the group's object identifier pub fn oid(&self) -> Result { OID::from_handle(botan_init!(botan_ec_group_get_curve_oid, self.obj)?) } + #[cfg(botan_ffi_20260506)] + /// Return the group's identity element + pub fn identity(&self) -> Result { + EcPoint::identity(self) + } + + #[cfg(botan_ffi_20260506)] + /// Return the group's generator element + pub fn generator(&self) -> Result { + EcPoint::generator(self) + } + /// Check two groups for equality pub fn equals(&self, other: &Self) -> Result { botan_bool_in_rc!(botan_ec_group_equal, self.obj, other.obj) diff --git a/botan/src/ec_point.rs b/botan/src/ec_point.rs new file mode 100644 index 0000000..46cafaa --- /dev/null +++ b/botan/src/ec_point.rs @@ -0,0 +1,218 @@ +use crate::{MPI, RandomNumberGenerator, utils::*}; +use botan_sys::*; +use core::ops::Add; + +#[cfg(botan_ffi_20260506)] +use crate::EcGroup; + +#[cfg(botan_ffi_20260506)] +#[derive(Debug)] +/// An Integer modulo the prime group order of an elliptic curve +pub struct EcScalar { + obj: botan_ec_scalar_t, +} + +#[cfg(botan_ffi_20260506)] +unsafe impl Sync for EcScalar {} +#[cfg(botan_ffi_20260506)] +unsafe impl Send for EcScalar {} + +#[cfg(botan_ffi_20260506)] +botan_impl_drop!(EcScalar, botan_ec_scalar_destroy); + +#[cfg(botan_ffi_20260506)] +impl EcScalar { + pub(crate) fn handle(&self) -> botan_ec_scalar_t { + self.obj + } + + pub(crate) fn from_handle(obj: botan_ec_scalar_t) -> Self { + Self { obj } + } + + /// Create a new scalar with a random value + pub fn random(group: &EcGroup, rng: &mut RandomNumberGenerator) -> Result { + let obj = botan_init!(botan_ec_scalar_random, group.handle(), rng.handle())?; + Ok(Self { obj }) + } + + /// Convert from an MPI to a scalar, fails if the MPI is negative or too large + pub fn from_mpi(group: &EcGroup, mpi: &MPI) -> Result { + let obj = botan_init!(botan_ec_scalar_from_mp, group.handle(), mpi.handle())?; + Ok(Self { obj }) + } + + /// Convert from a scalar to an MPI + pub fn to_mpi(&self) -> Result { + let obj = botan_init_at!(botan_ec_scalar_to_mp, self.obj;)?; + MPI::from_handle(obj) + } +} + +#[cfg(botan_ffi_20260506)] +#[derive(Debug)] +/// An elliptic curve point +pub struct EcPoint { + obj: botan_ec_point_t, +} + +#[cfg(botan_ffi_20260506)] +unsafe impl Sync for EcPoint {} +#[cfg(botan_ffi_20260506)] +unsafe impl Send for EcPoint {} + +#[cfg(botan_ffi_20260506)] +botan_impl_drop!(EcPoint, botan_ec_point_destroy); + +#[cfg(botan_ffi_20260506)] +impl EcPoint { + pub(crate) fn handle(&self) -> botan_ec_point_t { + self.obj + } + + /// Create a point set to the group identity + pub fn identity(group: &EcGroup) -> Result { + let obj = botan_init!(botan_ec_point_identity, group.handle())?; + Ok(Self { obj }) + } + + /// Create a point set to the group generator + pub fn generator(group: &EcGroup) -> Result { + let obj = botan_init!(botan_ec_point_generator, group.handle())?; + Ok(Self { obj }) + } + + /// Create a point from a set of (x,y) integers + /// The integers must be within the field and must satisfy the curve equation + pub fn from_xy(group: &EcGroup, x: &MPI, y: &MPI) -> Result { + let obj = botan_init!( + botan_ec_point_from_xy, + group.handle(), + x.handle(), + y.handle() + )?; + Ok(Self { obj }) + } + + /// Create a point from a SEC1 compressed or uncompressed format + pub fn from_bytes(group: &EcGroup, bytes: &[u8]) -> Result { + let obj = botan_init!( + botan_ec_point_from_bytes, + group.handle(), + bytes.as_ptr(), + bytes.len() + )?; + Ok(Self { obj }) + } + + /// Check if this point is the group identity + pub fn is_identity(&self) -> Result { + botan_bool_in_rc!(botan_ec_point_is_identity, self.obj) + } + + /// Check if this point and another one are equal + pub fn is_equal(&self, other: &EcPoint) -> Result { + botan_bool_in_rc!(botan_ec_point_equal, self.obj, other.obj) + } + + /// Create a new point with the negated value of this one + pub fn negate(&self) -> Result { + let obj = botan_init!(botan_ec_point_negate, self.obj)?; + Ok(Self { obj }) + } + + /// Add another point to this one, creating a new point + pub fn pt_add(&self, other: &EcPoint) -> Result { + let obj = botan_init!(botan_ec_point_add, self.obj, other.handle())?; + Ok(Self { obj }) + } + + /// Multiply this point by a scalar, creating a new point + pub fn mul(&self, scalar: &EcScalar, rng: &mut RandomNumberGenerator) -> Result { + let obj = botan_init!(botan_ec_point_mul, self.obj, scalar.handle(), rng.handle())?; + Ok(Self { obj }) + } + + /// Get the fixed length encoding of the affine x coordinate + pub fn to_x_bytes(&self) -> Result> { + call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { + botan_ec_point_view_x_bytes(self.obj, ctx, cb) + }) + } + + /// Get the fixed length encoding of the affine y coordinate + pub fn to_y_bytes(&self) -> Result> { + call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { + botan_ec_point_view_y_bytes(self.obj, ctx, cb) + }) + } + + /// Get the fixed length encoding of the affine x and y coordinates + pub fn to_xy_bytes(&self) -> Result> { + call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { + botan_ec_point_view_xy_bytes(self.obj, ctx, cb) + }) + } + + /// Get the fixed length SEC1 uncompressed encoding + pub fn to_uncompressed(&self) -> Result> { + call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { + botan_ec_point_view_uncompressed(self.obj, ctx, cb) + }) + } + + /// Get the fixed length SEC1 compressed encoding + pub fn to_compressed(&self) -> Result> { + call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { + botan_ec_point_view_compressed(self.obj, ctx, cb) + }) + } +} + +#[cfg(botan_ffi_20260506)] +impl PartialEq for EcPoint { + fn eq(&self, other: &Self) -> bool { + self.is_equal(other) + .expect("botan_ec_point_equal should succeed") + } +} + +#[cfg(botan_ffi_20260506)] +impl Eq for EcPoint {} + +#[cfg(botan_ffi_20260506)] +impl<'b> Add<&'b EcPoint> for &EcPoint { + type Output = EcPoint; + + fn add(self, other: &'b EcPoint) -> Self::Output { + self.pt_add(other) + .expect("botan_ec_point_add should succeed") + } +} + +#[cfg(botan_ffi_20260506)] +impl Add<&EcPoint> for EcPoint { + type Output = EcPoint; + + fn add(self, other: &EcPoint) -> EcPoint { + (&self).add(other) + } +} + +#[cfg(botan_ffi_20260506)] +impl Add for &EcPoint { + type Output = EcPoint; + + fn add(self, other: EcPoint) -> EcPoint { + self.add(&other) + } +} + +#[cfg(botan_ffi_20260506)] +impl Add for EcPoint { + type Output = EcPoint; + + fn add(self, other: EcPoint) -> EcPoint { + (&self).add(&other) + } +} diff --git a/botan/src/lib.rs b/botan/src/lib.rs index fc2a8e4..be28499 100644 --- a/botan/src/lib.rs +++ b/botan/src/lib.rs @@ -118,6 +118,7 @@ mod bcrypt; mod block; mod cipher; mod ec_group; +mod ec_point; mod fpe; mod hash; mod kdf; @@ -128,7 +129,6 @@ mod mp; mod otp; mod pbkdf; mod pk_ops; - mod pubkey; mod rng; mod utils; @@ -142,6 +142,7 @@ pub use bcrypt::*; pub use block::*; pub use cipher::*; pub use ec_group::*; +pub use ec_point::*; pub use fpe::*; pub use hash::*; pub use kdf::*; diff --git a/botan/src/pubkey.rs b/botan/src/pubkey.rs index 9dbfe48..40b54f5 100644 --- a/botan/src/pubkey.rs +++ b/botan/src/pubkey.rs @@ -1,6 +1,12 @@ use crate::utils::*; use botan_sys::*; +#[cfg(botan_ffi_20250506)] +use crate::EcGroup; + +#[cfg(botan_ffi_20260506)] +use crate::{EcPoint, EcScalar}; + use crate::mp::MPI; use crate::pk_ops::*; use crate::rng::RandomNumberGenerator; @@ -57,6 +63,22 @@ impl Privkey { Ok(Privkey::from_obj(obj)) } + /// Create a new EC private key + #[cfg(botan_ffi_20250506)] + pub fn create_ec( + alg: &str, + ec_group: &EcGroup, + rng: &mut RandomNumberGenerator, + ) -> Result { + let obj = botan_init!( + botan_ec_privkey_create, + make_cstr(alg)?.as_ptr(), + ec_group.handle(), + rng.handle() + )?; + Ok(Self { obj }) + } + /// Create a new ElGamal private key with a random group pub fn create_elgamal( p_bits: usize, @@ -485,6 +507,24 @@ impl Privkey { Ok(r) } + /// Return the private value of this key + /// + /// Only valid for EC based keys + #[cfg(botan_ffi_20260506)] + pub fn get_private_value(&self) -> Result { + let obj = botan_init_at!(botan_ec_privkey_get_private_key, self.obj;)?; + Ok(EcScalar::from_handle(obj)) + } + + /// Return the group associated with this key + /// + /// Only valid for EC based keys + #[cfg(botan_ffi_20260506)] + pub fn get_group(&self) -> Result { + let obj = botan_init_at!(botan_ec_privkey_get_group, self.obj;)?; + Ok(EcGroup::from_handle(obj)) + } + /// Get the raw bytes associated with this key /// /// This is not defined for certain schemes which do not have an obvious @@ -789,6 +829,15 @@ impl Pubkey { Ok(r) } + /// Return the group associated with this key + /// + /// Only valid for EC based keys + #[cfg(botan_ffi_20260506)] + pub fn get_group(&self) -> Result { + let obj = botan_init_at!(botan_ec_pubkey_get_group, self.obj; )?; + Ok(EcGroup::from_handle(obj)) + } + /// Return the raw byte encoding of this key /// /// This requires `botan_ffi_20250506`, otherwise a not implemented error is returned diff --git a/botan/tests/tests.rs b/botan/tests/tests.rs index 105423a..c93a232 100644 --- a/botan/tests/tests.rs +++ b/botan/tests/tests.rs @@ -1570,3 +1570,90 @@ fn test_ec_group() -> Result<(), botan::Error> { Ok(()) } + +#[cfg(botan_ffi_20260506)] +#[test] +fn test_ec_points() -> Result<(), botan::Error> { + if !botan::EcGroup::supports_named_group("secp256r1")? { + return Ok(()); + } + + let group = botan::EcGroup::from_name("secp256r1")?; + let mut rng = botan::RandomNumberGenerator::new()?; + + let forty_two = botan::MPI::new_from_u32(42)?; + let scalar_forty_two = botan::EcScalar::from_mpi(&group, &forty_two)?; + assert_eq!(forty_two, scalar_forty_two.to_mpi()?); + + let identity = group.identity()?; + let generator = group.generator()?; + let one = botan::EcScalar::from_mpi(&group, &botan::MPI::new_from_u32(1)?)?; + assert_eq!(identity, &identity + &identity); + + // test all the ADDs + { + let a = group.identity()?; + let b = group.identity()?; + + let _ = &a + &b; + let _ = &a + b; + + let b = group.identity()?; + let _ = a + &b; + + let a = group.identity()?; + let b = group.identity()?; + + let _ = a + b; + } + + let order_minus_one = group.order()? - 1; + let order_minus_one_scalar = botan::EcScalar::from_mpi(&group, &order_minus_one)?; + assert_eq!( + generator.mul(&order_minus_one_scalar, &mut rng)? + &generator, + identity + ); + assert_eq!(generator, generator.mul(&one, &mut rng)?); + assert_eq!(identity, identity.mul(&one, &mut rng)?); + assert_eq!( + generator.negate()?, + generator.mul(&order_minus_one_scalar, &mut rng)? + ); + + let pkey = botan::Privkey::create_ec("ECDSA", &group, &mut rng)?; + let private_value = pkey.get_private_value()?; + let public_key = pkey.pubkey()?; + let public_value = botan::EcPoint::from_bytes(&group, &public_key.ec_public_point()?)?; + + assert_eq!(pkey.get_group()?, group); + assert_eq!(public_key.get_group()?, group); + + let result = generator.mul(&private_value, &mut rng)? + &public_value; + assert!(!result.is_identity()?); + + let x_bytes = hex::encode(result.to_x_bytes()?); + let y_bytes = hex::encode(result.to_y_bytes()?); + let xy_bytes = hex::encode(result.to_xy_bytes()?); + let uncompressed_bytes = hex::encode(result.to_uncompressed()?); + let compressed_bytes = hex::encode(result.to_compressed()?); + + assert_eq!(xy_bytes, format!("{}{}", x_bytes, y_bytes)); + assert_eq!(uncompressed_bytes, format!("04{}", xy_bytes)); + assert!(compressed_bytes.starts_with("02") || compressed_bytes.starts_with("03")); + assert_eq!(&compressed_bytes[2..], x_bytes); + + let x_mpi = botan::MPI::from_str(&format!("0x{}", x_bytes))?; + let y_mpi = botan::MPI::from_str(&format!("0x{}", y_bytes))?; + + assert_eq!(result, botan::EcPoint::from_xy(&group, &x_mpi, &y_mpi)?); + assert_eq!( + result, + botan::EcPoint::from_bytes(&group, &result.to_uncompressed()?)? + ); + assert_eq!( + result, + botan::EcPoint::from_bytes(&group, &result.to_compressed()?)? + ); + + Ok(()) +}