Skip to content

Commit

Permalink
feat!: canonicalize ClientId keeping only the regular version where t…
Browse files Browse the repository at this point in the history
…he UserId portion is the hyphenated string representation of the UUID. Also apply this to 'getUserIdentities()'
  • Loading branch information
beltram committed Dec 1, 2023
1 parent e16624f commit e0d6d13
Show file tree
Hide file tree
Showing 24 changed files with 348 additions and 181 deletions.
12 changes: 8 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,29 @@ branch = "2.x"
package = "openmls"
git = "https://github.com/wireapp/openmls"
#tag = "v1.0.0-pre.core-crypto-1.0.0"
branch = "feat/rfc9420"
#branch = "feat/rfc9420"
branch = "feat/canonicalize-clientid"

[patch.crates-io.openmls_traits]
package = "openmls_traits"
git = "https://github.com/wireapp/openmls"
#tag = "v1.0.0-pre.core-crypto-1.0.0"
branch = "feat/rfc9420"
#branch = "feat/rfc9420"
branch = "feat/canonicalize-clientid"

[patch.crates-io.openmls_basic_credential]
package = "openmls_basic_credential"
git = "https://github.com/wireapp/openmls"
#tag = "v1.0.0-pre.core-crypto-1.0.0"
branch = "feat/rfc9420"
#branch = "feat/rfc9420"
branch = "feat/canonicalize-clientid"

[patch.crates-io.openmls_x509_credential]
package = "openmls_x509_credential"
git = "https://github.com/wireapp/openmls"
#tag = "v1.0.0-pre.core-crypto-1.0.0"
branch = "feat/rfc9420"
#branch = "feat/rfc9420"
branch = "feat/canonicalize-clientid"

[patch.crates-io.hpke]
git = "https://github.com/wireapp/rust-hpke.git"
Expand Down
8 changes: 4 additions & 4 deletions crypto-ffi/bindings/js/CoreCrypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1674,7 +1674,7 @@ export class CoreCrypto {
* Creates an enrollment instance with private key material you can use in order to fetch
* a new x509 certificate from the acme server.
*
* @param clientId - client identifier with user b64Url encoded & clientId hex encoded e.g. `t6wRpI8BRSeviBwwiFp5MQ:[email protected]`
* @param clientId - client identifier with user b64Url encoded & clientId hex encoded e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:[email protected]`
* @param displayName - human-readable name displayed in the application e.g. `Smith, Alice M (QA)`
* @param handle - user handle e.g. `[email protected]`
* @param expiryDays - generated x509 certificate expiry
Expand All @@ -1691,7 +1691,7 @@ export class CoreCrypto {
* Generates an E2EI enrollment instance for a "regular" client (with a Basic credential) willing to migrate to E2EI.
* Once the enrollment is finished, use the instance in {@link CoreCrypto.e2eiRotateAll} to do the rotation.
*
* @param clientId - client identifier with user b64Url encoded & clientId hex encoded e.g. `t6wRpI8BRSeviBwwiFp5MQ:[email protected]`
* @param clientId - client identifier with user b64Url encoded & clientId hex encoded e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:[email protected]`
* @param displayName - human-readable name displayed in the application e.g. `Smith, Alice M (QA)`
* @param handle - user handle e.g. `[email protected]`
* @param expiryDays - generated x509 certificate expiry
Expand All @@ -1710,7 +1710,7 @@ export class CoreCrypto {
* has been revoked. It lets you change the DisplayName or the handle
* if you need to. Once the enrollment is finished, use the instance in {@link CoreCrypto.e2eiRotateAll} to do the rotation.
*
* @param clientId - client identifier with user b64Url encoded & clientId hex encoded e.g. `t6wRpI8BRSeviBwwiFp5MQ:[email protected]`
* @param clientId - client identifier with user b64Url encoded & clientId hex encoded e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:[email protected]`
* @param expiryDays - generated x509 certificate expiry
* @param ciphersuite - for generating signing key material
* @param displayName - human-readable name displayed in the application e.g. `Smith, Alice M (QA)`
Expand Down Expand Up @@ -1821,7 +1821,7 @@ export class CoreCrypto {
* If no member has a x509 certificate, it will return an empty Vec.
*
* @param conversationId - identifier of the conversation
* @param userIds - user identifiers e.g. t6wRpI8BRSeviBwwiFp5MQ which is a base64UrlUnpadded UUIDv4
* @param userIds - user identifiers hyphenated UUIDv4 e.g. 'bd4c7053-1c5a-4020-9559-cd7bf7961954'
* @returns a Map with all the identities for a given users. Consumers are then recommended to reduce those identities to determine the actual status of a user.
*/
async getUserIdentities(conversationId: ConversationId, userIds: string[]): Promise<Map<string, WireIdentity[]>> {
Expand Down
2 changes: 1 addition & 1 deletion crypto-ffi/bindings/js/test/CoreCrypto.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,7 @@ test("end-to-end-identity", async () => {
const encoder = new TextEncoder();
const jsonToByteArray = json => encoder.encode(JSON.stringify(json, null, 0));

const clientId = "t6wRpI8BRSeviBwwiFp5MQ:[email protected]";
const clientId = "b7ac11a4-8f01-4527-af88-1c30885a7931:[email protected]";
const displayName = "Alice Smith";
const handle = "alice_wire";
const expiryDays = 90;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class CoreCryptoCentral private constructor(private val cc: CoreCrypto, private
/**
* Creates an enrollment instance with private key material you can use in order to fetch a new x509 certificate from the acme server.
*
* @param clientId client identifier with user b64Url encoded & clientId hex encoded e.g. `t6wRpI8BRSeviBwwiFp5MQ:[email protected]`
* @param clientId client identifier with user b64Url encoded & clientId hex encoded e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:[email protected]`
* @param displayName human-readable name displayed in the application e.g. `Smith, Alice M (QA)`
* @param handle user handle e.g. `[email protected]`
* @param expiryDays generated x509 certificate expiry
Expand All @@ -79,7 +79,7 @@ class CoreCryptoCentral private constructor(private val cc: CoreCrypto, private
* Generates an E2EI enrollment instance for a "regular" client (with a Basic credential) willing to migrate to E2EI.
* Once the enrollment is finished, use the instance in [e2eiRotateAll] to do the rotation.
*
* @param clientId client identifier with user b64Url encoded & clientId hex encoded e.g. `t6wRpI8BRSeviBwwiFp5MQ:[email protected]`
* @param clientId client identifier with user b64Url encoded & clientId hex encoded e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:[email protected]`
* @param displayName human-readable name displayed in the application e.g. `Smith, Alice M (QA)`
* @param handle user handle e.g. `[email protected]`
* @param expiryDays generated x509 certificate expiry
Expand Down Expand Up @@ -112,7 +112,7 @@ class CoreCryptoCentral private constructor(private val cc: CoreCrypto, private
* their credential, either because the former one is expired or it has been revoked. It lets you change the DisplayName
* or the handle if you need to. Once the enrollment is finished, use the instance in [e2eiRotateAll] to do the rotation.
*
* @param clientId client identifier with user b64Url encoded & clientId hex encoded e.g. `t6wRpI8BRSeviBwwiFp5MQ:[email protected]`
* @param clientId client identifier with user b64Url encoded & clientId hex encoded e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:[email protected]`
* @param expiryDays generated x509 certificate expiry
* @param ciphersuite for generating signing key material
* @param displayName human-readable name displayed in the application e.g. `Smith, Alice M (QA)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ class MLSClient(private val cc: com.wire.crypto.CoreCrypto) {
* If no member has a x509 certificate, it will return an empty Vec.
*
* @param id conversation identifier
* @param userIds user identifiers e.g. t6wRpI8BRSeviBwwiFp5MQ which is a base64UrlUnpadded UUIDv4
* @param userIds user identifiers hyphenated UUIDv4 e.g. 'bd4c7053-1c5a-4020-9559-cd7bf7961954'
* @returns a Map with all the identities for a given users. Consumers are then recommended to reduce those identities to determine the actual status of a user.
*/
suspend fun getUserIdentities(id: MLSGroupId, userIds: List<String>): Map<String, List<WireIdentity>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal class E2EITest {
val keyStore = root.resolve("keystore-$aliceId")
val cc = CoreCryptoCentral(keyStore.absolutePath, "secret")
val enrollment = cc.e2eiNewEnrollment(
clientId = "t6wRpI8BRSeviBwwiFp5MQ:[email protected]",
clientId = "b7ac11a4-8f01-4527-af88-1c30885a7931:[email protected]",
displayName = "Alice Smith",
handle = "alice_wire",
expiryDays = 90u,
Expand Down
8 changes: 4 additions & 4 deletions crypto-ffi/bindings/swift/Sources/CoreCrypto/CoreCrypto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,7 @@ public class CoreCryptoWrapper {
/// Creates an enrollment instance with private key material you can use in order to fetch
/// a new x509 certificate from the acme server.
///
/// - parameter clientId: client identifier with user b64Url encoded & clientId hex encoded e.g. `t6wRpI8BRSeviBwwiFp5MQ:[email protected]`
/// - parameter clientId: client identifier with user b64Url encoded & clientId hex encoded e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:[email protected]`
/// - parameter displayName: human readable name displayed in the application e.g. `Smith, Alice M (QA)`
/// - parameter handle: user handle e.g. `[email protected]`
/// - parameter expiryDays: generated x509 certificate expiry
Expand All @@ -1162,7 +1162,7 @@ public class CoreCryptoWrapper {
/// Generates an E2EI enrollment instance for a "regular" client (with a Basic credential) willing to migrate to E2EI.
/// Once the enrollment is finished, use the instance in ``CoreCrypto/e2eiRotateAll`` to do the rotation.
///
/// - parameter clientId: client identifier with user b64Url encoded & clientId hex encoded e.g. `t6wRpI8BRSeviBwwiFp5MQ:[email protected]`
/// - parameter clientId: client identifier with user b64Url encoded & clientId hex encoded e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:[email protected]`
/// - parameter displayName: human readable name displayed in the application e.g. `Smith, Alice M (QA)`
/// - parameter handle: user handle e.g. `[email protected]`
/// - parameter expiryDays: generated x509 certificate expiry
Expand All @@ -1178,7 +1178,7 @@ public class CoreCryptoWrapper {
/// their credential, either because the former one is expired or it has been revoked. It lets you change
/// the DisplayName or the handle if you need to. Once the enrollment is finished, use the instance in ``CoreCrypto/e2eiRotateAll`` to do the rotation.
///
/// - parameter clientId: client identifier with user b64Url encoded & clientId hex encoded e.g. `t6wRpI8BRSeviBwwiFp5MQ:[email protected]`
/// - parameter clientId: client identifier with user b64Url encoded & clientId hex encoded e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:[email protected]`
/// - parameter expiryDays: generated x509 certificate expiry
/// - parameter ciphersuite: For generating signing key material.
/// - parameter displayName: human readable name displayed in the application e.g. `Smith, Alice M (QA)`
Expand Down Expand Up @@ -1258,7 +1258,7 @@ public class CoreCryptoWrapper {
/// If no member has a x509 certificate, it will return an empty Vec.
///
/// - parameter conversationId: conversation identifier
/// - parameter userIds: user identifiers e.g. t6wRpI8BRSeviBwwiFp5MQ which is a base64UrlUnpadded UUIDv4
/// - parameter userIds: user identifiers hyphenated UUIDv4 e.g. 'bd4c7053-1c5a-4020-9559-cd7bf7961954'
/// - returns: a Map with all the identities for a given users. Consumers are then recommended to reduce those identities to determine the actual status of a user.
public func getUserIdentities(conversationId: ConversationId, userIds: [String]) async throws -> [String: [WireIdentity]] {
return try await self.coreCrypto.getUserIdentities(conversationId: conversationId, userIds: userIds)
Expand Down
2 changes: 2 additions & 0 deletions crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ oid-registry = "0.6"
async-recursion = "1"
uniffi = { workspace = true, optional = true }
itertools = "0.12"
base64-simd = "0.8"
uuid = { version = "1.6", features = ["v4"] }

[dependencies.proteus-wasm]
version = "2.1"
Expand Down
4 changes: 2 additions & 2 deletions crypto/src/e2e_identity/conversation_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ pub mod tests {
not_after: expiration,
..Default::default()
};
let cert = CertificateBundle::new_from_builder(case.signature_scheme(), builder);
let cert = CertificateBundle::new_from_builder(builder, case.signature_scheme());
let cb = Client::new_x509_credential_bundle(cert).unwrap();
let commit = alice_central.e2ei_rotate(&id, &cb).await.unwrap().commit;
alice_central.commit_accepted(&id).await.unwrap();
Expand Down Expand Up @@ -247,7 +247,7 @@ pub mod tests {
not_after: expiration,
..Default::default()
};
let cert = CertificateBundle::new_from_builder(case.signature_scheme(), builder);
let cert = CertificateBundle::new_from_builder(builder, case.signature_scheme());
let cb = Client::new_x509_credential_bundle(cert).unwrap();
alice_central.e2ei_rotate(&id, &cb).await.unwrap();
alice_central.commit_accepted(&id).await.unwrap();
Expand Down
143 changes: 143 additions & 0 deletions crypto/src/e2e_identity/id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use crate::{prelude::ClientId, CryptoError, CryptoResult};

#[cfg(test)]
const DOMAIN: &str = "wire.com";
const COLON: u8 = 58;

/// This format: 'bd4c7053-1c5a-4020-9559-cd7bf7961954:[email protected]'
#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Into, derive_more::Deref)]
pub struct WireQualifiedClientId(ClientId);

#[cfg(test)]
impl WireQualifiedClientId {
pub fn get_user_id(&self) -> String {
let mut split = self.0.split(|b| b == &COLON);
let user_id = split.next().unwrap();
uuid::Uuid::try_parse_ascii(user_id).unwrap().to_string()
}
}

#[cfg(test)]
impl WireQualifiedClientId {
pub fn generate() -> Self {
let user_id = uuid::Uuid::new_v4().to_string();
let device_id = rand::random::<u64>();
let client_id = format!("{user_id}:{device_id:x}@{DOMAIN}");
Self(client_id.into_bytes().into())
}

pub fn to_static_str(&self) -> &'static str {
Box::leak(Box::new(self.to_string()))
}
}

#[cfg(test)]
impl std::fmt::Display for WireQualifiedClientId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", std::str::from_utf8(self.as_slice()).unwrap())
}
}

/// e.g. from 'vUxwUxxaQCCVWc1795YZVA:[email protected]'
impl<'a> TryFrom<&'a [u8]> for WireQualifiedClientId {
type Error = CryptoError;

fn try_from(bytes: &'a [u8]) -> CryptoResult<Self> {
const COLON: u8 = 58;
let mut split = bytes.split(|b| b == &COLON);
let user_id = split.next().ok_or(CryptoError::InvalidClientId)?;

let user_id = base64_simd::URL_SAFE_NO_PAD
.decode_to_vec(user_id)
.map_err(|_| CryptoError::InvalidClientId)?;

let user_id = uuid::Uuid::from_slice(&user_id).map_err(|_| CryptoError::InvalidClientId)?;
let mut buf = [0; uuid::fmt::Hyphenated::LENGTH];
let user_id = user_id.hyphenated().encode_lower(&mut buf);

let rest = split.next().ok_or(CryptoError::InvalidClientId)?;
if split.next().is_some() {
return Err(CryptoError::InvalidClientId);
}

let client_id = [user_id.as_bytes(), &[COLON], rest].concat();
Ok(Self(client_id.into()))
}
}

impl std::str::FromStr for WireQualifiedClientId {
type Err = CryptoError;

fn from_str(s: &str) -> CryptoResult<Self> {
s.as_bytes().try_into()
}
}

/// This format: 'vUxwUxxaQCCVWc1795YZVA:[email protected]'
#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Into, derive_more::Deref)]
pub struct QualifiedE2eiClientId(ClientId);

#[cfg(test)]
impl QualifiedE2eiClientId {
pub fn generate() -> Self {
Self::generate_from_user_id(&uuid::Uuid::new_v4())
}

pub fn generate_from_user_id(user_id: &uuid::Uuid) -> Self {
use base64::Engine as _;

let user_id = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(user_id.as_bytes());

let device_id = rand::random::<u64>();
let client_id = format!("{user_id}:{device_id:x}@{DOMAIN}");
Self(client_id.into_bytes().into())
}

pub fn to_static_str(&self) -> &'static str {
Box::leak(Box::new(self.to_string()))
}

pub fn from_str_unchecked(s: &str) -> Self {
Self(s.as_bytes().into())
}
}

/// e.g. from 'bd4c7053-1c5a-4020-9559-cd7bf7961954:[email protected]'
impl<'a> TryFrom<&'a [u8]> for QualifiedE2eiClientId {
type Error = CryptoError;

fn try_from(bytes: &'a [u8]) -> CryptoResult<Self> {
let mut split = bytes.split(|b| b == &COLON);
let user_id = split.next().ok_or(CryptoError::InvalidClientId)?;

let user_id = std::str::from_utf8(user_id)?
.parse::<uuid::Uuid>()
.map_err(|_| CryptoError::InvalidClientId)?;

let user_id = base64_simd::URL_SAFE_NO_PAD.encode_to_string(user_id.as_bytes());

let rest = split.next().ok_or(CryptoError::InvalidClientId)?;
if split.next().is_some() {
return Err(CryptoError::InvalidClientId);
}

let client_id = [user_id.as_bytes(), &[COLON], rest].concat();
Ok(Self(client_id.into()))
}
}

#[cfg(test)]
impl std::str::FromStr for QualifiedE2eiClientId {
type Err = CryptoError;

fn from_str(s: &str) -> CryptoResult<Self> {
s.as_bytes().try_into()
}
}

#[cfg(test)]
impl std::fmt::Display for QualifiedE2eiClientId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", std::str::from_utf8(self.as_slice()).unwrap())
}
}
Loading

0 comments on commit e0d6d13

Please sign in to comment.