From f0666b33d2a144e9a6499054eab76cc448b2c3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 9 Feb 2024 17:35:07 +0000 Subject: [PATCH] validating request attributes --- .../credentials/src/coconut/bandwidth/mod.rs | 2 +- nym-api/src/coconut/api_routes/mod.rs | 44 ++++++++++++++++++- nym-api/src/coconut/error.rs | 31 ++++++++++++- nym-api/src/coconut/storage/mod.rs | 4 +- 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/common/credentials/src/coconut/bandwidth/mod.rs b/common/credentials/src/coconut/bandwidth/mod.rs index 195db20598..8a0e8f4136 100644 --- a/common/credentials/src/coconut/bandwidth/mod.rs +++ b/common/credentials/src/coconut/bandwidth/mod.rs @@ -6,7 +6,7 @@ use std::sync::OnceLock; pub use issuance::IssuanceBandwidthCredential; pub use issued::IssuedBandwidthCredential; pub use nym_credentials_interface::{ - CredentialSigningData, CredentialSpendingData, CredentialType, Parameters, + CredentialSigningData, CredentialSpendingData, CredentialType, Parameters, UnknownCredentialType }; pub mod freepass; diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index 76a53c0d8e..45fef58525 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -18,16 +18,56 @@ use nym_coconut_bandwidth_contract_common::spend_credential::{ funds_from_cosmos_msgs, SpendCredentialStatus, }; use nym_coconut_dkg_common::types::EpochId; +use nym_credentials::coconut::bandwidth::freepass::MAX_FREE_PASS_VALIDITY; use nym_credentials::coconut::bandwidth::{ - bandwidth_credential_params, IssuanceBandwidthCredential, + bandwidth_credential_params, CredentialType, IssuanceBandwidthCredential, }; use nym_validator_client::nyxd::Coin; use rocket::serde::json::Json; use rocket::State as RocketState; use std::ops::Deref; +use time::OffsetDateTime; mod helpers; +fn validate_freepass_public_attributes(res: &FreePassRequest) -> Result<()> { + let public_attributes = &res.public_attributes_plain; + + if public_attributes.len() != IssuanceBandwidthCredential::PUBLIC_ATTRIBUTES as usize { + return Err(CoconutError::InvalidFreePassAttributes { + got: public_attributes.len(), + expected: IssuanceBandwidthCredential::PUBLIC_ATTRIBUTES as usize, + }); + } + + // SAFETY: we just ensured correct number of attributes + let expiry_raw = public_attributes.first().unwrap(); + let type_raw = public_attributes.get(1).unwrap(); + + let parsed_type = type_raw.parse::()?; + if parsed_type != CredentialType::FreePass { + return Err(CoconutError::InvalidFreePassTypeAttribute { got: parsed_type }); + } + + let expiry_timestamp: i64 = expiry_raw + .parse() + .map_err(|source| CoconutError::ExpiryDateParsingFailure { source })?; + + let expiry_date = OffsetDateTime::from_unix_timestamp(expiry_timestamp).map_err(|source| { + CoconutError::InvalidExpiryDate { + unix_timestamp: expiry_timestamp, + source, + } + })?; + let now = OffsetDateTime::now_utc(); + + if expiry_date > now + MAX_FREE_PASS_VALIDITY { + return Err(CoconutError::TooLongFreePass { expiry_date }); + } + + Ok(()) +} + #[post("/free-pass", data = "")] pub async fn post_free_pass( freepass_request_body: Json, @@ -36,6 +76,8 @@ pub async fn post_free_pass( debug!("Received free pass sign request"); trace!("body: {:?}", freepass_request_body); + validate_freepass_public_attributes(&freepass_request_body)?; + // grab the admin of the bandwidth contract let Some(authorised_admin) = state.get_bandwidth_contract_admin().await? else { error!("our bandwidth contract does not have an admin set! We won't be able to migrate the contract! We should redeploy it ASAP"); diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 969b7dfeb2..d5f6ac81d6 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -3,7 +3,7 @@ use crate::node_status_api::models::NymApiStorageError; use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId}; -use nym_credentials::coconut::bandwidth::CredentialType; +use nym_credentials::coconut::bandwidth::{CredentialType, UnknownCredentialType}; use nym_crypto::asymmetric::{ encryption::KeyRecoveryError, identity::{Ed25519RecoveryError, SignatureError}, @@ -18,6 +18,8 @@ use rocket::{response, Request, Response}; use std::io::Cursor; use std::num::ParseIntError; use thiserror::Error; +use time::error::ComponentRange; +use time::OffsetDateTime; pub type Result = std::result::Result; @@ -50,6 +52,33 @@ pub enum CoconutError { #[error("only secp256k1 keys are supported for free pass issuance")] UnsupportedNonSecp256k1Key, + #[error("received credential request for an unknown type: {0}")] + UnknownCredentialType(#[from] UnknownCredentialType), + + #[error("the provided free pass request had an unexpected number of public attributes. got {got} but expected {expected}")] + InvalidFreePassAttributes { got: usize, expected: usize }, + + #[error("the provided free pass request had an invalid type attribute (got: '{got}')")] + InvalidFreePassTypeAttribute { got: CredentialType }, + + #[error("failed to parse the free pass expiry date: {source}")] + ExpiryDateParsingFailure { + #[source] + source: ParseIntError, + }, + + #[error("failed to parse expiry timestamp into proper datetime: {source}")] + InvalidExpiryDate { + unix_timestamp: i64, + #[source] + source: ComponentRange, + }, + + #[error( + "the provided free pass request has too long expiry (expiry is set to on {expiry_date})" + )] + TooLongFreePass { expiry_date: OffsetDateTime }, + #[error("the received bandwidth voucher did not contain deposit value")] MissingBandwidthValue, diff --git a/nym-api/src/coconut/storage/mod.rs b/nym-api/src/coconut/storage/mod.rs index 89156ee7da..f77703cea8 100644 --- a/nym-api/src/coconut/storage/mod.rs +++ b/nym-api/src/coconut/storage/mod.rs @@ -169,10 +169,10 @@ impl CoconutStorageExt for NymApiStorage { } async fn get_current_freepass_nonce(&self) -> Result { - todo!() + Ok(self.manager.get_current_freepass_nonce().await?) } async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), NymApiStorageError> { - todo!() + Ok(self.manager.update_and_validate_freepass_nonce(new).await?) } }