Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for NIST P-256 curve #185

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ vector-tests = []
hfs = []
pqclean_kyber1024 = ["pqcrypto-kyber", "pqcrypto-traits", "hfs", "default-resolver"]
xchachapoly = ["chacha20poly1305", "default-resolver"]
p256 = ["dep:p256", "default-resolver"]
risky-raw-split = []

[[bench]]
Expand All @@ -45,7 +46,8 @@ aes-gcm = { version = "0.10", optional = true }
chacha20poly1305 = { version = "0.10", optional = true }
blake2 = { version = "0.10", optional = true }
sha2 = { version = "0.10", optional = true }
curve25519-dalek = { version = "4", optional = true }
curve25519-dalek = { version = "4.1.3", optional = true }
p256 = { version = "0.13.2", features = ["ecdh"], optional = true }

pqcrypto-kyber = { version = "0.8", optional = true }
pqcrypto-traits = { version = "0.3", optional = true }
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ crypto implementations when available.
| CSPRNG | ✔ | ✔ |
| 25519 | ✔ | ✔ |
| 448 | | |
| P-256 | ✔ | |
| AESGCM | ✔ | ✔ |
| ChaChaPoly | ✔ | ✔ |
| SHA256 | ✔ | ✔ |
Expand Down
2 changes: 1 addition & 1 deletion src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub const TAGLEN: usize = 16;

pub const MAXHASHLEN: usize = 64;
pub const MAXBLOCKLEN: usize = 128;
pub const MAXDHLEN: usize = 56;
pub const MAXDHLEN: usize = 65;
pub const MAXMSGLEN: usize = 65535;

#[cfg(feature = "hfs")]
Expand Down
36 changes: 18 additions & 18 deletions src/handshakestate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl HandshakeState {
symmetricstate.initialize(&params.name);
symmetricstate.mix_hash(prologue);

let dh_len = s.pub_len();
let pub_len = s.pub_len();
if initiator {
for token in tokens.premsg_pattern_i {
symmetricstate.mix_hash(
Expand All @@ -100,7 +100,7 @@ impl HandshakeState {
_ => unreachable!(),
}
.get()
.ok_or(StateProblem::MissingKeyMaterial)?[..dh_len],
.ok_or(StateProblem::MissingKeyMaterial)?[..pub_len],
);
}
} else {
Expand All @@ -112,7 +112,7 @@ impl HandshakeState {
_ => unreachable!(),
}
.get()
.ok_or(StateProblem::MissingKeyMaterial)?[..dh_len],
.ok_or(StateProblem::MissingKeyMaterial)?[..pub_len],
);
}
for token in tokens.premsg_pattern_r {
Expand Down Expand Up @@ -152,7 +152,7 @@ impl HandshakeState {
}

pub(crate) fn dh_len(&self) -> usize {
self.s.pub_len()
self.s.dh_len()
}

#[cfg(feature = "hfs")]
Expand Down Expand Up @@ -356,39 +356,39 @@ impl HandshakeState {
}
let last = self.pattern_position == (self.message_patterns.len() - 1);

let dh_len = self.dh_len();
let pub_len = self.e.pub_len();
let mut ptr = message;
for token in &self.message_patterns[self.pattern_position] {
match *token {
Token::E => {
if ptr.len() < dh_len {
if ptr.len() < pub_len {
return Err(Error::Input);
}
self.re[..dh_len].copy_from_slice(&ptr[..dh_len]);
ptr = &ptr[dh_len..];
self.symmetricstate.mix_hash(&self.re[..dh_len]);
self.re[..pub_len].copy_from_slice(&ptr[..pub_len]);
ptr = &ptr[pub_len..];
self.symmetricstate.mix_hash(&self.re[..pub_len]);
if self.params.handshake.is_psk() {
self.symmetricstate.mix_key(&self.re[..dh_len]);
self.symmetricstate.mix_key(&self.re[..pub_len]);
}
self.re.enable();
},
Token::S => {
let data = if self.symmetricstate.has_key() {
if ptr.len() < dh_len + TAGLEN {
if ptr.len() < pub_len + TAGLEN {
return Err(Error::Input);
}
let temp = &ptr[..dh_len + TAGLEN];
ptr = &ptr[dh_len + TAGLEN..];
let temp = &ptr[..pub_len + TAGLEN];
ptr = &ptr[pub_len + TAGLEN..];
temp
} else {
if ptr.len() < dh_len {
if ptr.len() < pub_len {
return Err(Error::Input);
}
let temp = &ptr[..dh_len];
ptr = &ptr[dh_len..];
let temp = &ptr[..pub_len];
ptr = &ptr[pub_len..];
temp
};
self.symmetricstate.decrypt_and_mix_hash(data, &mut self.rs[..dh_len])?;
self.symmetricstate.decrypt_and_mix_hash(data, &mut self.rs[..pub_len])?;
self.rs.enable();
},
Token::Psk(n) => match self.psks[n as usize] {
Expand Down Expand Up @@ -472,7 +472,7 @@ impl HandshakeState {
/// pattern, for example).
#[must_use]
pub fn get_remote_static(&self) -> Option<&[u8]> {
self.rs.get().map(|rs| &rs[..self.dh_len()])
self.rs.get().map(|rs| &rs[..self.s.pub_len()])
}

/// Get the handshake hash.
Expand Down
14 changes: 13 additions & 1 deletion src/params/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ impl FromStr for BaseChoice {
/// Which Diffie-Hellman primitive to use. One of `25519` or `448`, per the spec.
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum DHChoice {
/// The Curve25519 ellpitic curve.
/// The Curve25519 elliptic curve.
Curve25519,
/// The Curve448 elliptic curve.
Curve448,
#[cfg(feature = "p256")]
/// The P-256 elliptic curve.
P256,
}

impl FromStr for DHChoice {
Expand All @@ -51,6 +54,8 @@ impl FromStr for DHChoice {
match s {
"25519" => Ok(Curve25519),
"448" => Ok(Curve448),
#[cfg(feature = "p256")]
"P256" => Ok(P256),
_ => Err(PatternProblem::UnsupportedDhType.into()),
}
}
Expand Down Expand Up @@ -270,6 +275,13 @@ mod tests {
assert!(p.handshake.modifiers.list.is_empty());
}

#[test]
#[cfg(feature = "p256")]
fn test_p256() {
let p: NoiseParams = "Noise_XX_P256_AESGCM_SHA256".parse().unwrap();
assert_eq!(p.dh, DHChoice::P256);
}

#[test]
fn test_basic_deferred() {
let p: NoiseParams = "Noise_X1X1_25519_AESGCM_SHA256".parse().unwrap();
Expand Down
94 changes: 94 additions & 0 deletions src/resolvers/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use blake2::{Blake2b, Blake2b512, Blake2s, Blake2s256};
use chacha20poly1305::XChaCha20Poly1305;
use chacha20poly1305::{aead::AeadInPlace, ChaCha20Poly1305, KeyInit};
use curve25519_dalek::montgomery::MontgomeryPoint;
#[cfg(feature = "p256")]
use p256::{self, elliptic_curve::sec1::ToEncodedPoint, EncodedPoint};
#[cfg(feature = "pqclean_kyber1024")]
use pqcrypto_kyber::kyber1024;
#[cfg(feature = "pqclean_kyber1024")]
Expand Down Expand Up @@ -38,6 +40,8 @@ impl CryptoResolver for DefaultResolver {
match *choice {
DHChoice::Curve25519 => Some(Box::<Dh25519>::default()),
DHChoice::Curve448 => None,
#[cfg(feature = "p256")]
DHChoice::P256 => Some(Box::<P256>::default()),
}
}

Expand Down Expand Up @@ -74,6 +78,14 @@ struct Dh25519 {
pubkey: [u8; 32],
}

/// Wraps p256
#[cfg(feature = "p256")]
#[derive(Default)]
struct P256 {
privkey: [u8; 32],
pubkey: EncodedPoint,
}

/// Wraps `aes-gcm`'s AES256-GCM implementation.
#[derive(Default)]
struct CipherAesGcm {
Expand Down Expand Up @@ -175,6 +187,67 @@ impl Dh for Dh25519 {
}
}

#[cfg(feature = "p256")]
impl P256 {
fn derive_pubkey(&mut self) {
let secret_key = p256::SecretKey::from_bytes(&self.privkey.into()).unwrap();
let public_key = secret_key.public_key();
let encoded_pub = public_key.to_encoded_point(false);
self.pubkey = encoded_pub;
}
}

#[cfg(feature = "p256")]
impl Dh for P256 {
fn name(&self) -> &'static str {
"P256"
}

fn pub_len(&self) -> usize {
65 // Uncompressed SEC-1 encoding
}

fn priv_len(&self) -> usize {
32 // Scalar
}

fn dh_len(&self) -> usize {
32
}

fn set(&mut self, privkey: &[u8]) {
let mut bytes = [0u8; 32];
copy_slices!(privkey, bytes);
self.privkey = bytes;
self.derive_pubkey();
}

fn generate(&mut self, rng: &mut dyn Random) {
let mut bytes = [0u8; 32];
rng.fill_bytes(&mut bytes);
self.privkey = bytes;
self.derive_pubkey();
}

fn pubkey(&self) -> &[u8] {
self.pubkey.as_bytes()
}

fn privkey(&self) -> &[u8] {
&self.privkey
}

fn dh(&self, pubkey: &[u8], out: &mut [u8]) -> Result<(), Error> {
let secret_key = p256::SecretKey::from_bytes(&self.privkey.into()).or(Err(Error::Dh))?;
let secret_key_scalar = secret_key.to_nonzero_scalar();
let pub_key: p256::elliptic_curve::PublicKey<p256::NistP256> =
p256::PublicKey::from_sec1_bytes(&pubkey).or(Err(Error::Dh))?;
let dh_output = p256::ecdh::diffie_hellman(secret_key_scalar, pub_key.as_affine());
copy_slices!(dh_output.raw_secret_bytes(), out);
Ok(())
}
}

impl Cipher for CipherAesGcm {
fn name(&self) -> &'static str {
"AESGCM"
Expand Down Expand Up @@ -613,6 +686,27 @@ mod tests {
);
}

#[test]
#[cfg(feature = "p256")]
fn test_p256() {
let mut keypair = P256::default();
let scalar =
Vec::<u8>::from_hex("58c77a30bb0fa177286346d18f59678ac1b8d3637ee65f1bd88a8f52e49ef189")
.unwrap();
keypair.set(&scalar);
let public = Vec::<u8>::from_hex(
"042009fddefeed3b342696b11683b423db8ede2ef5cd66af9b7db2772f7deaf3d\
f1a69a4648d990ae2a4e5928f156f32e15fa08ba4465df8cb17838dc2afb719d2",
)
.unwrap();
let mut output = [0u8; 32];
keypair.dh(&public, &mut output).unwrap();
assert_eq!(
hex::encode(output),
"07505b308650d07e6ead11dd36bbf6f24bf99fc6479649fadd3939faa33ddeb3"
);
}

#[test]
fn test_aesgcm() {
// AES256-GCM tests - gcm-spec.pdf
Expand Down
5 changes: 5 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ pub trait Dh: Send + Sync {
/// # Errors
/// Returns `Error::Dh` in the event that the Diffie-Hellman failed.
fn dh(&self, pubkey: &[u8], out: &mut [u8]) -> Result<(), Error>;

/// The lenght in bytes of of the DH key exchange. Defaults to the public key.
fn dh_len(&self) -> usize {
self.pub_len()
}
}

/// Cipher operations
Expand Down