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::{collections::HashMap, sync::Arc};

use openssl::{
    pkey::{Id, PKey},
    x509::X509,
};
use pem;

use super::{PkiBackend, PkiBackendInner};
use crate::{
    context::Context,
    errors::RvError,
    logical::{Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response},
    new_fields, new_fields_internal, new_path, new_path_internal,
    storage::StorageEntry,
    utils::{cert, cert::CertBundle},
};

impl PkiBackend {
    pub fn config_ca_path(&self) -> Path {
        let pki_backend_ref = Arc::clone(&self.inner);

        let path = new_path!({
            pattern: "config/ca",
            fields: {
                "pem_bundle": {
                    field_type: FieldType::Str,
                    description: "PEM-format, concatenated unencrypted secret key and certificate"
                }
            },
            operations: [
                {op: Operation::Write, handler: pki_backend_ref.write_path_ca}
            ],
            help: r#"
This configures the CA information used for credentials
generated by this backend. This must be a PEM-format, concatenated
unencrypted secret key and certificate.

For security reasons, you can only view the certificate when reading this endpoint
                        "#
        });

        path
    }
}

impl PkiBackendInner {
    pub fn write_path_ca(&self, _backend: &dyn Backend, req: &mut Request) -> Result<Option<Response>, RvError> {
        let pem_bundle_value = req.get_data("pem_bundle")?;
        let pem_bundle = pem_bundle_value.as_str().ok_or(RvError::ErrRequestFieldInvalid)?;

        let items = pem::parse_many(pem_bundle)?;
        let mut key_found = false;
        let mut i = 0;

        let mut cert_bundle = CertBundle::default();

        for item in items {
            if item.tag() == "CERTIFICATE" {
                let cert = X509::from_der(item.contents())?;
                if !cert::is_ca_cert(&cert) {
                    return Err(RvError::ErrPkiPemBundleInvalid);
                }

                if i == 0 {
                    cert_bundle.certificate = cert;
                } else {
                    cert_bundle.ca_chain.push(cert);
                }
                i += 1;
            }
            if item.tag() == "PRIVATE KEY" {
                if key_found {
                    return Err(RvError::ErrPkiPemBundleInvalid);
                }

                let key = PKey::private_key_from_der(item.contents())?;
                match key.id() {
                    Id::RSA => {
                        cert_bundle.private_key_type = "rsa".to_string();
                    }
                    Id::EC => {
                        cert_bundle.private_key_type = "ec".to_string();
                    }
                    Id::SM2 => {
                        cert_bundle.private_key_type = "sm2".to_string();
                    }
                    Id::ED25519 => {
                        cert_bundle.private_key_type = "ed25519".to_string();
                    }
                    _ => {
                        cert_bundle.private_key_type = "other".to_string();
                    }
                }
                cert_bundle.private_key = key;
                key_found = true;
            }
        }

        cert_bundle.verify()?;

        self.store_ca_bundle(req, &cert_bundle)?;

        let entry = StorageEntry { key: "crl".to_string(), value: Vec::new() };

        req.storage_put(&entry)?;

        Ok(None)
    }

    pub fn fetch_ca_bundle(&self, req: &Request) -> Result<CertBundle, RvError> {
        let entry = req.storage_get("config/ca_bundle")?;
        if entry.is_none() {
            return Err(RvError::ErrPkiCaNotConfig);
        }

        let ca_bundle: CertBundle = serde_json::from_slice(entry.unwrap().value.as_slice())?;
        Ok(ca_bundle)
    }

    pub fn store_ca_bundle(&self, req: &mut Request, ca_bundle: &CertBundle) -> Result<(), RvError> {
        let mut entry = StorageEntry::new("config/ca_bundle", ca_bundle)?;

        req.storage_put(&entry)?;

        entry.key = "ca".to_string();
        entry.value = ca_bundle.certificate.to_pem().unwrap();
        req.storage_put(&entry)?;

        let serial_number_hex = ca_bundle.serial_number.replace(":", "-").to_lowercase();
        self.store_cert(req, &serial_number_hex, &ca_bundle.certificate)?;

        Ok(())
    }

    pub fn delete_ca_bundle(&self, req: &Request) -> Result<(), RvError> {
        let ca_bundle = self.fetch_ca_bundle(req)?;
        let serial_number_hex = ca_bundle.serial_number.replace(":", "-").to_lowercase();

        self.delete_cert(req, &serial_number_hex)?;

        req.storage_delete("config/ca_bundle")?;

        Ok(())
    }
}