mirror of
https://git.proxmox.com/git/proxmox
synced 2025-08-06 01:19:34 +00:00
add util::Csr for CSR generation
This is essentially taken from pmg-rs and should be used from there. Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
37b4c4f654
commit
a947050ec1
17
src/error.rs
17
src/error.rs
@ -44,7 +44,11 @@ pub enum Error {
|
||||
BadOrderData(String),
|
||||
|
||||
/// An openssl error occurred during a crypto operation.
|
||||
Ssl(SslErrorStack),
|
||||
RawSsl(SslErrorStack),
|
||||
|
||||
/// An openssl error occurred during a crypto operation.
|
||||
/// With some textual context.
|
||||
Ssl(&'static str, SslErrorStack),
|
||||
|
||||
/// An otherwise uncaught serde error happened.
|
||||
Json(serde_json::Error),
|
||||
@ -61,6 +65,9 @@ pub enum Error {
|
||||
/// If built with the `client` feature, this is where client specific errors which are not from
|
||||
/// errors forwarded from `curl` end up.
|
||||
Client(String),
|
||||
|
||||
/// A non-openssl error occurred while building data for the CSR.
|
||||
Csr(String),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
@ -98,18 +105,22 @@ impl fmt::Display for Error {
|
||||
Error::BadOrderData(err) => {
|
||||
write!(f, "bad response to new-order query or creation: {}", err)
|
||||
}
|
||||
Error::Ssl(err) => fmt::Display::fmt(err, f),
|
||||
Error::RawSsl(err) => fmt::Display::fmt(err, f),
|
||||
Error::Ssl(context, err) => {
|
||||
write!(f, "{}: {}", context, err)
|
||||
}
|
||||
Error::Json(err) => fmt::Display::fmt(err, f),
|
||||
Error::Custom(err) => fmt::Display::fmt(err, f),
|
||||
Error::HttpClient(err) => fmt::Display::fmt(err, f),
|
||||
Error::Client(err) => fmt::Display::fmt(err, f),
|
||||
Error::Csr(err) => fmt::Display::fmt(err, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SslErrorStack> for Error {
|
||||
fn from(e: SslErrorStack) -> Self {
|
||||
Error::Ssl(e)
|
||||
Error::RawSsl(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ pub mod authorization;
|
||||
pub mod directory;
|
||||
pub mod error;
|
||||
pub mod order;
|
||||
pub mod util;
|
||||
|
||||
pub use account::Account;
|
||||
pub use authorization::{Authorization, Challenge};
|
||||
|
98
src/util.rs
Normal file
98
src/util.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::PKey;
|
||||
use openssl::rsa::Rsa;
|
||||
use openssl::x509::{X509Extension, X509Name, X509Req};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
pub struct Csr {
|
||||
/// DER encoded certificate request.
|
||||
pub data: Vec<u8>,
|
||||
|
||||
/// PEM formatted PKCS#8 private key.
|
||||
pub private_key_pem: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Csr {
|
||||
/// Generate a CSR in DER format with a PEM formatted PKCS8 private key.
|
||||
///
|
||||
/// The `identifiers` should be a list of domains. The `attributes` should have standard names
|
||||
/// recognized by openssl.
|
||||
pub fn generate(
|
||||
identifiers: &[impl AsRef<str>],
|
||||
attributes: &HashMap<String, &str>,
|
||||
) -> Result<Self, Error> {
|
||||
if identifiers.is_empty() {
|
||||
return Err(Error::Csr(format!("cannot generate empty CSR")));
|
||||
}
|
||||
|
||||
let private_key = Rsa::generate(4096)
|
||||
.and_then(PKey::from_rsa)
|
||||
.map_err(|err| Error::Ssl("failed to generate RSA key: {}", err))?;
|
||||
|
||||
let private_key_pem = private_key
|
||||
.private_key_to_pem_pkcs8()
|
||||
.map_err(|err| Error::Ssl("failed to format private key as PEM pkcs8: {}", err))?;
|
||||
|
||||
let mut name = X509Name::builder()?;
|
||||
if !attributes.contains_key("CN") {
|
||||
name.append_entry_by_nid(Nid::COMMONNAME, identifiers[0].as_ref())?;
|
||||
}
|
||||
for (key, value) in attributes {
|
||||
name.append_entry_by_text(key, value)?;
|
||||
}
|
||||
let name = name.build();
|
||||
|
||||
let mut csr = X509Req::builder()?;
|
||||
csr.set_subject_name(&name)?;
|
||||
csr.set_pubkey(&private_key)?;
|
||||
|
||||
let context = csr.x509v3_context(None);
|
||||
let mut ext = openssl::stack::Stack::new()?;
|
||||
ext.push(X509Extension::new_nid(
|
||||
None,
|
||||
None,
|
||||
Nid::BASIC_CONSTRAINTS,
|
||||
"CA:FALSE",
|
||||
)?)?;
|
||||
ext.push(X509Extension::new_nid(
|
||||
None,
|
||||
None,
|
||||
Nid::KEY_USAGE,
|
||||
"digitalSignature,keyEncipherment",
|
||||
)?)?;
|
||||
ext.push(X509Extension::new_nid(
|
||||
None,
|
||||
None,
|
||||
Nid::EXT_KEY_USAGE,
|
||||
"serverAuth,clientAuth",
|
||||
)?)?;
|
||||
ext.push(X509Extension::new_nid(
|
||||
None,
|
||||
Some(&context),
|
||||
Nid::SUBJECT_ALT_NAME,
|
||||
&identifiers
|
||||
.into_iter()
|
||||
.try_fold(String::new(), |mut acc, dns| {
|
||||
if !acc.is_empty() {
|
||||
acc.push(',');
|
||||
}
|
||||
use std::fmt::Write;
|
||||
write!(acc, "DNS:{}", dns.as_ref())?;
|
||||
Ok::<_, std::fmt::Error>(acc)
|
||||
})
|
||||
.map_err(|err| Error::Csr(err.to_string()))?,
|
||||
)?)?;
|
||||
csr.add_extensions(&ext)?;
|
||||
|
||||
csr.sign(&private_key, MessageDigest::sha256())?;
|
||||
|
||||
Ok(Self {
|
||||
data: csr.build().to_der()?,
|
||||
private_key_pem,
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user