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#[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::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::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_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}