Start to implement devices enrollment
This commit is contained in:
		
							
								
								
									
										15
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -598,9 +598,11 @@ dependencies = [
 | 
				
			|||||||
 "openssl-sys",
 | 
					 "openssl-sys",
 | 
				
			||||||
 "rand",
 | 
					 "rand",
 | 
				
			||||||
 "reqwest",
 | 
					 "reqwest",
 | 
				
			||||||
 | 
					 "semver",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 "serde_json",
 | 
					 "serde_json",
 | 
				
			||||||
 "thiserror",
 | 
					 "thiserror",
 | 
				
			||||||
 | 
					 "uuid",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@@ -1828,6 +1830,9 @@ name = "semver"
 | 
				
			|||||||
version = "1.0.23"
 | 
					version = "1.0.23"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
 | 
					checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "serde"
 | 
					name = "serde"
 | 
				
			||||||
@@ -2238,6 +2243,16 @@ version = "0.2.2"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 | 
					checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "uuid"
 | 
				
			||||||
 | 
					version = "1.9.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "getrandom",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "vcpkg"
 | 
					name = "vcpkg"
 | 
				
			||||||
version = "0.2.15"
 | 
					version = "0.2.15"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,4 +26,6 @@ actix-identity = "0.7.1"
 | 
				
			|||||||
actix-session = { version = "0.9.0", features = ["cookie-session"] }
 | 
					actix-session = { version = "0.9.0", features = ["cookie-session"] }
 | 
				
			||||||
actix-cors = "0.7.0"
 | 
					actix-cors = "0.7.0"
 | 
				
			||||||
actix-remote-ip = "0.1.0"
 | 
					actix-remote-ip = "0.1.0"
 | 
				
			||||||
futures-util = "0.3.30"
 | 
					futures-util = "0.3.30"
 | 
				
			||||||
 | 
					uuid = { version = "1.9.1", features = ["v4", "serde"] }
 | 
				
			||||||
 | 
					semver = { version = "1.0.23", features = ["serde"] }
 | 
				
			||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					use crate::devices::device::DeviceId;
 | 
				
			||||||
use clap::{Parser, Subcommand};
 | 
					use clap::{Parser, Subcommand};
 | 
				
			||||||
use std::path::{Path, PathBuf};
 | 
					use std::path::{Path, PathBuf};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -154,7 +155,7 @@ impl AppConfig {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Get PKI root CA cert path
 | 
					    /// Get PKI root CA cert path
 | 
				
			||||||
    pub fn root_ca_cert_path(&self) -> PathBuf {
 | 
					    pub fn root_ca_cert_path(&self) -> PathBuf {
 | 
				
			||||||
        self.pki_path().join("root_ca.pem")
 | 
					        self.pki_path().join("root_ca.crt")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Get PKI root CA CRL path
 | 
					    /// Get PKI root CA CRL path
 | 
				
			||||||
@@ -169,7 +170,7 @@ impl AppConfig {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Get PKI web CA cert path
 | 
					    /// Get PKI web CA cert path
 | 
				
			||||||
    pub fn web_ca_cert_path(&self) -> PathBuf {
 | 
					    pub fn web_ca_cert_path(&self) -> PathBuf {
 | 
				
			||||||
        self.pki_path().join("web_ca.pem")
 | 
					        self.pki_path().join("web_ca.crt")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Get PKI web CA CRL path
 | 
					    /// Get PKI web CA CRL path
 | 
				
			||||||
@@ -184,7 +185,7 @@ impl AppConfig {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Get PKI devices CA cert path
 | 
					    /// Get PKI devices CA cert path
 | 
				
			||||||
    pub fn devices_ca_cert_path(&self) -> PathBuf {
 | 
					    pub fn devices_ca_cert_path(&self) -> PathBuf {
 | 
				
			||||||
        self.pki_path().join("devices_ca.pem")
 | 
					        self.pki_path().join("devices_ca.crt")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Get PKI devices CA CRL path
 | 
					    /// Get PKI devices CA CRL path
 | 
				
			||||||
@@ -199,13 +200,33 @@ impl AppConfig {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Get PKI server cert path
 | 
					    /// Get PKI server cert path
 | 
				
			||||||
    pub fn server_cert_path(&self) -> PathBuf {
 | 
					    pub fn server_cert_path(&self) -> PathBuf {
 | 
				
			||||||
        self.pki_path().join("server.pem")
 | 
					        self.pki_path().join("server.crt")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Get PKI server private key path
 | 
					    /// Get PKI server private key path
 | 
				
			||||||
    pub fn server_priv_key_path(&self) -> PathBuf {
 | 
					    pub fn server_priv_key_path(&self) -> PathBuf {
 | 
				
			||||||
        self.pki_path().join("server.key")
 | 
					        self.pki_path().join("server.key")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get devices configuration storage path
 | 
				
			||||||
 | 
					    pub fn devices_config_path(&self) -> PathBuf {
 | 
				
			||||||
 | 
					        self.storage_path().join("devices")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get device configuration path
 | 
				
			||||||
 | 
					    pub fn device_config_path(&self, id: &DeviceId) -> PathBuf {
 | 
				
			||||||
 | 
					        self.devices_config_path().join(format!("{}.conf", id.0))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get device certificate path
 | 
				
			||||||
 | 
					    pub fn device_cert_path(&self, id: &DeviceId) -> PathBuf {
 | 
				
			||||||
 | 
					        self.devices_config_path().join(format!("{}.crt", id.0))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get device CSR path
 | 
				
			||||||
 | 
					    pub fn device_csr_path(&self, id: &DeviceId) -> PathBuf {
 | 
				
			||||||
 | 
					        self.devices_config_path().join(format!("{}.csr", id.0))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										52
									
								
								central_backend/src/devices/device.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								central_backend/src/devices/device.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct DeviceInfo {
 | 
				
			||||||
 | 
					    reference: String,
 | 
				
			||||||
 | 
					    version: semver::Version,
 | 
				
			||||||
 | 
					    max_relays: usize,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
 | 
				
			||||||
 | 
					pub struct DeviceId(pub String);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct Device {
 | 
				
			||||||
 | 
					    /// The device ID
 | 
				
			||||||
 | 
					    id: DeviceId,
 | 
				
			||||||
 | 
					    /// Information about the device
 | 
				
			||||||
 | 
					    device: DeviceInfo,
 | 
				
			||||||
 | 
					    /// Name given to the device on the Web UI
 | 
				
			||||||
 | 
					    name: String,
 | 
				
			||||||
 | 
					    /// Description given to the device on the Web UI
 | 
				
			||||||
 | 
					    description: String,
 | 
				
			||||||
 | 
					    /// Specify whether the device is enabled or not
 | 
				
			||||||
 | 
					    enabled: bool,
 | 
				
			||||||
 | 
					    /// Specify whether the device has been validated or not
 | 
				
			||||||
 | 
					    validated: bool,
 | 
				
			||||||
 | 
					    /// Information about the relays handled by the device
 | 
				
			||||||
 | 
					    relays: Vec<DeviceRelay>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Structure that contains information about the minimal expected execution
 | 
				
			||||||
 | 
					/// time of a device
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct DailyMinRuntime {
 | 
				
			||||||
 | 
					    min_runtime: usize,
 | 
				
			||||||
 | 
					    reset_time: usize,
 | 
				
			||||||
 | 
					    catch_up_hours: Vec<usize>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
 | 
				
			||||||
 | 
					pub struct DeviceRelayID(uuid::Uuid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct DeviceRelay {
 | 
				
			||||||
 | 
					    id: DeviceRelayID,
 | 
				
			||||||
 | 
					    name: String,
 | 
				
			||||||
 | 
					    enabled: bool,
 | 
				
			||||||
 | 
					    priority: usize,
 | 
				
			||||||
 | 
					    consumption: usize,
 | 
				
			||||||
 | 
					    minimal_uptime: usize,
 | 
				
			||||||
 | 
					    minimal_downtime: usize,
 | 
				
			||||||
 | 
					    daily_runtime: Option<DailyMinRuntime>,
 | 
				
			||||||
 | 
					    depends_on: Vec<DeviceRelay>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								central_backend/src/devices/devices_list.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								central_backend/src/devices/devices_list.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
 | 
					use crate::devices::device::{Device, DeviceId};
 | 
				
			||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct DevicesList(HashMap<DeviceId, Device>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl DevicesList {
 | 
				
			||||||
 | 
					    /// Load the list of devices. This method should be called only once during the whole execution
 | 
				
			||||||
 | 
					    /// of the program
 | 
				
			||||||
 | 
					    pub fn load() -> anyhow::Result<Self> {
 | 
				
			||||||
 | 
					        let mut list = Self(HashMap::new());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for f in std::fs::read_dir(AppConfig::get().devices_config_path())? {
 | 
				
			||||||
 | 
					            let f = f?.file_name();
 | 
				
			||||||
 | 
					            let f = f.to_string_lossy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let dev_id = match f.strip_suffix(".conf") {
 | 
				
			||||||
 | 
					                Some(s) => DeviceId(s.to_string()),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // This is not a device configuration file
 | 
				
			||||||
 | 
					                None => continue,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let device_conf = std::fs::read(AppConfig::get().device_config_path(&dev_id))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            list.0.insert(dev_id, serde_json::from_slice(&device_conf)?);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(list)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Check if a device with a given id exists or not
 | 
				
			||||||
 | 
					    pub fn exists(&self, id: &DeviceId) -> bool {
 | 
				
			||||||
 | 
					        self.0.contains_key(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								central_backend/src/devices/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								central_backend/src/devices/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					pub mod device;
 | 
				
			||||||
 | 
					pub mod devices_list;
 | 
				
			||||||
@@ -1,16 +1,20 @@
 | 
				
			|||||||
use crate::constants;
 | 
					use crate::constants;
 | 
				
			||||||
 | 
					use crate::devices::device::DeviceId;
 | 
				
			||||||
 | 
					use crate::devices::devices_list::DevicesList;
 | 
				
			||||||
use crate::energy::consumption;
 | 
					use crate::energy::consumption;
 | 
				
			||||||
use crate::energy::consumption::EnergyConsumption;
 | 
					use crate::energy::consumption::EnergyConsumption;
 | 
				
			||||||
use actix::prelude::*;
 | 
					use actix::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct EnergyActor {
 | 
					pub struct EnergyActor {
 | 
				
			||||||
    curr_consumption: EnergyConsumption,
 | 
					    curr_consumption: EnergyConsumption,
 | 
				
			||||||
 | 
					    devices: DevicesList,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl EnergyActor {
 | 
					impl EnergyActor {
 | 
				
			||||||
    pub async fn new() -> anyhow::Result<Self> {
 | 
					    pub async fn new() -> anyhow::Result<Self> {
 | 
				
			||||||
        Ok(Self {
 | 
					        Ok(Self {
 | 
				
			||||||
            curr_consumption: consumption::get_curr_consumption().await?,
 | 
					            curr_consumption: consumption::get_curr_consumption().await?,
 | 
				
			||||||
 | 
					            devices: DevicesList::load()?,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -62,3 +66,16 @@ impl Handler<GetCurrConsumption> for EnergyActor {
 | 
				
			|||||||
        self.curr_consumption
 | 
					        self.curr_consumption
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get current consumption
 | 
				
			||||||
 | 
					#[derive(Message)]
 | 
				
			||||||
 | 
					#[rtype(result = "bool")]
 | 
				
			||||||
 | 
					pub struct CheckDeviceExists(DeviceId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Handler<CheckDeviceExists> for EnergyActor {
 | 
				
			||||||
 | 
					    type Result = bool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn handle(&mut self, msg: CheckDeviceExists, _ctx: &mut Context<Self>) -> Self::Result {
 | 
				
			||||||
 | 
					        self.devices.exists(&msg.0)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
pub mod app_config;
 | 
					pub mod app_config;
 | 
				
			||||||
pub mod constants;
 | 
					pub mod constants;
 | 
				
			||||||
pub mod crypto;
 | 
					pub mod crypto;
 | 
				
			||||||
 | 
					pub mod devices;
 | 
				
			||||||
pub mod energy;
 | 
					pub mod energy;
 | 
				
			||||||
pub mod server;
 | 
					pub mod server;
 | 
				
			||||||
pub mod utils;
 | 
					pub mod utils;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Initialize storage
 | 
					    // Initialize storage
 | 
				
			||||||
    create_directory_if_missing(AppConfig::get().pki_path()).unwrap();
 | 
					    create_directory_if_missing(AppConfig::get().pki_path()).unwrap();
 | 
				
			||||||
 | 
					    create_directory_if_missing(AppConfig::get().devices_config_path()).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Initialize PKI
 | 
					    // Initialize PKI
 | 
				
			||||||
    pki::initialize_root_ca().expect("Failed to initialize Root CA!");
 | 
					    pki::initialize_root_ca().expect("Failed to initialize Root CA!");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -103,6 +103,12 @@ impl From<actix_identity::error::LoginError> for HttpErr {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<openssl::error::ErrorStack> for HttpErr {
 | 
				
			||||||
 | 
					    fn from(value: openssl::error::ErrorStack) -> Self {
 | 
				
			||||||
 | 
					        HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<HttpResponse> for HttpErr {
 | 
					impl From<HttpResponse> for HttpErr {
 | 
				
			||||||
    fn from(value: HttpResponse) -> Self {
 | 
					    fn from(value: HttpResponse) -> Self {
 | 
				
			||||||
        HttpErr::HTTPResponse(value)
 | 
					        HttpErr::HTTPResponse(value)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										32
									
								
								central_backend/src/server/devices_api/mgmt_controller.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								central_backend/src/server/devices_api/mgmt_controller.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					use crate::devices::device::DeviceInfo;
 | 
				
			||||||
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
 | 
					use openssl::x509::X509Req;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct EnrollRequest {
 | 
				
			||||||
 | 
					    /// Device CSR
 | 
				
			||||||
 | 
					    csr: String,
 | 
				
			||||||
 | 
					    /// Associated device information
 | 
				
			||||||
 | 
					    info: DeviceInfo,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Enroll a new device
 | 
				
			||||||
 | 
					pub async fn enroll(req: web::Json<EnrollRequest>) -> HttpResult {
 | 
				
			||||||
 | 
					    let csr = match X509Req::from_pem(req.csr.as_bytes()) {
 | 
				
			||||||
 | 
					        Ok(r) => r,
 | 
				
			||||||
 | 
					        Err(e) => {
 | 
				
			||||||
 | 
					            log::error!("Failed to parse given CSR! {e}");
 | 
				
			||||||
 | 
					            return Ok(HttpResponse::BadRequest().json("Failed to parse given CSR!"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !csr.verify(csr.public_key()?.as_ref())? {
 | 
				
			||||||
 | 
					        log::error!("Invalid CSR signature!");
 | 
				
			||||||
 | 
					        return Ok(HttpResponse::BadRequest().json("Could not verify CSR signature!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    println!("{:#?}", &req);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json("go on"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1 +1,2 @@
 | 
				
			|||||||
 | 
					pub mod mgmt_controller;
 | 
				
			||||||
pub mod utils_controller;
 | 
					pub mod utils_controller;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ use crate::constants;
 | 
				
			|||||||
use crate::crypto::pki;
 | 
					use crate::crypto::pki;
 | 
				
			||||||
use crate::energy::energy_actor::EnergyActorAddr;
 | 
					use crate::energy::energy_actor::EnergyActorAddr;
 | 
				
			||||||
use crate::server::auth_middleware::AuthChecker;
 | 
					use crate::server::auth_middleware::AuthChecker;
 | 
				
			||||||
use crate::server::devices_api::utils_controller;
 | 
					use crate::server::devices_api::{mgmt_controller, utils_controller};
 | 
				
			||||||
use crate::server::unsecure_server::*;
 | 
					use crate::server::unsecure_server::*;
 | 
				
			||||||
use crate::server::web_api::*;
 | 
					use crate::server::web_api::*;
 | 
				
			||||||
use actix_cors::Cors;
 | 
					use actix_cors::Cors;
 | 
				
			||||||
@@ -136,6 +136,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
 | 
				
			|||||||
                "/devices_api/utils/time",
 | 
					                "/devices_api/utils/time",
 | 
				
			||||||
                web::get().to(utils_controller::curr_time),
 | 
					                web::get().to(utils_controller::curr_time),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/devices_api/mgmt/enroll",
 | 
				
			||||||
 | 
					                web::post().to(mgmt_controller::enroll),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .bind_openssl(&AppConfig::get().listen_address, builder)?
 | 
					    .bind_openssl(&AppConfig::get().listen_address, builder)?
 | 
				
			||||||
    .run()
 | 
					    .run()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ pub async fn serve_pki_file(path: web::Path<ServeCRLPath>) -> HttpResult {
 | 
				
			|||||||
    for f in std::fs::read_dir(AppConfig::get().pki_path())? {
 | 
					    for f in std::fs::read_dir(AppConfig::get().pki_path())? {
 | 
				
			||||||
        let f = f?;
 | 
					        let f = f?;
 | 
				
			||||||
        let file_name = f.file_name().to_string_lossy().to_string();
 | 
					        let file_name = f.file_name().to_string_lossy().to_string();
 | 
				
			||||||
        if !file_name.ends_with(".crl") && !file_name.ends_with(".pem") {
 | 
					        if !file_name.ends_with(".crl") && !file_name.ends_with(".crt") {
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,11 @@
 | 
				
			|||||||
# Python client
 | 
					# Python client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Reformat code:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					black src/*.py
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Run the client:
 | 
					Run the client:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
import requests
 | 
					import requests
 | 
				
			||||||
from src.args import args
 | 
					from src.args import args
 | 
				
			||||||
 | 
					import src.constants as constants
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_secure_origin() -> str:
 | 
					def get_secure_origin() -> str:
 | 
				
			||||||
    res = requests.get(f"{args.unsecure_origin}/secure_origin")
 | 
					    res = requests.get(f"{args.unsecure_origin}/secure_origin")
 | 
				
			||||||
@@ -7,8 +9,32 @@ def get_secure_origin() -> str:
 | 
				
			|||||||
        raise Exception(f"Get secure origin failed with status {res.status_code}")
 | 
					        raise Exception(f"Get secure origin failed with status {res.status_code}")
 | 
				
			||||||
    return res.text
 | 
					    return res.text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_root_ca() -> str:
 | 
					def get_root_ca() -> str:
 | 
				
			||||||
    res = requests.get(f"{args.unsecure_origin}/pki/root_ca.pem")
 | 
					    res = requests.get(f"{args.unsecure_origin}/pki/root_ca.crt")
 | 
				
			||||||
    if res.status_code < 200 or res.status_code > 299:
 | 
					    if res.status_code < 200 or res.status_code > 299:
 | 
				
			||||||
        raise Exception(f"Get root CA failed with status {res.status_code}")
 | 
					        raise Exception(f"Get root CA failed with status {res.status_code}")
 | 
				
			||||||
    return res.text
 | 
					    return res.text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def device_info():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Get device information to return with enrollment and sync requests
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        "reference": constants.DEV_REFERENCE,
 | 
				
			||||||
 | 
					        "version": constants.DEV_VERSION,
 | 
				
			||||||
 | 
					        "max_relays": len(args.relay_gpios_list),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def enroll_device(csr: str) -> str:
 | 
				
			||||||
 | 
					    res = requests.post(
 | 
				
			||||||
 | 
					        f"{args.secure_origin}/devices_api/mgmt/enroll",
 | 
				
			||||||
 | 
					        json={"csr": csr, "info": device_info()},
 | 
				
			||||||
 | 
					        verify=args.root_ca_path,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    if res.status_code < 200 or res.status_code > 299:
 | 
				
			||||||
 | 
					        print(res.text)
 | 
				
			||||||
 | 
					        raise Exception(f"Enrollment failed with status {res.status_code}")
 | 
				
			||||||
 | 
					    return res.text
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,25 @@
 | 
				
			|||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
parser = argparse.ArgumentParser(
 | 
					parser = argparse.ArgumentParser(description="SolarEnergy Python-based client")
 | 
				
			||||||
    description='SolarEnergy Python-based client')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
parser.add_argument("--unsecure_origin", help="Change unsecure API origin", default="http://localhost:8080")
 | 
					parser.add_argument(
 | 
				
			||||||
 | 
					    "--unsecure_origin",
 | 
				
			||||||
 | 
					    help="Change unsecure API origin",
 | 
				
			||||||
 | 
					    default="http://localhost:8080",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
parser.add_argument("--storage", help="Change storage location", default="storage")
 | 
					parser.add_argument("--storage", help="Change storage location", default="storage")
 | 
				
			||||||
 | 
					parser.add_argument(
 | 
				
			||||||
 | 
					    "--relay_gpios",
 | 
				
			||||||
 | 
					    help="Comma-separated list of GPIO used to modify relays",
 | 
				
			||||||
 | 
					    default="5,6,7",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
args = parser.parse_args()
 | 
					args = parser.parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
args.secure_origin_path = os.path.join(args.storage, "SECURE_ORIGIN")
 | 
					args.secure_origin_path = os.path.join(args.storage, "SECURE_ORIGIN")
 | 
				
			||||||
args.root_ca_path = os.path.join(args.storage, "root_ca.pem")
 | 
					args.root_ca_path = os.path.join(args.storage, "root_ca.crt")
 | 
				
			||||||
args.dev_priv_key_path = os.path.join(args.storage, "dev.key")
 | 
					args.dev_priv_key_path = os.path.join(args.storage, "dev.key")
 | 
				
			||||||
args.dev_csr_path = os.path.join(args.storage, "dev.csr")
 | 
					args.dev_csr_path = os.path.join(args.storage, "dev.csr")
 | 
				
			||||||
 | 
					args.dev_crt_path = os.path.join(args.storage, "dev.crt")
 | 
				
			||||||
 | 
					args.relay_gpios_list = list(map(lambda x: int(x), args.relay_gpios.split(",")))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								python_device/src/constants.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								python_device/src/constants.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					# Device reference. This value should never be changed
 | 
				
			||||||
 | 
					DEV_REFERENCE = "PyDev"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Current device version. Must follow semver semantic
 | 
				
			||||||
 | 
					DEV_VERSION = "0.0.1"
 | 
				
			||||||
@@ -21,7 +21,6 @@ with open(args.secure_origin_path, "r") as f:
 | 
				
			|||||||
print(f"Secure origin = {args.secure_origin}")
 | 
					print(f"Secure origin = {args.secure_origin}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
print("Check system root CA")
 | 
					print("Check system root CA")
 | 
				
			||||||
if not os.path.isfile(args.root_ca_path):
 | 
					if not os.path.isfile(args.root_ca_path):
 | 
				
			||||||
    origin = api.get_root_ca()
 | 
					    origin = api.get_root_ca()
 | 
				
			||||||
@@ -43,3 +42,12 @@ if not os.path.isfile(args.dev_csr_path):
 | 
				
			|||||||
    csr = pki.gen_csr(priv_key=priv_key, cn=f"PyDev {utils.rand_str(10)}")
 | 
					    csr = pki.gen_csr(priv_key=priv_key, cn=f"PyDev {utils.rand_str(10)}")
 | 
				
			||||||
    with open(args.dev_csr_path, "w") as f:
 | 
					    with open(args.dev_csr_path, "w") as f:
 | 
				
			||||||
        f.write(csr)
 | 
					        f.write(csr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print("Check device enrollment...")
 | 
				
			||||||
 | 
					if not os.path.isfile(args.dev_crt_path):
 | 
				
			||||||
 | 
					    with open(args.dev_csr_path, "r") as f:
 | 
				
			||||||
 | 
					        csr = "".join(f.read())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print("Enrolling device...")
 | 
				
			||||||
 | 
					    crt = api.enroll_device(csr)
 | 
				
			||||||
 | 
					    print("res" + crt)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,16 @@
 | 
				
			|||||||
from OpenSSL import crypto
 | 
					from OpenSSL import crypto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def gen_priv_key():
 | 
					def gen_priv_key():
 | 
				
			||||||
    key = crypto.PKey()
 | 
					    key = crypto.PKey()
 | 
				
			||||||
    key.generate_key(crypto.TYPE_RSA, 2048)
 | 
					    key.generate_key(crypto.TYPE_RSA, 2048)
 | 
				
			||||||
    return crypto.dump_privatekey(crypto.FILETYPE_PEM, key).decode("utf-8")
 | 
					    return crypto.dump_privatekey(crypto.FILETYPE_PEM, key).decode("utf-8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_priv_key(priv_key: str) -> crypto.PKey:
 | 
					def parse_priv_key(priv_key: str) -> crypto.PKey:
 | 
				
			||||||
    return crypto.load_privatekey(crypto.FILETYPE_PEM, priv_key)
 | 
					    return crypto.load_privatekey(crypto.FILETYPE_PEM, priv_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def gen_csr(priv_key: str, cn: str) -> str:
 | 
					def gen_csr(priv_key: str, cn: str) -> str:
 | 
				
			||||||
    priv_key = parse_priv_key(priv_key)
 | 
					    priv_key = parse_priv_key(priv_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,5 +18,5 @@ def gen_csr(priv_key: str, cn: str) -> str:
 | 
				
			|||||||
    req.get_subject().CN = cn
 | 
					    req.get_subject().CN = cn
 | 
				
			||||||
    req.set_pubkey(priv_key)
 | 
					    req.set_pubkey(priv_key)
 | 
				
			||||||
    req.sign(priv_key, "sha256")
 | 
					    req.sign(priv_key, "sha256")
 | 
				
			||||||
    
 | 
					
 | 
				
			||||||
    return crypto.dump_certificate_request(crypto.FILETYPE_PEM, req).decode("utf-8")
 | 
					    return crypto.dump_certificate_request(crypto.FILETYPE_PEM, req).decode("utf-8")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,8 @@
 | 
				
			|||||||
import string
 | 
					import string
 | 
				
			||||||
import random
 | 
					import random
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def rand_str(len: int) -> str:
 | 
					def rand_str(len: int) -> str:
 | 
				
			||||||
    return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(len))
 | 
					    return "".join(
 | 
				
			||||||
 | 
					        random.choice(string.ascii_uppercase + string.digits) for _ in range(len)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user