127 lines
3.7 KiB
Rust
127 lines
3.7 KiB
Rust
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<EnrollRequest>, 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<ReqWithDevID>,
|
|
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<ReqWithDevID>, 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))
|
|
}
|