Start to build sync route

This commit is contained in:
2024-09-04 22:43:23 +02:00
parent ee938a3aa6
commit 1b02a812b4
7 changed files with 191 additions and 14 deletions

View File

@@ -76,6 +76,17 @@ impl CertData {
crl: None,
})
}
/// Check if a certificate is revoked
pub fn is_revoked(&self, cert: &X509) -> anyhow::Result<bool> {
let crl = X509Crl::from_pem(&std::fs::read(
self.crl.as_ref().ok_or(PKIError::MissingCRL)?,
)?)?;
let res = crl.get_by_cert(cert);
Ok(matches!(res, CrlStatus::Revoked(_)))
}
}
/// Generate private key
@@ -480,21 +491,10 @@ pub fn gen_certificate_for_device(csr: &X509Req) -> anyhow::Result<String> {
Ok(String::from_utf8(cert)?)
}
/// Check if a certificate is revoked
fn is_revoked(cert: &X509, ca: &CertData) -> anyhow::Result<bool> {
let crl = X509Crl::from_pem(&std::fs::read(
ca.crl.as_ref().ok_or(PKIError::MissingCRL)?,
)?)?;
let res = crl.get_by_cert(cert);
Ok(matches!(res, CrlStatus::Revoked(_)))
}
/// Revoke a certificate
pub fn revoke(cert: &X509, ca: &CertData) -> anyhow::Result<()> {
// Check if certificate is already revoked
if is_revoked(cert, ca)? {
if ca.is_revoked(cert)? {
// No op
return Ok(());
}

View File

@@ -238,3 +238,28 @@ impl Handler<DeleteDeviceRelay> for EnergyActor {
self.devices.relay_delete(msg.0)
}
}
#[derive(serde::Serialize)]
pub struct RelaySyncStatus {
enabled: bool,
}
/// Synchronize a device
#[derive(Message)]
#[rtype(result = "anyhow::Result<Vec<RelaySyncStatus>>")]
pub struct SynchronizeDevice(pub DeviceId, pub DeviceInfo);
impl Handler<SynchronizeDevice> for EnergyActor {
type Result = anyhow::Result<Vec<RelaySyncStatus>>;
fn handle(&mut self, msg: SynchronizeDevice, _ctx: &mut Context<Self>) -> Self::Result {
// TODO : implement real code
let mut v = vec![];
for i in 0..msg.1.max_relays {
v.push(RelaySyncStatus {
enabled: i % 2 == 0,
});
}
Ok(v)
}
}

View File

@@ -1,11 +1,14 @@
use crate::app_config::AppConfig;
use crate::crypto::pki;
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 jsonwebtoken::{Algorithm, DecodingKey, Validation};
use openssl::nid::Nid;
use openssl::x509::X509Req;
use openssl::x509::{X509Req, X509};
use std::collections::HashSet;
#[derive(Debug, serde::Deserialize)]
pub struct EnrollRequest {
@@ -124,3 +127,81 @@ pub async fn get_certificate(query: web::Query<ReqWithDevID>, actor: WebEnergyAc
.content_type("application/x-pem-file")
.body(cert))
}
#[derive(serde::Deserialize)]
pub struct SyncRequest {
payload: String,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Claims {
info: DeviceInfo,
}
/// Synchronize device
pub async fn sync_device(body: web::Json<SyncRequest>, actor: WebEnergyActor) -> HttpResult {
// First, we need to extract device kid from query
let Ok(jwt_header) = jsonwebtoken::decode_header(&body.payload) else {
log::error!("Failed to decode JWT header!");
return Ok(HttpResponse::BadRequest().json("Failed to decode JWT header!"));
};
let Some(kid) = jwt_header.kid else {
log::error!("Missing KID in JWT!");
return Ok(HttpResponse::BadRequest().json("Missing KID in JWT!"));
};
// Fetch device information
let Some(device) = actor
.send(energy_actor::GetSingleDevice(DeviceId(kid)))
.await?
else {
log::error!("Sent a JWT for a device which does not exists!");
return Ok(HttpResponse::NotFound().json("Sent a JWT for a device which does not exists!"));
};
if !device.validated {
log::error!("Sent a JWT for a device which is not validated!");
return Ok(HttpResponse::PreconditionFailed()
.json("Sent a JWT for a device which is not validated!"));
}
// Check certificate revocation status
let cert_bytes = std::fs::read(AppConfig::get().device_cert_path(&device.id))?;
let certificate = X509::from_pem(&cert_bytes)?;
if pki::CertData::load_devices_ca()?.is_revoked(&certificate)? {
log::error!("Sent a JWT using a revoked certificate!");
return Ok(
HttpResponse::PreconditionFailed().json("Sent a JWT using a revoked certificate!")
);
}
let (key, alg) = match DecodingKey::from_ec_pem(&cert_bytes) {
Ok(key) => (key, Algorithm::ES256),
Err(e) => {
log::warn!("Failed to decode certificate as EC certificate {e}, trying RSA...");
(
DecodingKey::from_rsa_pem(&cert_bytes).expect("Failed to decode RSA certificate"),
Algorithm::RS256,
)
}
};
let mut validation = Validation::new(alg);
validation.validate_exp = false;
validation.required_spec_claims = HashSet::default();
let c = match jsonwebtoken::decode::<Claims>(&body.payload, &key, &validation) {
Ok(c) => c,
Err(e) => {
log::error!("Failed to validate JWT! {e}");
return Ok(HttpResponse::PreconditionFailed().json("Failed to validate JWT!"));
}
};
let res = actor
.send(energy_actor::SynchronizeDevice(device.id, c.claims.info))
.await??;
Ok(HttpResponse::Ok().json(res))
}

View File

@@ -194,6 +194,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
"/devices_api/mgmt/get_certificate",
web::get().to(mgmt_controller::get_certificate),
)
.route(
"/devices_api/mgmt/sync",
web::post().to(mgmt_controller::sync_device),
)
// Web app
.route("/", web::get().to(web_app_controller::root_index))
.route(