1 /*
2 * (C) 2010,2014,2015,2018 Jack Lloyd
3 * (C) 2017 René Korthaus, Rohde & Schwarz Cybersecurity
4 *
5 * Botan is released under the Simplified BSD License (see license.txt)
6 */
7 
8 #include "cli.h"
9 
10 #if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
11 
12 #include <botan/certstor.h>
13 #include <botan/pk_keys.h>
14 #include <botan/pkcs8.h>
15 #include <botan/x509_ca.h>
16 #include <botan/x509cert.h>
17 #include <botan/x509path.h>
18 #include <botan/x509self.h>
19 #include <botan/data_src.h>
20 #include <botan/parsing.h>
21 
22 #if defined(BOTAN_HAS_OCSP)
23    #include <botan/ocsp.h>
24 #endif
25 
26 #if defined(BOTAN_HAS_CERTSTOR_SYSTEM)
27    #include <botan/certstor_system.h>
28 #endif
29 
30 namespace Botan_CLI {
31 
32 #if defined(BOTAN_HAS_CERTSTOR_SYSTEM)
33 
34 class Trust_Root_Info final : public Command
35    {
36    public:
Trust_Root_Info()37       Trust_Root_Info() : Command("trust_roots --dn --dn-only --display") {}
38 
group() const39       std::string group() const override
40          {
41          return "x509";
42          }
43 
description() const44       std::string description() const override
45          {
46          return "List certs in the system trust store";
47          }
48 
go()49       void go() override
50          {
51          Botan::System_Certificate_Store trust_roots;
52 
53          const auto dn_list = trust_roots.all_subjects();
54 
55          if(flag_set("dn-only"))
56             {
57             for(auto dn : dn_list)
58                output() << dn << "\n";
59             }
60          else
61             {
62             for(auto dn : dn_list)
63                {
64                // Some certstores have more than one cert with a particular DN
65                for(auto cert : trust_roots.find_all_certs(dn, std::vector<uint8_t>()))
66                   {
67                   if(flag_set("dn"))
68                      output() << "# " << dn << "\n";
69 
70                   if(flag_set("display"))
71                      output() << cert->to_string() << "\n";
72 
73                   output() << cert->PEM_encode() << "\n";
74                   }
75                }
76 
77             }
78          }
79 
80    };
81 
82 BOTAN_REGISTER_COMMAND("trust_roots", Trust_Root_Info);
83 
84 #endif
85 
86 class Sign_Cert final : public Command
87    {
88    public:
Sign_Cert()89       Sign_Cert()
90          : Command("sign_cert --ca-key-pass= --hash=SHA-256 "
91                    "--duration=365 --emsa= ca_cert ca_key pkcs10_req") {}
92 
group() const93       std::string group() const override
94          {
95          return "x509";
96          }
97 
description() const98       std::string description() const override
99          {
100          return "Create a CA-signed X.509 certificate from a PKCS #10 CSR";
101          }
102 
go()103       void go() override
104          {
105          Botan::X509_Certificate ca_cert(get_arg("ca_cert"));
106 
107          const std::string key_file = get_arg("ca_key");
108          const std::string pass = get_passphrase_arg("Password for " + key_file, "ca-key-pass");
109          const std::string emsa = get_arg("emsa");
110          const std::string hash = get_arg("hash");
111 
112          std::unique_ptr<Botan::Private_Key> key;
113          if(!pass.empty())
114             {
115             key.reset(Botan::PKCS8::load_key(key_file, rng(), pass));
116             }
117          else
118             {
119             key.reset(Botan::PKCS8::load_key(key_file, rng()));
120             }
121 
122          if(!key)
123             {
124             throw CLI_Error("Failed to load key from " + key_file);
125             }
126 
127          std::map<std::string, std::string> options;
128          if(emsa.empty() == false)
129             options["padding"] = emsa;
130 
131          Botan::X509_CA ca(ca_cert, *key, options, hash, rng());
132 
133          Botan::PKCS10_Request req(get_arg("pkcs10_req"));
134 
135          auto now = std::chrono::system_clock::now();
136 
137          Botan::X509_Time start_time(now);
138 
139          typedef std::chrono::duration<int, std::ratio<86400>> days;
140 
141          Botan::X509_Time end_time(now + days(get_arg_sz("duration")));
142 
143          Botan::X509_Certificate new_cert = ca.sign_request(req, rng(), start_time, end_time);
144 
145          output() << new_cert.PEM_encode();
146          }
147    };
148 
149 BOTAN_REGISTER_COMMAND("sign_cert", Sign_Cert);
150 
151 class Cert_Info final : public Command
152    {
153    public:
Cert_Info()154       Cert_Info() : Command("cert_info --fingerprint file") {}
155 
group() const156       std::string group() const override
157          {
158          return "x509";
159          }
160 
description() const161       std::string description() const override
162          {
163          return "Parse X.509 certificate and display data fields";
164          }
165 
go()166       void go() override
167          {
168          const std::string arg_file = get_arg("file");
169 
170          std::vector<uint8_t> data = slurp_file(get_arg("file"));
171 
172          Botan::DataSource_Memory in(data);
173 
174          while(!in.end_of_data())
175             {
176             try
177                {
178                Botan::X509_Certificate cert(in);
179 
180                try
181                   {
182                   output() << cert.to_string() << std::endl;
183                   }
184                catch(Botan::Exception& e)
185                   {
186                   // to_string failed - report the exception and continue
187                   output() << "X509_Certificate::to_string failed: " << e.what() << "\n";
188                   }
189 
190                if(flag_set("fingerprint"))
191                   output() << "Fingerprint: " << cert.fingerprint("SHA-256") << std::endl;
192                }
193             catch(Botan::Exception& e)
194                {
195                if(!in.end_of_data())
196                   {
197                   output() << "X509_Certificate parsing failed " << e.what() << "\n";
198                   }
199                }
200             }
201          }
202    };
203 
204 BOTAN_REGISTER_COMMAND("cert_info", Cert_Info);
205 
206 #if defined(BOTAN_HAS_OCSP) && defined(BOTAN_HAS_HTTP_UTIL)
207 
208 class OCSP_Check final : public Command
209    {
210    public:
OCSP_Check()211       OCSP_Check() : Command("ocsp_check --timeout=3000 subject issuer") {}
212 
group() const213       std::string group() const override
214          {
215          return "x509";
216          }
217 
description() const218       std::string description() const override
219          {
220          return "Verify an X.509 certificate against the issuers OCSP responder";
221          }
222 
go()223       void go() override
224          {
225          Botan::X509_Certificate subject(get_arg("subject"));
226          Botan::X509_Certificate issuer(get_arg("issuer"));
227          std::chrono::milliseconds timeout(get_arg_sz("timeout"));
228 
229          Botan::Certificate_Store_In_Memory cas;
230          cas.add_certificate(issuer);
231          Botan::OCSP::Response resp = Botan::OCSP::online_check(issuer, subject, &cas, timeout);
232 
233          auto status = resp.status_for(issuer, subject, std::chrono::system_clock::now());
234 
235          if(status == Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD)
236             {
237             output() << "OCSP check OK\n";
238             }
239          else
240             {
241             output() << "OCSP check failed " << Botan::Path_Validation_Result::status_string(status) << "\n";
242             }
243          }
244    };
245 
246 BOTAN_REGISTER_COMMAND("ocsp_check", OCSP_Check);
247 
248 #endif // OCSP && HTTP
249 
250 class Cert_Verify final : public Command
251    {
252    public:
Cert_Verify()253       Cert_Verify() : Command("cert_verify subject *ca_certs") {}
254 
group() const255       std::string group() const override
256          {
257          return "x509";
258          }
259 
description() const260       std::string description() const override
261          {
262          return "Verify if the passed X.509 certificate passes path validation";
263          }
264 
go()265       void go() override
266          {
267          Botan::X509_Certificate subject_cert(get_arg("subject"));
268          Botan::Certificate_Store_In_Memory trusted;
269 
270          for(auto const& certfile : get_arg_list("ca_certs"))
271             {
272             trusted.add_certificate(Botan::X509_Certificate(certfile));
273             }
274 
275          Botan::Path_Validation_Restrictions restrictions;
276 
277          Botan::Path_Validation_Result result =
278             Botan::x509_path_validate(subject_cert,
279                                       restrictions,
280                                       trusted);
281 
282          if(result.successful_validation())
283             {
284             output() << "Certificate passes validation checks\n";
285             }
286          else
287             {
288             output() << "Certificate did not validate - " << result.result_string() << "\n";
289             }
290          }
291    };
292 
293 BOTAN_REGISTER_COMMAND("cert_verify", Cert_Verify);
294 
295 class Gen_Self_Signed final : public Command
296    {
297    public:
Gen_Self_Signed()298       Gen_Self_Signed()
299          : Command("gen_self_signed key CN --country= --dns= "
300                    "--organization= --email= --path-limit=1 --days=365 --key-pass= --ca --hash=SHA-256 --emsa= --der") {}
301 
group() const302       std::string group() const override
303          {
304          return "x509";
305          }
306 
description() const307       std::string description() const override
308          {
309          return "Generate a self signed X.509 certificate";
310          }
311 
go()312       void go() override
313          {
314          const std::string key_file = get_arg("key");
315          const std::string passphrase = get_passphrase_arg("Passphrase for " + key_file, "key-pass");
316          std::unique_ptr<Botan::Private_Key> key(Botan::PKCS8::load_key(key_file, rng(), passphrase));
317 
318          if(!key)
319             {
320             throw CLI_Error("Failed to load key from " + get_arg("key"));
321             }
322 
323          const uint32_t lifetime = static_cast<uint32_t>(get_arg_sz("days") * 24 * 60 * 60);
324 
325          Botan::X509_Cert_Options opts("", lifetime);
326 
327          opts.common_name  = get_arg("CN");
328          opts.country      = get_arg("country");
329          opts.organization = get_arg("organization");
330          opts.email        = get_arg("email");
331          opts.more_dns = Botan::split_on(get_arg("dns"), ',');
332          const bool der_format = flag_set("der");
333 
334          std::string emsa = get_arg("emsa");
335 
336          if(emsa.empty() == false)
337             opts.set_padding_scheme(emsa);
338 
339          if(flag_set("ca"))
340             {
341             opts.CA_key(get_arg_sz("path-limit"));
342             }
343 
344          Botan::X509_Certificate cert = Botan::X509::create_self_signed_cert(opts, *key, get_arg("hash"), rng());
345 
346          if(der_format)
347             {
348             auto der = cert.BER_encode();
349             output().write(reinterpret_cast<const char*>(der.data()), der.size());
350             }
351          else
352             output() << cert.PEM_encode();
353          }
354    };
355 
356 BOTAN_REGISTER_COMMAND("gen_self_signed", Gen_Self_Signed);
357 
358 class Generate_PKCS10 final : public Command
359    {
360    public:
Generate_PKCS10()361       Generate_PKCS10()
362          : Command("gen_pkcs10 key CN --country= --organization= "
363                    "--ca --path-limit=1 --email= --dns= --ext-ku= --key-pass= --hash=SHA-256 --emsa=") {}
364 
group() const365       std::string group() const override
366          {
367          return "x509";
368          }
369 
description() const370       std::string description() const override
371          {
372          return "Generate a PKCS #10 certificate signing request (CSR)";
373          }
374 
go()375       void go() override
376          {
377          std::unique_ptr<Botan::Private_Key> key(Botan::PKCS8::load_key(get_arg("key"), rng(), get_arg("key-pass")));
378 
379          if(!key)
380             {
381             throw CLI_Error("Failed to load key from " + get_arg("key"));
382             }
383 
384          Botan::X509_Cert_Options opts;
385 
386          opts.common_name  = get_arg("CN");
387          opts.country      = get_arg("country");
388          opts.organization = get_arg("organization");
389          opts.email        = get_arg("email");
390          opts.more_dns     = Botan::split_on(get_arg("dns"), ',');
391 
392          if(flag_set("ca"))
393             {
394             opts.CA_key(get_arg_sz("path-limit"));
395             }
396 
397          for(std::string ext_ku : Botan::split_on(get_arg("ext-ku"), ','))
398             {
399             opts.add_ex_constraint(ext_ku);
400             }
401 
402          std::string emsa = get_arg("emsa");
403 
404          if(emsa.empty() == false)
405             opts.set_padding_scheme(emsa);
406 
407          Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *key, get_arg("hash"), rng());
408 
409          output() << req.PEM_encode();
410          }
411    };
412 
413 BOTAN_REGISTER_COMMAND("gen_pkcs10", Generate_PKCS10);
414 
415 }
416 
417 #endif
418