Skip to content

Commit

Permalink
verification for Grant/Legacy credential types
Browse files Browse the repository at this point in the history
  • Loading branch information
insipx committed Mar 28, 2024
1 parent 9bba788 commit 98c50df
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 6 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions xmtp_id/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ xmtp_proto.workspace = true
openmls_traits.workspace = true
openmls.workspace = true
openmls_basic_credential.workspace = true
openmls_rust_crypto.workspace = true
prost.workspace = true
tls_codec.workspace = true
chrono.workspace = true
serde.workspace = true
async-trait.workspace = true
futures.workspace = true
135 changes: 135 additions & 0 deletions xmtp_id/src/credential.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use prost::Message;
use xmtp_mls::{
credential::{GrantMessagingAccessAssociation, LegacyCreateIdentityAssociation},

Check warning on line 3 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / workspace

unused imports: `GrantMessagingAccessAssociation`, `LegacyCreateIdentityAssociation`

warning: unused imports: `GrantMessagingAccessAssociation`, `LegacyCreateIdentityAssociation` --> xmtp_id/src/credential.rs:3:30 | 3 | credential::{Credential, GrantMessagingAccessAssociation, LegacyCreateIdentityAssociation}, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default
types::Address,
};
use xmtp_proto::xmtp::mls::message_contents::MlsCredential as CredentialProto;

pub enum AssociationType {
ExternallyOwned,
SmartContract,
Legacy,
}

#[derive(thiserror::Error, Debug)]
pub enum VerificationError {
#[error(
"Address mismatch in Association: Provided:{provided_addr:?} != signed:{signing_addr:?}"
)]
AddressMismatch {
provided_addr: Address,
signing_addr: Address,
},
#[error("Installation public key mismatch")]
InstallationPublicKeyMismatch,
}

pub struct VerifiedCredential {
pub account_address: String,
pub account_type: AssociationType,
}

impl VerifiedCredential {
pub fn account_address(&self) -> String {
self.account_address.clone()
}

pub fn account_type(&self) -> AssociationType {
self.account_type.clone()

Check failure on line 38 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / Test

no method named `clone` found for enum `AssociationType` in the current scope
}
}

pub struct VerificationRequest {
expected_account_address: String,
installation_public_key: Vec<u8>,
credential: Vec<u8>,
}

type VerificationResult = Result<VerifiedCredential, VerificationError>;

pub trait Credential {
fn address(&self) -> String;
fn installation_public_key(&self) -> Vec<u8>;
fn created_ns(&self) -> u64;
}

#[async_trait::async_trait]
pub trait CredentialVerifier {
async fn verify_credential(request: VerificationRequest) -> VerificationResult;
async fn batch_verify_credentials(
credentials_to_verify: Vec<VerificationRequest>,
) -> Vec<VerificationResult> {
let mut results = Vec::new();
for credential in credentials_to_verify {
results.push(Self::verify_credential(credential));
}

futures::future::join_all(results).await
}
}

impl<'a> Credential for &'a GrantMessagingAccessAssociation {
fn address(&self) -> String {
self.account_address().clone()
}

fn installation_public_key(&self) -> Vec<u8> {

Check failure on line 76 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / workspace

the trait bound `std::vec::Vec<u8>: prost::bytes::Buf` is not satisfied

error[E0277]: the trait bound `std::vec::Vec<u8>: prost::bytes::Buf` is not satisfied --> xmtp_id/src/credential.rs:76:45 | 76 | let proto = CredentialProto::decode(request.credential); | ----------------------- ^^^^^^^^^^^^^^^^^^ the trait `prost::bytes::Buf` is not implemented for `std::vec::Vec<u8>` | | | required by a bound introduced by this call | = help: the following other types implement trait `prost::bytes::Buf`: std::boxed::Box<T> prost::bytes::Bytes prost::bytes::BytesMut tungstenite::buffer::ReadBuffer<CHUNK_SIZE> prost::bytes::buf::Chain<T, U> prost::bytes::buf::Take<T> tonic::codec::buffer::DecodeBuf<'_> std::collections::VecDeque<u8> and 3 others note: required by a bound in `prost::Message::decode` --> /home/runner/.cargo/registry/src/index.crates.io-6f17d22bba15001f/prost-0.12.3/src/message.rs:112:12 | 110 | fn decode<B>(mut buf: B) -> Result<Self, DecodeError> | ------ required by a bound in this associated function 111 | where 112 | B: Buf, | ^^^ required by this bound in `Message::decode`
self.installation_public_key().clone()
}

Check failure on line 78 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / workspace

mismatched types

error[E0308]: mismatched types --> xmtp_id/src/credential.rs:78:46 | 78 | Credential::from_proto_validated(proto, None, Some(request.installation_public_key)); | -------------------------------- ^^^^^ expected `MlsCredential`, found `Result<MlsCredential, DecodeError>` | | | arguments to this function are incorrect | = note: expected struct `xmtp_proto::xmtp::mls::message_contents::MlsCredential` found enum `std::result::Result<xmtp_proto::xmtp::mls::message_contents::MlsCredential, prost::DecodeError>` note: associated function defined here --> /home/runner/work/libxmtp/libxmtp/xmtp_mls/src/credential/mod.rs:92:12 | 92 | pub fn from_proto_validated( | ^^^^^^^^^^^^^^^^^^^^ help: consider using `Result::expect` to unwrap the `std::result::Result<xmtp_proto::xmtp::mls::message_contents::MlsCredential, prost::DecodeError>` value, panicking if the value is a `Result::Err` | 78 | Credential::from_proto_validated(proto.expect("REASON"), None, Some(request.installation_public_key)); | +++++++++++++++++

Check failure on line 78 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / workspace

mismatched types

error[E0308]: mismatched types --> xmtp_id/src/credential.rs:78:64 | 78 | Credential::from_proto_validated(proto, None, Some(request.installation_public_key)); | ---- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&[u8]`, found `Vec<u8>` | | | arguments to this enum variant are incorrect | = note: expected reference `&[u8]` found struct `std::vec::Vec<u8>` help: the type constructed contains `std::vec::Vec<u8>` due to the type of the argument passed --> xmtp_id/src/credential.rs:78:59 | 78 | Credential::from_proto_validated(proto, None, Some(request.installation_public_key)); | ^^^^^-------------------------------^ | | | this argument influences the type of `Some` note: tuple variant defined here --> /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/core/src/option.rs:578:5 help: consider borrowing here | 78 | Credential::from_proto_validated(proto, None, Some(&request.installation_public_key)); | +

fn created_ns(&self) -> u64 {

Check failure on line 80 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / workspace

mismatched types

error[E0308]: mismatched types --> xmtp_id/src/credential.rs:80:13 | 79 | Ok(match credential { | ---------- this expression has type `std::result::Result<xmtp_mls::credential::Credential, xmtp_mls::credential::AssociationError>` 80 | Credential::GrantMessagingAccess(cred) => VerifiedCredential { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<Credential, ...>`, found `Credential` | = note: expected enum `std::result::Result<xmtp_mls::credential::Credential, xmtp_mls::credential::AssociationError>` found enum `xmtp_mls::credential::Credential` help: try wrapping the pattern in `Ok` | 80 | Ok(Credential::GrantMessagingAccess(cred)) => VerifiedCredential { | +++ +
GrantMessagingAccessAssociation::created_ns(self)
}
}

Check failure on line 84 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / workspace

mismatched types

error[E0308]: mismatched types --> xmtp_id/src/credential.rs:84:13 | 79 | Ok(match credential { | ---------- this expression has type `std::result::Result<xmtp_mls::credential::Credential, xmtp_mls::credential::AssociationError>` ... 84 | Credential::LegacyCreateIdentity(cred) => VerifiedCredential { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<Credential, ...>`, found `Credential` | = note: expected enum `std::result::Result<xmtp_mls::credential::Credential, xmtp_mls::credential::AssociationError>` found enum `xmtp_mls::credential::Credential` help: try wrapping the pattern in `Ok` | 84 | Ok(Credential::LegacyCreateIdentity(cred)) => VerifiedCredential { | +++ +
fn validate_credential(
credential: impl Credential,
request: VerificationRequest,
) -> Result<(), VerificationError> {
if credential.address() != request.expected_account_address {
return Err(VerificationError::AddressMismatch {
provided_addr: request.expected_account_address.to_string(),
signing_addr: credential.address(),
});
}

if credential.installation_public_key() != request.installation_public_key {
return Err(VerificationError::InstallationPublicKeyMismatch);
}

Ok(())
}

#[async_trait::async_trait]
impl CredentialVerifier for GrantMessagingAccessAssociation {
async fn verify_credential(request: VerificationRequest) -> VerificationResult {
let proto = CredentialProto::decode(request.credential);

Check failure on line 106 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / Test

the trait bound `Vec<u8>: Buf` is not satisfied
let credential = GrantMessagingAccessAssociation::from_proto_validated(

Check failure on line 107 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / Test

arguments to this function are incorrect
proto,
Some(request.installation_public_key),
);
validate_credential(&credential, request)?;

Check failure on line 111 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / Test

the trait bound `&std::result::Result<xmtp_mls::credential::GrantMessagingAccessAssociation, AssociationError>: credential::Credential` is not satisfied

Ok(VerifiedCredential {
account_address: credential.account_address(),

Check failure on line 114 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / Test

no method named `account_address` found for enum `std::result::Result` in the current scope
account_type: AssociationType::EOA,

Check failure on line 115 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / Test

no variant or associated item named `EOA` found for enum `AssociationType` in the current scope
})
}
}

#[async_trait::async_trait]
impl CredentialVerifier for LegacyCreateIdentityAssociation {
async fn verify_credential(request: VerificationRequest) -> VerificationResult {
let proto = CredentialProto::decode(request.credential);

Check failure on line 123 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / Test

the trait bound `Vec<u8>: Buf` is not satisfied
let credential = LegacyCreateIdentityAssociation::from_proto_validated(

Check failure on line 124 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / Test

arguments to this function are incorrect
proto,
Some(request.installation_public_key),
);
validate_credential(&credential, request)?;

Check failure on line 128 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / Test

the trait bound `&std::result::Result<xmtp_mls::credential::LegacyCreateIdentityAssociation, AssociationError>: credential::Credential` is not satisfied

Ok(VerifiedCredential {
account_address: credential.account_address(),

Check failure on line 131 in xmtp_id/src/credential.rs

View workflow job for this annotation

GitHub Actions / Test

no method named `account_address` found for enum `std::result::Result` in the current scope
account_type: AssociationType::Legacy,
})
}
}
2 changes: 2 additions & 0 deletions xmtp_id/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ pub enum IdentityError {
KeyGenerationError(#[from] CryptoError),
#[error("uninitialized identity")]
UninitializedIdentity,
#[error("protobuf deserialization: {0}")]
Deserialization(#[from] prost::DecodeError),
}
22 changes: 20 additions & 2 deletions xmtp_id/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
mod credential;
pub mod error;
mod verified_key_package;

use std::sync::RwLock;

use openmls::prelude::Credential as OpenMlsCredential;
use openmls_basic_credential::SignatureKeyPair;
use prost::Message;

Check warning on line 9 in xmtp_id/src/lib.rs

View workflow job for this annotation

GitHub Actions / workspace

unused import: `prost::Message`

warning: unused import: `prost::Message` --> xmtp_id/src/lib.rs:9:5 | 9 | use prost::Message; | ^^^^^^^^^^^^^^
use xmtp_mls::{
configuration::CIPHERSUITE, credential::UnsignedGrantMessagingAccessData, types::Address,
utils::time::now_ns,
configuration::CIPHERSUITE, credential::Credential,

Check warning on line 11 in xmtp_id/src/lib.rs

View workflow job for this annotation

GitHub Actions / workspace

unused import: `credential::Credential`

warning: unused import: `credential::Credential` --> xmtp_id/src/lib.rs:11:33 | 11 | configuration::CIPHERSUITE, credential::Credential, | ^^^^^^^^^^^^^^^^^^^^^^
credential::UnsignedGrantMessagingAccessData, types::Address, utils::time::now_ns,
};
use xmtp_proto::xmtp::mls::message_contents::MlsCredential as CredentialProto;

Check warning on line 14 in xmtp_id/src/lib.rs

View workflow job for this annotation

GitHub Actions / workspace

unused import: `xmtp_proto::xmtp::mls::message_contents::MlsCredential as CredentialProto`

warning: unused import: `xmtp_proto::xmtp::mls::message_contents::MlsCredential as CredentialProto` --> xmtp_id/src/lib.rs:14:5 | 14 | use xmtp_proto::xmtp::mls::message_contents::MlsCredential as CredentialProto; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

use crate::error::IdentityError;

Check warning on line 17 in xmtp_id/src/lib.rs

View workflow job for this annotation

GitHub Actions / workspace

unused import: `VerifiedCredential`

warning: unused import: `VerifiedCredential` --> xmtp_id/src/lib.rs:17:59 | 17 | credential::{CredentialVerifier, VerificationRequest, VerifiedCredential}, | ^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -57,6 +61,20 @@ impl Identity {
.clone()
.ok_or(IdentityError::UninitializedIdentity)
}

pub(crate) fn get_validated_account_address(
credential: &[u8],
installation_public_key: &[u8],
) -> Result<String, IdentityError> {
let proto = CredentialProto::decode(credential)?;
let credential = Credential::from_proto_validated(
proto,
None, // expected_account_address

Check failure on line 72 in xmtp_id/src/lib.rs

View workflow job for this annotation

GitHub Actions / workspace

arguments to this function are incorrect

error[E0308]: arguments to this function are incorrect --> xmtp_id/src/lib.rs:72:23 | 72 | let request = VerificationRequest::new(credential, installation_public_key); | ^^^^^^^^^^^^^^^^^^^^^^^^ | note: expected `Vec<u8>`, found `&[u8]` --> xmtp_id/src/lib.rs:72:48 | 72 | let request = VerificationRequest::new(credential, installation_public_key); | ^^^^^^^^^^ = note: expected struct `std::vec::Vec<u8>` found reference `&[u8]` note: expected `Vec<u8>`, found `&[u8]` --> xmtp_id/src/lib.rs:72:60 | 72 | let request = VerificationRequest::new(credential, installation_public_key); | ^^^^^^^^^^^^^^^^^^^^^^^ = note: expected struct `std::vec::Vec<u8>` found reference `&[u8]` note: associated function defined here --> xmtp_id/src/credential.rs:48:12 | 48 | pub fn new(installation_public_key: Vec<u8>, credential: Vec<u8>) -> Self { | ^^^ -------------------------------- ------------------- help: try using a conversion method | 72 | let request = VerificationRequest::new(credential.to_vec(), installation_public_key); | +++++++++ help: try using a conversion method | 72 | let request = VerificationRequest::new(credential, installation_public_key.to_vec()); | +++++++++
Some(installation_public_key),

Check failure on line 73 in xmtp_id/src/lib.rs

View workflow job for this annotation

GitHub Actions / workspace

the `?` operator can only be applied to values that implement `std::ops::Try`

error[E0277]: the `?` operator can only be applied to values that implement `std::ops::Try` --> xmtp_id/src/lib.rs:73:26 | 73 | let credential = CredentialVerifier::verify_credential(request)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `?` operator cannot be applied to type `std::pin::Pin<std::boxed::Box<dyn futures::Future<Output = std::result::Result<credential::VerifiedCredential, credential::VerificationError>> + std::marker::Send>>` | = help: the trait `std::ops::Try` is not implemented for `Pin<Box<dyn Future<Output = Result<VerifiedCredential, VerificationError>> + Send>>`
)?;

Ok(credential.address())
}
}

#[cfg(test)]
Expand Down
177 changes: 177 additions & 0 deletions xmtp_id/src/verified_key_package.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/// Key Package Verification (Copied from `xmtp_mls/src/verified_key_package.rs`)
use openmls::prelude::{KeyPackage, KeyPackageIn, KeyPackageVerifyError};
use openmls_rust_crypto::RustCrypto;
use thiserror::Error;
use tls_codec::{Deserialize, Error as TlsSerializationError};

use crate::{error::IdentityError, Identity};
use xmtp_mls::{configuration::MLS_PROTOCOL_VERSION, types::Address};

#[derive(Debug, Error)]
pub enum KeyPackageVerificationError {
#[error("serialization error: {0}")]
Serialization(#[from] TlsSerializationError),
#[error("mls validation: {0}")]
MlsValidation(#[from] KeyPackageVerifyError),
#[error("identity: {0}")]
Identity(#[from] IdentityError),
#[error("invalid application id")]
InvalidApplicationId,
#[error("application id ({0}) does not match the credential address ({1}).")]
ApplicationIdCredentialMismatch(String, String),
#[error("invalid lifetime")]
InvalidLifetime,
#[error("generic: {0}")]
Generic(String),
}

#[derive(Debug, Clone, PartialEq)]
pub struct VerifiedKeyPackage {
pub inner: KeyPackage,
pub account_address: String,
}

impl VerifiedKeyPackage {
pub fn new(inner: KeyPackage, account_address: String) -> Self {
Self {
inner,
account_address,
}
}

// Validates starting with a KeyPackage (which is already validated by OpenMLS)
pub fn from_key_package(kp: KeyPackage) -> Result<Self, KeyPackageVerificationError> {
let leaf_node = kp.leaf_node();
let identity_bytes = leaf_node.credential().identity();
let pub_key_bytes = leaf_node.signature_key().as_slice();
let account_address = identity_to_account_address(identity_bytes, pub_key_bytes)?;
let application_id = extract_application_id(&kp)?;
if !account_address.eq(&application_id) {
return Err(
KeyPackageVerificationError::ApplicationIdCredentialMismatch(
application_id,
account_address,
),
);
}
if !kp.life_time().is_valid() {
return Err(KeyPackageVerificationError::InvalidLifetime);
}

Ok(Self::new(kp, account_address))
}

// Validates starting with a KeyPackageIn as bytes (which is not validated by OpenMLS)
pub fn from_bytes(
crypto_provider: &RustCrypto,
data: &[u8],
) -> Result<VerifiedKeyPackage, KeyPackageVerificationError> {
let kp_in: KeyPackageIn = KeyPackageIn::tls_deserialize_exact(data)?;
let kp = kp_in.validate(crypto_provider, MLS_PROTOCOL_VERSION)?;

Self::from_key_package(kp)
}

pub fn installation_id(&self) -> Vec<u8> {
self.inner.leaf_node().signature_key().as_slice().to_vec()
}

pub fn hpke_init_key(&self) -> Vec<u8> {
self.inner.hpke_init_key().as_slice().to_vec()
}
}

fn identity_to_account_address(
credential_bytes: &[u8],
installation_key_bytes: &[u8],
) -> Result<String, KeyPackageVerificationError> {
Ok(Identity::get_validated_account_address(
credential_bytes,
installation_key_bytes,
)?)
}

fn extract_application_id(kp: &KeyPackage) -> Result<Address, KeyPackageVerificationError> {
let application_id_bytes = kp
.leaf_node()
.extensions()
.application_id()
.ok_or_else(|| KeyPackageVerificationError::InvalidApplicationId)?
.as_slice()
.to_vec();

String::from_utf8(application_id_bytes)
.map_err(|_| KeyPackageVerificationError::InvalidApplicationId)
}

#[cfg(test)]
mod tests {
use openmls::{
credentials::CredentialWithKey,
extensions::{
ApplicationIdExtension, Extension, ExtensionType, Extensions, LastResortExtension,
},
group::config::CryptoConfig,
prelude::Capabilities,
prelude_test::KeyPackage,
versions::ProtocolVersion,
};
use xmtp_cryptography::utils::generate_local_wallet;

use xmtp_mls::{
builder::ClientBuilder,
configuration::CIPHERSUITE,
verified_key_package::{KeyPackageVerificationError, VerifiedKeyPackage},
};

#[tokio::test]
async fn test_invalid_application_id() {
let client = ClientBuilder::new_test_client(&generate_local_wallet()).await;
let conn = client.store.conn().unwrap();
let provider = client.mls_provider(&conn);

// Build a key package
let last_resort = Extension::LastResort(LastResortExtension::default());
// Make sure the application id doesn't match the account address
let invalid_application_id = "invalid application id".as_bytes();
let application_id =
Extension::ApplicationId(ApplicationIdExtension::new(invalid_application_id));
let leaf_node_extensions = Extensions::single(application_id);
let capabilities = Capabilities::new(
None,
Some(&[CIPHERSUITE]),
Some(&[ExtensionType::LastResort, ExtensionType::ApplicationId]),
None,
None,
);
// TODO: Set expiration
let kp = KeyPackage::builder()
.leaf_node_capabilities(capabilities)
.key_package_extensions(Extensions::single(last_resort))
.leaf_node_extensions(leaf_node_extensions)
.build(
CryptoConfig {
ciphersuite: CIPHERSUITE,
version: ProtocolVersion::default(),
},
&provider,
&client.identity.installation_keys,
CredentialWithKey {
credential: client.identity.credential().unwrap(),
signature_key: client.identity.installation_keys.to_public_vec().into(),
},
)
.unwrap();

let verified_kp_result = VerifiedKeyPackage::from_key_package(kp);
assert!(verified_kp_result.is_err());
assert_eq!(
KeyPackageVerificationError::ApplicationIdCredentialMismatch(
String::from_utf8(invalid_application_id.to_vec()).unwrap(),
client.account_address()
)
.to_string(),
verified_kp_result.err().unwrap().to_string()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl GrantMessagingAccessAssociation {
Self::new_validated(unsigned_data, signature)
}

pub(crate) fn from_proto_validated(
pub fn from_proto_validated(
proto: GrantMessagingAccessAssociationProto,
expected_installation_public_key: &[u8],
) -> Result<Self, AssociationError> {
Expand Down
Loading

0 comments on commit 98c50df

Please sign in to comment.