Files
SolarEnergy/central_backend/src/server/devices_api/jwt_parser.rs

97 lines
3.2 KiB
Rust

use crate::app_config::AppConfig;
use crate::crypto::pki;
use crate::devices::device::{Device, DeviceId};
use crate::energy::energy_actor;
use crate::server::WebEnergyActor;
use jsonwebtoken::{Algorithm, DecodingKey, Validation};
use openssl::x509::X509;
use serde::de::DeserializeOwned;
use std::collections::HashSet;
#[derive(thiserror::Error, Debug)]
pub enum JWTError {
#[error("Failed to decode JWT header")]
FailedDecodeJWT,
#[error("Missing KID in JWT!")]
MissingKidInJWT,
#[error("Sent a JWT for a device which does not exists!")]
DeviceDoesNotExists,
#[error("Sent a JWT for a device which is not validated!")]
DeviceNotValidated,
#[error("Sent a JWT using a revoked certificate!")]
RevokedCertificate,
#[error("Failed to validate JWT!")]
FailedValidateJWT,
}
#[derive(serde::Deserialize)]
pub struct JWTRequest {
pub payload: String,
}
impl JWTRequest {
pub async fn parse_jwt<E: DeserializeOwned>(
&self,
actor: WebEnergyActor,
) -> anyhow::Result<(Device, E)> {
// First, we need to extract device kid from query
let Ok(jwt_header) = jsonwebtoken::decode_header(&self.payload) else {
log::error!("Failed to decode JWT header!");
return Err(JWTError::FailedDecodeJWT.into());
};
let Some(kid) = jwt_header.kid else {
log::error!("Missing KID in JWT!");
return Err(JWTError::MissingKidInJWT.into());
};
// 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 Err(JWTError::DeviceDoesNotExists.into());
};
if !device.validated {
log::error!("Sent a JWT for a device which is not validated!");
return Err(JWTError::DeviceNotValidated.into());
}
// 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 Err(JWTError::RevokedCertificate.into());
}
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::<E>(&self.payload, &key, &validation) {
Ok(c) => c,
Err(e) => {
log::error!("Failed to validate JWT! {e}");
return Err(JWTError::FailedValidateJWT.into());
}
};
Ok((device, c.claims))
}
}