niftygate_certificate/command/
generate.rs

1use crate::{constants::*, load, parse, RawCertificate};
2use anyhow::Result;
3use rcgen::{
4  BasicConstraints, Certificate, CertificateParams, CustomExtension, DistinguishedName,
5  ExtendedKeyUsagePurpose, GeneralSubtree, IsCa, KeyIdMethod, KeyPair, KeyUsagePurpose,
6  NameConstraints, SanType, SignatureAlgorithm,
7};
8use time::OffsetDateTime;
9use std::{fmt::Debug, path::PathBuf};
10use structopt::StructOpt;
11use thiserror::Error;
12// use time::OffsetDateTime;
13
14#[derive(Debug, Error)]
15enum CommandError {
16  #[error("Missing output path for either key or certificate. CLI argument validation should have prevented this. (╯°□°)╯︵ ┻━┻")]
17  MissingOutputPath,
18}
19
20#[derive(Debug, StructOpt)]
21#[structopt(about = "Generates a Certificate")]
22pub struct Command {
23  #[structopt(env, long, takes_value = false)]
24  is_authority: bool,
25  #[structopt(env, long, takes_value = false)]
26  unconstrained: bool,
27  #[structopt(env, long, takes_value = false)]
28  without_default_key_usages: bool,
29  #[structopt(env, long, takes_value = false)]
30  without_authority_key_identifier: bool,
31  #[structopt(env, long, takes_value = false)]
32  with_all_key_usages: bool,
33  #[structopt(env, long, takes_value = false)]
34  with_all_extended_key_usages: bool,
35
36  #[structopt(env, long, short = "l", value_name = "U8", default_value = "0")]
37  constrain_path_length: u8,
38  #[structopt(env, long, short = "N", value_name = "U64")]
39  serial_number: Option<u64>,
40
41  #[structopt(env, long, short = "I", value_name = "Path", parse(try_from_str = load::certificate))]
42  in_certificate: Option<RawCertificate>,
43  #[structopt(env, long, short = "i", value_name = "Path", parse(try_from_str = load::key_pair))]
44  in_key: Option<KeyPair>,
45
46  #[structopt(env, long, short = "O", value_name = "Path", requires = "out-key")]
47  out_certificate: Option<PathBuf>,
48  #[structopt(
49    env,
50    long,
51    short = "o",
52    value_name = "Path",
53    requires = "out-certificate"
54  )]
55  out_key: Option<PathBuf>,
56
57  #[structopt(env, long, short = "C", value_name = "Path", requires = "ca-key", parse(try_from_str = load::certificate))]
58  ca_certificate: Option<RawCertificate>,
59  #[structopt(env, long, short = "c", value_name = "Path", requires = "ca-certificate", parse(try_from_str = load::key_pair))]
60  ca_key: Option<KeyPair>,
61
62  // #[structopt(env, long, short = "B", value_name = "RFC3339", default_value = "1975-01-01T00:00:00+00:00", parse(try_from_str = parse::datetime_utc))]
63  #[structopt(env, long, short = "B", value_name = "RFC3339", default_value = "1975-01-01T00:00:00+00:00", parse(try_from_str = parse::offset_date_time))]
64  valid_not_before: OffsetDateTime,
65  // #[structopt(env, long, short = "A", value_name = "RFC3339", default_value = "2999-04-20T03:13:37+00:00", parse(try_from_str = parse::datetime_utc))]
66  #[structopt(env, long, short = "A", value_name = "RFC3339", default_value = "2999-04-20T03:13:37+00:00", parse(try_from_str = parse::offset_date_time))]
67  valid_not_after: OffsetDateTime,
68
69  #[structopt(env, long, short = "n", value_name = "DN", default_value = "CN=niftygate certificate", parse(try_from_str = parse::distinguished_name))]
70  distinguished_name: DistinguishedName,
71  #[structopt(env, long, short = "s", value_name = "Type=Value", parse(try_from_str = parse::san_type))]
72  subject_alt_name: Vec<SanType>,
73  #[structopt(env, long, short = "p", value_name = "Type=Value", parse(try_from_str = parse::name_constraint))]
74  name_constraint_permit: Vec<GeneralSubtree>,
75  #[structopt(env, long, short = "e", value_name = "Type=Value", parse(try_from_str = parse::name_constraint))]
76  name_constraint_exclude: Vec<GeneralSubtree>,
77
78  #[structopt(
79    env,
80    long,
81    short = "S",
82    value_name = "Algorithm",
83    case_insensitive = true,
84    default_value = ALG_ECDSA_P256_SHA256,
85    possible_value = ALG_ECDSA_P256_SHA256,
86    possible_value = ALG_ECDSA_P384_SHA384,
87    possible_value = ALG_ED25519,
88    possible_value = ALG_RSA_SHA256,
89    possible_value = ALG_RSA_SHA384,
90    possible_value = ALG_RSA_SHA512,
91    parse(try_from_str = parse::signature_algorithm)
92  )]
93  signature_algorithm: &'static SignatureAlgorithm,
94  #[structopt(
95    env,
96    long,
97    short = "M",
98    value_name = "Method",
99    case_insensitive = true,
100    default_value = KIDM_SHA256,
101    possible_value = KIDM_SHA256,
102    possible_value = KIDM_SHA384,
103    possible_value = KIDM_SHA512,
104    parse(try_from_str = parse::key_identifier_method)
105  )]
106  key_identifier_method: KeyIdMethod,
107  #[structopt(
108    env,
109    long,
110    short = "U",
111    value_name = "Purpose",
112    case_insensitive = true,
113    possible_value = KU_CONTENT_COMMITMENT,
114    possible_value = KU_CRL_SIGN,
115    possible_value = KU_DATA_ENCIPHERMENT,
116    possible_value = KU_DECIPHER_ONLY,
117    possible_value = KU_DIGITAL_SIGNATURE,
118    possible_value = KU_ENCIPHER_ONLY,
119    possible_value = KU_KEY_AGREEMENT,
120    possible_value = KU_KEY_CERT_SIGN,
121    possible_value = KU_KEY_ENCIPHERMENT,
122    parse(try_from_str = parse::key_usage_purpose)
123  )]
124  key_usage: Vec<KeyUsagePurpose>,
125  #[structopt(
126    env,
127    long,
128    short = "E",
129    value_name = "Purpose",
130    case_insensitive = true,
131    possible_value = EKU_ANY,
132    possible_value = EKU_CLIENT_AUTH,
133    possible_value = EKU_CODE_SIGNING,
134    possible_value = EKU_EMAIL_PROTECTION,
135    possible_value = EKU_OSCP_SIGNING,
136    possible_value = EKU_SERVER_AUTH,
137    possible_value = EKU_TIME_STAMPING,
138    parse(try_from_str = parse::extended_key_usage_purpose)
139  )]
140  extended_key_usage: Vec<ExtendedKeyUsagePurpose>,
141}
142
143impl Command {
144  pub(crate) fn execute(self) -> Result<()> {
145    let mut params = CertificateParams::from(&self);
146    params.key_pair = self.in_key;
147    let certificate = Certificate::from_params(params)?;
148
149    let ca = match (self.ca_certificate, self.ca_key) {
150      (Some(raw), Some(key_pair)) => {
151        let ca_cert = raw.0;
152        let params = CertificateParams::from_ca_cert_der(&ca_cert, key_pair)?;
153        let ca = Certificate::from_params(params)?;
154        Some(ca)
155      }
156      _ => None,
157    };
158
159    let secret = certificate.serialize_private_key_pem();
160    let public = match ca {
161      Some(ca) => certificate.serialize_pem_with_signer(&ca)?,
162      None => certificate.serialize_pem()?,
163    };
164
165    match (self.out_certificate, self.out_key) {
166      (None, None) => {
167        println!("{}", public);
168        println!("{}", secret);
169      }
170      (Some(cert_path), Some(key_path)) => {
171        std::fs::write(cert_path, public.as_bytes())?;
172        std::fs::write(key_path, secret.as_bytes())?;
173      }
174      _ => return Err(CommandError::MissingOutputPath.into()),
175    }
176
177    Ok(())
178  }
179}
180
181impl From<&Command> for CertificateParams {
182  fn from(command: &Command) -> Self {
183    let mut params = CertificateParams::default();
184    params.alg = command.into();
185    params.custom_extensions = command.into();
186    params.distinguished_name = command.into();
187    params.extended_key_usages = command.into();
188    params.is_ca = command.into();
189    params.key_identifier_method = command.into();
190    // params.key_pair = command.into();
191    params.key_usages = command.into();
192    params.name_constraints = command.into();
193    params.not_after = command.valid_not_after;
194    params.not_before = command.valid_not_before;
195    params.serial_number = command.serial_number;
196    params.subject_alt_names = command.into();
197    params.use_authority_key_identifier_extension = !command.without_authority_key_identifier;
198    params
199  }
200}
201
202impl From<Command> for Option<KeyPair> {
203  fn from(command: Command) -> Self {
204    command.in_key
205  }
206}
207
208impl From<&Command> for &SignatureAlgorithm {
209  fn from(command: &Command) -> Self {
210    command.signature_algorithm
211  }
212}
213
214impl From<&Command> for DistinguishedName {
215  fn from(command: &Command) -> Self {
216    command.distinguished_name.clone()
217  }
218}
219
220impl From<&Command> for KeyIdMethod {
221  fn from(command: &Command) -> Self {
222    command.key_identifier_method.clone()
223  }
224}
225
226impl From<&Command> for Vec<SanType> {
227  fn from(command: &Command) -> Self {
228    command.subject_alt_name.clone()
229  }
230}
231
232impl From<&Command> for IsCa {
233  fn from(command: &Command) -> Self {
234    if command.is_authority {
235      if command.unconstrained {
236        IsCa::Ca(BasicConstraints::Unconstrained)
237      } else {
238        IsCa::Ca(BasicConstraints::Constrained(command.constrain_path_length))
239      }
240    } else {
241      IsCa::SelfSignedOnly
242    }
243  }
244}
245
246impl From<&Command> for Option<NameConstraints> {
247  fn from(command: &Command) -> Self {
248    if command.name_constraint_permit.is_empty() && command.name_constraint_exclude.is_empty() {
249      None
250    } else {
251      Some(NameConstraints {
252        permitted_subtrees: command.name_constraint_permit.clone(),
253        excluded_subtrees: command.name_constraint_exclude.clone(),
254      })
255    }
256  }
257}
258
259impl From<&Command> for Vec<KeyUsagePurpose> {
260  fn from(command: &Command) -> Self {
261    if command.with_all_key_usages {
262      return KU_ALL
263        .iter()
264        .flat_map(|&name| parse::key_usage_purpose(name))
265        .collect();
266    }
267
268    let mut key_usages = command.key_usage.clone();
269    if !command.without_default_key_usages {
270      if command.is_authority {
271        key_usages.push(KeyUsagePurpose::KeyCertSign);
272      } else {
273        key_usages.push(KeyUsagePurpose::DigitalSignature);
274        key_usages.push(KeyUsagePurpose::KeyEncipherment);
275      }
276    }
277    key_usages
278  }
279}
280
281impl From<&Command> for Vec<ExtendedKeyUsagePurpose> {
282  fn from(command: &Command) -> Self {
283    if command.with_all_extended_key_usages {
284      return EKU_ALL
285        .iter()
286        .flat_map(|&name| parse::extended_key_usage_purpose(name))
287        .collect();
288    }
289
290    let mut extended_key_usages = command.extended_key_usage.clone();
291    if !command.without_default_key_usages && !command.is_authority {
292      extended_key_usages.push(ExtendedKeyUsagePurpose::ServerAuth);
293    }
294    extended_key_usages
295  }
296}
297
298impl From<&Command> for Vec<CustomExtension> {
299  fn from(_command: &Command) -> Self {
300    Vec::new()
301  }
302}