use std::fs::read_dir;
use std::str::FromStr;
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use openssl::{
error::ErrorStack,
hash::MessageDigest,
pkey::PKey,
sign::Verifier,
stack::Stack,
x509::{store::X509StoreBuilder, verify::X509VerifyFlags, X509, X509StoreContext},
};
use x509_parser::der_parser::oid;
use x509_parser::prelude::{FromDer, X509Certificate};
use crate::smart_id::models::{AuthenticationIdentity, CertificateLevel, CertificateParser, SessionEndResultCode, SmartIdAuthenticationResponse, SmartIdAuthenticationResult, SmartIdAuthenticationResultError};
use crate::smart_id::utils::certificate_attributes::CertificateAttributes;
use crate::smart_id::utils::national_identity_number::NationalIdentityNumber;
pub struct AuthenticationResponseValidator {
trusted_ca_certificates: Vec<String>,
}
impl AuthenticationResponseValidator {
pub fn new(resources_location: Option<&str>) -> Result<Self, ErrorStack> {
let resources_location = match resources_location {
Some(location) => location.to_owned(),
None => format!("{}/../../../resources", env!("CARGO_MANIFEST_DIR")),
};
let trusted_ca_certificates =
Self::initialize_trusted_ca_certificates_from_resources(&resources_location)?;
Ok(Self {
trusted_ca_certificates,
})
}
pub fn validate(
&self,
authentication_response: &SmartIdAuthenticationResponse,
) -> Result<SmartIdAuthenticationResult, ErrorStack> {
self.validate_authentication_response(authentication_response)
.unwrap();
let (_, certificate) = X509Certificate::from_der(&authentication_response.certificate.as_bytes()).unwrap();
let mut authentication_result = SmartIdAuthenticationResult::new();
let identity = self.construct_authentication_identity(
&certificate,
&authentication_response.certificate,
)?;
authentication_result.set_authentication_identity(identity);
if !self.verify_response_end_result(authentication_response) {
authentication_result.set_valid(false);
authentication_result.add_error(SmartIdAuthenticationResultError::InvalidEndResult);
}
if !self.verify_signature(authentication_response)? {
authentication_result.set_valid(false);
authentication_result
.add_error(SmartIdAuthenticationResultError::SignatureVerificationFailure);
}
if !self.verify_certificate_expiry(&certificate) {
authentication_result.set_valid(false);
authentication_result.add_error(SmartIdAuthenticationResultError::CertificateExpired);
}
if !self.is_certificate_trusted(authentication_response.to_owned().certificate)? {
authentication_result.set_valid(false);
authentication_result
.add_error(SmartIdAuthenticationResultError::CertificateNotTrusted);
}
if !self.verify_certificate_level(authentication_response) {
authentication_result.set_valid(false);
authentication_result
.add_error(SmartIdAuthenticationResultError::CertificateLevelMismatch);
}
Ok(authentication_result)
}
fn validate_authentication_response(
&self,
authentication_response: &SmartIdAuthenticationResponse,
) -> Result<(), anyhow::Error> {
if authentication_response.certificate.is_empty() {
return Err(anyhow!(
"Certificate is not present in the authentication response"
));
}
if authentication_response.value_in_base64.is_empty() {
return Err(anyhow!(
"Signature is not present in the authentication response"
));
}
if authentication_response.signed_data.is_empty() {
return Err(anyhow!(
"Signable data is not present in the authentication response"
));
}
Ok(())
}
fn verify_response_end_result(
&self,
authentication_response: &SmartIdAuthenticationResponse,
) -> bool {
authentication_response.end_result == SessionEndResultCode::Ok
}
fn verify_signature(
&self,
authentication_response: &SmartIdAuthenticationResponse,
) -> Result<bool, ErrorStack> {
let prepared_certificate =
CertificateParser::get_der_certificate(authentication_response.certificate.clone()).unwrap();
let signature = authentication_response.get_value().unwrap();
let public_key = PKey::public_key_from_pem(prepared_certificate.as_slice()).unwrap();
let mut verifier = Verifier::new(MessageDigest::sha512(), &public_key).unwrap();
verifier.update(authentication_response.signed_data.as_bytes()).unwrap();
Ok(verifier.verify(signature.as_slice()).unwrap())
}
fn verify_certificate_expiry(
&self,
authentication_certificate: &X509Certificate<'_>,
) -> bool {
let valid_to = authentication_certificate.validity.not_after.timestamp();
let now = Utc::now().timestamp();
valid_to > now
}
fn verify_certificate_level(
&self,
authentication_response: &SmartIdAuthenticationResponse,
) -> bool {
let cert_level = CertificateLevel::from_str(&authentication_response.to_owned().certificate_level).unwrap();
let requested_certificate_level =
&authentication_response.requested_certificate_level.as_ref().unwrap();
requested_certificate_level.is_empty() || cert_level.is_equal_or_above(requested_certificate_level.as_str())
}
fn construct_authentication_identity(
&self,
certificate: &X509Certificate<'_>,
x509_certificate: &str,
) -> Result<AuthenticationIdentity, ErrorStack> {
let mut identity = AuthenticationIdentity::new();
identity.set_auth_certificate(x509_certificate.to_owned());
let tbs_certificate = &certificate.tbs_certificate;
let subject = &tbs_certificate.subject;
if let Some(given_name) = subject.iter_by_oid(&oid!(2.5.4.42)).next() {
identity.set_given_name(String::from_utf8_lossy(given_name.attr_value().data).to_string());
}
if let Some(surname) = subject.iter_by_oid(&oid!(2.5.4.4)).next() {
identity.set_sur_name(String::from_utf8_lossy(surname.attr_value().data).to_string());
}
if let Some(identity_code) = subject.iter_by_oid(&oid!(2.5.4.5)).next() {
let identity_code = String::from_utf8_lossy(identity_code.attr_value().data).to_string();
identity.set_identity_code(identity_code.to_owned());
let identity_number = identity_code.splitn(2, '-').nth(1);
identity.set_identity_number(identity_number.unwrap().to_string());
}
if let Some(country) = subject.iter_country().next() {
identity.set_country(String::from_utf8_lossy(country.attr_value().data).to_string());
}
identity.set_date_of_birth(Self::get_date_of_birth(&identity)?);
Ok(identity)
}
fn initialize_trusted_ca_certificates_from_resources(
resources_location: &str,
) -> Result<Vec<String>, ErrorStack> {
let mut trusted_ca_certificates = Vec::new();
let trusted_certificates_directory = format!("{}/trusted_certificates", resources_location);
for entry in read_dir(trusted_certificates_directory).unwrap() {
if let Ok(file) = entry {
let path = file.path();
if !path.is_dir() && !file.file_name().to_string_lossy().starts_with(".") {
trusted_ca_certificates.push(path.to_string_lossy().into_owned());
}
}
}
Ok(trusted_ca_certificates)
}
fn is_certificate_trusted(&self, certificate: String) -> Result<bool, ErrorStack> {
let certificate_as_pem = CertificateParser::get_der_certificate(certificate).unwrap();
let x509 = X509::from_pem(certificate_as_pem.as_slice()).unwrap();
let mut store = X509StoreBuilder::new().unwrap();
for ca_certificate in &self.trusted_ca_certificates {
let ca_certificate_pem = std::fs::read_to_string(ca_certificate).unwrap();
let ca_x509 = X509::from_pem(ca_certificate_pem.as_bytes()).unwrap();
store.add_cert(ca_x509)?;
}
store.set_flags(X509VerifyFlags::PARTIAL_CHAIN)?;
let mut store_ctx = X509StoreContext::new()?;
store_ctx.init(&store.build(), &x509, &Stack::new().unwrap(), |c| {
c.verify_cert()
})?;
Ok(store_ctx.verify_cert()?)
}
fn get_date_of_birth(
identity: &AuthenticationIdentity,
) -> Result<Option<DateTime<Utc>>, ErrorStack> {
let certificate_attribute_util = CertificateAttributes::new();
let date_of_birth_from_certificate_field = certificate_attribute_util
.get_date_of_birth_certificate_attribute(&identity.auth_certificate);
if let Some(date_of_birth) = date_of_birth_from_certificate_field {
return Ok(Some(date_of_birth));
}
let national_identity_number_util = NationalIdentityNumber::new();
let date_of_birth = national_identity_number_util
.get_date_of_birth(&identity)
.unwrap();
Ok(date_of_birth)
}
}