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. 38
      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). 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 ## [0.11.0] - 2020-09-19
### Added ### Added

20
acme_common/src/crypto.rs

@ -7,10 +7,29 @@ mod key_type;
mod openssl_certificate; mod openssl_certificate;
mod openssl_hash; mod openssl_hash;
mod openssl_keys; mod openssl_keys;
mod openssl_subject_attribute;
mod openssl_version; mod openssl_version;
pub const CRT_NB_DAYS_VALIDITY: u32 = 7; 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)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum BaseHashFunction { pub enum BaseHashFunction {
Sha256, Sha256,
@ -54,4 +73,5 @@ pub use key_type::KeyType;
pub use openssl_certificate::{Csr, X509Certificate}; pub use openssl_certificate::{Csr, X509Certificate};
pub use openssl_hash::HashFunction; pub use openssl_hash::HashFunction;
pub use openssl_keys::{gen_keypair, KeyPair}; pub use openssl_keys::{gen_keypair, KeyPair};
pub use openssl_subject_attribute::SubjectAttribute;
pub use openssl_version::{get_lib_name, get_lib_version}; 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::b64_encode;
use crate::crypto::HashFunction; use crate::crypto::HashFunction;
use crate::error::Error; use crate::error::Error;
@ -8,7 +8,7 @@ use openssl::hash::MessageDigest;
use openssl::stack::Stack; use openssl::stack::Stack;
use openssl::x509::extension::{BasicConstraints, SubjectAlternativeName}; use openssl::x509::extension::{BasicConstraints, SubjectAlternativeName};
use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509Req, X509ReqBuilder, X509}; use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509Req, X509ReqBuilder, X509};
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::net::IpAddr; use std::net::IpAddr;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
@ -43,9 +43,18 @@ impl Csr {
digest: HashFunction, digest: HashFunction,
domains: &[String], domains: &[String],
ips: &[String], ips: &[String],
subject_attributes: &HashMap<SubjectAttribute, String>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut builder = X509ReqBuilder::new()?; let mut builder = X509ReqBuilder::new()?;
builder.set_pubkey(&key_pair.inner_key)?; 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 ctx = builder.x509v3_context(None);
let mut san = SubjectAlternativeName::new(); let mut san = SubjectAlternativeName::new();
for dns in domains.iter() { 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, cert.csr_digest,
domains.as_slice(), domains.as_slice(),
ips.as_slice(), ips.as_slice(),
&cert.subject_attributes,
)?; )?;
cert.trace(&format!("new CSR:\n{}", csr.to_pem()?)); cert.trace(&format!("new CSR:\n{}", csr.to_pem()?));
let csr = json!({ 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::identifier::{Identifier, IdentifierType};
use crate::logs::HasLogger; use crate::logs::HasLogger;
use crate::storage::{certificate_files_exists, get_certificate, FileManager}; 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 acme_common::error::Error;
use log::{debug, info, trace, warn}; use log::{debug, info, trace, warn};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -14,6 +14,7 @@ use std::time::Duration;
pub struct Certificate { pub struct Certificate {
pub account_name: String, pub account_name: String,
pub identifiers: Vec<Identifier>, pub identifiers: Vec<Identifier>,
pub subject_attributes: HashMap<SubjectAttribute, String>,
pub key_type: KeyType, pub key_type: KeyType,
pub csr_digest: HashFunction, pub csr_digest: HashFunction,
pub kp_reuse: bool, pub kp_reuse: bool,

52
acmed/src/config.rs

@ -3,7 +3,7 @@ use crate::hooks;
use crate::identifier::IdentifierType; use crate::identifier::IdentifierType;
use crate::storage::FileManager; use crate::storage::FileManager;
use acme_common::b64_decode; 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 acme_common::error::Error;
use glob::glob; use glob::glob;
use log::info; 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> { fn get_stdin(hook: &Hook) -> Result<hooks::HookStdin, Error> {
match &hook.stdin { match &hook.stdin {
Some(file) => match &hook.stdin_str { Some(file) => match &hook.stdin_str {
@ -379,6 +387,8 @@ pub struct Certificate {
pub account: String, pub account: String,
pub endpoint: String, pub endpoint: String,
pub identifiers: Vec<Identifier>, pub identifiers: Vec<Identifier>,
#[serde(default)]
pub subject_attributes: SubjectAttributes,
pub key_type: Option<String>, pub key_type: Option<String>,
pub csr_digest: Option<String>, pub csr_digest: Option<String>,
pub kp_reuse: Option<bool>, 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> { fn create_dir(path: &str) -> Result<(), Error> {
if Path::new(path).is_dir() { if Path::new(path).is_dir() {
Ok(()) Ok(())

1
acmed/src/main_event_loop.rs

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

38
man/en/acmed.toml.5

@ -172,7 +172,7 @@ A mailto URI as defined by
This URI cannot contains neither "hfields" nor more than one "addr-spec" in the "to" component. This URI cannot contains neither "hfields" nor more than one "addr-spec" in the "to" component.
.El .El
.It Cm key_type Ar string .It Cm key_type Ar string
Name of the asymmetric cryptography algorithm used to generate the key pair. Possible values are :
Name of the asymmetric cryptography algorithm used to generate the key pair. Possible values are:
.Bl -dash -compact .Bl -dash -compact
.It .It
rsa2048 rsa2048
@ -257,8 +257,40 @@ The IP address.
.It Ic env Ar table .It Ic env Ar table
Table of environment variables that will be accessible from hooks. Table of environment variables that will be accessible from hooks.
.El .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 .It Ic key_type Ar string
Name of the asymmetric cryptography algorithm used to generate the certificate's key pair. Possible values are :
Name of the asymmetric cryptography algorithm used to generate the certificate's key pair. Possible values are:
.Bl -dash -compact .Bl -dash -compact
.It .It
rsa2048 rsa2048
@ -271,7 +303,7 @@ ecdsa_p256
ecdsa_p384 ecdsa_p384
.El .El
.It Ic csr_digest Ar string .It Ic csr_digest Ar string
Name of the certificate's signing request digest algorithm. Possible values are :
Name of the certificate's signing request digest algorithm. Possible values are:
.Bl -dash -compact .Bl -dash -compact
.It .It
sha256 sha256

Loading…
Cancel
Save