Browse Source

Allow to specify subject attributes for certificates

pull/39/head
Rodolphe Breard 4 years ago
parent
commit
1a1c1bed91
  1. 6
      CHANGELOG.md
  2. 20
      acme_common/src/crypto.rs
  3. 13
      acme_common/src/crypto/openssl_certificate.rs
  4. 24
      acme_common/src/crypto/openssl_subject_attribute.rs
  5. 1
      acmed/src/acme_proto.rs
  6. 3
      acmed/src/certificate.rs
  7. 52
      acmed/src/config.rs
  8. 1
      acmed/src/main_event_loop.rs
  9. 32
      man/en/acmed.toml.5

6
CHANGELOG.md

@ -13,6 +13,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Some subject attributes can now be specified.
## [0.11.0] - 2020-09-19
### Added

20
acme_common/src/crypto.rs

@ -7,10 +7,29 @@ mod key_type;
mod openssl_certificate;
mod openssl_hash;
mod openssl_keys;
mod openssl_subject_attribute;
mod openssl_version;
pub const CRT_NB_DAYS_VALIDITY: u32 = 7;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum BaseSubjectAttribute {
CountryName,
LocalityName,
StateOrProvinceName,
StreetAddress,
OrganizationName,
OrganizationalUnitName,
Name,
GivenName,
Initials,
Title,
Surname,
Pseudonym,
GenerationQualifier,
FriendlyName,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BaseHashFunction {
Sha256,
@ -54,4 +73,5 @@ pub use key_type::KeyType;
pub use openssl_certificate::{Csr, X509Certificate};
pub use openssl_hash::HashFunction;
pub use openssl_keys::{gen_keypair, KeyPair};
pub use openssl_subject_attribute::SubjectAttribute;
pub use openssl_version::{get_lib_name, get_lib_version};

13
acme_common/src/crypto/openssl_certificate.rs

@ -1,4 +1,4 @@
use super::{gen_keypair, KeyPair, KeyType};
use super::{gen_keypair, KeyPair, KeyType, SubjectAttribute};
use crate::b64_encode;
use crate::crypto::HashFunction;
use crate::error::Error;
@ -8,7 +8,7 @@ use openssl::hash::MessageDigest;
use openssl::stack::Stack;
use openssl::x509::extension::{BasicConstraints, SubjectAlternativeName};
use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509Req, X509ReqBuilder, X509};
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::net::IpAddr;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
@ -43,9 +43,18 @@ impl Csr {
digest: HashFunction,
domains: &[String],
ips: &[String],
subject_attributes: &HashMap<SubjectAttribute, String>,
) -> Result<Self, Error> {
let mut builder = X509ReqBuilder::new()?;
builder.set_pubkey(&key_pair.inner_key)?;
if !subject_attributes.is_empty() {
let mut snb = X509NameBuilder::new()?;
for (sattr, val) in subject_attributes.iter() {
snb.append_entry_by_nid(sattr.get_nid(), &val)?;
}
let name = snb.build();
builder.set_subject_name(&name)?;
}
let ctx = builder.x509v3_context(None);
let mut san = SubjectAlternativeName::new();
for dns in domains.iter() {

24
acme_common/src/crypto/openssl_subject_attribute.rs

@ -0,0 +1,24 @@
use openssl::nid::Nid;
pub type SubjectAttribute = super::BaseSubjectAttribute;
impl SubjectAttribute {
pub fn get_nid(&self) -> Nid {
match self {
SubjectAttribute::CountryName => Nid::COUNTRYNAME,
SubjectAttribute::LocalityName => Nid::LOCALITYNAME,
SubjectAttribute::StateOrProvinceName => Nid::STATEORPROVINCENAME,
SubjectAttribute::StreetAddress => Nid::STREETADDRESS,
SubjectAttribute::OrganizationName => Nid::ORGANIZATIONNAME,
SubjectAttribute::OrganizationalUnitName => Nid::ORGANIZATIONALUNITNAME,
SubjectAttribute::Name => Nid::NAME,
SubjectAttribute::GivenName => Nid::GIVENNAME,
SubjectAttribute::Initials => Nid::INITIALS,
SubjectAttribute::Title => Nid::TITLE,
SubjectAttribute::Surname => Nid::SURNAME,
SubjectAttribute::Pseudonym => Nid::PSEUDONYM,
SubjectAttribute::GenerationQualifier => Nid::GENERATIONQUALIFIER,
SubjectAttribute::FriendlyName => Nid::FRIENDLYNAME,
}
}
}

1
acmed/src/acme_proto.rs

@ -187,6 +187,7 @@ pub fn request_certificate(
cert.csr_digest,
domains.as_slice(),
ips.as_slice(),
&cert.subject_attributes,
)?;
cert.trace(&format!("new CSR:\n{}", csr.to_pem()?));
let csr = json!({

3
acmed/src/certificate.rs

@ -3,7 +3,7 @@ use crate::hooks::{self, ChallengeHookData, Hook, HookEnvData, HookType, PostOpe
use crate::identifier::{Identifier, IdentifierType};
use crate::logs::HasLogger;
use crate::storage::{certificate_files_exists, get_certificate, FileManager};
use acme_common::crypto::{HashFunction, KeyType, X509Certificate};
use acme_common::crypto::{HashFunction, KeyType, SubjectAttribute, X509Certificate};
use acme_common::error::Error;
use log::{debug, info, trace, warn};
use std::collections::{HashMap, HashSet};
@ -14,6 +14,7 @@ use std::time::Duration;
pub struct Certificate {
pub account_name: String,
pub identifiers: Vec<Identifier>,
pub subject_attributes: HashMap<SubjectAttribute, String>,
pub key_type: KeyType,
pub csr_digest: HashFunction,
pub kp_reuse: bool,

52
acmed/src/config.rs

@ -3,7 +3,7 @@ use crate::hooks;
use crate::identifier::IdentifierType;
use crate::storage::FileManager;
use acme_common::b64_decode;
use acme_common::crypto::{HashFunction, JwsSignatureAlgorithm, KeyType};
use acme_common::crypto::{HashFunction, JwsSignatureAlgorithm, KeyType, SubjectAttribute};
use acme_common::error::Error;
use glob::glob;
use log::info;
@ -24,6 +24,14 @@ macro_rules! set_cfg_attr {
};
}
macro_rules! push_subject_attr {
($hm: expr, $attr: expr, $attr_type: ident) => {
if let Some(v) = &$attr {
$hm.insert(SubjectAttribute::$attr_type, v.to_owned());
}
};
}
fn get_stdin(hook: &Hook) -> Result<hooks::HookStdin, Error> {
match &hook.stdin {
Some(file) => match &hook.stdin_str {
@ -379,6 +387,8 @@ pub struct Certificate {
pub account: String,
pub endpoint: String,
pub identifiers: Vec<Identifier>,
#[serde(default)]
pub subject_attributes: SubjectAttributes,
pub key_type: Option<String>,
pub csr_digest: Option<String>,
pub kp_reuse: Option<bool>,
@ -546,6 +556,46 @@ impl Identifier {
}
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SubjectAttributes {
pub country_name: Option<String>,
pub locality_name: Option<String>,
pub state_or_province_name: Option<String>,
pub street_address: Option<String>,
pub organization_name: Option<String>,
pub organizational_unit_name: Option<String>,
pub name: Option<String>,
pub given_name: Option<String>,
pub initials: Option<String>,
pub title: Option<String>,
pub surname: Option<String>,
pub pseudonym: Option<String>,
pub generation_qualifier: Option<String>,
pub friendly_name: Option<String>,
}
impl SubjectAttributes {
pub fn to_generic(&self) -> HashMap<SubjectAttribute, String> {
let mut ret = HashMap::new();
push_subject_attr!(ret, self.country_name, CountryName);
push_subject_attr!(ret, self.locality_name, LocalityName);
push_subject_attr!(ret, self.state_or_province_name, StateOrProvinceName);
push_subject_attr!(ret, self.street_address, StreetAddress);
push_subject_attr!(ret, self.organization_name, OrganizationName);
push_subject_attr!(ret, self.organizational_unit_name, OrganizationalUnitName);
push_subject_attr!(ret, self.name, Name);
push_subject_attr!(ret, self.given_name, GivenName);
push_subject_attr!(ret, self.initials, Initials);
push_subject_attr!(ret, self.title, Title);
push_subject_attr!(ret, self.surname, Surname);
push_subject_attr!(ret, self.pseudonym, Pseudonym);
push_subject_attr!(ret, self.generation_qualifier, GenerationQualifier);
push_subject_attr!(ret, self.friendly_name, FriendlyName);
ret
}
}
fn create_dir(path: &str) -> Result<(), Error> {
if Path::new(path).is_dir() {
Ok(())

1
acmed/src/main_event_loop.rs

@ -126,6 +126,7 @@ impl MainEventLoop {
let cert = Certificate {
account_name: crt.account.clone(),
identifiers: crt.get_identifiers()?,
subject_attributes: crt.subject_attributes.to_generic(),
key_type,
csr_digest: crt.get_csr_digest()?,
kp_reuse: crt.get_kp_reuse(),

32
man/en/acmed.toml.5

@ -257,6 +257,38 @@ The IP address.
.It Ic env Ar table
Table of environment variables that will be accessible from hooks.
.El
.It subject_attributes Ar table
Table where the certificate's subject attributes are specified. Possible keys are:
.Bl -dash -compact
.It
country_name
.It
locality_name
.It
state_or_province_name
.It
street_address
.It
organization_name
.It
organizational_unit_name
.It
name
.It
given_name
.It
initials
.It
title
.It
surname
.It
pseudonym
.It
generation_qualifier
.It
friendly_name
.El
.It Ic key_type Ar string
Name of the asymmetric cryptography algorithm used to generate the certificate's key pair. Possible values are:
.Bl -dash -compact

Loading…
Cancel
Save