Skip to content

Commit

Permalink
noise_sv2: keep current (std) API unchanged, add a no_std compliant A…
Browse files Browse the repository at this point in the history
…PI with `*_with_rng` and `*_with_now`
  • Loading branch information
Georges Palauqui committed Dec 7, 2024
1 parent 9f2a908 commit da6e1c1
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 50 deletions.
33 changes: 8 additions & 25 deletions protocols/v2/noise-sv2/examples/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,52 +25,35 @@ const PARITY: Parity = Parity::Even;
const RESPONDER_CERT_VALIDITY: u32 = 3600;

// Generates a secp256k1 public/private key pair for the responder.
fn generate_key<R: rand::Rng + ?Sized>(rng: &mut R) -> Keypair {
fn generate_key() -> Keypair {
let secp = Secp256k1::new();
let (secret_key, _) = secp.generate_keypair(rng);
let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng());
let kp = Keypair::from_secret_key(&secp, &secret_key);
if kp.x_only_public_key().1 == PARITY {
kp
} else {
generate_key(rng)
generate_key()
}
}

fn main() {
let mut secret_message = "Ciao, Mondo!".as_bytes().to_vec();

let responder_key_pair = generate_key(&mut rand::thread_rng());
let responder_key_pair = generate_key();

let mut initiator = Initiator::new(
Some(responder_key_pair.public_key().into()),
&mut rand::thread_rng(),
);
let mut responder = Responder::new(
responder_key_pair,
RESPONDER_CERT_VALIDITY,
&mut rand::thread_rng(),
);
let mut initiator = Initiator::new(Some(responder_key_pair.public_key().into()));
let mut responder = Responder::new(responder_key_pair, RESPONDER_CERT_VALIDITY);

let first_message = initiator
.step_0()
.expect("Initiator failed first step of handshake");

let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as u32;

let (second_message, mut responder_state) = responder
.step_1(first_message, now, &mut rand::thread_rng())
.step_1(first_message)
.expect("Responder failed second step of handshake");

let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as u32;

let mut initiator_state = initiator
.step_2(second_message, now)
.step_2(second_message)
.expect("Initiator failed third step of handshake");

initiator_state
Expand Down
30 changes: 26 additions & 4 deletions protocols/v2/noise-sv2/src/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,19 @@ pub trait HandshakeOp<Cipher: AeadCipher>: CipherState<Cipher> {
// Generates a fresh key pair, consisting of a secret key and a corresponding public key,
// using the [`Secp256k1`] elliptic curve. If the generated public key does not match the
// expected parity, a new key pair is generated to ensure consistency.
fn generate_key<R: rand::Rng + ?Sized>(rng: &mut R) -> Keypair {
#[cfg(feature = "std")]
fn generate_key() -> Keypair {
Self::generate_key_with_rng(&mut rand::thread_rng())
}

fn generate_key_with_rng<R: rand::Rng + ?Sized>(rng: &mut R) -> Keypair {
let secp = Secp256k1::new();
let (secret_key, _) = secp.generate_keypair(rng);
let kp = Keypair::from_secret_key(&secp, &secret_key);
if kp.x_only_public_key().1 == crate::PARITY {
kp
} else {
Self::generate_key(rng)
Self::generate_key_with_rng(rng)
}
}

Expand Down Expand Up @@ -468,8 +473,25 @@ mod test {

#[test]
fn test_ecdh() {
let key_pair_1 = TestHandShake::generate_key(&mut rand::thread_rng());
let key_pair_2 = TestHandShake::generate_key(&mut rand::thread_rng());
let key_pair_1 = TestHandShake::generate_key();
let key_pair_2 = TestHandShake::generate_key();

let secret_1 = key_pair_1.secret_bytes();
let secret_2 = key_pair_2.secret_bytes();

let pub_1 = key_pair_1.x_only_public_key();
let pub_2 = key_pair_2.x_only_public_key();

let ecdh_1 = TestHandShake::ecdh(&secret_1, &pub_2.0.serialize());
let ecdh_2 = TestHandShake::ecdh(&secret_2, &pub_1.0.serialize());

assert!(ecdh_1 == ecdh_2);
}

#[test]
fn test_ecdh_with_rng() {
let key_pair_1 = TestHandShake::generate_key_with_rng(&mut rand::thread_rng());
let key_pair_2 = TestHandShake::generate_key_with_rng(&mut rand::thread_rng());

let secret_1 = key_pair_1.secret_bytes();
let secret_2 = key_pair_2.secret_bytes();
Expand Down
40 changes: 33 additions & 7 deletions protocols/v2/noise-sv2/src/initiator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,21 @@ impl Initiator {
/// If the responder public key is provided, the initiator uses this key to authenticate the
/// responder during the handshake. The initial initiator state is instantiated with the
/// ephemeral key pair and handshake hash.
pub fn new<R: rand::Rng + ?Sized>(pk: Option<XOnlyPublicKey>, rng: &mut R) -> Box<Self> {
#[cfg(feature = "std")]
pub fn new(pk: Option<XOnlyPublicKey>) -> Box<Self> {
Self::new_with_rng(pk, &mut rand::thread_rng())
}
pub fn new_with_rng<R: rand::Rng + ?Sized>(
pk: Option<XOnlyPublicKey>,
rng: &mut R,
) -> Box<Self> {
let mut self_ = Self {
handshake_cipher: None,
k: None,
n: 0,
ck: [0; 32],
h: [0; 32],
e: Self::generate_key(rng),
e: Self::generate_key_with_rng(rng),
responder_authority_pk: pk,
c1: None,
c2: None,
Expand All @@ -191,22 +198,30 @@ impl Initiator {
/// valid [`XOnlyPublicKey`], an [`Error::InvalidRawPublicKey`] error is returned.
///
/// Typically used when the initiator is aware of the responder's public key in advance.
pub fn from_raw_k<R: rand::Rng + ?Sized>(
#[cfg(feature = "std")]
pub fn from_raw_k(key: [u8; 32]) -> Result<Box<Self>, Error> {
Self::from_raw_k_with_rng(key, &mut rand::thread_rng())

Check warning on line 203 in protocols/v2/noise-sv2/src/initiator.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/initiator.rs#L203

Added line #L203 was not covered by tests
}
pub fn from_raw_k_with_rng<R: rand::Rng + ?Sized>(

Check warning on line 205 in protocols/v2/noise-sv2/src/initiator.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/initiator.rs#L205

Added line #L205 was not covered by tests
key: [u8; 32],
rng: &mut R,
) -> Result<Box<Self>, Error> {
let pk =
secp256k1::XOnlyPublicKey::from_slice(&key).map_err(|_| Error::InvalidRawPublicKey)?;
Ok(Self::new(Some(pk), rng))
Ok(Self::new_with_rng(Some(pk), rng))

Check warning on line 211 in protocols/v2/noise-sv2/src/initiator.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/initiator.rs#L211

Added line #L211 was not covered by tests
}

/// Creates a new [`Initiator`] without requiring the responder's authority public key.
/// This function initializes the [`Initiator`] with a default empty state and is intended
/// for use when both the initiator and responder are within the same network. In this case,
/// the initiator does not validate the responder's static key from a certificate. However,
/// the connection remains encrypted.
pub fn without_pk<R: rand::Rng + ?Sized>(rng: &mut R) -> Result<Box<Self>, Error> {
Ok(Self::new(None, rng))
#[cfg(feature = "std")]
pub fn without_pk() -> Result<Box<Self>, Error> {
Self::without_pk_with_rng(&mut rand::thread_rng())

Check warning on line 221 in protocols/v2/noise-sv2/src/initiator.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/initiator.rs#L221

Added line #L221 was not covered by tests
}
pub fn without_pk_with_rng<R: rand::Rng + ?Sized>(rng: &mut R) -> Result<Box<Self>, Error> {
Ok(Self::new_with_rng(None, rng))

Check warning on line 224 in protocols/v2/noise-sv2/src/initiator.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/initiator.rs#L223-L224

Added lines #L223 - L224 were not covered by tests
}

/// Executes the initial step of the Noise NX protocol handshake.
Expand Down Expand Up @@ -248,9 +263,20 @@ impl Initiator {
/// for secure communication. If the provided `message` has an incorrect length, it returns an
/// [`Error::InvalidMessageLength`]. If decryption or signature verification fails, it returns
/// an [`Error::InvalidCertificate`].
#[cfg(feature = "std")]
pub fn step_2(
&mut self,
message: [u8; INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE],
) -> Result<NoiseCodec, Error> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as u32;
self.step_2_with_now(message, now)
}
pub fn step_2_with_now(
&mut self,
message: [u8; INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE],
now: u32,
) -> Result<NoiseCodec, Error> {
// 2. interprets first 64 bytes as ElligatorSwift encoding of x-coordinate of public key
Expand Down Expand Up @@ -316,7 +342,7 @@ impl Initiator {
.0
.serialize();
let rs_pk_xonly = XOnlyPublicKey::from_slice(&rs_pub_key).unwrap();
if signature_message.verify(&rs_pk_xonly, &self.responder_authority_pk, now) {
if signature_message.verify_with_now(&rs_pk_xonly, &self.responder_authority_pk, now) {
let (temp_k1, temp_k2) = Self::hkdf_2(self.get_ck(), &[]);
let c1 = ChaCha20Poly1305::new(&temp_k1.into());
let c2 = ChaCha20Poly1305::new(&temp_k2.into());
Expand Down
46 changes: 39 additions & 7 deletions protocols/v2/noise-sv2/src/responder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,23 @@ impl Responder {
/// protocol handshake. It generates ephemeral and static key pairs for the responder and
/// prepares the handshake state. The authority keypair and certificate validity period are
/// also configured.
pub fn new<R: rand::Rng + ?Sized>(a: Keypair, cert_validity: u32, rng: &mut R) -> Box<Self> {
#[cfg(feature = "std")]
pub fn new(a: Keypair, cert_validity: u32) -> Box<Self> {
Self::new_with_rng(a, cert_validity, &mut rand::thread_rng())
}
pub fn new_with_rng<R: rand::Rng + ?Sized>(
a: Keypair,
cert_validity: u32,
rng: &mut R,
) -> Box<Self> {
let mut self_ = Self {
handshake_cipher: None,
k: None,
n: 0,
ck: [0; 32],
h: [0; 32],
e: Self::generate_key(rng),
s: Self::generate_key(rng),
e: Self::generate_key_with_rng(rng),
s: Self::generate_key_with_rng(rng),
a,
c1: None,
c2: None,
Expand All @@ -200,7 +208,15 @@ impl Responder {
/// the responder's authority credentials. It verifies that the provided public key matches the
/// corresponding private key, ensuring the authenticity of the authority key pair. The
/// certificate validity duration is also set here. Fails if the key pair is mismatched.
pub fn from_authority_kp<R: rand::Rng + ?Sized>(
#[cfg(feature = "std")]
pub fn from_authority_kp(
public: &[u8; 32],
private: &[u8; 32],
cert_validity: Duration,
) -> Result<Box<Self>, Error> {
Self::from_authority_kp_with_rng(public, private, cert_validity, &mut rand::thread_rng())

Check warning on line 217 in protocols/v2/noise-sv2/src/responder.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/responder.rs#L217

Added line #L217 was not covered by tests
}
pub fn from_authority_kp_with_rng<R: rand::Rng + ?Sized>(

Check warning on line 219 in protocols/v2/noise-sv2/src/responder.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/responder.rs#L219

Added line #L219 was not covered by tests
public: &[u8; 32],
private: &[u8; 32],
cert_validity: Duration,
Expand All @@ -211,7 +227,7 @@ impl Responder {
let kp = Keypair::from_secret_key(&secp, &secret);
let pub_ = kp.x_only_public_key().0.serialize();
if public == &pub_[..] {
Ok(Self::new(kp, cert_validity.as_secs() as u32, rng))
Ok(Self::new_with_rng(kp, cert_validity.as_secs() as u32, rng))

Check warning on line 230 in protocols/v2/noise-sv2/src/responder.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/responder.rs#L230

Added line #L230 was not covered by tests
} else {
Err(Error::InvalidRawPublicKey)
}
Expand All @@ -232,7 +248,23 @@ impl Responder {
///
/// On failure, the method returns an error if there is an issue during encryption, decryption,
/// or any other step of the handshake process.
pub fn step_1<R: rand::Rng + rand::CryptoRng>(
#[cfg(feature = "std")]
pub fn step_1(
&mut self,
elligatorswift_theirs_ephemeral_serialized: [u8; ELLSWIFT_ENCODING_SIZE],
) -> Result<([u8; INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE], NoiseCodec), aes_gcm::Error> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as u32;

self.step_1_with_now_rng(
elligatorswift_theirs_ephemeral_serialized,
now,
&mut rand::thread_rng(),
)
}
pub fn step_1_with_now_rng<R: rand::Rng + rand::CryptoRng>(
&mut self,
elligatorswift_theirs_ephemeral_serialized: [u8; ELLSWIFT_ENCODING_SIZE],
now: u32,
Expand Down Expand Up @@ -356,7 +388,7 @@ impl Responder {
ret[7] = not_valid_after[1];
ret[8] = not_valid_after[2];
ret[9] = not_valid_after[3];
SignatureNoiseMessage::sign(&mut ret, &self.s.x_only_public_key().0, &self.a, rng);
SignatureNoiseMessage::sign_with_rng(&mut ret, &self.s.x_only_public_key().0, &self.a, rng);
ret
}

Expand Down
16 changes: 14 additions & 2 deletions protocols/v2/noise-sv2/src/signature_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,15 @@ impl SignatureNoiseMessage {
//
// If an authority public key is not provided, the function assumes that the signature
// is already valid without further verification.
pub fn verify(
#[cfg(feature = "std")]
pub fn verify(self, pk: &XOnlyPublicKey, authority_pk: &Option<XOnlyPublicKey>) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as u32;
self.verify_with_now(pk, authority_pk, now)
}
pub fn verify_with_now(
self,
pk: &XOnlyPublicKey,
authority_pk: &Option<XOnlyPublicKey>,
Expand Down Expand Up @@ -102,7 +110,11 @@ impl SignatureNoiseMessage {
// Creates a Schnorr signature for the message, combining the version, validity period, and
// the static public key of the server (`static_pk`). The resulting signature is then written
// into the provided message buffer (`msg`).
pub fn sign<R: rand::Rng + rand::CryptoRng>(
#[cfg(feature = "std")]
pub fn sign(msg: &mut [u8; 74], static_pk: &XOnlyPublicKey, kp: &Keypair) {
Self::sign_with_rng(msg, static_pk, kp, &mut rand::thread_rng());
}
pub fn sign_with_rng<R: rand::Rng + rand::CryptoRng>(
msg: &mut [u8; 74],
static_pk: &XOnlyPublicKey,
kp: &Keypair,
Expand Down
27 changes: 22 additions & 5 deletions protocols/v2/noise-sv2/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,40 @@ use crate::{handshake::HandshakeOp, initiator::Initiator, responder::Responder};

#[test]
fn test_1() {
let key_pair = Responder::generate_key(&mut rand::thread_rng());
let key_pair = Responder::generate_key();

let mut initiator = Initiator::new(Some(key_pair.public_key().into()), &mut rand::thread_rng());
let mut responder = Responder::new(key_pair, 31449600, &mut rand::thread_rng());
let mut initiator = Initiator::new(Some(key_pair.public_key().into()));
let mut responder = Responder::new(key_pair, 31449600);
let first_message = initiator.step_0().unwrap();
let (second_message, mut codec_responder) = responder.step_1(first_message).unwrap();
let mut codec_initiator = initiator.step_2(second_message).unwrap();
let mut message = "ciao".as_bytes().to_vec();
codec_initiator.encrypt(&mut message).unwrap();
assert!(message != "ciao".as_bytes().to_vec());
codec_responder.decrypt(&mut message).unwrap();

assert!(message == "ciao".as_bytes().to_vec());
}
#[test]
fn test_1_with_rng() {
let key_pair = Responder::generate_key_with_rng(&mut rand::thread_rng());

let mut initiator: Box<Initiator> =
Initiator::new_with_rng(Some(key_pair.public_key().into()), &mut rand::thread_rng());
let mut responder = Responder::new_with_rng(key_pair, 31449600, &mut rand::thread_rng());
let first_message = initiator.step_0().unwrap();
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as u32;
let (second_message, mut codec_responder) = responder
.step_1(first_message, now, &mut rand::thread_rng())
.step_1_with_now_rng(first_message, now, &mut rand::thread_rng())
.unwrap();
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as u32;
let mut codec_initiator = initiator.step_2(second_message, now).unwrap();
let mut codec_initiator = initiator.step_2_with_now(second_message, now).unwrap();
let mut message = "ciao".as_bytes().to_vec();
codec_initiator.encrypt(&mut message).unwrap();
assert!(message != "ciao".as_bytes().to_vec());
Expand Down

0 comments on commit da6e1c1

Please sign in to comment.