enforce subaddress for tx proof and jwp

This commit is contained in:
creating2morrow 2023-05-06 04:38:13 -04:00
parent 847aaeb0d0
commit f821c4b572
5 changed files with 106 additions and 25 deletions

View file

@ -1,4 +1,4 @@
name: Release Artifacts name: cargo-build-release
on: on:
push: push:

View file

@ -17,7 +17,8 @@ enum RpcFields {
Balance, Balance,
CheckTxProof, CheckTxProof,
Close, Close,
Create, CreateAddress,
CreateWallet,
Export, Export,
Finalize, Finalize,
GetTxProof, GetTxProof,
@ -43,7 +44,8 @@ impl RpcFields {
RpcFields::Balance => String::from("get_balance"), RpcFields::Balance => String::from("get_balance"),
RpcFields::CheckTxProof => String::from("check_tx_proof"), RpcFields::CheckTxProof => String::from("check_tx_proof"),
RpcFields::Close => String::from("close_wallet"), RpcFields::Close => String::from("close_wallet"),
RpcFields::Create => String::from("create_wallet"), RpcFields::CreateAddress => String::from("create_address"),
RpcFields::CreateWallet => String::from("create_wallet"),
RpcFields::Export => String::from("export_multisig_info"), RpcFields::Export => String::from("export_multisig_info"),
RpcFields::Finalize => String::from("finalize_multisig"), RpcFields::Finalize => String::from("finalize_multisig"),
RpcFields::GetTxProof => String::from("get_tx_proof"), RpcFields::GetTxProof => String::from("get_tx_proof"),
@ -278,7 +280,7 @@ pub async fn create_wallet(filename: String) -> bool {
let req = reqres::XmrRpcCreateRequest { let req = reqres::XmrRpcCreateRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
id: RpcFields::Id.value(), id: RpcFields::Id.value(),
method: RpcFields::Create.value(), method: RpcFields::CreateWallet.value(),
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
@ -630,7 +632,7 @@ pub async fn check_tx_proof(txp: &proof::TxProof) -> reqres::XmrRpcCheckTxProofR
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params: reqres::XmrRpcCheckTxProofParams = reqres::XmrRpcCheckTxProofParams { let params: reqres::XmrRpcCheckTxProofParams = reqres::XmrRpcCheckTxProofParams {
address: String::from(&txp.address), address: String::from(&txp.subaddress),
message: String::from(&txp.message), message: String::from(&txp.message),
signature: String::from(&txp.signature), signature: String::from(&txp.signature),
txid: String::from(&txp.hash), txid: String::from(&txp.hash),
@ -662,7 +664,7 @@ pub async fn get_tx_proof(ptxp: proof::TxProof) -> reqres::XmrRpcGetTxProofRespo
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params: reqres::XmrRpcGetTxProofParams = reqres::XmrRpcGetTxProofParams { let params: reqres::XmrRpcGetTxProofParams = reqres::XmrRpcGetTxProofParams {
address: String::from(&ptxp.address), address: String::from(&ptxp.subaddress),
message: String::from(&ptxp.message), message: String::from(&ptxp.message),
txid: String::from(&ptxp.hash), txid: String::from(&ptxp.hash),
}; };
@ -779,6 +781,33 @@ pub async fn sweep_all(address: String) -> reqres::XmrRpcSweepAllResponse {
} }
} }
/// Performs the xmr rpc 'create_address' method
pub async fn create_address() -> reqres::XmrRpcCreateAddressResponse {
info!("creating new subaddress");
let client = reqwest::Client::new();
let host = get_rpc_host();
let params: reqres::XmrRpcCreateAddressParams = reqres::XmrRpcCreateAddressParams { account_index: 0 };
let req = reqres::XmrRpcCreateAddressRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(),
id: RpcFields::Id.value(),
method: RpcFields::CreateAddress.value(),
params,
};
let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req)
.send_with_digest_auth(&login.username, &login.credential).await {
Ok(response) => {
let res = response.json::<reqres::XmrRpcCreateAddressResponse>().await;
debug!("{} response: {:?}", RpcFields::CreateAddress.value(), res);
match res {
Ok(res) => res,
_ => Default::default(),
}
}
Err(_) => Default::default()
}
}
// Daemon requests // Daemon requests
//------------------------------------------------------------------- //-------------------------------------------------------------------
/// Performs the xmr daemon 'get_info' method /// Performs the xmr daemon 'get_info' method

View file

@ -14,7 +14,7 @@ use std::collections::BTreeMap;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct TxProof { pub struct TxProof {
pub address: String, pub subaddress: String,
pub confirmations: u64, pub confirmations: u64,
pub hash: String, pub hash: String,
pub message: String, pub message: String,
@ -24,7 +24,7 @@ pub struct TxProof {
impl Default for TxProof { impl Default for TxProof {
fn default() -> Self { fn default() -> Self {
TxProof { TxProof {
address: utils::empty_string(), subaddress: utils::empty_string(),
confirmations: 0, confirmations: 0,
hash: utils::empty_string(), hash: utils::empty_string(),
message: utils::empty_string(), message: utils::empty_string(),
@ -38,8 +38,9 @@ impl Default for TxProof {
/// provide proof of payment. /// provide proof of payment.
pub async fn create_invoice() -> reqres::Invoice { pub async fn create_invoice() -> reqres::Invoice {
info!("creating invoice"); info!("creating invoice");
let m_address = monero::get_address().await; // create a new subaddress
let address = m_address.result.address; let c_address = monero::create_address().await;
let address = c_address.result.address;
let pay_threshold = utils::get_payment_threshold(); let pay_threshold = utils::get_payment_threshold();
let conf_threshold = utils::get_conf_threshold(); let conf_threshold = utils::get_conf_threshold();
reqres::Invoice { address, conf_threshold, pay_threshold } reqres::Invoice { address, conf_threshold, pay_threshold }
@ -70,14 +71,14 @@ pub async fn create_jwp(proof: &TxProof) -> String {
..Default::default() ..Default::default()
}; };
let mut claims = BTreeMap::new(); let mut claims = BTreeMap::new();
let address = &proof.address; let address = &proof.subaddress;
let created = chrono::Utc::now().timestamp(); let created = chrono::Utc::now().timestamp();
let created_str = format!("{}", created); let created_str = format!("{}", created);
let hash = &proof.hash; let hash = &proof.hash;
let expire = &format!("{}", utils::get_payment_threshold()); let expire = &format!("{}", utils::get_payment_threshold());
let message = &proof.message; let message = &proof.message;
let signature = &proof.signature; let signature = &proof.signature;
claims.insert("address", address); claims.insert("subaddress", address);
claims.insert("created", &created_str); claims.insert("created", &created_str);
claims.insert("hash", hash); claims.insert("hash", hash);
claims.insert("expire", expire); claims.insert("expire", expire);
@ -115,7 +116,7 @@ pub async fn prove_payment(contact: String, txp: &TxProof) -> Result<reqres::Jwp
/// ///
/// is a JWP (JSON Web Proof) with the contents: /// is a JWP (JSON Web Proof) with the contents:
/// ///
/// `address`: this server's xmr address or subaddress /// `subaddress`: a subaddress belonging to this nevmes instance
/// ///
/// `created`: UTC timestamp the proof was created. /// `created`: UTC timestamp the proof was created.
/// <i>Future use</i> Potential offline payments. /// <i>Future use</i> Potential offline payments.
@ -146,8 +147,6 @@ impl<'r> FromRequest<'r> for PaymentProof {
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> { async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let proof = request.headers().get_one("proof"); let proof = request.headers().get_one("proof");
let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await;
let nevmes_address = m_address.result.address;
match proof { match proof {
Some(proof) => { Some(proof) => {
// check validity of address, payment amount and tx confirmations // check validity of address, payment amount and tx confirmations
@ -160,8 +159,9 @@ impl<'r> FromRequest<'r> for PaymentProof {
return match jwp { return match jwp {
Ok(j) => { Ok(j) => {
let claims = j.claims(); let claims = j.claims();
let address = &claims["address"]; let subaddress = &claims["subaddress"];
if address != &nevmes_address { let is_valid_subaddress = validate_subaddress(subaddress).await;
if !is_valid_subaddress {
return Outcome::Failure(( return Outcome::Failure((
Status::PaymentRequired, Status::PaymentRequired,
PaymentProofError::Invalid, PaymentProofError::Invalid,
@ -172,7 +172,7 @@ impl<'r> FromRequest<'r> for PaymentProof {
let signature = &claims["signature"]; let signature = &claims["signature"];
// verify proof // verify proof
let txp: TxProof = TxProof { let txp: TxProof = TxProof {
address: String::from(address), subaddress: String::from(subaddress),
hash: String::from(hash), hash: String::from(hash),
confirmations: 0, confirmations: 0,
message: String::from(message), message: String::from(message),
@ -224,7 +224,7 @@ async fn validate_proof(txp: &TxProof) -> TxProof {
&& p.result.confirmations < cth && p.result.received >= pth; && p.result.confirmations < cth && p.result.received >= pth;
if lgtm { if lgtm {
return TxProof { return TxProof {
address: String::from(&txp.address), subaddress: String::from(&txp.subaddress),
hash: String::from(&txp.hash), hash: String::from(&txp.hash),
confirmations: p.result.confirmations, confirmations: p.result.confirmations,
message: String::from(&txp.message), message: String::from(&txp.message),
@ -233,3 +233,16 @@ async fn validate_proof(txp: &TxProof) -> TxProof {
} }
Default::default() Default::default()
} }
/// Validate that the subaddress in the proof was
///
/// created by us. TODO(c2m): Store the index for quicker
///
/// lookups.
async fn validate_subaddress(subaddress: &String) -> bool {
let m_address = monero::get_address().await;
let all_address = m_address.result.addresses;
let mut address_list: Vec<String> = Vec::new();
for s_address in all_address { address_list.push(s_address.address); }
return address_list.contains(&subaddress);
}

View file

@ -114,6 +114,11 @@ pub struct XmrRpcSweepAllParams {
pub address: String, pub address: String,
} }
#[derive(Deserialize, Serialize, Debug)]
pub struct XmrRpcCreateAddressParams {
pub account_index: u8,
}
// requests // requests
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
pub struct XmrRpcValidateAddressRequest { pub struct XmrRpcValidateAddressRequest {
@ -242,6 +247,14 @@ pub struct XmrRpcSweepAllRequest {
pub params: XmrRpcSweepAllParams, pub params: XmrRpcSweepAllParams,
} }
#[derive(Deserialize, Serialize, Debug)]
pub struct XmrRpcCreateAddressRequest {
pub jsonrpc: String,
pub id: String,
pub method: String,
pub params: XmrRpcCreateAddressParams,
}
// results // results
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct XmrRpcValidateAddressResult { pub struct XmrRpcValidateAddressResult {
@ -295,13 +308,13 @@ pub struct XmrRpcSignMultisigResult {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct SubAddressInfo { pub struct SubAddressInfo {
pub account_index: u8, pub account_index: u64,
pub address_index: u8, pub address_index: u64,
pub address: String, pub address: String,
pub balance: u128, pub balance: u128,
pub unlocked_balance: u128, pub unlocked_balance: u128,
pub label: String, pub label: String,
pub num_unspent_outputs: u8, pub num_unspent_outputs: u64,
pub time_to_unlock: u128, pub time_to_unlock: u128,
pub blocks_to_unlock: u128, pub blocks_to_unlock: u128,
} }
@ -309,7 +322,7 @@ pub struct SubAddressInfo {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct Address { pub struct Address {
pub address: String, pub address: String,
pub address_index: u8, pub address_index: u64,
pub label: String, pub label: String,
pub used: bool, pub used: bool,
} }
@ -412,6 +425,14 @@ pub struct XmrRpcSweepAllResult {
pub weight_list: Vec<u128>, pub weight_list: Vec<u128>,
} }
#[derive(Deserialize, Debug)]
pub struct XmrRpcCreateAddressResult {
pub address: String,
pub address_index: u64,
pub address_indices: Vec<u64>,
pub addresses: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct XmrDaemonGetInfoResult { pub struct XmrDaemonGetInfoResult {
pub adjusted_time: u64, pub adjusted_time: u64,
@ -786,6 +807,24 @@ impl Default for XmrRpcSweepAllResponse {
} }
} }
} }
#[derive(Deserialize, Debug)]
pub struct XmrRpcCreateAddressResponse {
pub result: XmrRpcCreateAddressResult,
}
impl Default for XmrRpcCreateAddressResponse {
fn default() -> Self {
XmrRpcCreateAddressResponse {
result: XmrRpcCreateAddressResult {
address: utils::empty_string(),
address_index: 0,
address_indices: Vec::new(),
addresses: Vec::new(),
}
}
}
}
// END XMR Structs // END XMR Structs

View file

@ -654,7 +654,7 @@ fn send_payment_req(
let ptxp_hash = String::from(&transfer.result.tx_hash); let ptxp_hash = String::from(&transfer.result.tx_hash);
let ftxp_hash = String::from(&transfer.result.tx_hash); let ftxp_hash = String::from(&transfer.result.tx_hash);
let ptxp: proof::TxProof = proof::TxProof { let ptxp: proof::TxProof = proof::TxProof {
address: ptxp_address, subaddress: ptxp_address,
confirmations: 0, confirmations: 0,
hash: ptxp_hash, hash: ptxp_hash,
message: utils::empty_string(), message: utils::empty_string(),
@ -664,7 +664,7 @@ fn send_payment_req(
let get_txp: reqres::XmrRpcGetTxProofResponse = monero::get_tx_proof(ptxp).await; let get_txp: reqres::XmrRpcGetTxProofResponse = monero::get_tx_proof(ptxp).await;
// use the signature to create the FINALIZED transaction proof // use the signature to create the FINALIZED transaction proof
let ftxp: proof::TxProof = proof::TxProof { let ftxp: proof::TxProof = proof::TxProof {
address: ftxp_address, subaddress: ftxp_address,
confirmations: 0, confirmations: 0,
hash: ftxp_hash, hash: ftxp_hash,
message: utils::empty_string(), message: utils::empty_string(),