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