use crate::app_config::AppConfig; use crate::devices::device::{DeviceId, DeviceInfo}; use crate::energy::energy_actor; use crate::server::custom_error::HttpResult; use crate::server::WebEnergyActor; use actix_web::{web, HttpResponse}; use openssl::nid::Nid; 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, actor: WebEnergyActor) -> HttpResult { // Check device information if let Some(e) = req.info.error() { log::error!("Failed to validate device information! {e}"); return Ok(HttpResponse::BadRequest().json(e)); } // Check CSR 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!")); } let cn = match csr.subject_name().entries_by_nid(Nid::COMMONNAME).next() { None => { log::error!("Missing Common Name in CSR!"); return Ok(HttpResponse::BadRequest().json("Missing Common Name in CSR!")); } Some(cn) => cn.data().as_utf8()?.to_string(), }; if !lazy_regex::regex!("[a-zA-Z0-9 ]{1,100}").is_match(&cn) { log::error!("Given Common Name is invalid!"); return Ok(HttpResponse::BadRequest().json("Invalid Common Name in CSR!")); } let device_id = DeviceId(cn); log::info!( "Received enrollment request for device with ID {device_id:?} - {:#?}", req.info ); if actor .send(energy_actor::CheckDeviceExists(device_id.clone())) .await? { log::error!("Device could not be enrolled: it already exists!"); return Ok( HttpResponse::Conflict().json("A device with the same ID has already been enrolled!") ); } actor .send(energy_actor::EnrollDevice(device_id, req.0.info, csr)) .await??; Ok(HttpResponse::Accepted().json("Device successfully enrolled")) } #[derive(serde::Deserialize)] pub struct ReqWithDevID { id: DeviceId, } #[derive(serde::Serialize)] #[serde(tag = "status")] enum EnrollmentDeviceStatus { Unknown, Pending, Validated, } /// Check device enrollment status pub async fn enrollment_status( query: web::Query, actor: WebEnergyActor, ) -> HttpResult { let dev = actor .send(energy_actor::GetSingleDevice(query.id.clone())) .await?; let status = match dev { None => EnrollmentDeviceStatus::Unknown, Some(d) if d.validated => EnrollmentDeviceStatus::Validated, _ => EnrollmentDeviceStatus::Pending, }; Ok(HttpResponse::Ok().json(status)) } /// Get device certificate pub async fn get_certificate(query: web::Query, actor: WebEnergyActor) -> HttpResult { let dev = actor .send(energy_actor::GetSingleDevice(query.id.clone())) .await?; let dev = match dev { Some(d) if d.validated => d, _ => { log::error!("Device attempted to retrieve an unavailable certificate!"); return Ok(HttpResponse::UnprocessableEntity().json("Certificate not available yet!")); } }; let cert = std::fs::read(AppConfig::get().device_cert_path(&dev.id))?; Ok(HttpResponse::Ok() .content_type("application/x-pem-file") .body(cert)) }