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

feat: draft-20 [WPB-1192] #42

Open
wants to merge 54 commits into
base: upstream
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
2f9acdc
WIP: Draft-20 OpenMLS Async Edition
OtaK Apr 19, 2023
0fd1f8f
feat: Implement Group Context Extensions proposal support
augustocdias May 17, 2023
e3930d9
fix: Make LeafNode lifetime visible
OtaK May 22, 2023
fb2b49c
fix: Pass pending proposals to check member support on gce extensions
augustocdias May 23, 2023
aae07a2
Opened up API
OtaK May 23, 2023
97b5068
Added deref impl for RatchetTree
OtaK May 24, 2023
dd60193
API Opening
OtaK May 24, 2023
f4b9d95
More fixes
OtaK May 25, 2023
8c6ac9c
feat: Implement x509 credentials support - CL-171
augustocdias May 23, 2023
a405566
Fixed compilation issues
OtaK May 30, 2023
8781a48
Derive Clone where needed
OtaK May 31, 2023
5a48731
impl from bytes for HashReference
beltram May 31, 2023
23249b2
Allow hashref from slice
OtaK May 31, 2023
1ac46dd
expose MlsMessageOut -> In
beltram May 31, 2023
c868028
impl From<u32> for Leaf|ParentNodeIndex
OtaK May 31, 2023
020645a
fix conversion discrepancy
beltram May 31, 2023
d38d720
expose private message epoch()
beltram May 31, 2023
42a9399
Added way to externally change state
OtaK May 31, 2023
f49aec9
expose x509 required fields
beltram Jun 1, 2023
51611f2
fix external commit bug happening when the joiner merges the external…
beltram Jun 1, 2023
2c9a429
clean unused methods
beltram Jun 2, 2023
a749632
quiet rust 1.70 lints
beltram Jun 2, 2023
da7dff2
fix AuthenticatedContentIn::content visibility
beltram Jun 2, 2023
12bd342
quiet clippy lints
beltram Jun 2, 2023
752b5a8
fix: replace home-baked constant-time implementation by subtle
beltram Jun 5, 2023
0148ed9
chore: remove already resolved TODO
beltram Jun 5, 2023
322138a
chore: remove some panicking code from `hkdf_extract`
beltram Jun 5, 2023
8492426
fix: review x509
beltram Jun 5, 2023
531d567
remove rayon
beltram Jun 6, 2023
f2eac36
fix wasm compilation and add wasm-bindgen
beltram Jun 7, 2023
dd95646
remove tokio and use async_std for test due to integration with rstest
beltram Jun 7, 2023
06a5178
fix failing test `secret_incompatible`
beltram Jun 7, 2023
40eb734
add more wasm-bindgen tests and fix the one related to keypackage lif…
beltram Jun 7, 2023
588fc2c
feat: Building blocks for reinit implementation
augustocdias Jun 1, 2023
eaecb42
fix: return specialized error for very old epochs
augustocdias Jun 7, 2023
04af534
fix: x509 cert time validation was not compatible with WASM
beltram Jun 8, 2023
3a03087
test: fix `test_past_secrets_in_group` failing tests
beltram Jun 8, 2023
b48b685
fix: Stop issuing full commits when not needed
OtaK Jun 8, 2023
3cdea56
fix: update commit were not updating the keys
beltram Jun 9, 2023
635dc83
fix: use latest tls_codec commits to fix a bug related to variable le…
beltram Jun 9, 2023
e7665c7
feat: Add root certificates group context
augustocdias Jun 9, 2023
9e5ccef
fix: switch to CredentialType in the PerDomainTrustActor extension
augustocdias Jun 14, 2023
d4151a2
chore: use pattern matching in PerDomainTrustAnchor constructor
beltram Jun 14, 2023
58430d3
fix: remove tls discriminants from `MlsCredentialType`
beltram Jun 14, 2023
83499a6
feat: Added support for X25519KYBER768DRAFT00_AES128GCM_SHA256_Ed2551…
OtaK Jun 19, 2023
bc73bf7
fix: Docs errors
OtaK Jun 21, 2023
8b11170
fix(dep): Pin tls_codec versions
OtaK Jun 21, 2023
05c1004
chore: Use stabilized tls_codec 0.3.0
OtaK Jun 22, 2023
91a2f39
feat: all methods required for updating with a LeafNode
beltram Jun 15, 2023
3bdb276
chore: expose group context
augustocdias Jun 21, 2023
37964fe
chore: add helpers for detecting duplicate messages
beltram Jul 7, 2023
67e13ef
chore: expose `VerifiableGroupInfo::group_id`
beltram Jul 11, 2023
e84e0ff
WPB-2823
beltram Jul 11, 2023
5c459a9
chore: expose `PrivateMessage::content_type()`
beltram Jul 20, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ jobs:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo doc -p openmls --message-format json
- run: cargo doc -p openmls
25 changes: 10 additions & 15 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,22 @@ members = [
"openmls_rust_crypto",
"fuzz",
"cli",
"interop_client",
# "interop_client",
"memory_keystore",
"evercrypt_backend",
"delivery-service/ds",
"delivery-service/ds-lib",
"basic_credential"
"basic_credential",
"x509_credential"
]
resolver = "2"

# Patching unreleased crates
[workspace.dependencies]
tls_codec = { version = "0.3.0-pre.3", features = ["derive", "serde", "mls"] }
async-trait = "0.1"

[patch.crates-io.hpke-rs]
git = "https://github.com/franziskuskiefer/hpke-rs.git"
[workspace.dependencies.tls_codec]
version = "0.3.0"
features = ["derive", "serde", "mls"]

[patch.crates-io.hpke-rs-crypto]
git = "https://github.com/franziskuskiefer/hpke-rs.git"

[patch.crates-io.hpke-rs-evercrypt]
git = "https://github.com/franziskuskiefer/hpke-rs.git"

[patch.crates-io.hpke-rs-rust-crypto]
git = "https://github.com/franziskuskiefer/hpke-rs.git"
[workspace.dependencies.tls_codec_derive]
version = "0.3.0"
features = ["derive", "serde", "mls"]
12 changes: 9 additions & 3 deletions basic_credential/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ readme = "README.md"
[dependencies]
openmls_traits = { version = "0.1.0", path = "../traits" }
tls_codec = { workspace = true }
async-trait = { workspace = true }
serde = "1.0"

# Rust Crypto
ed25519-dalek = { version = "1.0" }
p256 = { version = "0.11" }
rand-07 = {version = "0.7", package = "rand" } # only needed because of ed25519-dalek
ed25519-dalek = { version = "2.0.0-rc.2", features = ["rand_core"] }
p256 = "0.13"
p384 = "0.13"
secrecy = { version = "0.8", features = ["serde"] }
rand_core = "0.6"
getrandom = { version = "0.2", features = ["js"] }

[dev-dependencies]
rand = "0.8"

[features]
Expand Down
210 changes: 151 additions & 59 deletions basic_credential/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,82 @@
//!
//! For now this credential uses only RustCrypto.

use secrecy::{ExposeSecret, SecretVec};
use std::fmt::Debug;

use openmls_traits::{
key_store::{MlsEntity, MlsEntityId, OpenMlsKeyStore},
signatures::Signer,
types::{CryptoError, Error, SignatureScheme},
types::{CryptoError, SignatureScheme},
};

use p256::ecdsa::SigningKey;

// See https://github.com/rust-analyzer/rust-analyzer/issues/7243
// for the rust-analyzer issue with the following line.
use ed25519_dalek::Signer as DalekSigner;
use rand::rngs::OsRng;
use tls_codec::{TlsDeserialize, TlsSerialize, TlsSize};
fn expose_sk<S: serde::Serializer>(data: &SecretVec<u8>, ser: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeSeq as _;
let exposed = data.expose_secret();
let mut seq = ser.serialize_seq(Some(exposed.len()))?;
for b in exposed.iter() {
seq.serialize_element(b)?;
}
seq.end()
}

/// A signature key pair for the basic credential.
///
/// This can be used as keys to implement the MLS basic credential. It is a simple
/// private and public key pair with corresponding signature scheme.
#[derive(TlsSerialize, TlsSize, TlsDeserialize, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "clonable", derive(Clone))]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct SignatureKeyPair {
private: Vec<u8>,
#[serde(serialize_with = "expose_sk")]
private: SecretVec<u8>,
public: Vec<u8>,
signature_scheme: SignatureScheme,
}

#[cfg(feature = "clonable")]
impl Clone for SignatureKeyPair {
fn clone(&self) -> Self {
Self {
private: self.private.expose_secret().clone().into(),
public: self.public.clone(),
signature_scheme: self.signature_scheme,
}
}
}

impl secrecy::SerializableSecret for SignatureKeyPair {}

impl tls_codec::Size for SignatureKeyPair {
fn tls_serialized_len(&self) -> usize {
self.private.expose_secret().tls_serialized_len()
+ self.public.tls_serialized_len()
+ self.signature_scheme.tls_serialized_len()
}
}

impl tls_codec::Deserialize for SignatureKeyPair {
fn tls_deserialize<R: std::io::Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
where
Self: Sized,
{
let private = Vec::<u8>::tls_deserialize(bytes)?.into();
let public = Vec::<u8>::tls_deserialize(bytes)?;
let signature_scheme = SignatureScheme::tls_deserialize(bytes)?;
Ok(Self {
private,
public,
signature_scheme,
})
}
}

impl tls_codec::Serialize for SignatureKeyPair {
fn tls_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
let mut written = self.private.expose_secret().tls_serialize(writer)?;
written += self.public.tls_serialize(writer)?;
written += self.signature_scheme.tls_serialize(writer)?;
Ok(written)
}
}

impl Debug for SignatureKeyPair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SignatureKeyPair")
Expand All @@ -42,58 +90,43 @@ impl Debug for SignatureKeyPair {
}
}

impl Signer for SignatureKeyPair {
fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, Error> {
match self.signature_scheme {
SignatureScheme::ECDSA_SECP256R1_SHA256 => {
let k = SigningKey::from_bytes(&self.private).map_err(|_| Error::SigningError)?;
let signature = k.sign(payload);
Ok(signature.to_der().to_bytes().into())
}
SignatureScheme::ED25519 => {
let k = ed25519_dalek::Keypair::from_bytes(&self.private)
.map_err(|_| Error::SigningError)?;
let signature = k.sign(payload);
Ok(signature.to_bytes().into())
}
_ => Err(Error::SigningError),
}
impl openmls_traits::signatures::DefaultSigner for SignatureKeyPair {
fn private_key(&self) -> &[u8] {
self.private.expose_secret().as_slice()
}

fn signature_scheme(&self) -> SignatureScheme {
self.signature_scheme
}
}

/// Compute the ID for a [`Signature`] in the key store.
fn id(public_key: &[u8], signature_scheme: SignatureScheme) -> Vec<u8> {
const LABEL: &[u8; 22] = b"RustCryptoSignatureKey";
let mut id = public_key.to_vec();
id.extend_from_slice(LABEL);
let signature_scheme = (signature_scheme as u16).to_be_bytes();
id.extend_from_slice(&signature_scheme);
id
}

impl MlsEntity for SignatureKeyPair {
const ID: MlsEntityId = MlsEntityId::SignatureKeyPair;
}

impl SignatureKeyPair {
/// Generates a fresh signature keypair using the [`SignatureScheme`].
pub fn new(signature_scheme: SignatureScheme) -> Result<Self, CryptoError> {
let (private, public) = match signature_scheme {
pub fn new(
signature_scheme: SignatureScheme,
csprng: &mut impl rand_core::CryptoRngCore,
) -> Result<Self, CryptoError> {
let (private, public): (SecretVec<u8>, Vec<u8>) = match signature_scheme {
SignatureScheme::ECDSA_SECP256R1_SHA256 => {
let k = SigningKey::random(&mut OsRng);
let pk = k.verifying_key().to_encoded_point(false).as_bytes().into();
(k.to_bytes().as_slice().into(), pk)
let sk = p256::ecdsa::SigningKey::random(csprng);
let pk = sk.verifying_key().to_encoded_point(false).to_bytes().into();
(sk.to_bytes().to_vec().into(), pk)
}
SignatureScheme::ECDSA_SECP384R1_SHA384 => {
let sk = p384::ecdsa::SigningKey::random(csprng);
let pk = sk.verifying_key().to_encoded_point(false).to_bytes().into();
(sk.to_bytes().to_vec().into(), pk)
}
SignatureScheme::ED25519 => {
let k = ed25519_dalek::Keypair::generate(&mut rand_07::rngs::OsRng).to_bytes();
let pk = k[ed25519_dalek::SECRET_KEY_LENGTH..].to_vec();
let sk = ed25519_dalek::SigningKey::generate(csprng);
let pk = sk.verifying_key();
// full key here because we need it to sign...
let sk_pk = k.into();
(sk_pk, pk)
let sk_pk: Vec<u8> = sk.to_bytes().into();
(sk_pk.into(), pk.to_bytes().into())
}
_ => return Err(CryptoError::UnsupportedSignatureScheme),
};
Expand All @@ -108,31 +141,70 @@ impl SignatureKeyPair {
/// Create a new signature key pair from the raw keys.
pub fn from_raw(signature_scheme: SignatureScheme, private: Vec<u8>, public: Vec<u8>) -> Self {
Self {
private,
private: private.into(),
public,
signature_scheme,
}
}

fn id(&self) -> Vec<u8> {
id(&self.public, self.signature_scheme)
/// Create a new KeyPair but verify that the private key actually matches the public key
pub fn try_from_raw(
signature_scheme: SignatureScheme,
private: Vec<u8>,
public: Vec<u8>,
) -> Result<Self, CryptoError> {
match signature_scheme {
SignatureScheme::ED25519 => {
let sk = ed25519_dalek::SigningKey::try_from(
&private[..ed25519_dalek::SECRET_KEY_LENGTH],
)
.map_err(|_| CryptoError::InvalidKey)?;
let pk = ed25519_dalek::VerifyingKey::try_from(&public[..])
.map_err(|_| CryptoError::InvalidKey)?;

if sk.verifying_key() != pk {
return Err(CryptoError::MismatchKeypair);
}
}
SignatureScheme::ECDSA_SECP256R1_SHA256 => {
let sk = p256::ecdsa::SigningKey::try_from(&private[..])
.map_err(|_| CryptoError::InvalidKey)?;
let pk = p256::ecdsa::VerifyingKey::try_from(&public[..])
.map_err(|_| CryptoError::InvalidKey)?;
if sk.verifying_key() != &pk {
return Err(CryptoError::MismatchKeypair);
}
}
SignatureScheme::ECDSA_SECP384R1_SHA384 => {
let sk = p384::ecdsa::SigningKey::try_from(&private[..])
.map_err(|_| CryptoError::InvalidKey)?;
let pk = p384::ecdsa::VerifyingKey::try_from(&public[..])
.map_err(|_| CryptoError::InvalidKey)?;
if sk.verifying_key() != &pk {
return Err(CryptoError::MismatchKeypair);
}
}
_ => {}
};

Ok(Self {
private: private.into(),
public,
signature_scheme,
})
}

/// Store this signature key pair in the key store.
pub fn store<T>(&self, key_store: &T) -> Result<(), <T as OpenMlsKeyStore>::Error>
pub async fn store<T>(&self, key_store: &T) -> Result<(), <T as OpenMlsKeyStore>::Error>
where
T: OpenMlsKeyStore,
{
key_store.store(&self.id(), self)
key_store.store(&self.public, self).await
}

/// Read a signature key pair from the key store.
pub fn read(
key_store: &impl OpenMlsKeyStore,
public_key: &[u8],
signature_scheme: SignatureScheme,
) -> Option<Self> {
key_store.read(&id(public_key, signature_scheme))
pub async fn read(key_store: &impl OpenMlsKeyStore, public_key: &[u8]) -> Option<Self> {
key_store.read(public_key).await
}

/// Get the public key as byte slice.
Expand All @@ -152,6 +224,26 @@ impl SignatureKeyPair {

#[cfg(feature = "test-utils")]
pub fn private(&self) -> &[u8] {
&self.private
self.private.expose_secret()
}
}

#[cfg(test)]
pub mod tests {
use super::*;

#[test]
fn signature_keypair_try_from_raw_should_work() {
let schemes = [
SignatureScheme::ED25519,
SignatureScheme::ECDSA_SECP256R1_SHA256,
SignatureScheme::ECDSA_SECP384R1_SHA384,
];
for scheme in schemes {
let kp = SignatureKeyPair::new(scheme, &mut rand::thread_rng()).unwrap();
let sk = kp.private.expose_secret().clone();
let pk = kp.public.clone();
SignatureKeyPair::try_from_raw(scheme, sk, pk).unwrap();
}
}
}
5 changes: 2 additions & 3 deletions book/src/user_manual/identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ Note that the implementation of the Authentication Service and, thus, the detail
## Creating and using credentials

OpenMLS allows clients to create `Credentials`.
A `BasicCredential`, currently the only credential type supported by OpenMLS, consists only of the `identity`.
Thus, to create a fresh `Credential`, the following inputs are required:
A `BasicCredential`, consists only of the `identity`.
Thus, to create a fresh `Credential`, the following input is required:

- `identity: Vec<u8>`: An octet string that uniquely identifies the client.
- `credential_type: CredentialType`: The type of the credential, in this case `CredentialType::Basic`.

```rust,no_run,noplayground
{{#include ../../../openmls/tests/book_code.rs:create_basic_credential}}
Expand Down
5 changes: 3 additions & 2 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
name = "cli"
version = "0.1.0"
authors = ["OpenMLS Authors"]
edition = "2018"
edition = "2021"

[dependencies]
url = "2.2"
reqwest = { version = "0.11", features = ["blocking", "json"] }
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1.24", features = ["full"] }
base64 = "0.13"
log = "0.4"
pretty_env_logger = "0.4"
Expand Down
Loading