rusty_vault 0.2.1

RustyVault is a powerful identity-based secrets management software, providing features such as cryptographic key management, encryption as a service, public key cryptography, certificates management, identity credentials management and so forth. RustyVault's RESTful API is designed to be fully compatible with Hashicorp Vault.
Documentation
use std::time::{Duration, SystemTime};

use humantime::{parse_duration, parse_rfc3339};
use openssl::x509::X509NameBuilder;

use super::path_roles::RoleEntry;
use crate::{errors::RvError, logical::Request, utils::cert::Certificate};

pub const DEFAULT_MAX_TTL: Duration = Duration::from_secs(365 * 24 * 60 * 60 as u64);

pub fn get_role_params(req: &mut Request) -> Result<RoleEntry, RvError> {
    let mut ttl = DEFAULT_MAX_TTL;
    if let Ok(ttl_value) = req.get_data("ttl") {
        let ttl_str = ttl_value.as_str().ok_or(RvError::ErrRequestFieldInvalid)?;
        if ttl_str != "" {
            ttl = parse_duration(ttl_str)?;
        }
    }
    let not_before_duration_u64 =
        req.get_data_or_default("not_before_duration")?.as_u64().ok_or(RvError::ErrRequestFieldInvalid)?;
    let not_before_duration = Duration::from_secs(not_before_duration_u64);
    let key_type_value = req.get_data_or_default("key_type")?;
    let key_type = key_type_value.as_str().ok_or(RvError::ErrRequestFieldInvalid)?;
    let mut key_bits = req.get_data_or_default("key_bits")?.as_u64().ok_or(RvError::ErrRequestFieldInvalid)?;
    match key_type {
        "rsa" => {
            if key_bits == 0 {
                key_bits = 2048;
            }

            if key_bits != 2048 && key_bits != 3072 && key_bits != 4096 {
                return Err(RvError::ErrPkiKeyBitsInvalid);
            }
        }
        "ec" => {
            if key_bits == 0 {
                key_bits = 256;
            }

            if key_bits != 224 && key_bits != 256 && key_bits != 384 && key_bits != 512 {
                return Err(RvError::ErrPkiKeyBitsInvalid);
            }
        }
        #[cfg(feature = "crypto_adaptor_tongsuo")]
        "sm2" => {
            if key_bits == 0 {
                key_bits = 256;
            }

            if key_bits != 256 {
                return Err(RvError::ErrPkiKeyBitsInvalid);
            }
        }
        _ => return Err(RvError::ErrPkiKeyTypeInvalid),
    }

    let signature_bits = req.get_data_or_default("signature_bits")?.as_u64().ok_or(RvError::ErrRequestFieldInvalid)?;
    let use_pss = req.get_data_or_default("use_pss")?.as_bool().ok_or(RvError::ErrRequestFieldInvalid)?;
    let country = req.get_data_or_default("country")?.as_str().ok_or(RvError::ErrRequestFieldInvalid)?.to_string();
    let province = req.get_data_or_default("province")?.as_str().ok_or(RvError::ErrRequestFieldInvalid)?.to_string();
    let locality = req.get_data_or_default("locality")?.as_str().ok_or(RvError::ErrRequestFieldInvalid)?.to_string();
    let organization =
        req.get_data_or_default("organization")?.as_str().ok_or(RvError::ErrRequestFieldInvalid)?.to_string();
    let ou = req.get_data_or_default("ou")?.as_str().ok_or(RvError::ErrRequestFieldInvalid)?.to_string();
    let street_address =
        req.get_data_or_default("street_address")?.as_str().ok_or(RvError::ErrRequestFieldInvalid)?.to_string();
    let postal_code =
        req.get_data_or_default("postal_code")?.as_str().ok_or(RvError::ErrRequestFieldInvalid)?.to_string();
    let not_after = req.get_data_or_default("not_after")?.as_str().ok_or(RvError::ErrRequestFieldInvalid)?.to_string();

    let role_entry = RoleEntry {
        ttl,
        not_before_duration,
        use_pss,
        key_type: key_type.to_string(),
        key_bits: key_bits as u32,
        signature_bits: signature_bits as u32,
        country,
        province,
        locality,
        organization,
        ou,
        street_address,
        postal_code,
        not_after,
        ..Default::default()
    };

    Ok(role_entry)
}

pub fn generate_certificate(role_entry: &RoleEntry, req: &mut Request) -> Result<Certificate, RvError> {
    let mut common_names = Vec::new();

    let common_name_value = req.get_data_or_default("common_name")?;
    let common_name = common_name_value.as_str().ok_or(RvError::ErrRequestFieldInvalid)?;
    if common_name != "" {
        common_names.push(common_name.to_string());
    }

    if let Ok(alt_names_value) = req.get_data("alt_names") {
        let alt_names = alt_names_value.as_str().ok_or(RvError::ErrRequestFieldInvalid)?;
        if alt_names != "" {
            for v in alt_names.split(',') {
                common_names.push(v.to_string());
            }
        }
    }

    let mut ip_sans = Vec::new();
    if let Ok(ip_sans_value) = req.get_data("ip_sans") {
        let ip_sans_str = ip_sans_value.as_str().ok_or(RvError::ErrRequestFieldInvalid)?;
        if ip_sans_str != "" {
            for v in ip_sans_str.split(',') {
                ip_sans.push(v.to_string());
            }
        }
    }

    let not_before = SystemTime::now() - Duration::from_secs(10);
    let not_after: SystemTime;
    if role_entry.not_after.len() > 18 {
        let parsed_time = parse_rfc3339(&role_entry.not_after)?;
        not_after = parsed_time.into();
    } else {
        if role_entry.ttl != Duration::from_secs(0) {
            not_after = not_before + role_entry.ttl;
        } else {
            not_after = not_before + role_entry.max_ttl;
        }
    }

    let mut subject_name = X509NameBuilder::new().unwrap();
    if role_entry.country.len() > 0 {
        subject_name.append_entry_by_text("C", &role_entry.country).unwrap();
    }
    if role_entry.province.len() > 0 {
        subject_name.append_entry_by_text("ST", &role_entry.province).unwrap();
    }
    if role_entry.locality.len() > 0 {
        subject_name.append_entry_by_text("L", &role_entry.locality).unwrap();
    }
    if role_entry.organization.len() > 0 {
        subject_name.append_entry_by_text("O", &role_entry.organization).unwrap();
    }
    if role_entry.ou.len() > 0 {
        subject_name.append_entry_by_text("OU", &role_entry.ou).unwrap();
    }
    if common_name != "" {
        subject_name.append_entry_by_text("CN", common_name).unwrap();
    }
    let subject = subject_name.build();

    let cert = Certificate {
        not_before,
        not_after,
        subject,
        dns_sans: common_names,
        ip_sans,
        key_type: role_entry.key_type.clone(),
        key_bits: role_entry.key_bits,
        ..Default::default()
    };

    Ok(cert)
}