97 lines
3.2 KiB
Rust
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))
|
|
}
|
|
}
|