Merge pull request #4436 from nymtech/feature/nym-id-scaffold

Feature/nym id scaffold
This commit is contained in:
Jędrzej Stuczyński
2024-03-01 09:14:40 +00:00
committed by GitHub
27 changed files with 403 additions and 265 deletions
Generated
+30
View File
@@ -5128,6 +5128,7 @@ dependencies = [
"nym-credentials",
"nym-credentials-interface",
"nym-crypto",
"nym-id-lib",
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-name-service-common",
@@ -5168,6 +5169,7 @@ dependencies = [
"nym-credentials",
"nym-crypto",
"nym-gateway-requests",
"nym-id-lib",
"nym-network-defaults",
"nym-pemstore",
"nym-sphinx",
@@ -5626,6 +5628,32 @@ dependencies = [
"serde",
]
[[package]]
name = "nym-id"
version = "0.1.0"
dependencies = [
"anyhow",
"bs58 0.5.0",
"clap 4.4.7",
"nym-bin-common",
"nym-credential-storage",
"nym-id-lib",
"tokio",
"tracing",
]
[[package]]
name = "nym-id-lib"
version = "0.1.0"
dependencies = [
"nym-credential-storage",
"nym-credentials",
"thiserror",
"time",
"tracing",
"zeroize",
]
[[package]]
name = "nym-inclusion-probability"
version = "0.1.0"
@@ -5864,6 +5892,7 @@ dependencies = [
"nym-credentials",
"nym-crypto",
"nym-exit-policy",
"nym-id-lib",
"nym-network-defaults",
"nym-ordered-buffer",
"nym-sdk",
@@ -6151,6 +6180,7 @@ dependencies = [
"nym-credentials",
"nym-crypto",
"nym-gateway-requests",
"nym-id-lib",
"nym-network-defaults",
"nym-ordered-buffer",
"nym-pemstore",
+1 -1
View File
@@ -112,7 +112,7 @@ members = [
"wasm/client",
# "wasm/full-nym-wasm",
"wasm/mix-fetch",
"wasm/node-tester",
"wasm/node-tester", "nym-id", "common/nym-id-lib",
]
default-members = [
+1
View File
@@ -51,5 +51,6 @@ nym-task = { path = "../../common/task" }
nym-topology = { path = "../../common/topology" }
nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["http-client"] }
nym-client-websocket-requests = { path = "websocket-requests" }
nym-id-lib = { path = "../../common/nym-id-lib" }
[dev-dependencies]
@@ -4,14 +4,10 @@
use crate::commands::try_load_current_config;
use crate::error::ClientError;
use clap::ArgGroup;
use log::{error, info};
use nym_credential_storage::models::StorableIssuedCredential;
use nym_credential_storage::storage::Storage;
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
use nym_credentials::IssuedBandwidthCredential;
use nym_id_lib::import_credential;
use std::fs;
use std::path::PathBuf;
use zeroize::Zeroizing;
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
bs58::decode(raw).into_vec()
@@ -33,8 +29,8 @@ pub(crate) struct Args {
pub(crate) credential_path: Option<PathBuf>,
// currently hidden as there exists only a single serialization standard
#[clap(long, hide = true, default_value_t = 1)]
pub(crate) version: u8,
#[clap(long, hide = true)]
pub(crate) version: Option<u8>,
}
pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
@@ -52,50 +48,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
fs::read(args.credential_path.unwrap())?
}
};
let raw_credential = Zeroizing::new(raw_credential);
// we're unpacking the data in order to make sure it's valid
// and to extract relevant metadata for storage purposes
let credential = match args.version {
1 => Zeroizing::new(
IssuedBandwidthCredential::unpack_v1(&raw_credential).map_err(|source| {
ClientError::CredentialDeserializationFailure {
storage_revision: 1,
source,
}
})?,
),
other => panic!("unknown credential serialization version {other}"),
};
info!("importing {}", credential.typ());
match credential.variant_data() {
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
info!("with value of {}", voucher_info.value())
}
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
info!("with expiry at {}", freepass_info.expiry_date());
if freepass_info.expired() {
error!("the free pass has already expired!");
// technically we can import it, but the gateway will just reject it so what's the point
return Err(ClientError::ExpiredCredentialImport {
expiration: freepass_info.expiry_date(),
});
}
}
}
let storable = StorableIssuedCredential {
serialization_revision: args.version,
credential_data: &raw_credential,
credential_type: credential.typ().to_string(),
epoch_id: credential
.epoch_id()
.try_into()
.expect("our epoch is has run over u32::MAX!"),
};
credentials_store.insert_issued_credential(storable).await?;
import_credential(credentials_store, raw_credential, args.version).await?;
Ok(())
}
+4 -19
View File
@@ -1,6 +1,6 @@
use nym_client_core::error::ClientCoreError;
use nym_credential_storage::error::StorageError;
use time::OffsetDateTime;
use nym_id_lib::NymIdError;
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
@@ -23,21 +23,6 @@ pub enum ClientError {
#[error("Attempted to start the client in invalid socket mode")]
InvalidSocketMode,
#[error("failed to store credential: {source}")]
CredentialStorageFailure {
#[from]
source: StorageError,
},
#[error(
"failed to deserialize provided credential using revision {storage_revision}: {source}"
)]
CredentialDeserializationFailure {
storage_revision: u8,
#[source]
source: nym_credentials::error::Error,
},
#[error("attempted to import an expired credential (it expired on {expiration})")]
ExpiredCredentialImport { expiration: OffsetDateTime },
#[error(transparent)]
NymIdError(#[from] NymIdError),
}
+1
View File
@@ -35,6 +35,7 @@ nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
nym-pemstore = { path = "../../common/pemstore" }
nym-topology = { path = "../../common/topology" }
nym-socks5-client-core = { path = "../../common/socks5-client-core" }
nym-id-lib = { path = "../../common/nym-id-lib" }
[features]
default = []
@@ -4,14 +4,10 @@
use crate::commands::try_load_current_config;
use crate::error::Socks5ClientError;
use clap::ArgGroup;
use log::{error, info};
use nym_credential_storage::models::StorableIssuedCredential;
use nym_credential_storage::storage::Storage;
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
use nym_credentials::IssuedBandwidthCredential;
use nym_id_lib::import_credential;
use std::fs;
use std::path::PathBuf;
use zeroize::Zeroizing;
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
bs58::decode(raw).into_vec()
@@ -33,8 +29,8 @@ pub(crate) struct Args {
pub(crate) credential_path: Option<PathBuf>,
// currently hidden as there exists only a single serialization standard
#[clap(long, hide = true, default_value_t = 1)]
pub(crate) version: u8,
#[clap(long, hide = true)]
pub(crate) version: Option<u8>,
}
pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
@@ -52,50 +48,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
fs::read(args.credential_path.unwrap())?
}
};
let raw_credential = Zeroizing::new(raw_credential);
// we're unpacking the data in order to make sure it's valid
// and to extract relevant metadata for storage purposes
let credential = match args.version {
1 => Zeroizing::new(
IssuedBandwidthCredential::unpack_v1(&raw_credential).map_err(|source| {
Socks5ClientError::CredentialDeserializationFailure {
storage_revision: 1,
source,
}
})?,
),
other => panic!("unknown credential serialization version {other}"),
};
info!("importing {}", credential.typ());
match credential.variant_data() {
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
info!("with value of {}", voucher_info.value())
}
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
info!("with expiry at {}", freepass_info.expiry_date());
if freepass_info.expired() {
error!("the free pass has already expired!");
// technically we can import it, but the gateway will just reject it so what's the point
return Err(Socks5ClientError::ExpiredCredentialImport {
expiration: freepass_info.expiry_date(),
});
}
}
}
let storable = StorableIssuedCredential {
serialization_revision: args.version,
credential_data: &raw_credential,
credential_type: credential.typ().to_string(),
epoch_id: credential
.epoch_id()
.try_into()
.expect("our epoch is has run over u32::MAX!"),
};
credentials_store.insert_issued_credential(storable).await?;
import_credential(credentials_store, raw_credential, args.version).await?;
Ok(())
}
+4 -19
View File
@@ -1,6 +1,6 @@
use nym_client_core::error::ClientCoreError;
use nym_credential_storage::error::StorageError;
use time::OffsetDateTime;
use nym_id_lib::NymIdError;
#[derive(thiserror::Error, Debug)]
pub enum Socks5ClientError {
@@ -23,21 +23,6 @@ pub enum Socks5ClientError {
#[error(transparent)]
ClientCoreError(#[from] ClientCoreError),
#[error("failed to store credential: {source}")]
CredentialStorageFailure {
#[from]
source: StorageError,
},
#[error(
"failed to deserialize provided credential using revision {storage_revision}: {source}"
)]
CredentialDeserializationFailure {
storage_revision: u8,
#[source]
source: nym_credentials::error::Error,
},
#[error("attempted to import an expired credential (it expired on {expiration})")]
ExpiredCredentialImport { expiration: OffsetDateTime },
#[error(transparent)]
NymIdError(#[from] NymIdError),
}
+1
View File
@@ -55,6 +55,7 @@ nym-credentials = { path = "../../common/credentials" }
nym-credentials-interface = { path = "../../common/credentials-interface" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-credential-utils = { path = "../../common/credential-utils" }
nym-id-lib = { path = "../../common/nym-id-lib" }
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
nym-types = { path = "../../common/types" }
@@ -5,15 +5,10 @@ use crate::utils::CommonConfigsWrapper;
use anyhow::bail;
use clap::ArgGroup;
use clap::Parser;
use log::{error, info};
use nym_credential_storage::initialise_persistent_storage;
use nym_credential_storage::models::StorableIssuedCredential;
use nym_credential_storage::storage::Storage;
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
use nym_credentials::IssuedBandwidthCredential;
use nym_id_lib::import_credential;
use std::fs;
use std::path::PathBuf;
use zeroize::Zeroizing;
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
bs58::decode(raw).into_vec()
@@ -35,8 +30,8 @@ pub struct Args {
pub(crate) credential_path: Option<PathBuf>,
// currently hidden as there exists only a single serialization standard
#[clap(long, hide = true, default_value_t = 1)]
pub(crate) version: u8,
#[clap(long, hide = true)]
pub(crate) version: Option<u8>,
}
pub async fn execute(args: Args) -> anyhow::Result<()> {
@@ -54,6 +49,7 @@ pub async fn execute(args: Args) -> anyhow::Result<()> {
"using credentials store at '{}'",
credentials_store.display()
);
let credentials_store = initialise_persistent_storage(credentials_store).await;
let raw_credential = match args.credential_data {
Some(data) => data,
@@ -62,44 +58,7 @@ pub async fn execute(args: Args) -> anyhow::Result<()> {
fs::read(args.credential_path.unwrap())?
}
};
let raw_credential = Zeroizing::new(raw_credential);
// we're unpacking the data in order to make sure it's valid
// and to extract relevant metadata for storage purposes
let credential = match args.version {
1 => Zeroizing::new(IssuedBandwidthCredential::unpack_v1(&raw_credential)?),
other => panic!("unknown credential serialization version {other}"),
};
let persistent_storage = initialise_persistent_storage(credentials_store).await;
info!("importing {}", credential.typ());
match credential.variant_data() {
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
info!("with value of {}", voucher_info.value())
}
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
info!("with expiry at {}", freepass_info.expiry_date());
if freepass_info.expired() {
error!("the free pass has already expired!");
// technically we can, but the gateway will just reject it so what's the point
bail!("can't import an expired free pass")
}
}
}
let storable = StorableIssuedCredential {
serialization_revision: args.version,
credential_data: &raw_credential,
credential_type: credential.typ().to_string(),
epoch_id: credential
.epoch_id()
.try_into()
.expect("our epoch is has run over u32::MAX!"),
};
persistent_storage
.insert_issued_credential(storable)
.await?;
import_credential(credentials_store, raw_credential, args.version).await?;
Ok(())
}
@@ -315,9 +315,12 @@ impl IssuanceBandwidthCredential {
}
// TODO: is that actually needed?
// idea: make it consistent with the issued credential and its vX serde
pub fn try_from_recovered_bytes(bytes: &[u8]) -> Result<Self, Error> {
use bincode::Options;
Ok(make_recovery_bincode_serializer().deserialize(bytes)?)
make_recovery_bincode_serializer()
.deserialize(bytes)
.map_err(|source| Error::RecoveryCredentialDeserializationFailure { source })
}
}
@@ -327,3 +330,18 @@ fn make_recovery_bincode_serializer() -> impl bincode::Options {
.with_big_endian()
.with_varint_encoding()
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
fn assert_zeroize<T: Zeroize>() {}
#[test]
fn credential_is_zeroized() {
assert_zeroize::<IssuanceBandwidthCredential>();
assert_zeroize_on_drop::<IssuanceBandwidthCredential>();
}
}
@@ -116,6 +116,15 @@ impl IssuedBandwidthCredential {
}
}
pub fn try_unpack(bytes: &[u8], revision: impl Into<Option<u8>>) -> Result<Self, Error> {
let revision = revision.into().unwrap_or(CURRENT_SERIALIZATION_REVISION);
match revision {
1 => Self::unpack_v1(bytes),
_ => Err(Error::UnknownSerializationRevision { revision }),
}
}
pub fn epoch_id(&self) -> EpochId {
self.epoch_id
}
@@ -138,7 +147,12 @@ impl IssuedBandwidthCredential {
/// Unpack (deserialize) the credential data from the given bytes using v1 serializer.
pub fn unpack_v1(bytes: &[u8]) -> Result<Self, Error> {
use bincode::Options;
Ok(make_storable_bincode_serializer().deserialize(bytes)?)
make_storable_bincode_serializer()
.deserialize(bytes)
.map_err(|source| Error::SerializationFailure {
source,
revision: 1,
})
}
pub fn randomise_signature(&mut self) {
@@ -191,3 +205,18 @@ fn make_storable_bincode_serializer() -> impl bincode::Options {
.with_big_endian()
.with_varint_encoding()
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
fn assert_zeroize<T: Zeroize>() {}
#[test]
fn credential_is_zeroized() {
assert_zeroize::<IssuedBandwidthCredential>();
assert_zeroize_on_drop::<IssuedBandwidthCredential>();
}
}
+13 -2
View File
@@ -5,6 +5,7 @@ use nym_credentials_interface::CoconutError;
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
use nym_validator_client::ValidatorClientError;
use crate::coconut::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
use thiserror::Error;
#[derive(Debug, Error)]
@@ -12,8 +13,18 @@ pub enum Error {
#[error("IO error")]
IOError(#[from] std::io::Error),
#[error("failed to (de)serialize credential structure: {0}")]
SerializationFailure(#[from] bincode::Error),
#[error("failed to deserialize a recovery credential: {source}")]
RecoveryCredentialDeserializationFailure { source: bincode::Error },
#[error("failed to (de)serialize provided credential using revision {revision}: {source}")]
SerializationFailure {
#[source]
source: bincode::Error,
revision: u8,
},
#[error("unknown credential serializatio revision {revision}. the current (and max supported) version is {CURRENT_SERIALIZATION_REVISION}")]
UnknownSerializationRevision { revision: u8 },
#[error("The detailed description is yet to be determined")]
BandwidthCredentialError,
+1
View File
@@ -9,3 +9,4 @@ pub use coconut::bandwidth::{
IssuedBandwidthCredential,
};
pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
pub use error::Error;
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "nym-id-lib"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror.workspace = true
time.workspace = true
tracing.workspace = true
zeroize.workspace = true
nym-credential-storage = { path = "../credential-storage" }
nym-credentials = { path = "../credentials" }
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::error::Error;
use thiserror::Error;
use time::OffsetDateTime;
#[derive(Debug, Error)]
pub enum NymIdError {
#[error("failed to deserialize provided credential: {source}")]
CredentialDeserializationFailure { source: nym_credentials::Error },
#[error("attempted to import an expired credential (it expired on {expiration})")]
ExpiredCredentialImport { expiration: OffsetDateTime },
#[error("failed to store credential in the provided store: {source}")]
StorageError {
source: Box<dyn Error + Send + Sync>,
},
}
@@ -0,0 +1,71 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::NymIdError;
use nym_credential_storage::models::StorableIssuedCredential;
use nym_credential_storage::storage::Storage;
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
use nym_credentials::IssuedBandwidthCredential;
use tracing::{debug, warn};
use zeroize::Zeroizing;
pub async fn import_credential<S>(
credentials_store: S,
raw_credential: Vec<u8>,
credential_version: impl Into<Option<u8>>,
) -> Result<(), NymIdError>
where
S: Storage,
<S as Storage>::StorageError: Send + Sync + 'static,
{
let raw_credential = Zeroizing::new(raw_credential);
// note: the type itself implements ZeroizeOnDrop
let credential = IssuedBandwidthCredential::try_unpack(&raw_credential, credential_version)
.map_err(|source| NymIdError::CredentialDeserializationFailure { source })?;
debug!(
"attempting to import credential of type {}",
credential.typ()
);
match credential.variant_data() {
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
debug!("with value of {}", voucher_info.value())
}
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
debug!("with expiry at {}", freepass_info.expiry_date());
if freepass_info.expired() {
warn!("the free pass has already expired!");
// technically we can import it, but the gateway will just reject it so what's the point
return Err(NymIdError::ExpiredCredentialImport {
expiration: freepass_info.expiry_date(),
});
}
}
}
// SAFETY:
// for the epoch to run over u32::MAX, we'd have to advance it for few centuries every block...
// the alternative is a very particularly malformed serialized data, but at that point blowing up is the right call
// because we can't rely on it anyway
#[allow(clippy::expect_used)]
let storable = StorableIssuedCredential {
serialization_revision: credential.current_serialization_revision(),
credential_data: &raw_credential,
credential_type: credential.typ().to_string(),
epoch_id: credential
.epoch_id()
.try_into()
.expect("our epoch is has run over u32::MAX!"),
};
credentials_store
.insert_issued_credential(storable)
.await
.map_err(|source| NymIdError::StorageError {
source: Box::new(source),
})?;
Ok(())
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
pub mod error;
pub mod import_credential;
pub use error::NymIdError;
pub use import_credential::import_credential;
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "nym-id"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
bs58.workspace = true
clap = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tracing.workspace = true
nym-bin-common = { path = "../common/bin-common", features = ["output_format", "basic_tracing"] }
nym-credential-storage = { path = "../common/credential-storage" }
nym-id-lib = { path = "../common/nym-id-lib" }
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_bin_common::bin_info_owned;
use nym_bin_common::output_format::OutputFormat;
#[derive(clap::Args)]
pub(crate) struct Args {
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
pub(crate) fn execute(args: Args) {
println!("{}", args.output.format(&bin_info_owned!()))
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::ArgGroup;
use nym_id_lib::import_credential;
use std::fs;
use std::path::PathBuf;
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
bs58::decode(raw).into_vec()
}
#[derive(clap::Args)]
#[clap(group(ArgGroup::new("cred_data").required(true)))]
pub(crate) struct Args {
/// Explicitly provide the encoded credential data (as base58)
#[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)]
pub(crate) credential_data: Option<Vec<u8>>,
/// Specifies the path to file containing binary credential data
#[clap(long, group = "cred_data")]
pub(crate) credential_path: Option<PathBuf>,
/// Specifies path to the credentials storage
#[clap(long)]
pub credentials_store_path: PathBuf,
// currently hidden as there exists only a single serialization standard
#[clap(long, hide = true)]
pub(crate) version: Option<u8>,
}
pub(crate) async fn execute(args: Args) -> anyhow::Result<()> {
let credentials_store =
nym_credential_storage::initialise_persistent_storage(args.credentials_store_path).await;
let raw_credential = match args.credential_data {
Some(data) => data,
None => {
// SAFETY: one of those arguments must have been set
#[allow(clippy::unwrap_used)]
fs::read(args.credential_path.unwrap())?
}
};
import_credential(credentials_store, raw_credential, args.version).await?;
Ok(())
}
+45
View File
@@ -0,0 +1,45 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod build_info;
mod import_credential;
mod setup;
use clap::{Parser, Subcommand};
use nym_bin_common::bin_info;
use std::sync::OnceLock;
fn pretty_build_info_static() -> &'static str {
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
}
#[derive(Parser)]
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
pub(crate) struct Cli {
#[clap(subcommand)]
command: Commands,
}
impl Cli {
pub async fn execute(self) -> anyhow::Result<()> {
match self.command {
Commands::ImportCredential(args) => import_credential::execute(args).await?,
Commands::BuildInfo(args) => build_info::execute(args),
}
Ok(())
}
}
#[derive(Subcommand)]
pub(crate) enum Commands {
// TODO: to be determined how it's going to work in nymvpn et al.
// ///
// Setup,
/// Attempt to import a bandwidth credential into the provided storage.
ImportCredential(import_credential::Args),
/// Show build information of this binary
BuildInfo(build_info::Args),
}
+2
View File
@@ -0,0 +1,2 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
use crate::commands::Cli;
use clap::Parser;
use nym_bin_common::logging::setup_tracing_logger;
mod commands;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
setup_tracing_logger();
let cli = Cli::parse();
cli.execute().await
}
@@ -61,7 +61,7 @@ nym-statistics-common = { path = "../../common/statistics" }
nym-task = { path = "../../common/task" }
nym-types = { path = "../../common/types" }
nym-exit-policy = { path = "../../common/exit-policy", features = ["client"] }
nym-id-lib = { path = "../../common/nym-id-lib" }
[dev-dependencies]
tempfile = "3.5.0"
@@ -4,14 +4,10 @@
use crate::cli::try_load_current_config;
use crate::error::NetworkRequesterError;
use clap::ArgGroup;
use log::{error, info};
use nym_credential_storage::models::StorableIssuedCredential;
use nym_credential_storage::storage::Storage;
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
use nym_credentials::IssuedBandwidthCredential;
use nym_id_lib::import_credential;
use std::fs;
use std::path::PathBuf;
use zeroize::Zeroizing;
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
bs58::decode(raw).into_vec()
@@ -33,8 +29,8 @@ pub(crate) struct Args {
pub(crate) credential_path: Option<PathBuf>,
// currently hidden as there exists only a single serialization standard
#[clap(long, hide = true, default_value_t = 1)]
pub(crate) version: u8,
#[clap(long, hide = true)]
pub(crate) version: Option<u8>,
}
pub(crate) async fn execute(args: Args) -> Result<(), NetworkRequesterError> {
@@ -52,50 +48,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), NetworkRequesterError> {
fs::read(args.credential_path.unwrap())?
}
};
let raw_credential = Zeroizing::new(raw_credential);
// we're unpacking the data in order to make sure it's valid
// and to extract relevant metadata for storage purposes
let credential = match args.version {
1 => Zeroizing::new(
IssuedBandwidthCredential::unpack_v1(&raw_credential).map_err(|source| {
NetworkRequesterError::CredentialDeserializationFailure {
storage_revision: 1,
source,
}
})?,
),
other => panic!("unknown credential serialization version {other}"),
};
info!("importing {}", credential.typ());
match credential.variant_data() {
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
info!("with value of {}", voucher_info.value())
}
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
info!("with expiry at {}", freepass_info.expiry_date());
if freepass_info.expired() {
error!("the free pass has already expired!");
// technically we can import it, but the gateway will just reject it so what's the point
return Err(NetworkRequesterError::ExpiredCredentialImport {
expiration: freepass_info.expiry_date(),
});
}
}
}
let storable = StorableIssuedCredential {
serialization_revision: args.version,
credential_data: &raw_credential,
credential_type: credential.typ().to_string(),
epoch_id: credential
.epoch_id()
.try_into()
.expect("our epoch is has run over u32::MAX!"),
};
credentials_store.insert_issued_credential(storable).await?;
import_credential(credentials_store, raw_credential, args.version).await?;
Ok(())
}
@@ -2,11 +2,11 @@
// SPDX-License-Identifier: GPL-3.0-only
pub use nym_client_core::error::ClientCoreError;
use nym_credential_storage::error::StorageError;
use nym_exit_policy::policy::PolicyError;
use nym_id_lib::NymIdError;
use nym_socks5_requests::{RemoteAddress, Socks5RequestError};
use std::net::SocketAddr;
use time::OffsetDateTime;
#[derive(thiserror::Error, Debug)]
pub enum NetworkRequesterError {
@@ -70,21 +70,6 @@ pub enum NetworkRequesterError {
#[error("can't setup an exit policy without any upstream urls")]
NoUpstreamExitPolicy,
#[error("failed to store credential: {source}")]
CredentialStorageFailure {
#[from]
source: StorageError,
},
#[error(
"failed to deserialize provided credential using revision {storage_revision}: {source}"
)]
CredentialDeserializationFailure {
storage_revision: u8,
#[source]
source: nym_credentials::error::Error,
},
#[error("attempted to import an expired credential (it expired on {expiration})")]
ExpiredCredentialImport { expiration: OffsetDateTime },
#[error(transparent)]
NymIdError(#[from] NymIdError),
}