diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index 11dfb865f..83b1c36ac 100644 --- a/masq_lib/src/shared_schema.rs +++ b/masq_lib/src/shared_schema.rs @@ -77,6 +77,17 @@ pub const NEIGHBORS_HELP: &str = "One or more Node descriptors for running Nodes if you don't specify a neighbor, your Node will start without being connected to any MASQ \ Network, although other Nodes will be able to connect to yours if they know your Node's descriptor. \ --neighbors is meaningless in --neighborhood-mode zero-hop."; +pub const NEW_PUBLIC_KEY_HELP: &str = "Whenever you start it, the Node will try to use the same public key \ + it used last time. That's '--new-public-key off'. If you want it to select a new public key when it \ + starts, then specify '--new-public-key on', and you'll get a different one this time...which it will \ + reuse next time unless you specify '--new-public-key on' again.\n\n\ + You should be careful about restarting your Node with the same public key too quickly. If your new \ + Node tries to join the Network before the Network has forgotten your old Node, every Node you try \ + to connect to will ignore you.\n\n\ + There are some conditions under which the Node cannot use the same public key it used last time: \ + for example, if there was no last time, or if you don't specify a `--db-password`. If you're in \ + one of these situations and you demand the old public key with `--new-public-key off`, the Node \ + will refuse to start."; // generated valid encoded keys for future needs // UJNoZW5p/PDVqEjpr3b+8jZ/93yPG8i5dOAgE1bhK+A @@ -463,6 +474,14 @@ pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> { .min_values(0) .help(NEIGHBORS_HELP), ) + .arg( + Arg::with_name("new-public-key") + .long("new-public-key") + .value_name("NEW-PUBLIC-KEY") + .takes_value(true) + .possible_values(&["on", "off"]) + .help(NEW_PUBLIC_KEY_HELP), + ) .arg(real_user_arg()) .arg( Arg::with_name("scans") diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index 25e20aa7e..9c9f05a04 100644 --- a/node/src/actor_system_factory.rs +++ b/node/src/actor_system_factory.rs @@ -1140,6 +1140,7 @@ mod tests { data_directory: PathBuf::new(), node_descriptor: NodeDescriptor::default(), mapping_protocol_opt: None, + new_public_key_opt: None, real_user: RealUser::null(), neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::Standard( @@ -1211,6 +1212,7 @@ mod tests { cryptde_pair: CRYPTDE_PAIR.clone(), mapping_protocol_opt: Some(Igdp), real_user: RealUser::null(), + new_public_key_opt: None, neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::Standard( NodeAddr::new(&IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), &[1234, 2345]), @@ -1507,6 +1509,7 @@ mod tests { cryptde_pair: CRYPTDE_PAIR.clone(), mapping_protocol_opt: None, real_user: RealUser::null(), + new_public_key_opt: None, neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::ConsumeOnly(vec![]), min_hops: MIN_HOPS_FOR_TEST, @@ -1690,6 +1693,7 @@ mod tests { cryptde_pair: CRYPTDE_PAIR.clone(), mapping_protocol_opt: None, real_user: RealUser::null(), + new_public_key_opt: None, neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::Standard( NodeAddr::new(&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), &[]), diff --git a/node/src/bootstrapper.rs b/node/src/bootstrapper.rs index b70bc208c..351e61699 100644 --- a/node/src/bootstrapper.rs +++ b/node/src/bootstrapper.rs @@ -361,6 +361,7 @@ pub struct BootstrapperConfig { pub node_descriptor: NodeDescriptor, pub cryptde_pair: CryptDEPair, pub mapping_protocol_opt: Option, + pub new_public_key_opt: Option, pub real_user: RealUser, pub payment_thresholds_opt: Option, @@ -406,6 +407,7 @@ impl BootstrapperConfig { Box::new(CryptDEReal::disabled()), ), mapping_protocol_opt: None, + new_public_key_opt: None, real_user: RealUser::new(None, None, None), payment_thresholds_opt: Default::default(), diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index e12713cda..df12bbb28 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -1042,6 +1042,26 @@ impl ValueRetriever for Neighbors { } } +struct NewPublicKey {} +impl ValueRetriever for NewPublicKey { + fn value_name(&self) -> &'static str { + "new-public-key" + } + + fn computed_default( + &self, + _bootstrapper_config: &BootstrapperConfig, + _persistent_config: &dyn PersistentConfiguration, + _db_password_opt: &Option, + ) -> Option<(String, UiSetupResponseValueStatus)> { + Some(("".to_string(), Blank)) + } + + fn is_required(&self, _params: &SetupCluster) -> bool { + false + } +} + struct PaymentThresholds {} impl ValueRetriever for PaymentThresholds { fn value_name(&self) -> &'static str { @@ -1213,6 +1233,7 @@ fn value_retrievers(dirs_wrapper: &dyn DirsWrapper) -> Vec for NodeConfiguratorStandardUnprivileg configure_database(&unprivileged_config, persistent_config.as_mut())?; let cryptde_pair = if multi_config.occurrences_of("fake-public-key") == 0 { configure_cryptdes( + None, persistent_config.as_mut(), &unprivileged_config.db_password_opt, ) @@ -300,6 +301,15 @@ pub fn privileged_parse_args( None => vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 53)], }; + privileged_config.new_public_key_opt = match value_m!(multi_config, "new-public-key", String) { + Some(value) => match value.as_str() { + "on" => Some(true), + "off" => Some(false), + _ => panic!("Bad clap validation for new-public-key: {}", value), + }, + None => None, + }; + privileged_config.log_level = value_m!(multi_config, "log-level", LevelFilter).unwrap_or(LevelFilter::Warn); @@ -345,17 +355,30 @@ fn configure_database( } fn configure_cryptdes( + new_public_key: Option, persistent_config: &mut dyn PersistentConfiguration, db_password_opt: &Option, ) -> CryptDEPair { let cryptde_pair = if let Some(db_password) = db_password_opt { let chain = Chain::from(persistent_config.chain_name().as_str()); - let main_result = persistent_config.cryptde(db_password); + let main_result = match new_public_key { + None | Some(false) => persistent_config.cryptde(db_password), + Some(true) => { + let main_cryptde: Box = Box::new(CryptDEReal::new(chain)); + persistent_config + .set_cryptde(main_cryptde.as_ref(), db_password) + .expect("Failed to set cryptde"); + Ok(Some(main_cryptde)) + } + }; match main_result { Ok(Some(last_main_cryptde)) => { CryptDEPair::new(last_main_cryptde, Box::new(CryptDEReal::new(chain))) } Ok(None) => { + if new_public_key == Some(false) { + panic!("--new-public-key off: Cannot reestablish old public key: no old public key available"); + } let main_cryptde: Box = Box::new(CryptDEReal::new(chain)); persistent_config .set_cryptde(main_cryptde.as_ref(), db_password) @@ -366,6 +389,9 @@ fn configure_cryptdes( Err(e) => panic!("Could not read last cryptde from database: {:?}", e), } } else { + if new_public_key == Some(false) { + panic!("--new-public-key off: Cannot reestablish old public key: no --db-password provided"); + } let chain = Chain::from(persistent_config.chain_name().as_str()); let main_cryptde: Box = Box::new(CryptDEReal::new(chain)); let alias_cryptde: Box = Box::new(CryptDEReal::new(chain)); @@ -640,7 +666,17 @@ mod tests { } #[test] - fn configure_cryptdes_handles_missing_password_with_uninitialized_cryptdes() { + #[should_panic( + expected = "--new-public-key off: Cannot reestablish old public key: no --db-password provided" + )] + fn configure_cryptdes_handles_missing_password_with_uninitialized_cryptdes_and_npk_off() { + let mut persistent_config = PersistentConfigurationMock::new(); + + configure_cryptdes(Some(false), &mut persistent_config, &None); + } + + #[test] + fn configure_cryptdes_handles_missing_password_with_uninitialized_cryptdes_and_npk_on() { let cryptde_params_arc = Arc::new(Mutex::new(vec![])); let set_cryptde_params_arc = Arc::new(Mutex::new(vec![])); let mut persistent_config = PersistentConfigurationMock::new() @@ -648,7 +684,7 @@ mod tests { .set_cryptde_params(&set_cryptde_params_arc) .chain_name_result(TEST_DEFAULT_CHAIN.to_string()); - let _result = configure_cryptdes(&mut persistent_config, &None); + let _result = configure_cryptdes(Some(true), &mut persistent_config, &None); let cryptde_params = cryptde_params_arc.lock().unwrap(); assert_eq!(cryptde_params.len(), 0); @@ -657,7 +693,7 @@ mod tests { } #[test] - fn configure_cryptdes_handles_missing_last_cryptde() { + fn configure_cryptdes_handles_missing_last_cryptde_with_no_npk_param() { let cryptde_params_arc = Arc::new(Mutex::new(vec![])); let set_cryptde_params_arc = Arc::new(Mutex::new(vec![])); let mut persistent_config = PersistentConfigurationMock::new() @@ -667,7 +703,11 @@ mod tests { .set_cryptde_params(&set_cryptde_params_arc) .set_cryptde_result(Ok(())); - let result = configure_cryptdes(&mut persistent_config, &Some("db_password".to_string())); + let result = configure_cryptdes( + None, + &mut persistent_config, + &Some("db_password".to_string()), + ); let cryptde_params = cryptde_params_arc.lock().unwrap(); assert_eq!(*cryptde_params, vec!["db_password".to_string()]); @@ -677,6 +717,44 @@ mod tests { assert_eq!(call.1, "db_password".to_string()); } + #[test] + #[should_panic( + expected = "--new-public-key off: Cannot reestablish old public key: no old public key available" + )] + fn configure_cryptdes_handles_missing_last_cryptde_with_npk_off() { + let cryptde_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = PersistentConfigurationMock::new() + .cryptde_params(&cryptde_params_arc) + .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) + .cryptde_result(Ok(None)); + + configure_cryptdes( + Some(false), + &mut persistent_config, + &Some("db_password".to_string()), + ); + } + + #[test] + fn configure_cryptdes_handles_missing_last_cryptde_with_npk_on() { + let set_cryptde_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = PersistentConfigurationMock::new() + .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) + .set_cryptde_params(&set_cryptde_params_arc) + .set_cryptde_result(Ok(())); + + let result = configure_cryptdes( + Some(true), + &mut persistent_config, + &Some("db_password".to_string()), + ); + + let set_cryptde_params = set_cryptde_params_arc.lock().unwrap(); + let call = &set_cryptde_params[0]; + assert_eq!(call.0.public_key(), result.main.public_key()); + assert_eq!(call.1, "db_password".to_string()); + } + #[test] #[should_panic(expected = "Could not read last cryptde from database: NotPresent")] fn configure_cryptdes_panics_if_database_throws_error() { @@ -685,11 +763,39 @@ mod tests { .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) .cryptde_result(Err(PersistentConfigError::NotPresent)); - let _ = configure_cryptdes(&mut persistent_config, &Some("db_password".to_string())); + let _ = configure_cryptdes( + None, + &mut persistent_config, + &Some("db_password".to_string()), + ); + } + + #[test] + fn configure_cryptdes_handles_populated_database_with_no_npk_param() { + let _guard = EnvironmentGuard::new(); + let stored_main_cryptde_box = Box::new(CryptDEReal::new(TEST_DEFAULT_CHAIN)); + let cryptde_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = PersistentConfigurationMock::new() + .cryptde_params(&cryptde_params_arc) + .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) + .cryptde_result(Ok(Some(stored_main_cryptde_box.dup()))); + + let result = configure_cryptdes( + None, + &mut persistent_config, + &Some("db_password".to_string()), + ); + + assert_eq!( + result.main.public_key(), + stored_main_cryptde_box.public_key() + ); + let cryptde_params = cryptde_params_arc.lock().unwrap(); + assert_eq!(*cryptde_params, vec!["db_password".to_string()]); } #[test] - fn configure_cryptdes_handles_populated_database() { + fn configure_cryptdes_handles_populated_database_with_npk_off() { let _guard = EnvironmentGuard::new(); let stored_main_cryptde_box = Box::new(CryptDEReal::new(TEST_DEFAULT_CHAIN)); let cryptde_params_arc = Arc::new(Mutex::new(vec![])); @@ -698,7 +804,11 @@ mod tests { .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) .cryptde_result(Ok(Some(stored_main_cryptde_box.dup()))); - let result = configure_cryptdes(&mut persistent_config, &Some("db_password".to_string())); + let result = configure_cryptdes( + Some(false), + &mut persistent_config, + &Some("db_password".to_string()), + ); assert_eq!( result.main.public_key(), @@ -708,6 +818,35 @@ mod tests { assert_eq!(*cryptde_params, vec!["db_password".to_string()]); } + #[test] + fn configure_cryptdes_handles_populated_database_with_npk_on() { + let _guard = EnvironmentGuard::new(); + let stored_main_cryptde_box = Box::new(CryptDEReal::new(TEST_DEFAULT_CHAIN)); + let set_cryptde_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = PersistentConfigurationMock::new() + .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) + .set_cryptde_params(&set_cryptde_params_arc) + .set_cryptde_result(Ok(())); + + let result = configure_cryptdes( + Some(true), + &mut persistent_config, + &Some("db_password".to_string()), + ); + + assert_ne!( + result.main.public_key(), + stored_main_cryptde_box.public_key() + ); + let set_cryptde_params = set_cryptde_params_arc.lock().unwrap(); + assert_eq!( + set_cryptde_params[0].0.public_key(), + result.main.public_key() + ); + assert_eq!(set_cryptde_params[0].1, "db_password".to_string()); + assert_eq!(set_cryptde_params.len(), 1); + } + fn make_default_cli_params() -> ArgsBuilder { ArgsBuilder::new().param("--ip", "1.2.3.4") } @@ -845,6 +984,7 @@ mod tests { "--consuming-private-key", "ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01", ) + .param("--new-public-key", "on") .param("--real-user", "999:999:/home/booga") .param("--chain", "polygon-amoy"); let mut config = BootstrapperConfig::new(); @@ -878,12 +1018,28 @@ mod tests { None, ); assert_eq!(config.data_directory, home_dir); + assert_eq!(config.new_public_key_opt, Some(true)); assert_eq!( config.real_user, RealUser::new(Some(999), Some(999), Some(PathBuf::from("/home/booga"))) ); } + #[test] + fn privileged_parse_args_works_with_on_off_parameters() { + let _guard = EnvironmentGuard::new(); + running_test(); + let args = ArgsBuilder::new().param("--new-public-key", "on"); + let mut config = BootstrapperConfig::new(); + let vcls: Vec> = + vec![Box::new(CommandLineVcl::new(args.into()))]; + let multi_config = make_new_multi_config(&app_node(), vcls).unwrap(); + + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); + + assert_eq!(config.new_public_key_opt, Some(true)); + } + #[test] fn privileged_parse_args_creates_configuration_with_defaults() { let _guard = EnvironmentGuard::new(); @@ -903,6 +1059,7 @@ mod tests { ); assert_eq!(config.crash_point, CrashPoint::None); assert_eq!(config.ui_gateway_config.ui_port, DEFAULT_UI_PORT); + assert_eq!(config.new_public_key_opt, None); assert_eq!( config.real_user, RealUser::new(None, None, None).populate(&DirsWrapperReal::default()) @@ -958,6 +1115,7 @@ mod tests { ); assert_eq!(config.crash_point, CrashPoint::None); assert_eq!(config.ui_gateway_config.ui_port, DEFAULT_UI_PORT); + assert_eq!(config.new_public_key_opt, None); assert_eq!( config.real_user, RealUser::new(None, None, None).populate(&DirsWrapperReal::default()) @@ -1018,6 +1176,9 @@ mod tests { config_file .write_all(b"neighborhood-mode = \"zero-hop\"\n") .unwrap(); + config_file + .write_all(b"new-public-key = \"off\"\n") + .unwrap(); config_file .write_all(b"payment-thresholds = \"3333|55|33|646|999|999\"\n") .unwrap(); diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index 8326e0157..0dcdecda4 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -821,7 +821,11 @@ pub mod tests { ]), ); - assert!(result.is_ok()); + assert!( + result.is_ok(), + "Result should have been Ok(()), but was: {:?}", + result + ); let real_user = RealUser::new(Some(123), Some(456), Some("/home/alice".into())); let chown_params = chown_params_arc.lock().unwrap(); assert_eq!(