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

Support for serde serialize and deserialize #48

Merged
merged 1 commit into from Nov 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion Cargo.toml
Expand Up @@ -29,11 +29,16 @@ travis-ci = { repository = "dalek-cryptography/x25519-dalek", branch = "master"}
features = ["nightly"]

[dependencies]
curve25519-dalek = { version = "1", default-features = false }
curve25519-dalek = { version = "2.0.0-alpha.0", default-features = false }
rand_core = { version = "0.3", default-features = false }
clear_on_drop = { version = "0.2" }
# `serde` is renamed to `our_serde` in order to avoid a name collision between
# importing the serde dependency and enabling the curve25519-dalek/serde feature
our_serde = { package = "serde", version = "1", default-features = false, optional = true, features = ["derive"] }
hdevalence marked this conversation as resolved.
Show resolved Hide resolved
zeroize = { version = "1", default-features = false }

[dev-dependencies]
bincode = "1"
criterion = "0.2"
rand_os = "0.1"

Expand All @@ -43,6 +48,7 @@ harness = false

[features]
default = ["std", "u64_backend"]
serde = ["our_serde", "curve25519-dalek/serde"]
std = ["curve25519-dalek/std"]
nightly = ["curve25519-dalek/nightly", "clear_on_drop/nightly"]
u64_backend = ["curve25519-dalek/u64_backend"]
Expand Down
8 changes: 3 additions & 5 deletions benches/x25519.rs
Expand Up @@ -19,12 +19,10 @@ extern crate x25519_dalek;

use criterion::Criterion;



use rand_os::OsRng;

use x25519_dalek::PublicKey;
use x25519_dalek::EphemeralSecret;
use x25519_dalek::PublicKey;

fn bench_diffie_hellman(c: &mut Criterion) {
let mut csprng: OsRng = OsRng::new().unwrap();
Expand All @@ -39,12 +37,12 @@ fn bench_diffie_hellman(c: &mut Criterion) {
});
}

criterion_group!{
criterion_group! {
name = x25519_benches;
config = Criterion::default();
targets =
bench_diffie_hellman,
}
criterion_main!{
criterion_main! {
x25519_benches,
}
191 changes: 141 additions & 50 deletions src/x25519.rs
Expand Up @@ -20,13 +20,18 @@ use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
use curve25519_dalek::montgomery::MontgomeryPoint;
use curve25519_dalek::scalar::Scalar;

use rand_core::RngCore;
use rand_core::CryptoRng;
use rand_core::RngCore;

/// A `PublicKey` is the corresponding public key converted from
/// an `EphemeralSecret` or a `StaticSecret` key.
#[cfg_attr(feature = "serde", serde(crate = "our_serde"))]
#[cfg_attr(
feature = "serde",
derive(our_serde::Serialize, our_serde::Deserialize)
)]
#[derive(Copy, Clone, Debug)]
pub struct PublicKey(pub (crate) MontgomeryPoint);
pub struct PublicKey(pub(crate) MontgomeryPoint);

impl From<[u8; 32]> for PublicKey {
/// Given a byte array, construct a x25519 `PublicKey`.
Expand All @@ -45,7 +50,7 @@ impl PublicKey {

/// A `EphemeralSecret` is a short lived Diffie-Hellman secret key
/// used to create a `SharedSecret` when given their `PublicKey`.
pub struct EphemeralSecret(pub (crate) Scalar);
pub struct EphemeralSecret(pub(crate) Scalar);

/// Overwrite ephemeral secret key material with null bytes when it goes out of scope.
impl Drop for EphemeralSecret {
Expand All @@ -63,15 +68,15 @@ impl EphemeralSecret {

/// Generate an x25519 `EphemeralSecret` key.
pub fn new<T>(csprng: &mut T) -> Self
where T: RngCore + CryptoRng
where
T: RngCore + CryptoRng,
{
let mut bytes = [0u8; 32];

csprng.fill_bytes(&mut bytes);

EphemeralSecret(clamp_scalar(bytes))
}

}

impl<'a> From<&'a EphemeralSecret> for PublicKey {
Expand All @@ -80,14 +85,20 @@ impl<'a> From<&'a EphemeralSecret> for PublicKey {
fn from(secret: &'a EphemeralSecret) -> PublicKey {
PublicKey((&ED25519_BASEPOINT_TABLE * &secret.0).to_montgomery())
}

}

/// A `StaticSecret` is a static Diffie-Hellman secret key that
/// can be saved and loaded to create a `SharedSecret` when given
/// their `PublicKey`.
#[cfg_attr(feature = "serde", serde(crate = "our_serde"))]
#[cfg_attr(
feature = "serde",
derive(our_serde::Serialize, our_serde::Deserialize)
)]
#[derive(Clone)]
pub struct StaticSecret(pub (crate) Scalar);
pub struct StaticSecret(
#[cfg_attr(feature = "serde", serde(with = "AllowUnreducedScalarBytes"))] pub(crate) Scalar,
);

/// Overwrite static secret key material with null bytes when it goes out of scope.
impl Drop for StaticSecret {
Expand All @@ -105,7 +116,8 @@ impl StaticSecret {

/// Generate a x25519 `StaticSecret` key.
pub fn new<T>(csprng: &mut T) -> Self
where T: RngCore + CryptoRng
where
T: RngCore + CryptoRng,
{
let mut bytes = [0u8; 32];

Expand All @@ -118,7 +130,6 @@ impl StaticSecret {
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}

}

impl From<[u8; 32]> for StaticSecret {
Expand All @@ -134,12 +145,11 @@ impl<'a> From<&'a StaticSecret> for PublicKey {
fn from(secret: &'a StaticSecret) -> PublicKey {
PublicKey((&ED25519_BASEPOINT_TABLE * &secret.0).to_montgomery())
}

}

/// A `SharedSecret` is a Diffie-Hellman shared secret that’s generated
/// from your `EphemeralSecret` or `StaticSecret` and their `PublicKey`.
pub struct SharedSecret(pub (crate) MontgomeryPoint);
pub struct SharedSecret(pub(crate) MontgomeryPoint);

/// Overwrite shared secret material with null bytes when it goes out of scope.
impl Drop for SharedSecret {
Expand All @@ -165,7 +175,7 @@ impl SharedSecret {
///
/// A `Scalar`.
fn clamp_scalar(mut scalar: [u8; 32]) -> Scalar {
scalar[0] &= 248;
scalar[0] &= 248;
scalar[31] &= 127;
scalar[31] |= 64;

Expand All @@ -187,6 +197,24 @@ pub const X25519_BASEPOINT_BYTES: [u8; 32] = [
9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];

/// Derived serialization methods will not work on a StaticSecret because x25519 requires
/// non-canonical scalars which are rejected by curve25519-dalek. Thus we provide a way to convert
/// the bytes directly to a scalar using Serde's remote derive functionality.
#[cfg_attr(feature = "serde", serde(crate = "our_serde"))]
#[cfg_attr(
feature = "serde",
derive(our_serde::Serialize, our_serde::Deserialize)
)]
#[cfg_attr(feature = "serde", serde(remote = "Scalar"))]
struct AllowUnreducedScalarBytes(
#[cfg_attr(feature = "serde", serde(getter = "Scalar::to_bytes"))] [u8; 32],
);
impl From<AllowUnreducedScalarBytes> for Scalar {
fn from(bytes: AllowUnreducedScalarBytes) -> Scalar {
clamp_scalar(bytes.0)
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down Expand Up @@ -226,6 +254,57 @@ mod test {
}
}

#[test]
#[cfg(feature = "serde")]
fn serde_bincode_public_key_roundtrip() {
use bincode;

let public_key = PublicKey::from(X25519_BASEPOINT_BYTES);

let encoded = bincode::serialize(&public_key).unwrap();
let decoded: PublicKey = bincode::deserialize(&encoded).unwrap();

assert_eq!(encoded.len(), 32);
assert_eq!(decoded.as_bytes(), public_key.as_bytes());
}

#[test]
#[cfg(feature = "serde")]
fn serde_bincode_public_key_matches_from_bytes() {
use bincode;

let expected = PublicKey::from(X25519_BASEPOINT_BYTES);
let decoded: PublicKey = bincode::deserialize(&X25519_BASEPOINT_BYTES).unwrap();

assert_eq!(decoded.as_bytes(), expected.as_bytes());
}

#[test]
#[cfg(feature = "serde")]
fn serde_bincode_static_secret_roundtrip() {
use bincode;

let static_secret = StaticSecret(clamp_scalar([0x24; 32]));

let encoded = bincode::serialize(&static_secret).unwrap();
let decoded: StaticSecret = bincode::deserialize(&encoded).unwrap();

assert_eq!(encoded.len(), 32);
assert_eq!(decoded.to_bytes(), static_secret.to_bytes());
}

#[test]
#[cfg(feature = "serde")]
fn serde_bincode_static_secret_matches_from_bytes() {
use bincode;

let expected = StaticSecret(clamp_scalar([0x24; 32]));
let clamped_bytes = clamp_scalar([0x24; 32]).to_bytes();
let decoded: StaticSecret = bincode::deserialize(&clamped_bytes).unwrap();

assert_eq!(decoded.to_bytes(), expected.to_bytes());
}

fn do_rfc7748_ladder_test1(input_scalar: [u8; 32], input_point: [u8; 32], expected: [u8; 32]) {
let result = x25519(input_scalar, input_point);

Expand All @@ -235,41 +314,41 @@ mod test {
#[test]
fn rfc7748_ladder_test1_vectorset1() {
let input_scalar: [u8; 32] = [
0xa5, 0x46, 0xe3, 0x6b, 0xf0, 0x52, 0x7c, 0x9d,
0x3b, 0x16, 0x15, 0x4b, 0x82, 0x46, 0x5e, 0xdd,
0x62, 0x14, 0x4c, 0x0a, 0xc1, 0xfc, 0x5a, 0x18,
0x50, 0x6a, 0x22, 0x44, 0xba, 0x44, 0x9a, 0xc4, ];
0xa5, 0x46, 0xe3, 0x6b, 0xf0, 0x52, 0x7c, 0x9d, 0x3b, 0x16, 0x15, 0x4b, 0x82, 0x46,
0x5e, 0xdd, 0x62, 0x14, 0x4c, 0x0a, 0xc1, 0xfc, 0x5a, 0x18, 0x50, 0x6a, 0x22, 0x44,
0xba, 0x44, 0x9a, 0xc4,
];
let input_point: [u8; 32] = [
0xe6, 0xdb, 0x68, 0x67, 0x58, 0x30, 0x30, 0xdb,
0x35, 0x94, 0xc1, 0xa4, 0x24, 0xb1, 0x5f, 0x7c,
0x72, 0x66, 0x24, 0xec, 0x26, 0xb3, 0x35, 0x3b,
0x10, 0xa9, 0x03, 0xa6, 0xd0, 0xab, 0x1c, 0x4c, ];
0xe6, 0xdb, 0x68, 0x67, 0x58, 0x30, 0x30, 0xdb, 0x35, 0x94, 0xc1, 0xa4, 0x24, 0xb1,
0x5f, 0x7c, 0x72, 0x66, 0x24, 0xec, 0x26, 0xb3, 0x35, 0x3b, 0x10, 0xa9, 0x03, 0xa6,
0xd0, 0xab, 0x1c, 0x4c,
];
let expected: [u8; 32] = [
0xc3, 0xda, 0x55, 0x37, 0x9d, 0xe9, 0xc6, 0x90,
0x8e, 0x94, 0xea, 0x4d, 0xf2, 0x8d, 0x08, 0x4f,
0x32, 0xec, 0xcf, 0x03, 0x49, 0x1c, 0x71, 0xf7,
0x54, 0xb4, 0x07, 0x55, 0x77, 0xa2, 0x85, 0x52, ];
0xc3, 0xda, 0x55, 0x37, 0x9d, 0xe9, 0xc6, 0x90, 0x8e, 0x94, 0xea, 0x4d, 0xf2, 0x8d,
0x08, 0x4f, 0x32, 0xec, 0xcf, 0x03, 0x49, 0x1c, 0x71, 0xf7, 0x54, 0xb4, 0x07, 0x55,
0x77, 0xa2, 0x85, 0x52,
];

do_rfc7748_ladder_test1(input_scalar, input_point, expected);
}

#[test]
fn rfc7748_ladder_test1_vectorset2() {
let input_scalar: [u8; 32] = [
0x4b, 0x66, 0xe9, 0xd4, 0xd1, 0xb4, 0x67, 0x3c,
0x5a, 0xd2, 0x26, 0x91, 0x95, 0x7d, 0x6a, 0xf5,
0xc1, 0x1b, 0x64, 0x21, 0xe0, 0xea, 0x01, 0xd4,
0x2c, 0xa4, 0x16, 0x9e, 0x79, 0x18, 0xba, 0x0d, ];
0x4b, 0x66, 0xe9, 0xd4, 0xd1, 0xb4, 0x67, 0x3c, 0x5a, 0xd2, 0x26, 0x91, 0x95, 0x7d,
0x6a, 0xf5, 0xc1, 0x1b, 0x64, 0x21, 0xe0, 0xea, 0x01, 0xd4, 0x2c, 0xa4, 0x16, 0x9e,
0x79, 0x18, 0xba, 0x0d,
];
let input_point: [u8; 32] = [
0xe5, 0x21, 0x0f, 0x12, 0x78, 0x68, 0x11, 0xd3,
0xf4, 0xb7, 0x95, 0x9d, 0x05, 0x38, 0xae, 0x2c,
0x31, 0xdb, 0xe7, 0x10, 0x6f, 0xc0, 0x3c, 0x3e,
0xfc, 0x4c, 0xd5, 0x49, 0xc7, 0x15, 0xa4, 0x93, ];
0xe5, 0x21, 0x0f, 0x12, 0x78, 0x68, 0x11, 0xd3, 0xf4, 0xb7, 0x95, 0x9d, 0x05, 0x38,
0xae, 0x2c, 0x31, 0xdb, 0xe7, 0x10, 0x6f, 0xc0, 0x3c, 0x3e, 0xfc, 0x4c, 0xd5, 0x49,
0xc7, 0x15, 0xa4, 0x93,
];
let expected: [u8; 32] = [
0x95, 0xcb, 0xde, 0x94, 0x76, 0xe8, 0x90, 0x7d,
0x7a, 0xad, 0xe4, 0x5c, 0xb4, 0xb8, 0x73, 0xf8,
0x8b, 0x59, 0x5a, 0x68, 0x79, 0x9f, 0xa1, 0x52,
0xe6, 0xf8, 0xf7, 0x64, 0x7a, 0xac, 0x79, 0x57, ];
0x95, 0xcb, 0xde, 0x94, 0x76, 0xe8, 0x90, 0x7d, 0x7a, 0xad, 0xe4, 0x5c, 0xb4, 0xb8,
0x73, 0xf8, 0x8b, 0x59, 0x5a, 0x68, 0x79, 0x9f, 0xa1, 0x52, 0xe6, 0xf8, 0xf7, 0x64,
0x7a, 0xac, 0x79, 0x57,
];

do_rfc7748_ladder_test1(input_scalar, input_point, expected);
}
Expand All @@ -284,7 +363,7 @@ mod test {
let mut result: [u8; 32];

macro_rules! do_iterations {
($n:expr) => (
($n:expr) => {
for _ in 0..$n {
result = x25519(k, u);
// OBVIOUS THING THAT I'M GOING TO NOTE ANYWAY BECAUSE I'VE
Expand All @@ -298,7 +377,7 @@ mod test {
u = k.clone();
k = result;
}
)
};
}

// After one iteration:
Expand All @@ -309,19 +388,31 @@ mod test {
// 7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424

do_iterations!(1);
assert_eq!(k, [ 0x42, 0x2c, 0x8e, 0x7a, 0x62, 0x27, 0xd7, 0xbc,
0xa1, 0x35, 0x0b, 0x3e, 0x2b, 0xb7, 0x27, 0x9f,
0x78, 0x97, 0xb8, 0x7b, 0xb6, 0x85, 0x4b, 0x78,
0x3c, 0x60, 0xe8, 0x03, 0x11, 0xae, 0x30, 0x79, ]);
assert_eq!(
k,
[
0x42, 0x2c, 0x8e, 0x7a, 0x62, 0x27, 0xd7, 0xbc, 0xa1, 0x35, 0x0b, 0x3e, 0x2b, 0xb7,
0x27, 0x9f, 0x78, 0x97, 0xb8, 0x7b, 0xb6, 0x85, 0x4b, 0x78, 0x3c, 0x60, 0xe8, 0x03,
0x11, 0xae, 0x30, 0x79,
]
);
do_iterations!(999);
assert_eq!(k, [ 0x68, 0x4c, 0xf5, 0x9b, 0xa8, 0x33, 0x09, 0x55,
0x28, 0x00, 0xef, 0x56, 0x6f, 0x2f, 0x4d, 0x3c,
0x1c, 0x38, 0x87, 0xc4, 0x93, 0x60, 0xe3, 0x87,
0x5f, 0x2e, 0xb9, 0x4d, 0x99, 0x53, 0x2c, 0x51, ]);
assert_eq!(
k,
[
0x68, 0x4c, 0xf5, 0x9b, 0xa8, 0x33, 0x09, 0x55, 0x28, 0x00, 0xef, 0x56, 0x6f, 0x2f,
0x4d, 0x3c, 0x1c, 0x38, 0x87, 0xc4, 0x93, 0x60, 0xe3, 0x87, 0x5f, 0x2e, 0xb9, 0x4d,
0x99, 0x53, 0x2c, 0x51,
]
);
do_iterations!(999_000);
assert_eq!(k, [ 0x7c, 0x39, 0x11, 0xe0, 0xab, 0x25, 0x86, 0xfd,
0x86, 0x44, 0x97, 0x29, 0x7e, 0x57, 0x5e, 0x6f,
0x3b, 0xc6, 0x01, 0xc0, 0x88, 0x3c, 0x30, 0xdf,
0x5f, 0x4d, 0xd2, 0xd2, 0x4f, 0x66, 0x54, 0x24, ]);
assert_eq!(
k,
[
0x7c, 0x39, 0x11, 0xe0, 0xab, 0x25, 0x86, 0xfd, 0x86, 0x44, 0x97, 0x29, 0x7e, 0x57,
0x5e, 0x6f, 0x3b, 0xc6, 0x01, 0xc0, 0x88, 0x3c, 0x30, 0xdf, 0x5f, 0x4d, 0xd2, 0xd2,
0x4f, 0x66, 0x54, 0x24,
]
);
}
}