Repository: randombit/botan (cpp)

Issue:
Usage Question: How to generate intermediate CA through CLI
Hello,

Is it possible to generate an intermediate CA through CLI or do I have to use API to set Is CA as true?

I tried following CLI commands but I'm getting "Error: X509_CA: This certificate is not for a CA":

botan keygen --algo=ECDSA > ROOT.key
botan gen_self_signed ROOT.key Root --organization=test --hash=SHA-256 --ca > ROOT.crt
botan keygen --algo=ECDSA > Intermediate.key
botan gen_pkcs10 Intermediate.key Intermediate --organization=test --hash=SHA-256 --ext-ku=X509v3.BasicConstraints,X509v3.KeyUsage > Intermediate.csr
botan sign_cert --hash=SHA-256 --duration=365 ROOT.crt ROOT.key Intermediate.csr > Intermediate.crt
botan keygen --algo=ECDSA > Leaf.key
botan gen_pkcs10 Leaf.key Leaf --organization=test --hash=SHA-256 > Leaf.csr
botan sign_cert --hash=SHA-256 --duration=365 Intermediate.crt Intermediate.key Leaf.csr > Leaf.crt

"Error: X509_CA: This certificate is not for a CA"

I'm guessing --ext-ku just adds the field to pkcs10 request but bitset should be manually set?

Pull request description:
Allow creating intermediate CAs using CLI
Closes #1879

Patch:
diff --git a/src/cli/x509.cpp b/src/cli/x509.cpp
index e2b99a494..4c18b7ceb 100644
--- a/src/cli/x509.cpp
+++ b/src/cli/x509.cpp
@@ -239,7 +239,7 @@ class Gen_Self_Signed final : public Command
    public:
       Gen_Self_Signed()
          : Command("gen_self_signed key CN --country= --dns= "
-                   "--organization= --email= --days=365 --key-pass= --ca --hash=SHA-256 --emsa= --der") {}
+                   "--organization= --email= --path-limit=1 --days=365 --key-pass= --ca --hash=SHA-256 --emsa= --der") {}
 
       std::string group() const override
          {
@@ -280,7 +280,7 @@ class Gen_Self_Signed final : public Command
 
          if(flag_set("ca"))
             {
-            opts.CA_key();
+            opts.CA_key(get_arg_sz("path-limit"));
             }
 
          Botan::X509_Certificate cert = Botan::X509::create_self_signed_cert(opts, *key, get_arg("hash"), rng());
@@ -302,7 +302,7 @@ class Generate_PKCS10 final : public Command
    public:
       Generate_PKCS10()
          : Command("gen_pkcs10 key CN --country= --organization= "
-                   "--email= --dns= --ext-ku= --key-pass= --hash=SHA-256 --emsa=") {}
+                   "--ca --path-limit=1 --email= --dns= --ext-ku= --key-pass= --hash=SHA-256 --emsa=") {}
 
       std::string group() const override
          {
@@ -331,6 +331,11 @@ class Generate_PKCS10 final : public Command
          opts.email        = get_arg("email");
          opts.more_dns     = Botan::split_on(get_arg("dns"), ',');
 
+         if(flag_set("ca"))
+            {
+            opts.CA_key(get_arg_sz("path-limit"));
+            }
+
          for(std::string ext_ku : Botan::split_on(get_arg("ext-ku"), ','))
             {
             opts.add_ex_constraint(ext_ku);


Test patch:
diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py
index d09ecf611..9166489b9 100755
--- a/src/scripts/test_cli.py
+++ b/src/scripts/test_cli.py
@@ -438,6 +438,33 @@ def cli_cc_enc_tests():
     test_cli("cc_encrypt", ["8028028028028029", "pass"], "4308989841607208")
     test_cli("cc_decrypt", ["4308989841607208", "pass"], "8028028028028027")
 
+def cli_cert_issuance_tests():
+    tmp_dir = tempfile.mkdtemp(prefix='botan_cli')
+
+    root_key = os.path.join(tmp_dir, 'root.key')
+    root_crt = os.path.join(tmp_dir, 'root.crt')
+    int_key = os.path.join(tmp_dir, 'int.key')
+    int_crt = os.path.join(tmp_dir, 'int.crt')
+    int_csr = os.path.join(tmp_dir, 'int.csr')
+    leaf_key = os.path.join(tmp_dir, 'leaf.key')
+    leaf_crt = os.path.join(tmp_dir, 'leaf.crt')
+    leaf_csr = os.path.join(tmp_dir, 'leaf.csr')
+
+    test_cli("keygen", ["--params=2048", "--output=" + root_key], "")
+    test_cli("keygen", ["--params=2048", "--output=" + int_key], "")
+    test_cli("keygen", ["--params=2048", "--output=" + leaf_key], "")
+
+    test_cli("gen_self_signed",
+             [root_key, "Root", "--ca", "--path-limit=2", "--output="+root_crt], "")
+
+    test_cli("gen_pkcs10", "%s Intermediate --ca --output=%s" % (int_key, int_csr))
+    test_cli("sign_cert", "%s %s %s --output=%s" % (root_crt, root_key, int_csr, int_crt))
+
+    test_cli("gen_pkcs10", "%s Leaf --output=%s" % (leaf_key, leaf_csr))
+    test_cli("sign_cert", "%s %s %s --output=%s" % (int_crt, int_key, leaf_csr, leaf_crt))
+
+    test_cli("cert_verify" "%s %s %s" % (leaf_crt, int_crt, root_crt), "Certificate passes validation checks")
+
 def cli_timing_test_tests():
 
     timing_tests = ["bleichenbacher", "manger",
@@ -761,6 +788,7 @@ def main(args=None):
         cli_base64_tests,
         cli_bcrypt_tests,
         cli_cc_enc_tests,
+        cli_cert_issuance_tests,
         cli_compress_tests,
         cli_config_tests,
         cli_dl_group_info_tests,