Generate RSA key for instance actor automatically on the first run

This commit is contained in:
silverpill 2022-04-30 19:26:09 +00:00
parent a1af46f566
commit fac0172159
8 changed files with 64 additions and 33 deletions

View file

@ -69,13 +69,7 @@ psql -h localhost -p 55432 -U mitra mitra
### Run web service ### Run web service
Generate instance key: Create config file, adjust settings if needed:
```
cargo run --bin mitractl generate-rsa-key
```
Create config file, set `instance_rsa_key`, adjust other settings if needed:
``` ```
cp config.yaml.example config.yaml cp config.yaml.example config.yaml

View file

@ -11,8 +11,6 @@ instance_short_description: my fedi instance
# Long description can contain markdown syntax # Long description can contain markdown syntax
instance_description: my fedi instance instance_description: my fedi instance
# RSA private key can be generated with `mitractl generate-rsa-key`
instance_rsa_key: null
registrations_open: false registrations_open: false
# EIP-4361 login message # EIP-4361 login message
login_message: 'Do not sign this message on other sites!' login_message: 'Do not sign this message on other sites!'

View file

@ -357,7 +357,7 @@ mod tests {
#[test] #[test]
fn test_local_actor() { fn test_local_actor() {
let private_key = generate_weak_private_key().unwrap(); let private_key = generate_weak_private_key().unwrap();
let private_key_pem = serialize_private_key(private_key).unwrap(); let private_key_pem = serialize_private_key(&private_key).unwrap();
let profile = DbActorProfile { let profile = DbActorProfile {
username: "testuser".to_string(), username: "testuser".to_string(),
..Default::default() ..Default::default()

View file

@ -42,7 +42,7 @@ struct GenerateRsaKey;
impl GenerateRsaKey { impl GenerateRsaKey {
fn execute(&self) -> () { fn execute(&self) -> () {
let private_key = generate_private_key().unwrap(); let private_key = generate_private_key().unwrap();
let private_key_str = serialize_private_key(private_key).unwrap(); let private_key_str = serialize_private_key(&private_key).unwrap();
println!("{}", private_key_str); println!("{}", private_key_str);
} }
} }

View file

@ -1,4 +1,4 @@
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use log::{Level as LogLevel}; use log::{Level as LogLevel};
@ -10,7 +10,12 @@ use crate::activitypub::views::get_instance_actor_url;
use crate::errors::ConversionError; use crate::errors::ConversionError;
use crate::ethereum::utils::{parse_caip2_chain_id, ChainIdError}; use crate::ethereum::utils::{parse_caip2_chain_id, ChainIdError};
use crate::models::profiles::currencies::Currency; use crate::models::profiles::currencies::Currency;
use crate::utils::crypto::deserialize_private_key; use crate::utils::crypto::{
deserialize_private_key,
generate_private_key,
serialize_private_key,
};
use crate::utils::files::{set_file_permissions, write_file};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Environment { pub enum Environment {
@ -115,7 +120,9 @@ pub struct Config {
pub instance_title: String, pub instance_title: String,
pub instance_short_description: String, pub instance_short_description: String,
pub instance_description: String, pub instance_description: String,
instance_rsa_key: String,
#[serde(skip)]
instance_rsa_key: Option<RsaPrivateKey>,
#[serde(default)] #[serde(default)]
pub registrations_open: bool, // default is false pub registrations_open: bool, // default is false
@ -150,15 +157,11 @@ impl Config {
Ok(url) Ok(url)
} }
fn try_instance_rsa_key(&self) -> Result<RsaPrivateKey, rsa::pkcs8::Error> {
deserialize_private_key(&self.instance_rsa_key)
}
pub fn instance(&self) -> Instance { pub fn instance(&self) -> Instance {
Instance { Instance {
_url: self.try_instance_url().unwrap(), _url: self.try_instance_url().unwrap(),
_version: self.version.clone(), _version: self.version.clone(),
actor_key: self.try_instance_rsa_key().unwrap(), actor_key: self.instance_rsa_key.clone().unwrap(),
is_private: matches!(self.environment, Environment::Development), is_private: matches!(self.environment, Environment::Development),
} }
} }
@ -212,6 +215,28 @@ impl Instance {
} }
} }
/// Generates new instance RSA key or returns existing key
fn read_instance_rsa_key(storage_dir: &Path) -> RsaPrivateKey {
let private_key_path = storage_dir.join("instance_rsa_key");
if private_key_path.exists() {
let private_key_str = std::fs::read_to_string(&private_key_path)
.expect("failed to read instance RSA key");
let private_key = deserialize_private_key(&private_key_str)
.expect("failed to read instance RSA key");
private_key
} else {
let private_key = generate_private_key()
.expect("failed to generate RSA key");
let private_key_str = serialize_private_key(&private_key)
.expect("failed to serialize RSA key");
write_file(private_key_str.as_bytes(), &private_key_path)
.expect("failed to write instance RSA key");
set_file_permissions(&private_key_path, 0o600)
.expect("failed to set permissions on RSA key file");
private_key
}
}
pub fn parse_config() -> Config { pub fn parse_config() -> Config {
let env = parse_env(); let env = parse_env();
let config_yaml = std::fs::read_to_string(env.config_path) let config_yaml = std::fs::read_to_string(env.config_path)
@ -220,36 +245,38 @@ pub fn parse_config() -> Config {
.expect("invalid yaml data"); .expect("invalid yaml data");
// Override environment parameter in config if env variable is set // Override environment parameter in config if env variable is set
config.environment = env.environment.unwrap_or(config.environment); config.environment = env.environment.unwrap_or(config.environment);
// Set_version // Set version
config.version = env.crate_version; config.version = env.crate_version;
// Validate config // Validate config
if !config.storage_dir.exists() { if !config.storage_dir.exists() {
panic!("storage directory does not exist"); panic!("storage directory does not exist");
}; };
config.try_instance_url().expect("invalid instance URI");
if let Some(blockchain_config) = config.blockchain.as_ref() { if let Some(blockchain_config) = config.blockchain.as_ref() {
blockchain_config.try_ethereum_chain_id().unwrap(); blockchain_config.try_ethereum_chain_id().unwrap();
if !blockchain_config.contract_dir.exists() { if !blockchain_config.contract_dir.exists() {
panic!("contract directory does not exist"); panic!("contract directory does not exist");
}; };
}; };
config.try_instance_url().expect("invalid instance URI");
config.try_instance_rsa_key().expect("invalid instance RSA key");
if config.ipfs_api_url.is_some() != config.ipfs_gateway_url.is_some() { if config.ipfs_api_url.is_some() != config.ipfs_gateway_url.is_some() {
panic!("both ipfs_api_url and ipfs_gateway_url must be set"); panic!("both ipfs_api_url and ipfs_gateway_url must be set");
}; };
// Insert instance RSA key
config.instance_rsa_key = Some(read_instance_rsa_key(&config.storage_dir));
config config
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use rand::rngs::OsRng; use crate::utils::crypto::generate_weak_private_key;
use super::*; use super::*;
#[test] #[test]
fn test_instance_url_https_dns() { fn test_instance_url_https_dns() {
let instance_url = Url::parse("https://example.com/").unwrap(); let instance_url = Url::parse("https://example.com/").unwrap();
let instance_rsa_key = RsaPrivateKey::new(&mut OsRng, 512).unwrap(); let instance_rsa_key = generate_weak_private_key().unwrap();
let instance = Instance { let instance = Instance {
_url: instance_url, _url: instance_url,
_version: "1.0.0".to_string(), _version: "1.0.0".to_string(),
@ -265,7 +292,7 @@ mod tests {
#[test] #[test]
fn test_instance_url_http_ipv4() { fn test_instance_url_http_ipv4() {
let instance_url = Url::parse("http://1.2.3.4:3777/").unwrap(); let instance_url = Url::parse("http://1.2.3.4:3777/").unwrap();
let instance_rsa_key = RsaPrivateKey::new(&mut OsRng, 512).unwrap(); let instance_rsa_key = generate_weak_private_key().unwrap();
let instance = Instance { let instance = Instance {
_url: instance_url, _url: instance_url,
_version: "1.0.0".to_string(), _version: "1.0.0".to_string(),

View file

@ -123,7 +123,7 @@ pub async fn create_account(
Ok(Ok(private_key)) => private_key, Ok(Ok(private_key)) => private_key,
_ => return Err(HttpError::InternalError), _ => return Err(HttpError::InternalError),
}; };
let private_key_pem = serialize_private_key(private_key) let private_key_pem = serialize_private_key(&private_key)
.map_err(|_| HttpError::InternalError)?; .map_err(|_| HttpError::InternalError)?;
let AccountCreateData { username, invite_code, .. } = let AccountCreateData { username, invite_code, .. } =

View file

@ -34,7 +34,7 @@ pub fn generate_weak_private_key() -> Result<RsaPrivateKey, rsa::errors::Error>
} }
pub fn serialize_private_key( pub fn serialize_private_key(
private_key: RsaPrivateKey, private_key: &RsaPrivateKey,
) -> Result<String, rsa::pkcs8::Error> { ) -> Result<String, rsa::pkcs8::Error> {
private_key.to_pkcs8_pem().map(|val| val.to_string()) private_key.to_pkcs8_pem().map(|val| val.to_string())
} }

View file

@ -1,5 +1,11 @@
use std::fs::{remove_file, File}; use std::fs::{
remove_file,
set_permissions,
File,
Permissions,
};
use std::io::prelude::*; use std::io::prelude::*;
use std::os::unix::fs::PermissionsExt;
use std::path::Path; use std::path::Path;
use mime_guess::get_mime_extensions_str; use mime_guess::get_mime_extensions_str;
@ -36,9 +42,15 @@ fn get_file_name(data: &[u8], media_type: Option<&str>) -> String {
file_name file_name
} }
fn write_file(data: Vec<u8>, file_path: &Path) -> Result<(), FileError> { pub fn write_file(data: &[u8], file_path: &Path) -> Result<(), FileError> {
let mut file = File::create(file_path)?; let mut file = File::create(file_path)?;
file.write_all(&data)?; file.write_all(data)?;
Ok(())
}
pub fn set_file_permissions(file_path: &Path, mode: u32) -> Result<(), FileError> {
let permissions = Permissions::from_mode(mode);
set_permissions(file_path, permissions)?;
Ok(()) Ok(())
} }
@ -49,7 +61,7 @@ pub fn save_file(
let media_type = sniff_media_type(&data); let media_type = sniff_media_type(&data);
let file_name = get_file_name(&data, media_type.as_deref()); let file_name = get_file_name(&data, media_type.as_deref());
let file_path = output_dir.join(&file_name); let file_path = output_dir.join(&file_name);
write_file(data, &file_path)?; write_file(&data, &file_path)?;
Ok((file_name, media_type)) Ok((file_name, media_type))
} }
@ -61,7 +73,7 @@ pub fn save_b64_file(
let media_type = sniff_media_type(&data); let media_type = sniff_media_type(&data);
let file_name = get_file_name(&data, media_type.as_deref()); let file_name = get_file_name(&data, media_type.as_deref());
let file_path = output_dir.join(&file_name); let file_path = output_dir.join(&file_name);
write_file(data, &file_path)?; write_file(&data, &file_path)?;
Ok((file_name, media_type)) Ok((file_name, media_type))
} }
@ -78,7 +90,7 @@ pub fn save_validated_b64_file(
} }
let file_name = get_file_name(&data, Some(&media_type)); let file_name = get_file_name(&data, Some(&media_type));
let file_path = output_dir.join(&file_name); let file_path = output_dir.join(&file_name);
write_file(data, &file_path)?; write_file(&data, &file_path)?;
Ok((file_name, media_type)) Ok((file_name, media_type))
} }