Skip to content

Commit

Permalink
Credential check (#302)
Browse files Browse the repository at this point in the history
* Credential check

* Loop over credentials

* Use bandwidth controller instead

* Restructure credentials module

* Seperate error mod

* Error type reorder

* reorder

* clippy

* Task handling when exiting early

* Log tweak

* fix

* log

* clippy

* disarm

* formatting
  • Loading branch information
octol authored Apr 19, 2024
1 parent ef128cd commit 76b79dd
Show file tree
Hide file tree
Showing 14 changed files with 378 additions and 126 deletions.
135 changes: 69 additions & 66 deletions Cargo.lock

Large diffs are not rendered by default.

31 changes: 17 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ serde_json = "1.0.91"
signature = "1"
tap = "1.0.1"
thiserror = "1.0.38"
time = "0.3.36"
tokio = { version = "1.8" }
tokio-util = { version = "0.7.10", features = ["codec"] }
toml = "0.8.12"
Expand All @@ -74,17 +75,19 @@ uniffi = { version = "0.27.0", features = ["cli"] }
url = "2.4"
vergen = { version = "8.2.6", default-features = false }

nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-client-core = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-config = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-credential-storage = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-crypto = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-explorer-client = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-id = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-ip-packet-requests = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-node-requests = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-task = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-topology = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-validator-client = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-wireguard-types = { git = "https://github.com/nymtech/nym", rev = "fd238b1" }
nym-bandwidth-controller = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-client-core = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-config = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-credential-storage = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-credentials = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-crypto = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-explorer-client = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-id = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-ip-packet-requests = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-node-requests = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-task = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-topology = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-validator-client = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
nym-wireguard-types = { git = "https://github.com/nymtech/nym", rev = "b8b66fa" }
2 changes: 1 addition & 1 deletion nym-vpn-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ async fn import_credential(args: commands::ImportCredentialArgs, data_path: Path
ImportCredentialTypeEnum::Data(data) => data,
};
fs::create_dir_all(&data_path)?;
nym_vpn_lib::credentials::import_credential(raw_credential, data_path).await
Ok(nym_vpn_lib::credentials::import_credential(raw_credential, data_path).await?)
}

fn mixnet_data_path() -> Option<PathBuf> {
Expand Down
3 changes: 3 additions & 0 deletions nym-vpn-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ serde_json.workspace = true
signature.workspace = true
tap.workspace = true
thiserror.workspace = true
time.workspace = true
tokio = { workspace = true, features = ["process", "rt-multi-thread", "fs", "sync"] }
tokio-util = { workspace = true, features = ["codec"] }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
Expand All @@ -50,10 +51,12 @@ talpid-tunnel = { git = "https://github.com/nymtech/nym-vpn-mullvad-libs", rev =
talpid-types = { git = "https://github.com/nymtech/nym-vpn-mullvad-libs", rev = "a76d598a7a33e9668f16105b68f6a608b72c9a89" }
talpid-wireguard = { git = "https://github.com/nymtech/nym-vpn-mullvad-libs", rev = "a76d598a7a33e9668f16105b68f6a608b72c9a89" }

nym-bandwidth-controller.workspace = true
nym-bin-common.workspace = true
nym-client-core.workspace = true
nym-config.workspace = true
nym-credential-storage.workspace = true
nym-credentials.workspace = true
nym-crypto.workspace = true
nym-explorer-client.workspace = true
nym-id.workspace = true
Expand Down
41 changes: 0 additions & 41 deletions nym-vpn-lib/src/credentials.rs

This file was deleted.

107 changes: 107 additions & 0 deletions nym-vpn-lib/src/credentials/check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use std::{fs, path::PathBuf};

use nym_bandwidth_controller::{BandwidthController, PreparedCredential, RetrievedCredential};
use nym_credentials::{
coconut::bandwidth::{
bandwidth_credential_params, issued::BandwidthCredentialIssuedDataVariant,
},
obtain_aggregate_verification_key, IssuedBandwidthCredential,
};

use tracing::{debug, info};

use super::{
helpers::{get_coconut_api_clients, get_credentials_store, get_nyxd_client, CoconutClients},
CredentialError,
};

pub async fn check_raw_credential(raw_credential: Vec<u8>) -> Result<(), CredentialError> {
let version = None;
let credential = IssuedBandwidthCredential::try_unpack(&raw_credential, version)?;

// Check expiry
match credential.variant_data() {
BandwidthCredentialIssuedDataVariant::Voucher(_) => {
debug!("credential is a bandwidth voucher");
}
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
debug!("credential is a free pass");
if freepass_info.expired() {
return Err(CredentialError::FreepassExpired {
expiry_date: freepass_info.expiry_date().to_string(),
});
}
}
}

// TODO: verify?

Ok(())
}

pub async fn check_credential_base58(credential: &str) -> Result<(), CredentialError> {
let raw_credential = bs58::decode(credential)
.into_vec()
.map_err(|err| CredentialError::FailedToDecodeBase58Credential { source: err })?;
check_raw_credential(raw_credential).await
}

pub async fn check_credential_file(credential_file: PathBuf) -> Result<(), CredentialError> {
let raw_credential = fs::read(credential_file)?;
check_raw_credential(raw_credential).await
}

pub async fn check_imported_credential(
data_path: PathBuf,
gateway_id: &str,
) -> Result<(), CredentialError> {
let client = get_nyxd_client()?;
let (credentials_store, _location) = get_credentials_store(data_path.clone()).await?;
let bandwidth_controller = BandwidthController::new(credentials_store, client);
let usable_credential = bandwidth_controller
.get_next_usable_credential(gateway_id)
.await
.map_err(|err| CredentialError::FailedToGetNextUsableCredential {
location: data_path,
reason: err.to_string(),
})?;

let epoch_id = usable_credential.credential.epoch_id();
let client = get_nyxd_client()?;
let coconut_api_clients = match get_coconut_api_clients(client, epoch_id).await? {
CoconutClients::Clients(clients) => clients,
CoconutClients::NoContactAvailable => {
info!("No Coconut API clients on this network, we are ok");
return Ok(());
}
};

verify_credential(usable_credential, coconut_api_clients).await
}

async fn verify_credential(
usable_credential: RetrievedCredential,
coconut_api_clients: Vec<nym_validator_client::coconut::CoconutApiClient>,
) -> Result<(), CredentialError> {
let verification_key = obtain_aggregate_verification_key(&coconut_api_clients)?;
let spend_request = usable_credential
.credential
.prepare_for_spending(&verification_key)?;
let prepared_credential = PreparedCredential {
data: spend_request,
epoch_id: usable_credential.credential.epoch_id(),
credential_id: usable_credential.credential_id,
};

if !prepared_credential.data.validate_type_attribute() {
return Err(CredentialError::MissingBandwidthTypeAttribute);
}

let params = bandwidth_credential_params();
if prepared_credential.data.verify(params, &verification_key) {
info!("Successfully verified credential");
Ok(())
} else {
Err(CredentialError::FailedToVerifyCredential)
}
}
47 changes: 47 additions & 0 deletions nym-vpn-lib/src/credentials/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#[derive(Debug, thiserror::Error)]
pub enum CredentialError {
#[error(transparent)]
CoconutApiError(#[from] nym_validator_client::coconut::CoconutApiError),

#[error(transparent)]
NymSdkError(#[from] nym_sdk::Error),

#[error(transparent)]
NymCredentialsError(#[from] nym_credentials::Error),

#[error(transparent)]
IoError(#[from] std::io::Error),

#[error("the free pass has already expired! The expiration was set to {expiry_date}")]
FreepassExpired { expiry_date: String },

#[error("failed to import credential to: {location}: {source}")]
FailedToImportCredential {
location: std::path::PathBuf,
source: nym_id::NymIdError,
},

#[error("failed decode base58 credential: {source}")]
FailedToDecodeBase58Credential { source: bs58::decode::Error },

#[error("failed to get next usable credential: {reason}")]
FailedToGetNextUsableCredential {
location: std::path::PathBuf,
reason: String,
},

#[error("missing bandwidth type attribute")]
MissingBandwidthTypeAttribute,

#[error("failed to verify credential")]
FailedToVerifyCredential,

#[error("failed to get nyxd client: {0}")]
NyxdError(#[from] nym_validator_client::nyxd::error::NyxdError),

#[error("no nyxd endpoints found")]
NoNyxdEndpointsFound,

#[error("failed to query contract")]
FailedToQueryContract,
}
59 changes: 59 additions & 0 deletions nym-vpn-lib/src/credentials/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use std::path::PathBuf;

use nym_credential_storage::persistent_storage::PersistentStorage;

use nym_sdk::{mixnet::StoragePaths, NymNetworkDetails};
use nym_validator_client::{
coconut::{all_coconut_api_clients, CoconutApiError},
nyxd::{error::NyxdError, Config as NyxdClientConfig, NyxdClient},
QueryHttpRpcNyxdClient,
};
use tracing::debug;

use super::CredentialError;

pub(super) async fn get_credentials_store(
data_path: PathBuf,
) -> Result<(PersistentStorage, PathBuf), CredentialError> {
let storage_path = StoragePaths::new_from_dir(data_path)?;
let credential_db_path = storage_path.credential_database_path;
debug!("Credential store: {}", credential_db_path.display());
Ok((
nym_credential_storage::initialise_persistent_storage(credential_db_path.clone()).await,
credential_db_path,
))
}

pub(super) fn get_nyxd_client() -> Result<QueryHttpRpcNyxdClient, CredentialError> {
let network = NymNetworkDetails::new_from_env();
let config = NyxdClientConfig::try_from_nym_network_details(&network)?;

// Safe to use pick the first one?
let nyxd_url = network
.endpoints
.first()
.ok_or(CredentialError::NoNyxdEndpointsFound)?
.nyxd_url();

debug!("Connecting to nyx validator at: {}", nyxd_url);
Ok(NyxdClient::connect(config, nyxd_url.as_str())?)
}

pub(super) enum CoconutClients {
Clients(Vec<nym_validator_client::coconut::CoconutApiClient>),
NoContactAvailable,
}

pub(super) async fn get_coconut_api_clients(
nyxd_client: QueryHttpRpcNyxdClient,
epoch_id: u64,
) -> Result<CoconutClients, CredentialError> {
match all_coconut_api_clients(&nyxd_client, epoch_id).await {
Ok(clients) => Ok(CoconutClients::Clients(clients)),
Err(CoconutApiError::ContractQueryFailure { source }) => match source {
NyxdError::NoContractAddressAvailable(_) => Ok(CoconutClients::NoContactAvailable),
_ => Err(CredentialError::FailedToQueryContract),
},
Err(err) => Err(err.into()),
}
}
40 changes: 40 additions & 0 deletions nym-vpn-lib/src/credentials/import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::{fs, path::PathBuf};
use tracing::info;

use super::{helpers::get_credentials_store, CredentialError};

// Import binary credential data
pub async fn import_credential(
raw_credential: Vec<u8>,
data_path: PathBuf,
) -> Result<(), CredentialError> {
info!("Importing credential");
let (credentials_store, location) = get_credentials_store(data_path).await?;
let version = None;
nym_id::import_credential(credentials_store, raw_credential, version)
.await
.map_err(|err| CredentialError::FailedToImportCredential {
location,
source: err,
})
}

// Import credential data from a base58 string
pub async fn import_credential_base58(
credential: &str,
data_path: PathBuf,
) -> Result<(), CredentialError> {
let raw_credential = bs58::decode(credential)
.into_vec()
.map_err(|err| CredentialError::FailedToDecodeBase58Credential { source: err })?;
import_credential(raw_credential, data_path).await
}

// Import credential data from a binary file
pub async fn import_credential_file(
credential_file: PathBuf,
data_path: PathBuf,
) -> Result<(), CredentialError> {
let raw_credential = fs::read(credential_file)?;
import_credential(raw_credential, data_path).await
}
11 changes: 11 additions & 0 deletions nym-vpn-lib/src/credentials/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
mod check;
mod helpers;
mod import;

mod error;

pub use check::{
check_credential_base58, check_credential_file, check_imported_credential, check_raw_credential,
};
pub use error::CredentialError;
pub use import::{import_credential, import_credential_base58, import_credential_file};
8 changes: 8 additions & 0 deletions nym-vpn-lib/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ pub enum Error {
#[cfg(windows)]
#[error("administrator privileges required, try rerunning with administrator privileges: `runas /user:Administrator {binary_name} run`")]
AdminPrivilegesRequired { binary_name: String },

#[error("invalid credential: {reason}")]
InvalidCredential {
reason: crate::credentials::CredentialError,
},

#[error(transparent)]
CredentialError(#[from] crate::credentials::CredentialError),
}

// Result type based on our error type
Expand Down
Loading

0 comments on commit 76b79dd

Please sign in to comment.