Add init_api_secure function (#206)

* adding initial version of init_secure_api

* rustfmt

* fix ECDH algo

* rustfmt

* trying to figure out best way of doing encryption

* refactor secure requests and responses into json-rpc responses, with base64 payload for encrypted messages

* rustfmt

* return proper errors from encrypted api, include tests covering encrypted API error cases

* rustfmt

* add test for normal error (unencrypted)

* rustfmt

* change ports for test, add foreign listener to V2 sanity tests, add ability to select owner api port via command line

* rustfmt

* turn it to 11

* explicit teardown after rpc tests

* update tests with explicit teardowns

* update tests to perform explicit teardown

* fix warnings, ensure all tests teardown

* log output to diagnose CI windows build failures

* disable owner api doctests on windows

* rustfmt
This commit is contained in:
Yeastplume
2019-08-19 13:05:21 +01:00
committed by GitHub
parent 62d976f9ef
commit a58cae651e
33 changed files with 1224 additions and 294 deletions
+119 -7
View File
@@ -14,6 +14,7 @@
//! Common functions for wallet integration tests
extern crate grin_wallet;
use grin_wallet_config as config;
use grin_wallet_impls::test_framework::LocalWalletClient;
use grin_wallet_util::grin_util as util;
@@ -23,12 +24,14 @@ use std::sync::Arc;
use std::{env, fs};
use util::{Mutex, ZeroingString};
use grin_wallet_api::{EncryptedRequest, EncryptedResponse};
use grin_wallet_config::{GlobalWalletConfig, WalletConfig, GRIN_WALLET_DIR};
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl};
use grin_wallet_libwallet::{WalletInfo, WalletInst};
use grin_wallet_util::grin_core::global::{self, ChainTypes};
use grin_wallet_util::grin_keychain::ExtKeychain;
use util::secp::key::SecretKey;
use grin_wallet_util::grin_util::{from_hex, static_secp_instance};
use util::secp::key::{PublicKey, SecretKey};
use grin_wallet::cmd::wallet_args;
use grin_wallet_util::grin_api as api;
@@ -100,7 +103,7 @@ macro_rules! setup_proxy {
};
}
fn clean_output_dir(test_dir: &str) {
pub fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
}
@@ -192,7 +195,7 @@ pub fn instantiate_wallet(
Arc<
Mutex<
Box<
WalletInst<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
LocalWalletClient,
@@ -208,7 +211,7 @@ pub fn instantiate_wallet(
wallet_config.chain_type = None;
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(node_client).unwrap())
as Box<
WalletInst<
dyn WalletInst<
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
@@ -247,6 +250,25 @@ pub fn execute_command(
wallet_args::wallet_command(&args, config.clone(), client.clone(), true)
}
// as above, but without necessarily setting up the wallet
#[allow(dead_code)]
pub fn execute_command_no_setup(
app: &App,
test_dir: &str,
wallet_name: &str,
client: &LocalWalletClient,
arg_vec: Vec<&str>,
) -> Result<String, grin_wallet_controller::Error> {
let args = app.clone().get_matches_from(arg_vec);
let _ = get_wallet_subcommand(test_dir, wallet_name, args.clone());
let config = config::initial_setup_wallet(&ChainTypes::AutomatedTesting, None).unwrap();
let mut wallet_config = config.members.unwrap().wallet.clone();
wallet_config.chain_type = None;
wallet_config.api_secret_path = None;
wallet_config.node_api_secret_path = None;
wallet_args::wallet_command(&args, wallet_config, client.clone(), true)
}
pub fn post<IN>(url: &Url, api_secret: Option<String>, input: &IN) -> Result<String, api::Error>
where
IN: Serialize,
@@ -257,6 +279,7 @@ where
Ok(res)
}
#[allow(dead_code)]
pub fn send_request<OUT>(
id: u64,
dest: &str,
@@ -266,19 +289,30 @@ where
OUT: DeserializeOwned,
{
let url = Url::parse(dest).unwrap();
let req: Value = serde_json::from_str(req).unwrap();
let res: String = post(&url, None, &req).map_err(|e| {
let req_val: Value = serde_json::from_str(req).unwrap();
let res = post(&url, None, &req_val).map_err(|e| {
let err_string = format!("{}", e);
println!("{}", err_string);
thread::sleep(Duration::from_millis(200));
e
})?;
let res_val: Value = serde_json::from_str(&res).unwrap();
// encryption error, just return the string
if res_val["error"] != json!(null) {
return Ok(Err(WalletAPIReturnError {
message: res_val["error"]["message"].as_str().unwrap().to_owned(),
code: res_val["error"]["code"].as_i64().unwrap() as i32,
}));
}
let res = serde_json::from_str(&res).unwrap();
let res = easy_jsonrpc::Response::from_json_response(res).unwrap();
let res = res.outputs.get(&id).unwrap().clone().unwrap();
if res["Err"] != json!(null) {
Ok(Err(WalletAPIReturnError {
message: res["Err"].as_str().unwrap().to_owned(),
code: res["error"]["code"].as_i64().unwrap() as i32,
}))
} else {
// deserialize result into expected type
@@ -287,10 +321,88 @@ where
}
}
#[allow(dead_code)]
pub fn send_request_enc<OUT>(
sec_req_id: u32,
internal_request_id: u32,
dest: &str,
req: &str,
shared_key: &SecretKey,
) -> Result<Result<OUT, WalletAPIReturnError>, api::Error>
where
OUT: DeserializeOwned,
{
let url = Url::parse(dest).unwrap();
let req_val: Value = serde_json::from_str(req).unwrap();
let req = EncryptedRequest::from_json(sec_req_id, &req_val, &shared_key).unwrap();
let res = post(&url, None, &req).map_err(|e| {
let err_string = format!("{}", e);
println!("{}", err_string);
thread::sleep(Duration::from_millis(200));
e
})?;
let res_val: Value = serde_json::from_str(&res).unwrap();
// encryption error, just return the string
if res_val["error"] != json!(null) {
return Ok(Err(WalletAPIReturnError {
message: res_val["error"]["message"].as_str().unwrap().to_owned(),
code: res_val["error"]["code"].as_i64().unwrap() as i32,
}));
}
let enc_resp: EncryptedResponse = serde_json::from_str(&res).unwrap();
let res = enc_resp.decrypt(shared_key).unwrap();
if res["error"] != json!(null) {
return Ok(Err(WalletAPIReturnError {
message: res["error"]["message"].as_str().unwrap().to_owned(),
code: res["error"]["code"].as_i64().unwrap() as i32,
}));
}
let res = easy_jsonrpc::Response::from_json_response(res).unwrap();
let res = res
.outputs
.get(&(internal_request_id as u64))
.unwrap()
.clone()
.unwrap();
if res["Err"] != json!(null) {
Ok(Err(WalletAPIReturnError {
message: res["Err"].as_str().unwrap().to_owned(),
code: res_val["error"]["code"].as_i64().unwrap() as i32,
}))
} else {
// deserialize result into expected type
let value: OUT = serde_json::from_value(res["Ok"].clone()).unwrap();
Ok(Ok(value))
}
}
#[allow(dead_code)]
pub fn derive_ecdh_key(sec_key_str: &str, other_pubkey: &PublicKey) -> SecretKey {
let sec_key_bytes = from_hex(sec_key_str.to_owned()).unwrap();
let sec_key = {
let secp_inst = static_secp_instance();
let secp = secp_inst.lock();
SecretKey::from_slice(&secp, &sec_key_bytes).unwrap()
};
let secp_inst = static_secp_instance();
let secp = secp_inst.lock();
let mut shared_pubkey = other_pubkey.clone();
shared_pubkey.mul_assign(&secp, &sec_key).unwrap();
let x_coord = shared_pubkey.serialize_vec(&secp, true);
SecretKey::from_slice(&secp, &x_coord[1..]).unwrap()
}
// Types to make working with json responses easier
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WalletAPIReturnError {
message: String,
pub message: String,
pub code: i32,
}
#[derive(Clone, Debug, Serialize, Deserialize)]