From 38548d3f25960ed7ddda8ace664e9856ebbc9629 Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Fri, 4 Jun 2021 22:33:15 -0700 Subject: [PATCH] [1/rename] rename everything from "djb" to just "curve25519" - [NON RUST/RENAME] rename "DJB_TYPE" to "CURVE_25519_TYPE" in java and swift - [1+CRYPTO CRATE] make swift/build-ffi.sh --generate-ffi work - [1+CURVE] add docs and the Keyed trait to `curve*.rs` --- .../signal/libsignal/protocol/ecc/Curve.java | 4 +- .../libsignal/protocol/ecc/ECPublicKey.java | 4 +- node/ts/test/PublicAPITest.ts | 2 +- rust/bridge/shared/src/protocol.rs | 2 +- rust/crypto/src/aes_ctr.rs | 4 +- rust/crypto/src/aes_gcm.rs | 17 +- rust/protocol/src/curve.rs | 417 ++++++++++++++---- rust/protocol/src/curve/curve25519.rs | 42 +- rust/protocol/src/identity_key.rs | 1 + rust/protocol/src/lib.rs | 2 +- rust/protocol/src/sealed_sender.rs | 6 +- swift/Sources/SignalFfi/signal_ffi.h | 12 + .../LibSignalClientTests/PublicAPITests.swift | 4 +- 13 files changed, 413 insertions(+), 104 deletions(-) diff --git a/java/shared/java/org/signal/libsignal/protocol/ecc/Curve.java b/java/shared/java/org/signal/libsignal/protocol/ecc/Curve.java index b23e85a710..254acdeea4 100644 --- a/java/shared/java/org/signal/libsignal/protocol/ecc/Curve.java +++ b/java/shared/java/org/signal/libsignal/protocol/ecc/Curve.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2013-2016 Open Whisper Systems + * Copyright (C) 2013-2022 Open Whisper Systems * * Licensed according to the LICENSE file in this repository. */ @@ -7,7 +7,7 @@ import org.signal.libsignal.protocol.InvalidKeyException; public class Curve { - public static final int DJB_TYPE = 0x05; + public static final int CURVE_25519_TYPE = 0x05; public static ECKeyPair generateKeyPair() { ECPrivateKey privateKey = ECPrivateKey.generate(); diff --git a/java/shared/java/org/signal/libsignal/protocol/ecc/ECPublicKey.java b/java/shared/java/org/signal/libsignal/protocol/ecc/ECPublicKey.java index 099f34e333..5468150647 100644 --- a/java/shared/java/org/signal/libsignal/protocol/ecc/ECPublicKey.java +++ b/java/shared/java/org/signal/libsignal/protocol/ecc/ECPublicKey.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2013-2016 Open Whisper Systems + * Copyright (C) 2013-2022 Open Whisper Systems * * Licensed according to the LICENSE file in this repository. */ @@ -26,7 +26,7 @@ public ECPublicKey(byte[] serialized) { static public ECPublicKey fromPublicKeyBytes(byte[] key) { byte[] with_type = new byte[33]; - with_type[0] = 0x05; + with_type[0] = Curve.CURVE_25519_TYPE; System.arraycopy(key, 0, with_type, 1, 32); return new ECPublicKey(Native.ECPublicKey_Deserialize(with_type, 0)); } diff --git a/node/ts/test/PublicAPITest.ts b/node/ts/test/PublicAPITest.ts index 67ae01b29d..2b79e1c5ab 100644 --- a/node/ts/test/PublicAPITest.ts +++ b/node/ts/test/PublicAPITest.ts @@ -1666,7 +1666,7 @@ describe('SignalClient', () => { assert.throws(() => { SignalClient.PrivateKey.deserialize(invalid_key); - }, 'bad key length <33> for key with type '); + }, 'bad key length <33> for key with type '); assert.throws(() => { SignalClient.PublicKey.deserialize(invalid_key); diff --git a/rust/bridge/shared/src/protocol.rs b/rust/bridge/shared/src/protocol.rs index 1dac31bd49..49ea6b6b09 100644 --- a/rust/bridge/shared/src/protocol.rs +++ b/rust/bridge/shared/src/protocol.rs @@ -160,7 +160,7 @@ fn ECPrivateKey_Sign(key: &PrivateKey, message: &[u8]) -> Result> { #[bridge_fn_buffer(ffi = "privatekey_agree", node = "PrivateKey_Agree")] fn ECPrivateKey_Agree(private_key: &PrivateKey, public_key: &PublicKey) -> Result> { - Ok(private_key.calculate_agreement(public_key)?.into_vec()) + Ok(private_key.calculate_agreement(public_key)?.to_vec()) } #[bridge_fn_buffer(ffi = "identitykeypair_serialize")] diff --git a/rust/crypto/src/aes_ctr.rs b/rust/crypto/src/aes_ctr.rs index c35d5fbcd9..1adda137fd 100644 --- a/rust/crypto/src/aes_ctr.rs +++ b/rust/crypto/src/aes_ctr.rs @@ -11,7 +11,9 @@ use aes::{Aes256, NewBlockCipher}; pub struct Aes256Ctr32(aes::Aes256Ctr); impl Aes256Ctr32 { - pub const NONCE_SIZE: usize = aes::BLOCK_SIZE - 4; + pub const CTR_NONCE_SIZE: usize = aes::BLOCK_SIZE - 4; + + const NONCE_SIZE: usize = Self::CTR_NONCE_SIZE; pub fn new(aes256: Aes256, nonce: &[u8], init_ctr: u32) -> Result { if nonce.len() != Self::NONCE_SIZE { diff --git a/rust/crypto/src/aes_gcm.rs b/rust/crypto/src/aes_gcm.rs index a49b359ba1..befba6c2a0 100644 --- a/rust/crypto/src/aes_gcm.rs +++ b/rust/crypto/src/aes_gcm.rs @@ -10,8 +10,11 @@ use ghash::universal_hash::{NewUniversalHash, UniversalHash}; use ghash::GHash; use subtle::ConstantTimeEq; -pub const TAG_SIZE: usize = 16; -pub const NONCE_SIZE: usize = 12; +pub const GCM_TAG_SIZE: usize = 16; +pub const GCM_NONCE_SIZE: usize = 12; + +use GCM_NONCE_SIZE as NONCE_SIZE; +use GCM_TAG_SIZE as TAG_SIZE; #[derive(Clone)] struct GcmGhash { @@ -126,8 +129,12 @@ pub struct Aes256GcmEncryption { } impl Aes256GcmEncryption { - pub const TAG_SIZE: usize = TAG_SIZE; - pub const NONCE_SIZE: usize = NONCE_SIZE; + pub const GCM_ENCRYPTION_TAG_SIZE: usize = TAG_SIZE; + pub const GCM_ENCRYPTION_NONCE_SIZE: usize = NONCE_SIZE; + + const TAG_SIZE: usize = Self::GCM_ENCRYPTION_TAG_SIZE; + #[allow(dead_code)] + const NONCE_SIZE: usize = Self::GCM_ENCRYPTION_NONCE_SIZE; pub fn new(key: &[u8], nonce: &[u8], associated_data: &[u8]) -> Result { let (ctr, ghash) = setup_gcm(key, nonce, associated_data)?; @@ -140,7 +147,7 @@ impl Aes256GcmEncryption { Ok(()) } - pub fn compute_tag(self) -> Result<[u8; TAG_SIZE]> { + pub fn compute_tag(self) -> Result<[u8; Self::TAG_SIZE]> { self.ghash.finalize() } } diff --git a/rust/protocol/src/curve.rs b/rust/protocol/src/curve.rs index 33e683742a..e50c9ae838 100644 --- a/rust/protocol/src/curve.rs +++ b/rust/protocol/src/curve.rs @@ -1,56 +1,101 @@ // -// Copyright 2020-2021 Signal Messenger, LLC. +// Copyright 2020-2022 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // -pub(crate) mod curve25519; - +//! Cryptographic primitives for asymmetric keys. +//! +//! Some example operations: +//!``` +//! use libsignal_protocol::KeyPair; +//! use std::collections::HashSet; +//! +//! let alice = KeyPair::generate(&mut rand::thread_rng()); +//! assert!(alice == alice.clone()); +//! let bob = KeyPair::generate(&mut rand::thread_rng()); +//! assert!(alice != bob); +//! +//! // Keys can be hashed and put in sets. +//! let key_set: HashSet = [alice, bob].iter().cloned().collect(); +//! assert!(key_set.contains(&alice)); +//! assert!(key_set.contains(&bob)); +//!``` + +#![warn(missing_docs)] + +pub mod curve25519; + +use crate::utils::constant_time_cmp; use crate::{Result, SignalProtocolError}; use std::cmp::Ordering; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::fmt; +use std::hash; use arrayref::array_ref; +use displaydoc::Display; +use num_enum::{IntoPrimitive, TryFromPrimitive, TryFromPrimitiveError}; use rand::{CryptoRng, Rng}; use subtle::ConstantTimeEq; -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +/// Map the variant of key being used to and from a [u8]. +/// +/// Implements [SignalProtocolError::from] on top of [num_enum::TryFromPrimitive] to convert into +/// raising a [SignalProtocolError] if the encoded key variant is invalid. +/// +///``` +/// # fn main() -> libsignal_protocol::error::Result<()> { +/// use libsignal_protocol::KeyType; +/// use std::convert::{TryFrom, TryInto}; +/// +/// let encoded_key_type: u8 = KeyType::Curve25519.into(); +/// assert!(encoded_key_type == 0x05); +/// +/// let original_key_type: KeyType = encoded_key_type.try_into()?; +/// assert!(original_key_type == KeyType::Curve25519); +/// +/// let bad_encoded_key: u8 = 0x27; +/// assert!(KeyType::try_from(bad_encoded_key).is_err()); +/// # Ok(()) +/// # } +///``` +#[derive( + Debug, Display, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, TryFromPrimitive, IntoPrimitive, +)] +#[ignore_extra_doc_attributes] +#[repr(u8)] pub enum KeyType { - Djb, -} - -impl fmt::Display for KeyType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self, f) - } + /// + /// + /// See [Curve25519]. + /// + /// [Curve25519]: https://en.wikipedia.org/wiki/Curve25519. + Curve25519 = 0x05, } -impl KeyType { - fn value(&self) -> u8 { - match &self { - KeyType::Djb => 0x05u8, - } +impl From> for SignalProtocolError { + fn from(err: TryFromPrimitiveError) -> Self { + SignalProtocolError::BadKeyType(err.number) } } -impl TryFrom for KeyType { - type Error = SignalProtocolError; - - fn try_from(x: u8) -> Result { - match x { - 0x05u8 => Ok(KeyType::Djb), - t => Err(SignalProtocolError::BadKeyType(t)), - } - } +/// Interface for structs that perform operations parameterized by values of [KeyType]. +pub trait Keyed { + /// Return the variant of key this object employs. + fn key_type(&self) -> KeyType; } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] enum PublicKeyData { - DjbPublicKey([u8; curve25519::PUBLIC_KEY_LENGTH]), + Curve25519([u8; 32]), } -#[derive(Clone, Copy, Eq)] +/// Public key half of a [KeyPair]. +/// +/// Uses [Self::ct_eq] and [constant_time_cmp] to implement equality and ordering without leaking +/// too much information about the contents of the data being compared. +#[derive(Clone, Copy)] pub struct PublicKey { key: PublicKeyData, } @@ -60,64 +105,81 @@ impl PublicKey { Self { key } } + /// Deserialize a public key from a byte slice. pub fn deserialize(value: &[u8]) -> Result { if value.is_empty() { return Err(SignalProtocolError::NoKeyTypeIdentifier); } let key_type = KeyType::try_from(value[0])?; match key_type { - KeyType::Djb => { + KeyType::Curve25519 => { // We allow trailing data after the public key (why?) if value.len() < curve25519::PUBLIC_KEY_LENGTH + 1 { - return Err(SignalProtocolError::BadKeyLength(KeyType::Djb, value.len())); + return Err(SignalProtocolError::BadKeyLength( + KeyType::Curve25519, + value.len(), + )); } let mut key = [0u8; curve25519::PUBLIC_KEY_LENGTH]; key.copy_from_slice(&value[1..][..curve25519::PUBLIC_KEY_LENGTH]); Ok(PublicKey { - key: PublicKeyData::DjbPublicKey(key), + key: PublicKeyData::Curve25519(key), }) } } } + /// Return the bytes that make up this public key. pub fn public_key_bytes(&self) -> Result<&[u8]> { - match &self.key { - PublicKeyData::DjbPublicKey(v) => Ok(v), + match self.key { + PublicKeyData::Curve25519(ref v) => Ok(v), } } - pub fn from_djb_public_key_bytes(bytes: &[u8]) -> Result { + /// Create an instance by attempting to interpret `bytes` as a [KeyType::Curve25519] public key. + pub fn from_curve25519_public_key_bytes(bytes: &[u8]) -> Result { match <[u8; curve25519::PUBLIC_KEY_LENGTH]>::try_from(bytes) { - Err(_) => Err(SignalProtocolError::BadKeyLength(KeyType::Djb, bytes.len())), + Err(_) => Err(SignalProtocolError::BadKeyLength( + KeyType::Curve25519, + bytes.len(), + )), Ok(key) => Ok(PublicKey { - key: PublicKeyData::DjbPublicKey(key), + key: PublicKeyData::Curve25519(key), }), } } + /// Return a byte slice which can be deserialized with [Self::deserialize]. pub fn serialize(&self) -> Box<[u8]> { - let value_len = match &self.key { - PublicKeyData::DjbPublicKey(v) => v.len(), + let value_len = match self.key { + PublicKeyData::Curve25519(v) => v.len(), }; let mut result = Vec::with_capacity(1 + value_len); - result.push(self.key_type().value()); - match &self.key { - PublicKeyData::DjbPublicKey(v) => result.extend_from_slice(v), + result.push(self.key_type().into()); + match self.key { + PublicKeyData::Curve25519(v) => result.extend_from_slice(&v), } result.into_boxed_slice() } + /// Validate whether `signature` successfully matches `message` for this public key. + /// + /// Return `Ok(false)` if the signature fails to match or could not be read. pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> Result { self.verify_signature_for_multipart_message(&[message], signature) } + /// Validate whether `signature` successfully matches each segment of the multipart `message` + /// for this public key. + /// + /// Return `Ok(false)` if the signature fails to match or could not be read. pub fn verify_signature_for_multipart_message( &self, message: &[&[u8]], signature: &[u8], ) -> Result { match &self.key { - PublicKeyData::DjbPublicKey(pub_key) => { + PublicKeyData::Curve25519(pub_key) => { if signature.len() != curve25519::SIGNATURE_LENGTH { return Ok(false); } @@ -130,15 +192,17 @@ impl PublicKey { } } - fn key_data(&self) -> &[u8] { - match &self.key { - PublicKeyData::DjbPublicKey(ref k) => k.as_ref(), + pub(crate) fn key_data(&self) -> &[u8] { + match self.key { + PublicKeyData::Curve25519(ref k) => k.as_ref(), } } +} - pub fn key_type(&self) -> KeyType { - match &self.key { - PublicKeyData::DjbPublicKey(_) => KeyType::Djb, +impl Keyed for PublicKey { + fn key_type(&self) -> KeyType { + match self.key { + PublicKeyData::Curve25519(_) => KeyType::Curve25519, } } } @@ -176,6 +240,14 @@ impl PartialEq for PublicKey { } } +impl Eq for PublicKey {} + +impl hash::Hash for PublicKey { + fn hash(&self, state: &mut H) { + self.key_data().hash(state) + } +} + impl Ord for PublicKey { fn cmp(&self, other: &Self) -> Ordering { if self.key_type() != other.key_type() { @@ -196,27 +268,35 @@ impl fmt::Debug for PublicKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "PublicKey {{ key_type={}, serialize={:?} }}", + "PublicKey {{ key_type={:?}, serialize={:?} }}", self.key_type(), self.serialize() ) } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] enum PrivateKeyData { - DjbPrivateKey([u8; curve25519::PRIVATE_KEY_LENGTH]), + Curve25519([u8; curve25519::PRIVATE_KEY_LENGTH]), } -#[derive(Clone, Copy, Eq, PartialEq)] +/// Private key half of a [KeyPair]. +/// +/// Analogously to [PublicKey], uses [Self::ct_eq] and [constant_time_cmp] to implement equality and +/// ordering without leaking too much information about the contents of the data being compared. +#[derive(Clone, Copy)] pub struct PrivateKey { key: PrivateKeyData, } impl PrivateKey { + /// Try to parse a private key from the byte slice `value`. pub fn deserialize(value: &[u8]) -> Result { if value.len() != curve25519::PRIVATE_KEY_LENGTH { - Err(SignalProtocolError::BadKeyLength(KeyType::Djb, value.len())) + Err(SignalProtocolError::BadKeyLength( + KeyType::Curve25519, + value.len(), + )) } else { let mut key = [0u8; curve25519::PRIVATE_KEY_LENGTH]; key.copy_from_slice(&value[..curve25519::PRIVATE_KEY_LENGTH]); @@ -225,33 +305,30 @@ impl PrivateKey { key[31] &= 0x7F; key[31] |= 0x40; Ok(Self { - key: PrivateKeyData::DjbPrivateKey(key), + key: PrivateKeyData::Curve25519(key), }) } } + /// Return a byte slice which can be deserialized with [Self::deserialize]. pub fn serialize(&self) -> Vec { - match &self.key { - PrivateKeyData::DjbPrivateKey(v) => v.to_vec(), + match self.key { + PrivateKeyData::Curve25519(v) => v.to_vec(), } } + /// Derive a public key from the current private key's contents. pub fn public_key(&self) -> Result { - match &self.key { - PrivateKeyData::DjbPrivateKey(private_key) => { + match self.key { + PrivateKeyData::Curve25519(private_key) => { let public_key = - curve25519::PrivateKey::from(*private_key).derive_public_key_bytes(); - Ok(PublicKey::new(PublicKeyData::DjbPublicKey(public_key))) + curve25519::PrivateKey::from(private_key).derive_public_key_bytes(); + Ok(PublicKey::new(PublicKeyData::Curve25519(public_key))) } } } - pub fn key_type(&self) -> KeyType { - match &self.key { - PrivateKeyData::DjbPrivateKey(_) => KeyType::Djb, - } - } - + /// Calculate a signature for `message` given this private key. pub fn calculate_signature( &self, message: &[u8], @@ -260,27 +337,48 @@ impl PrivateKey { self.calculate_signature_for_multipart_message(&[message], csprng) } + /// Calculate a signature for the multipart `message` given this private key. pub fn calculate_signature_for_multipart_message( &self, message: &[&[u8]], csprng: &mut R, ) -> Result> { match self.key { - PrivateKeyData::DjbPrivateKey(k) => { + PrivateKeyData::Curve25519(k) => { let private_key = curve25519::PrivateKey::from(k); Ok(Box::new(private_key.calculate_signature(csprng, message))) } } } - pub fn calculate_agreement(&self, their_key: &PublicKey) -> Result> { + /// Calculate a shared secret between this private key and the public key `their_key`. + pub fn calculate_agreement( + &self, + their_key: &PublicKey, + ) -> Result<[u8; curve25519::AGREEMENT_LENGTH]> { match (self.key, their_key.key) { - (PrivateKeyData::DjbPrivateKey(priv_key), PublicKeyData::DjbPublicKey(pub_key)) => { + (PrivateKeyData::Curve25519(priv_key), PublicKeyData::Curve25519(pub_key)) => { let private_key = curve25519::PrivateKey::from(priv_key); - Ok(Box::new(private_key.calculate_agreement(&pub_key))) + Ok(private_key.calculate_agreement(&pub_key)) } } } + + pub(crate) fn key_data(&self) -> Box<[u8]> { + Box::from( + self.public_key() + .expect("should always have a public key translation") + .key_data(), + ) + } +} + +impl Keyed for PrivateKey { + fn key_type(&self) -> KeyType { + match self.key { + PrivateKeyData::Curve25519(_) => KeyType::Curve25519, + } + } } impl From for PrivateKey { @@ -297,45 +395,144 @@ impl TryFrom<&[u8]> for PrivateKey { } } -#[derive(Copy, Clone)] +impl subtle::ConstantTimeEq for PrivateKey { + fn ct_eq(&self, other: &PrivateKey) -> subtle::Choice { + if self.key_type() != other.key_type() { + return 0.ct_eq(&1); + } + self.key_data().as_ref().ct_eq(other.key_data().as_ref()) + } +} + +impl PartialEq for PrivateKey { + fn eq(&self, other: &PrivateKey) -> bool { + bool::from(self.ct_eq(other)) + } +} + +impl Eq for PrivateKey {} + +impl hash::Hash for PrivateKey { + fn hash(&self, state: &mut H) { + self.key_data().hash(state) + } +} + +impl Ord for PrivateKey { + fn cmp(&self, other: &Self) -> Ordering { + if self.key_type() != other.key_type() { + return self.key_type().cmp(&other.key_type()); + } + constant_time_cmp(self.key_data().as_ref(), other.key_data().as_ref()) + } +} + +impl PartialOrd for PrivateKey { + fn partial_cmp(&self, other: &PrivateKey) -> Option { + Some(self.cmp(other)) + } +} + +impl fmt::Debug for PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + // Do not print out private keys in debug logs. + "PrivateKey {{ key_type={:?}, serialize=<...> }}", + self.key_type(), + ) + } +} + +/// A matching public and private key pair which can be used to encrypt and sign messages. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct KeyPair { + /// The public half of this identity. pub public_key: PublicKey, + /// The private half of this identity. pub private_key: PrivateKey, } impl KeyPair { + /// Create a new identity from randomness in `csprng`. + /// + ///``` + /// use libsignal_protocol::KeyPair; + /// + /// // Create a new unique key pair from random state. + /// let alice = KeyPair::generate(&mut rand::thread_rng()); + /// assert!(alice == alice.clone()); + /// + /// // Any subsequently generated random key pair will be different. + /// let bob = KeyPair::generate(&mut rand::thread_rng()); + /// assert!(alice != bob); + ///``` pub fn generate(csprng: &mut R) -> Self { let private_key = curve25519::PrivateKey::new(csprng); - let public_key = PublicKey::from(PublicKeyData::DjbPublicKey( + let public_key = PublicKey::from(PublicKeyData::Curve25519( private_key.derive_public_key_bytes(), )); - let private_key = PrivateKey::from(PrivateKeyData::DjbPrivateKey( - private_key.private_key_bytes(), - )); + let private_key = + PrivateKey::from(PrivateKeyData::Curve25519(private_key.private_key_bytes())); - Self { - public_key, - private_key, - } + Self::new(public_key, private_key) } + /// Instantiate an identity from a known public/private key pair. + /// + ///``` + /// use libsignal_protocol::KeyPair; + /// + /// // Generate a random key pair. + /// let kp = KeyPair::generate(&mut rand::thread_rng()); + /// + /// // Reconstruct the key pair from its fields. + /// let KeyPair { public_key, private_key } = kp; + /// assert!(kp == KeyPair::new(public_key, private_key)); + ///``` pub fn new(public_key: PublicKey, private_key: PrivateKey) -> Self { + assert_eq!(public_key.key_type(), private_key.key_type()); Self { public_key, private_key, } } + /// Instantiate an identity from serialized public and private keys. + ///``` + /// # fn main() -> Result<(), libsignal_protocol::error::SignalProtocolError> { + /// use libsignal_protocol::KeyPair; + /// + /// // Generate a random key pair. + /// let kp = KeyPair::generate(&mut rand::thread_rng()); + /// + /// // Reconstruct the key pair from its fields. + /// let KeyPair { public_key, private_key } = kp; + /// assert!(kp == KeyPair::from_public_and_private( + /// &public_key.serialize(), + /// &private_key.serialize(), + /// )?); + /// # Ok(()) + /// # } + ///``` pub fn from_public_and_private(public_key: &[u8], private_key: &[u8]) -> Result { let public_key = PublicKey::try_from(public_key)?; let private_key = PrivateKey::try_from(private_key)?; - Ok(Self { - public_key, - private_key, - }) + Ok(Self::new(public_key, private_key)) } + /// Calculate a signature for `message` given the current identity's private key. + /// + ///``` + /// # fn main() -> Result<(), libsignal_protocol::error::SignalProtocolError> { + /// # use libsignal_protocol::KeyPair; + /// let kp = KeyPair::generate(&mut rand::thread_rng()); + /// # #[allow(unused_variables)] + /// let signature: Box<[u8]> = kp.calculate_signature(b"hello", &mut rand::thread_rng())?; + /// # Ok(()) + /// # } + ///``` pub fn calculate_signature( &self, message: &[u8], @@ -344,8 +541,58 @@ impl KeyPair { self.private_key.calculate_signature(message, csprng) } + /// Calculate a shared secret between our private key and the public key `their_key`. + /// + ///``` + /// # fn main() -> Result<(), libsignal_protocol::SignalProtocolError> { + /// # use libsignal_protocol::KeyPair; + /// let kp = KeyPair::generate(&mut rand::thread_rng()); + /// let kp2 = KeyPair::generate(&mut rand::thread_rng()); + /// assert!( + /// kp.calculate_agreement(&kp2.public_key)? == kp2.calculate_agreement(&kp.public_key)? + /// ); + /// # Ok(()) + /// # } + ///``` pub fn calculate_agreement(&self, their_key: &PublicKey) -> Result> { - self.private_key.calculate_agreement(their_key) + Ok(Box::from( + self.private_key.calculate_agreement(their_key)?.as_ref(), + )) + } + + /// Verify a signature for `message` produced by [Self::calculate_signature]. + ///``` + /// # fn main() -> Result<(), libsignal_protocol::error::SignalProtocolError> { + /// # use libsignal_protocol::KeyPair; + /// let kp = KeyPair::generate(&mut rand::thread_rng()); + /// let signature = kp.calculate_signature(b"hello", &mut rand::thread_rng())?; + /// assert!(KeyPair::verify_signature(&kp.public_key.serialize(), b"hello", &signature)?); + /// # Ok(()) + /// # } + ///``` + pub fn verify_signature( + their_public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result { + let their_public_key: PublicKey = their_public_key.try_into()?; + their_public_key.verify_signature(message, signature) + } +} + +impl Keyed for KeyPair { + fn key_type(&self) -> KeyType { + assert_eq!(self.public_key.key_type(), self.private_key.key_type()); + self.public_key.key_type() + } +} + +impl subtle::ConstantTimeEq for KeyPair { + fn ct_eq(&self, other: &KeyPair) -> subtle::Choice { + if self.key_type() != other.key_type() { + return 0.ct_eq(&1); + } + self.public_key.ct_eq(&other.public_key) & self.private_key.ct_eq(&other.private_key) } } diff --git a/rust/protocol/src/curve/curve25519.rs b/rust/protocol/src/curve/curve25519.rs index 8d4b9684f3..efae598a76 100644 --- a/rust/protocol/src/curve/curve25519.rs +++ b/rust/protocol/src/curve/curve25519.rs @@ -3,6 +3,17 @@ // SPDX-License-Identifier: AGPL-3.0-only // +//! Implementation of [X25519] and [Ed25519]. +//! +//! The underlying elliptic curve operations are performed by the [curve25519_dalek] and +//! [x25519_dalek] crates. +//! +//! We rely on [ConstantTimeEq] in multiple operations to make them more +//! resistant to timing attacks. +//! +//! [X25519]: https://en.wikipedia.org/wiki/Curve25519 +//! [Ed25519]: https://en.wikipedia.org/wiki/EdDSA#Ed25519 + use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; use curve25519_dalek::edwards::EdwardsPoint; use curve25519_dalek::montgomery::MontgomeryPoint; @@ -12,17 +23,36 @@ use sha2::{Digest, Sha512}; use subtle::ConstantTimeEq; use x25519_dalek::{PublicKey, StaticSecret}; -const AGREEMENT_LENGTH: usize = 32; +use std::fmt; + +/// Length of an agreed-upon key after a [DH] exchange. +/// +/// [DH]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange +pub const AGREEMENT_LENGTH: usize = 32; +/// Length of a private key. pub const PRIVATE_KEY_LENGTH: usize = 32; +/// Length of a public key. pub const PUBLIC_KEY_LENGTH: usize = 32; +/// Length of a signature. pub const SIGNATURE_LENGTH: usize = 64; +/// A flexible key pair type wrapping [PublicKey] and [StaticSecret]. +/// +/// Operations here are simple enough to *not* produce fallible [Result]s. Instead we use static +/// slice lengths to avoid having to handle any error cases. #[derive(Clone)] pub struct PrivateKey { secret: StaticSecret, } +impl fmt::Debug for PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PrivateKey {{ secret: {:?} }}", self.secret.to_bytes()) + } +} + impl PrivateKey { + /// Generate a [StaticSecret] and then a [PublicKey] from a [CryptoRng]. pub fn new(csprng: &mut R) -> Self where R: CryptoRng + Rng, @@ -31,6 +61,9 @@ impl PrivateKey { PrivateKey { secret } } + /// Do a [DH] key exchange to produce a slice that can be reproduced by another keypair. + /// + /// [DH]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange pub fn calculate_agreement( &self, their_public_key: &[u8; PUBLIC_KEY_LENGTH], @@ -43,7 +76,7 @@ impl PrivateKey { /// Calculates an XEdDSA signature using the X25519 private key directly. /// - /// Refer to https://signal.org/docs/specifications/xeddsa/#curve25519 for more details. + /// Refer to for more details. /// /// Note that this implementation varies slightly from that paper in that the sign bit is not /// fixed to 0, but rather passed back in the most significant bit of the signature which would @@ -100,6 +133,8 @@ impl PrivateKey { result } + /// Verify a signature from [Self::calculate_signature] against another key pair's public key. + /// pub fn verify_signature( their_public_key: &[u8; PUBLIC_KEY_LENGTH], message: &[&[u8]], @@ -140,10 +175,13 @@ impl PrivateKey { bool::from(cap_r_check.as_bytes().ct_eq(&cap_r)) } + /// Return the bytes of a [PublicKey] that another party can use to validate against + /// [Self::verify_signature]. pub fn derive_public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { *PublicKey::from(&self.secret).as_bytes() } + /// Return a serialization of this private key. pub fn private_key_bytes(&self) -> [u8; PRIVATE_KEY_LENGTH] { self.secret.to_bytes() } diff --git a/rust/protocol/src/identity_key.rs b/rust/protocol/src/identity_key.rs index 4777f5b803..28633b56ea 100644 --- a/rust/protocol/src/identity_key.rs +++ b/rust/protocol/src/identity_key.rs @@ -176,6 +176,7 @@ impl From for KeyPair { #[cfg(test)] mod tests { use super::*; + use crate::Keyed; use rand::rngs::OsRng; diff --git a/rust/protocol/src/lib.rs b/rust/protocol/src/lib.rs index fbfacc50d8..1124dad3b5 100644 --- a/rust/protocol/src/lib.rs +++ b/rust/protocol/src/lib.rs @@ -45,7 +45,7 @@ use error::Result; pub use { address::{DeviceId, ProtocolAddress}, - curve::{KeyPair, PrivateKey, PublicKey}, + curve::{KeyPair, KeyType, Keyed, PrivateKey, PublicKey}, error::SignalProtocolError, fingerprint::{DisplayableFingerprint, Fingerprint, ScannableFingerprint}, group_cipher::{ diff --git a/rust/protocol/src/sealed_sender.rs b/rust/protocol/src/sealed_sender.rs index caf3f6170a..bc40385f66 100644 --- a/rust/protocol/src/sealed_sender.rs +++ b/rust/protocol/src/sealed_sender.rs @@ -578,7 +578,9 @@ impl UnidentifiedSenderMessage { remaining.split_at(curve::curve25519::PUBLIC_KEY_LENGTH); Ok(Self::V2 { - ephemeral_public: PublicKey::from_djb_public_key_bytes(ephemeral_public)?, + ephemeral_public: PublicKey::from_curve25519_public_key_bytes( + ephemeral_public, + )?, encrypted_message_key: encrypted_message_key.into(), authentication_tag: encrypted_authentication_tag.into(), encrypted_message: encrypted_message.into(), @@ -990,7 +992,7 @@ mod sealed_sender_v2 { let agreement = our_keys .private_key() .calculate_agreement(their_key.public_key())?; - let mut agreement_key_input = agreement.into_vec(); + let mut agreement_key_input = agreement.to_vec(); agreement_key_input.extend_from_slice(&ephemeral_pub_key.serialize()); agreement_key_input.extend_from_slice(encrypted_message_key); match direction { diff --git a/swift/Sources/SignalFfi/signal_ffi.h b/swift/Sources/SignalFfi/signal_ffi.h index 3f377c1e70..f131d09090 100644 --- a/swift/Sources/SignalFfi/signal_ffi.h +++ b/swift/Sources/SignalFfi/signal_ffi.h @@ -204,6 +204,12 @@ typedef struct SignalPreKeyRecord SignalPreKeyRecord; typedef struct SignalPreKeySignalMessage SignalPreKeySignalMessage; +/** + * Private key half of a [KeyPair]. + * + * Analogously to [PublicKey], uses [Self::ct_eq] and [constant_time_cmp] to implement equality and + * ordering without leaking too much information about the contents of the data being compared. + */ typedef struct SignalPrivateKey SignalPrivateKey; /** @@ -211,6 +217,12 @@ typedef struct SignalPrivateKey SignalPrivateKey; */ typedef struct SignalProtocolAddress SignalProtocolAddress; +/** + * Public key half of a [KeyPair]. + * + * Uses [Self::ct_eq] and [constant_time_cmp] to implement equality and ordering without leaking + * too much information about the contents of the data being compared. + */ typedef struct SignalPublicKey SignalPublicKey; typedef struct SignalSenderCertificate SignalSenderCertificate; diff --git a/swift/Tests/LibSignalClientTests/PublicAPITests.swift b/swift/Tests/LibSignalClientTests/PublicAPITests.swift index bf93acff2a..a49d0523aa 100644 --- a/swift/Tests/LibSignalClientTests/PublicAPITests.swift +++ b/swift/Tests/LibSignalClientTests/PublicAPITests.swift @@ -1,5 +1,5 @@ // -// Copyright 2020 Signal Messenger, LLC. +// Copyright 2020-2022 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // @@ -74,7 +74,7 @@ class PublicAPITests: TestCaseBase { let pk = sk.publicKey let pk_bytes = pk.serialize() - XCTAssertEqual(pk_bytes[0], 0x05) // DJB + XCTAssertEqual(pk_bytes[0], 0x05) // Curve25519 XCTAssertEqual(pk_bytes.count, 33) let pk_raw = pk.keyBytes