diff --git a/SOURCES/0001-Generate-manpage.patch b/SOURCES/0001-Generate-manpage.patch
index f19bc36..cc55444 100644
--- a/SOURCES/0001-Generate-manpage.patch
+++ b/SOURCES/0001-Generate-manpage.patch
@@ -1,7 +1,7 @@
 From 71e2451c6ba4d5f17de9e24687b66b93f2e58954 Mon Sep 17 00:00:00 2001
 From: Stephen Gallagher <sgallagh@redhat.com>
 Date: Mon, 17 Sep 2018 09:58:25 -0400
-Subject: [PATCH 1/4] Generate manpage
+Subject: [PATCH 1/6] Generate manpage
 
 Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
 ---
@@ -12,9 +12,7 @@ diff --git a/meson.build b/meson.build
 index e6f33475cce6891d17656bcd10e1afabd43bdc07..a2ca4ba1472bfff61fbbd30ba1ddc7ecc89e723c 100644
 --- a/meson.build
 +++ b/meson.build
-@@ -5,11 +5,11 @@ project('sscg', 'c',
-           'c_std=gnu99',
-           'warning_level=1',
+@@ -7,7 +7,7 @@ project('sscg', 'c',
            'b_asneeded=true',
          ],
          license : 'MIT',
@@ -23,11 +21,7 @@ index e6f33475cce6891d17656bcd10e1afabd43bdc07..a2ca4ba1472bfff61fbbd30ba1ddc7ec
  
  cc = meson.get_compiler('c')
  test_cflags = [
-   '-Wpointer-arith',
-   '-Wmissing-declarations',
-@@ -139,5 +139,25 @@ cdata.set('version', meson.project_version())
- configure_file(
-     input : 'config.h.in',
+@@ -141,3 +141,23 @@ configure_file(
      output : 'config.h',
      configuration : cdata)
  
@@ -52,5 +46,5 @@ index e6f33475cce6891d17656bcd10e1afabd43bdc07..a2ca4ba1472bfff61fbbd30ba1ddc7ec
 +        'man8'),
 +)
 -- 
-2.19.1
+2.23.0
 
diff --git a/SOURCES/0002-Adjust-defaults-based-on-system-security-level.patch b/SOURCES/0002-Adjust-defaults-based-on-system-security-level.patch
index 7fe01d8..3fd62ce 100644
--- a/SOURCES/0002-Adjust-defaults-based-on-system-security-level.patch
+++ b/SOURCES/0002-Adjust-defaults-based-on-system-security-level.patch
@@ -1,7 +1,7 @@
 From 942d9fa4f582a372af3d0bd499f073760dec2335 Mon Sep 17 00:00:00 2001
 From: Stephen Gallagher <sgallagh@redhat.com>
 Date: Tue, 27 Nov 2018 13:24:37 -0500
-Subject: [PATCH 2/4] Adjust defaults based on system security level
+Subject: [PATCH 2/6] Adjust defaults based on system security level
 
 Also permit arbitrary keylengths.
 
@@ -29,9 +29,7 @@ diff --git a/include/sscg.h b/include/sscg.h
 index 2bd42bbee965c754efb91febd10b6a94af6f508e..3e97cfe49a5cd8fc734ecf43a94156e376227eb7 100644
 --- a/include/sscg.h
 +++ b/include/sscg.h
-@@ -137,10 +137,11 @@ struct sscg_options
-   const char *hostname;
-   char **subject_alt_names;
+@@ -139,6 +139,7 @@ struct sscg_options
  
    /* Encryption requirements */
    int key_strength;
@@ -39,15 +37,11 @@ index 2bd42bbee965c754efb91febd10b6a94af6f508e..3e97cfe49a5cd8fc734ecf43a94156e3
    const EVP_MD *hash_fn;
  
    /* Output Files */
-   char *ca_file;
-   char *ca_key_file;
 diff --git a/meson.build b/meson.build
 index a2ca4ba1472bfff61fbbd30ba1ddc7ecc89e723c..c7b08ed3d6dff686f08a90ca869ba5881a9e8aaa 100644
 --- a/meson.build
 +++ b/meson.build
-@@ -32,10 +32,11 @@ foreach cflag: test_cflags
-   endif
- endforeach
+@@ -34,6 +34,7 @@ endforeach
  
  pkg = import('pkgconfig')
  crypto = dependency('libcrypto')
@@ -55,11 +49,7 @@ index a2ca4ba1472bfff61fbbd30ba1ddc7ecc89e723c..c7b08ed3d6dff686f08a90ca869ba588
  path_utils = dependency('path_utils')
  talloc = dependency('talloc')
  
- popt = dependency(
-     'popt',
-@@ -47,10 +48,14 @@ if popt.found()
- else
-     popt = subproject('popt').get_variable('libpopt_a')
+@@ -49,6 +50,10 @@ else
      popt_incdirs = include_directories('subprojects/popt')
  endif
  
@@ -70,11 +60,7 @@ index a2ca4ba1472bfff61fbbd30ba1ddc7ecc89e723c..c7b08ed3d6dff686f08a90ca869ba588
  sscg_lib_srcs = [
      'src/authority.c',
      'src/bignum.c',
-     'src/key.c',
-     'src/service.c',
-@@ -68,10 +73,11 @@ sscg_lib_hdrs = [
- sscg_lib = static_library(
-     'sscg',
+@@ -70,6 +75,7 @@ sscg_lib = static_library(
      sources : sscg_lib_srcs,
      dependencies : [
          crypto,
@@ -82,11 +68,7 @@ index a2ca4ba1472bfff61fbbd30ba1ddc7ecc89e723c..c7b08ed3d6dff686f08a90ca869ba588
          talloc,
      ],
      install : false,
-     pic : true,
- )
-@@ -133,13 +139,13 @@ init_bignum_test = executable(
-     install : false,
- )
+@@ -135,9 +141,9 @@ init_bignum_test = executable(
  test('init_bignum_test', init_bignum_test)
  
  cdata = configuration_data()
@@ -98,15 +80,11 @@ index a2ca4ba1472bfff61fbbd30ba1ddc7ecc89e723c..c7b08ed3d6dff686f08a90ca869ba588
      output : 'config.h',
      configuration : cdata)
  
- # Generate a manpage from the POPT documentation
- help2man = find_program('help2man')
 diff --git a/src/sscg.c b/src/sscg.c
 index b2c7cbbfd9dc69d9f55a18bc91ed6023c0e64c2e..85a42404aa94524b560755d506b893300a4414cd 100644
 --- a/src/sscg.c
 +++ b/src/sscg.c
-@@ -15,30 +15,80 @@
-     along with sscg.  If not, see <http://www.gnu.org/licenses/>.
- 
+@@ -17,6 +17,7 @@
      Copyright 2017 by Stephen Gallagher <sgallagh@redhat.com>
  */
  
@@ -114,8 +92,7 @@ index b2c7cbbfd9dc69d9f55a18bc91ed6023c0e64c2e..85a42404aa94524b560755d506b89330
  #include <popt.h>
  #include <stdlib.h>
  #include <stdio.h>
- #include <string.h>
- #include <talloc.h>
+@@ -25,6 +26,7 @@
  #include <path_utils.h>
  #include <unistd.h>
  #include <openssl/evp.h>
@@ -123,7 +100,7 @@ index b2c7cbbfd9dc69d9f55a18bc91ed6023c0e64c2e..85a42404aa94524b560755d506b89330
  #include <sys/param.h>
  
  #include "config.h"
- #include "include/sscg.h"
+@@ -32,11 +34,59 @@
  #include "include/authority.h"
  #include "include/service.h"
  
@@ -184,11 +161,7 @@ index b2c7cbbfd9dc69d9f55a18bc91ed6023c0e64c2e..85a42404aa94524b560755d506b89330
    return 0;
  }
  
- static void
- print_options (struct sscg_options *opts)
-@@ -115,10 +165,11 @@ main (int argc, const char **argv)
- {
-   int ret, sret, opt;
+@@ -117,6 +167,7 @@ main (int argc, const char **argv)
    size_t i;
    poptContext pc;
    struct sscg_options *options;
@@ -196,11 +169,7 @@ index b2c7cbbfd9dc69d9f55a18bc91ed6023c0e64c2e..85a42404aa94524b560755d506b89330
  
    char *country = NULL;
    char *state = NULL;
-   char *locality = NULL;
-   char *organization = NULL;
-@@ -170,10 +221,13 @@ main (int argc, const char **argv)
- 
-   ret = set_default_options (options);
+@@ -172,6 +223,9 @@ main (int argc, const char **argv)
    if (ret != EOK)
      goto done;
  
@@ -210,11 +179,7 @@ index b2c7cbbfd9dc69d9f55a18bc91ed6023c0e64c2e..85a42404aa94524b560755d506b89330
    options->verbosity = SSCG_DEFAULT;
    struct poptOption long_options[] = {
      POPT_AUTOHELP{ "quiet",
-                    'q',
-                    POPT_ARG_VAL,
-@@ -291,11 +345,11 @@ main (int argc, const char **argv)
-       '\0',
-       POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT,
+@@ -293,7 +347,7 @@ main (int argc, const char **argv)
        &options->key_strength,
        0,
        _ ("Strength of the certificate private keys in bits."),
@@ -223,11 +188,7 @@ index b2c7cbbfd9dc69d9f55a18bc91ed6023c0e64c2e..85a42404aa94524b560755d506b89330
      {
        "hash-alg",
        '\0',
-       POPT_ARG_STRING,
-       &hash_alg,
-@@ -527,15 +581,15 @@ main (int argc, const char **argv)
-           options->subject_alt_names[i + 1] = NULL;
-           i++;
+@@ -529,11 +583,11 @@ main (int argc, const char **argv)
          }
      }
  
@@ -242,8 +203,6 @@ index b2c7cbbfd9dc69d9f55a18bc91ed6023c0e64c2e..85a42404aa94524b560755d506b89330
        ret = EINVAL;
        goto done;
      }
- 
-   if (!hash_alg)
 -- 
-2.19.1
+2.23.0
 
diff --git a/SOURCES/0003-Adjust-hash-defaults-based-on-system-security-level.patch b/SOURCES/0003-Adjust-hash-defaults-based-on-system-security-level.patch
index d3d1aee..66e8224 100644
--- a/SOURCES/0003-Adjust-hash-defaults-based-on-system-security-level.patch
+++ b/SOURCES/0003-Adjust-hash-defaults-based-on-system-security-level.patch
@@ -1,7 +1,7 @@
 From 298015e8a7cf35cc0de581203b44826d2ae1d406 Mon Sep 17 00:00:00 2001
 From: Stephen Gallagher <sgallagh@redhat.com>
 Date: Wed, 28 Nov 2018 08:00:08 -0500
-Subject: [PATCH 3/4] Adjust hash defaults based on system security level
+Subject: [PATCH 3/6] Adjust hash defaults based on system security level
 
 Unlike the key-strength, this does not set a minimum level because
 it's not a simple calculation. We will have to rely on libcrypto
@@ -17,9 +17,7 @@ diff --git a/include/sscg.h b/include/sscg.h
 index 3e97cfe49a5cd8fc734ecf43a94156e376227eb7..fc90b81a0060af28529f3be6922b1b1501559300 100644
 --- a/include/sscg.h
 +++ b/include/sscg.h
-@@ -138,10 +138,11 @@ struct sscg_options
-   char **subject_alt_names;
- 
+@@ -140,6 +140,7 @@ struct sscg_options
    /* Encryption requirements */
    int key_strength;
    int minimum_key_strength;
@@ -27,15 +25,11 @@ index 3e97cfe49a5cd8fc734ecf43a94156e376227eb7..fc90b81a0060af28529f3be6922b1b15
    const EVP_MD *hash_fn;
  
    /* Output Files */
-   char *ca_file;
-   char *ca_key_file;
 diff --git a/src/sscg.c b/src/sscg.c
 index 85a42404aa94524b560755d506b893300a4414cd..58855f764480d24d6c0f57460b22a3a83281e37e 100644
 --- a/src/sscg.c
 +++ b/src/sscg.c
-@@ -64,28 +64,38 @@ set_default_options (struct sscg_options *opts)
-     {
-     case 0:
+@@ -66,14 +66,21 @@ set_default_options (struct sscg_options *opts)
      case 1:
      case 2:
        /* Security level 2 and below permits lower key-strengths, but SSCG
@@ -60,9 +54,7 @@ index 85a42404aa94524b560755d506b893300a4414cd..58855f764480d24d6c0f57460b22a3a8
  
      default:
        /* Unknown security level. Default to the highest we know about */
-       fprintf (stderr,
-                "Unknown system security level %d. Defaulting to highest-known "
-                "level.\n",
+@@ -83,7 +90,10 @@ set_default_options (struct sscg_options *opts)
                 security_level);
        /* Fall through */
  
@@ -74,11 +66,7 @@ index 85a42404aa94524b560755d506b893300a4414cd..58855f764480d24d6c0f57460b22a3a8
      }
  
    opts->minimum_key_strength = opts->key_strength;
-   return 0;
- }
-@@ -175,11 +185,10 @@ main (int argc, const char **argv)
-   char *organization = NULL;
-   char *organizational_unit = NULL;
+@@ -177,7 +187,6 @@ main (int argc, const char **argv)
    char *email = NULL;
    char *hostname = NULL;
    char *packagename;
@@ -86,11 +74,7 @@ index 85a42404aa94524b560755d506b893300a4414cd..58855f764480d24d6c0f57460b22a3a8
    char **alternative_names = NULL;
  
    char *ca_file = NULL;
-   char *ca_key_file = NULL;
-   char *cert_file = NULL;
-@@ -349,14 +358,14 @@ main (int argc, const char **argv)
-       _ ("Strength of the certificate private keys in bits."),
-       minimum_key_strength_help },
+@@ -351,10 +360,10 @@ main (int argc, const char **argv)
      {
        "hash-alg",
        '\0',
@@ -104,11 +88,7 @@ index 85a42404aa94524b560755d506b893300a4414cd..58855f764480d24d6c0f57460b22a3a8
        _ ("{sha256,sha384,sha512}"),
      },
      {
-       "ca-file",
-       '\0',
-@@ -590,21 +599,14 @@ main (int argc, const char **argv)
-                options->minimum_key_strength);
-       ret = EINVAL;
+@@ -592,17 +601,10 @@ main (int argc, const char **argv)
        goto done;
      }
  
@@ -130,8 +110,6 @@ index 85a42404aa94524b560755d506b893300a4414cd..58855f764480d24d6c0f57460b22a3a8
    if (!options->hash_fn)
      {
        fprintf (stderr, "Unsupported hashing algorithm.");
-       ret = EINVAL;
-       goto done;
 -- 
-2.19.1
+2.23.0
 
diff --git a/SOURCES/0004-Properly-check-all-return-values.patch b/SOURCES/0004-Properly-check-all-return-values.patch
index e4c367a..9225fe7 100644
--- a/SOURCES/0004-Properly-check-all-return-values.patch
+++ b/SOURCES/0004-Properly-check-all-return-values.patch
@@ -1,7 +1,7 @@
 From 9e4497d1dd2a337be1f69e0cfb24ce8080690ccf Mon Sep 17 00:00:00 2001
 From: Stephen Gallagher <sgallagh@redhat.com>
 Date: Wed, 28 Nov 2018 09:16:29 -0500
-Subject: [PATCH 4/4] Properly check all return values
+Subject: [PATCH 4/6] Properly check all return values
 
 Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
 ---
@@ -14,9 +14,7 @@ diff --git a/src/authority.c b/src/authority.c
 index b735868416b7fb5d016f0854baf0f27cd5f98b26..4e0dccc6c1210beffb38acd9f7dfb6108ca4a4ad 100644
 --- a/src/authority.c
 +++ b/src/authority.c
-@@ -178,10 +178,11 @@ create_private_CA (TALLOC_CTX *mem_ctx,
-     }
-   sk_X509_EXTENSION_push (ca_certinfo->extensions, ex);
+@@ -180,6 +180,7 @@ create_private_CA (TALLOC_CTX *mem_ctx,
  
    /* Finalize the CSR */
    ret = sscg_x509v3_csr_finalize (ca_certinfo, pkey, csr);
@@ -24,15 +22,11 @@ index b735868416b7fb5d016f0854baf0f27cd5f98b26..4e0dccc6c1210beffb38acd9f7dfb610
  
    if (options->verbosity >= SSCG_DEBUG)
      {
-       fprintf (stderr, "DEBUG: Writing CA CSR to ./debug-ca.csr\n");
-       BIO *ca_csr_out = BIO_new_file ("./debug-ca.csr", "w");
 diff --git a/src/service.c b/src/service.c
 index b292e94063f032fd3c34a8134702063ea46bfa0c..34c976dbe905528000b181c24d1fa95da3cd1377 100644
 --- a/src/service.c
 +++ b/src/service.c
-@@ -124,10 +124,11 @@ create_service_cert (TALLOC_CTX *mem_ctx,
-   ret = sscg_x509v3_csr_new (tmp_ctx, svc_certinfo, pkey, &csr);
-   CHECK_OK (ret);
+@@ -126,6 +126,7 @@ create_service_cert (TALLOC_CTX *mem_ctx,
  
    /* Finalize the CSR */
    ret = sscg_x509v3_csr_finalize (svc_certinfo, pkey, csr);
@@ -40,15 +34,11 @@ index b292e94063f032fd3c34a8134702063ea46bfa0c..34c976dbe905528000b181c24d1fa95d
  
    if (options->verbosity >= SSCG_DEBUG)
      {
-       fprintf (stderr,
-                "DEBUG: Writing service certificate CSR to ./debug-svc.csr\n");
 diff --git a/src/x509.c b/src/x509.c
 index 6d152fc969d745cc5cf085116c8688866f9d6ab4..18f0627bc64e7cb503a9e81c36dbe726186d1144 100644
 --- a/src/x509.c
 +++ b/src/x509.c
-@@ -39,10 +39,11 @@ sscg_generate_serial (TALLOC_CTX *mem_ctx, struct sscg_bignum **serial)
-     {
-       return ENOMEM;
+@@ -41,6 +41,7 @@ sscg_generate_serial (TALLOC_CTX *mem_ctx, struct sscg_bignum **serial)
      }
  
    ret = sscg_init_bignum (tmp_ctx, 0, &bn);
@@ -56,8 +46,6 @@ index 6d152fc969d745cc5cf085116c8688866f9d6ab4..18f0627bc64e7cb503a9e81c36dbe726
  
    /* We'll create a random number of sizeof(unsigned long) - 1 bits
         to use as the serial. We use unsigned long to ensure that it
-        could be printed by BN_get_word() later. We omit the last bit
-        in order to ensure that we can't randomly get 0xffffffffL, which
 -- 
-2.19.1
+2.23.0
 
diff --git a/SOURCES/0005-Add-password-support-for-private-keys.patch b/SOURCES/0005-Add-password-support-for-private-keys.patch
new file mode 100644
index 0000000..4e21a9a
--- /dev/null
+++ b/SOURCES/0005-Add-password-support-for-private-keys.patch
@@ -0,0 +1,273 @@
+From 7190d08e1a166455e767769492b8c6b9f41bc0da Mon Sep 17 00:00:00 2001
+From: Stephen Gallagher <sgallagh@redhat.com>
+Date: Wed, 5 Jun 2019 17:08:23 -0400
+Subject: [PATCH 5/6] Add password support for private keys
+
+Fixes: https://github.com/sgallagher/sscg/issues/14
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+---
+ include/sscg.h |   7 +++
+ src/sscg.c     | 152 ++++++++++++++++++++++++++++++++++++++++++++++++-
+ 2 files changed, 157 insertions(+), 2 deletions(-)
+
+diff --git a/include/sscg.h b/include/sscg.h
+index fc90b81a0060af28529f3be6922b1b1501559300..ce9a7916e9432d0843d82af61d56ea7238ded682 100644
+--- a/include/sscg.h
++++ b/include/sscg.h
+@@ -141,8 +141,15 @@ struct sscg_options
+   int key_strength;
+   int minimum_key_strength;
+   char *hash_alg;
++  char *cipher_alg;
++  const EVP_CIPHER *cipher;
+   const EVP_MD *hash_fn;
+ 
++  bool ca_key_pass_prompt;
++  char *ca_key_pass;
++  bool cert_key_pass_prompt;
++  char *cert_key_pass;
++
+   /* Output Files */
+   char *ca_file;
+   char *ca_key_file;
+diff --git a/src/sscg.c b/src/sscg.c
+index 58855f764480d24d6c0f57460b22a3a83281e37e..9dc926c77038105ca881a612cccd1913bc2d42f1 100644
+--- a/src/sscg.c
++++ b/src/sscg.c
+@@ -97,6 +97,9 @@ set_default_options (struct sscg_options *opts)
+     }
+ 
+   opts->minimum_key_strength = opts->key_strength;
++
++  opts->cipher_alg = talloc_strdup (opts, "aes-256-cbc");
++
+   return 0;
+ }
+ 
+@@ -170,6 +173,42 @@ done:
+   return ret;
+ }
+ 
++
++/* This function takes a copy of a string into a talloc hierarchy and memsets
++ * the original string to zeroes to avoid leaking it when that memory is freed.
++ */
++static char *
++sscg_secure_string_steal (TALLOC_CTX *mem_ctx, char *src)
++{
++  char *dest = talloc_strdup (mem_ctx, src);
++
++  memset (src, 0, strlen (src));
++
++  return dest;
++}
++
++
++static int
++sscg_options_destructor (TALLOC_CTX *opts)
++{
++  struct sscg_options *options =
++    talloc_get_type_abort (opts, struct sscg_options);
++
++  /* Zero out the memory before freeing it so we don't leak passwords */
++  if (options->ca_key_pass)
++    {
++      memset (options->ca_key_pass, 0, strlen (options->ca_key_pass));
++    }
++
++  if (options->cert_key_pass)
++    {
++      memset (options->cert_key_pass, 0, strlen (options->cert_key_pass));
++    }
++
++  return 0;
++}
++
++
+ int
+ main (int argc, const char **argv)
+ {
+@@ -196,8 +235,11 @@ main (int argc, const char **argv)
+ 
+   int ca_mode = 0644;
+   int ca_key_mode = 0600;
++  char *ca_key_password = NULL;
++
+   int cert_mode = 0644;
+   int cert_key_mode = 0600;
++  char *cert_key_password = NULL;
+ 
+   char *create_mode = NULL;
+ 
+@@ -227,6 +269,7 @@ main (int argc, const char **argv)
+ 
+   options = talloc_zero (main_ctx, struct sscg_options);
+   CHECK_MEM (options);
++  talloc_set_destructor ((TALLOC_CTX *)options, sscg_options_destructor);
+ 
+   ret = set_default_options (options);
+   if (ret != EOK)
+@@ -366,6 +409,16 @@ main (int argc, const char **argv)
+       _ ("Hashing algorithm to use for signing."),
+       _ ("{sha256,sha384,sha512}"),
+     },
++    {
++      "cipher-alg",
++      '\0',
++      POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT,
++      &options->cipher_alg,
++      0,
++      _ ("Cipher to use for encrypting key files."),
++      _ ("{des-ede3-cbc,aes-256-cbc}"),
++    },
++
+     {
+       "ca-file",
+       '\0',
+@@ -404,6 +457,29 @@ main (int argc, const char **argv)
+       _ ("File mode of the created CA key. (default: 0600)"),
+       _ ("0600"),
+     },
++    {
++      "ca-key-password",
++      '\0',
++      POPT_ARG_STRING,
++      &ca_key_password,
++      0,
++      _ ("Provide a password for the CA key file. Note that this will be "
++         "visible in the process table for all users, so it should be used "
++         "for testing purposes only. Use --ca-keypassfile or "
++         "--ca-key-password-prompt for secure password entry."),
++      NULL
++    },
++
++    {
++      "ca-key-password-prompt",
++      'C',
++      POPT_ARG_NONE,
++      &options->ca_key_pass_prompt,
++      0,
++      _ ("Prompt to enter a password for the CA key file."),
++      NULL
++    },
++
+     {
+       "cert-file",
+       '\0',
+@@ -442,6 +518,29 @@ main (int argc, const char **argv)
+       _ ("File mode of the created certificate key. (default: 0600)"),
+       _ ("0600"),
+     },
++    {
++      "cert-key-password",
++      'p',
++      POPT_ARG_STRING,
++      &cert_key_password,
++      0,
++      _ ("Provide a password for the service key file. Note that this will be "
++         "visible in the process table for all users, so this flag should be "
++         "used for testing purposes only. Use --cert-keypassfile or "
++         "--cert-key-password-prompt for secure password entry."),
++      NULL
++    },
++
++    {
++      "cert-key-password-prompt",
++      'P',
++      POPT_ARG_NONE,
++      &options->cert_key_pass_prompt,
++      0,
++      _ ("Prompt to enter a password for the service key file."),
++      NULL
++    },
++
+     POPT_TABLEEND
+   };
+ 
+@@ -592,6 +691,20 @@ main (int argc, const char **argv)
+         }
+     }
+ 
++  /* Password handling */
++  if (ca_key_password)
++    {
++      options->ca_key_pass =
++        sscg_secure_string_steal (options, ca_key_password);
++    }
++
++  if (cert_key_password)
++    {
++      options->cert_key_pass =
++        sscg_secure_string_steal (options, cert_key_password);
++    }
++
++
+   if (options->key_strength < options->minimum_key_strength)
+     {
+       fprintf (stderr,
+@@ -601,6 +714,15 @@ main (int argc, const char **argv)
+       goto done;
+     }
+ 
++  /* Make sure we have a valid cipher */
++  options->cipher = EVP_get_cipherbyname (options->cipher_alg);
++  if (!options->cipher)
++    {
++      fprintf (stderr, "Invalid cipher specified: %s\n", options->cipher_alg);
++      ret = EINVAL;
++      goto done;
++    }
++
+   /* TODO: restrict this to approved hashes.
+    * For now, we'll only list SHA[256|384|512] in the help */
+   options->hash_fn = EVP_get_digestbyname (options->hash_alg);
+@@ -696,8 +818,21 @@ main (int argc, const char **argv)
+   cert_key_out = BIO_new_file (options->cert_key_file, create_mode);
+   CHECK_BIO (cert_key_out, options->cert_key_file);
+ 
++  /* This function has a default mechanism for prompting for the
++   * password if it is passed a cipher and gets a NULL password.
++   *
++   * Only pass the cipher if we have a password or were instructed
++   * to prompt for one.
++   */
+   sret = PEM_write_bio_PrivateKey (
+-    cert_key_out, svc_key->evp_pkey, NULL, NULL, 0, NULL, NULL);
++    cert_key_out,
++    svc_key->evp_pkey,
++    options->cert_key_pass_prompt || options->cert_key_pass ? options->cipher :
++                                                              NULL,
++    (unsigned char *)options->cert_key_pass,
++    options->cert_key_pass ? strlen (options->cert_key_pass) : 0,
++    NULL,
++    NULL);
+   CHECK_SSL (sret, PEM_write_bio_PrivateKey (svc));
+   BIO_get_fp (cert_key_out, &fp);
+ 
+@@ -776,8 +911,21 @@ main (int argc, const char **argv)
+         }
+       CHECK_BIO (ca_key_out, options->ca_key_file);
+ 
++      /* This function has a default mechanism for prompting for the
++       * password if it is passed a cipher and gets a NULL password.
++       *
++       * Only pass the cipher if we have a password or were instructed
++       * to prompt for one.
++       */
+       sret = PEM_write_bio_PrivateKey (
+-        ca_key_out, cakey->evp_pkey, NULL, NULL, 0, NULL, NULL);
++        ca_key_out,
++        cakey->evp_pkey,
++        options->ca_key_pass_prompt || options->ca_key_pass ? options->cipher :
++                                                              NULL,
++        (unsigned char *)options->ca_key_pass,
++        options->ca_key_pass ? strlen (options->ca_key_pass) : 0,
++        NULL,
++        NULL);
+       CHECK_SSL (sret, PEM_write_bio_PrivateKey (CA));
+       BIO_get_fp (ca_key_out, &fp);
+       if (options->verbosity >= SSCG_DEBUG)
+-- 
+2.23.0
+
diff --git a/SOURCES/0006-Allow-specifying-keyfile-password-by-file.patch b/SOURCES/0006-Allow-specifying-keyfile-password-by-file.patch
new file mode 100644
index 0000000..6487436
--- /dev/null
+++ b/SOURCES/0006-Allow-specifying-keyfile-password-by-file.patch
@@ -0,0 +1,153 @@
+From 9cb7daa54708dcf5e6500cd20ec7b1cc2f6f6350 Mon Sep 17 00:00:00 2001
+From: Stephen Gallagher <sgallagh@redhat.com>
+Date: Mon, 10 Jun 2019 10:15:42 -0400
+Subject: [PATCH 6/6] Allow specifying keyfile password by file
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+---
+ src/sscg.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 84 insertions(+)
+
+diff --git a/src/sscg.c b/src/sscg.c
+index 9dc926c77038105ca881a612cccd1913bc2d42f1..a02e4df66c6cf9ec1865f425b4a15da82fbfdc72 100644
+--- a/src/sscg.c
++++ b/src/sscg.c
+@@ -34,6 +34,10 @@
+ #include "include/authority.h"
+ #include "include/service.h"
+ 
++
++/* Same as OpenSSL CLI */
++#define MAX_PW_LEN 1024
++
+ static int
+ get_security_level (void)
+ {
+@@ -209,6 +213,44 @@ sscg_options_destructor (TALLOC_CTX *opts)
+ }
+ 
+ 
++static char *
++sscg_read_pw_file (TALLOC_CTX *mem_ctx, char *path)
++{
++  int i;
++  BIO *pwdbio = NULL;
++  char tpass[MAX_PW_LEN];
++  char *tmp = NULL;
++  char *password = NULL;
++
++  pwdbio = BIO_new_file (path, "r");
++  if (pwdbio == NULL)
++    {
++      fprintf (stderr, "Can't open file %s\n", path);
++      return NULL;
++    }
++
++  i = BIO_gets (pwdbio, tpass, MAX_PW_LEN);
++  BIO_free_all (pwdbio);
++  pwdbio = NULL;
++
++  if (i <= 0)
++    {
++      fprintf (stderr, "Error reading password from BIO\n");
++      return NULL;
++    }
++
++  tmp = strchr (tpass, '\n');
++  if (tmp != NULL)
++    *tmp = 0;
++
++  password = talloc_strdup (mem_ctx, tpass);
++
++  memset (tpass, 0, MAX_PW_LEN);
++
++  return password;
++}
++
++
+ int
+ main (int argc, const char **argv)
+ {
+@@ -236,10 +278,12 @@ main (int argc, const char **argv)
+   int ca_mode = 0644;
+   int ca_key_mode = 0600;
+   char *ca_key_password = NULL;
++  char *ca_key_passfile = NULL;
+ 
+   int cert_mode = 0644;
+   int cert_key_mode = 0600;
+   char *cert_key_password = NULL;
++  char *cert_key_passfile = NULL;
+ 
+   char *create_mode = NULL;
+ 
+@@ -470,6 +514,16 @@ main (int argc, const char **argv)
+       NULL
+     },
+ 
++    {
++      "ca-key-passfile",
++      '\0',
++      POPT_ARG_STRING,
++      &ca_key_passfile,
++      0,
++      _ ("A file containing the password to encrypt the CA key file."),
++      NULL
++    },
++
+     {
+       "ca-key-password-prompt",
+       'C',
+@@ -531,6 +585,16 @@ main (int argc, const char **argv)
+       NULL
+     },
+ 
++    {
++      "cert-key-passfile",
++      '\0',
++      POPT_ARG_STRING,
++      &cert_key_passfile,
++      0,
++      _ ("A file containing the password to encrypt the service key file."),
++      NULL
++    },
++
+     {
+       "cert-key-password-prompt",
+       'P',
+@@ -697,12 +761,32 @@ main (int argc, const char **argv)
+       options->ca_key_pass =
+         sscg_secure_string_steal (options, ca_key_password);
+     }
++  else if (ca_key_passfile)
++    {
++      options->ca_key_pass = sscg_read_pw_file (options, ca_key_passfile);
++      if (!options->ca_key_pass)
++        {
++          fprintf (
++            stderr, "Failed to read passphrase from %s", ca_key_passfile);
++          goto done;
++        }
++    }
+ 
+   if (cert_key_password)
+     {
+       options->cert_key_pass =
+         sscg_secure_string_steal (options, cert_key_password);
+     }
++  else if (cert_key_passfile)
++    {
++      options->cert_key_pass = sscg_read_pw_file (options, cert_key_passfile);
++      if (!options->cert_key_pass)
++        {
++          fprintf (
++            stderr, "Failed to read passphrase from %s", cert_key_passfile);
++          goto done;
++        }
++    }
+ 
+ 
+   if (options->key_strength < options->minimum_key_strength)
+-- 
+2.23.0
+
diff --git a/SOURCES/0007-Add-support-for-client-certificates-and-dhparams.patch b/SOURCES/0007-Add-support-for-client-certificates-and-dhparams.patch
new file mode 100644
index 0000000..e22236e
--- /dev/null
+++ b/SOURCES/0007-Add-support-for-client-certificates-and-dhparams.patch
@@ -0,0 +1,2651 @@
+From ceed1c19b6002164482eb358570a91a9563ce694 Mon Sep 17 00:00:00 2001
+From: Tim Burke <tim.burke@gmail.com>
+Date: Wed, 2 Oct 2019 13:10:23 -0700
+Subject: [PATCH 7/7] Add support for client certificates and dhparams
+
+Resolves: rhbz#1720667
+
+Add --crl-file option
+
+... to (optionally) create an empty Certificate Revocation List file.
+
+Default the mode for the file to 0644, similar to the default for the CA
+certificate. User can override this with a new --crl-mode option.
+
+Closes #10.
+
+Add function for DH parameter generation
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Generate DH parameters file
+
+Adds CLI options to enable and control dhparam file generation.
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Add serverAuth extendedKeyUsage for server certificates
+
+Related to https://github.com/sgallagher/sscg/issues/13
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Rename create_service_cert() to create_cert()
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Use a common macro for default file modes
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Add I/O utility routines
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Rework output file handling
+
+SSCG will now verify that it can open all of the files before it
+starts processing through them. In addition, it now has better
+validation that it isn't storing two keys into the same file, or
+dhparams/CRL in a file with other content.
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Check for invalid file combinations
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Add support for client certificates
+
+Fixes: https://github.com/sgallagher/sscg/issues/13
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+---
+ include/{service.h => cert.h} |  19 +-
+ include/dhparams.h            |  42 ++
+ include/io_utils.h            |  93 +++++
+ include/sscg.h                |  99 ++++-
+ meson.build                   |  51 ++-
+ meson_options.txt             |  24 ++
+ src/cert.c                    | 191 +++++++++
+ src/dhparams.c                | 146 +++++++
+ src/io_utils.c                | 424 +++++++++++++++++++
+ src/service.c                 | 164 --------
+ src/sscg.c                    | 753 +++++++++++++++++++++-------------
+ test/dhparams_test.c          | 106 +++++
+ 12 files changed, 1651 insertions(+), 461 deletions(-)
+ rename include/{service.h => cert.h} (67%)
+ create mode 100644 include/dhparams.h
+ create mode 100644 include/io_utils.h
+ create mode 100644 meson_options.txt
+ create mode 100644 src/cert.c
+ create mode 100644 src/dhparams.c
+ create mode 100644 src/io_utils.c
+ delete mode 100644 src/service.c
+ create mode 100644 test/dhparams_test.c
+
+diff --git a/include/service.h b/include/cert.h
+similarity index 67%
+rename from include/service.h
+rename to include/cert.h
+index 20083caf905f8e70360539053134c0e9702c304a..11d04e50ebb2e08af781d0aa14b9aec930ff2f50 100644
+--- a/include/service.h
++++ b/include/cert.h
+@@ -21,15 +21,16 @@
+ #include "x509.h"
+ #include "key.h"
+ 
+-#ifndef _SERVICE_H
+-#define _SERVICE_H
++#ifndef _SSCG_CERT_H
++#define _SSCG_CERT_H
+ 
+ int
+-create_service_cert (TALLOC_CTX *mem_ctx,
+-                     const struct sscg_options *options,
+-                     struct sscg_x509_cert *ca_cert,
+-                     struct sscg_evp_pkey *ca_key,
+-                     struct sscg_x509_cert **_svc_cert,
+-                     struct sscg_evp_pkey **_svc_key);
++create_cert (TALLOC_CTX *mem_ctx,
++             const struct sscg_options *options,
++             struct sscg_x509_cert *ca_cert,
++             struct sscg_evp_pkey *ca_key,
++             enum sscg_cert_type type,
++             struct sscg_x509_cert **_svc_cert,
++             struct sscg_evp_pkey **_svc_key);
+ 
+-#endif /* _SERVICE_H */
++#endif /* _SSCG_CERT_H */
+diff --git a/include/dhparams.h b/include/dhparams.h
+new file mode 100644
+index 0000000000000000000000000000000000000000..baa4fb3b297c5747fcce20f6950239779f71f19a
+--- /dev/null
++++ b/include/dhparams.h
+@@ -0,0 +1,42 @@
++/*
++    This file is part of sscg.
++
++    sscg is free software: you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation, either version 3 of the License, or
++    (at your option) any later version.
++
++    sscg is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with sscg.  If not, see <http://www.gnu.org/licenses/>.
++
++    Copyright 2019 by Stephen Gallagher <sgallagh@redhat.com>
++*/
++
++#ifndef _SSCG_DHPARAMS_H
++#define _SSCG_DHPARAMS_H
++
++#include <talloc.h>
++
++#include "include/sscg.h"
++
++struct sscg_dhparams
++{
++  int prime_len;
++  int generator;
++  DH *dh;
++  BN_GENCB *cb;
++};
++
++int
++create_dhparams (TALLOC_CTX *mem_ctx,
++                 enum sscg_verbosity options,
++                 int prime_len,
++                 int generator,
++                 struct sscg_dhparams **_dhparams);
++
++#endif /* _SSCG_DHPARAMS_H */
+diff --git a/include/io_utils.h b/include/io_utils.h
+new file mode 100644
+index 0000000000000000000000000000000000000000..6a89a476b3d982447b6603153c6765835cd67464
+--- /dev/null
++++ b/include/io_utils.h
+@@ -0,0 +1,93 @@
++/*
++    This file is part of sscg.
++
++    sscg is free software: you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation, either version 3 of the License, or
++    (at your option) any later version.
++
++    sscg is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with sscg.  If not, see <http://www.gnu.org/licenses/>.
++
++    Copyright 2019 by Stephen Gallagher <sgallagh@redhat.com>
++*/
++
++#ifndef _SSCG_IO_UTILS_H
++#define _SSCG_IO_UTILS_H
++
++#include <openssl/ssl.h>
++#include <stdbool.h>
++#include <talloc.h>
++
++#include "include/sscg.h"
++
++
++struct sscg_stream
++{
++  BIO *bio;
++  char *path;
++  int mode;
++  int filetypes;
++};
++
++
++int
++sscg_normalize_path (TALLOC_CTX *mem_ctx,
++                     const char *path,
++                     char **_normalized_path);
++
++
++struct sscg_stream *
++sscg_io_utils_get_stream_by_path (struct sscg_stream **streams,
++                                  const char *normalized_path);
++
++
++struct sscg_stream *
++sscg_io_utils_get_stream_by_type (struct sscg_stream **streams,
++                                  enum sscg_file_type filetype);
++
++
++BIO *
++sscg_io_utils_get_bio_by_type (struct sscg_stream **streams,
++                               enum sscg_file_type filetype);
++
++
++const char *
++sscg_io_utils_get_path_by_type (struct sscg_stream **streams,
++                                enum sscg_file_type filetype);
++
++
++/**
++ * sscg_io_utils_add_output_file:
++ * @streams: The array of streams from the sscg_options
++ * @filetype:
++ * @path: The path to the file on disk.
++ * @mode: The filesystem mode this file should have when written to disk.
++ * See chmod(1) for the possible values.
++ * @overwrite: If true, replace any existing file at @normalized_path. If
++ * false, opening will fail if it already exists and return an error.
++ *
++ * Prepares all output filenames to be opened. Files are not created until
++ * sscg_io_utils_open_output_files() is called.
++ */
++int
++sscg_io_utils_add_output_file (struct sscg_stream **streams,
++                               enum sscg_file_type filetype,
++                               const char *path,
++                               int mode);
++
++
++int
++sscg_io_utils_open_output_files (struct sscg_stream **streams, bool overwrite);
++
++/* If this function fails, some of the output files may be left as 0400 */
++int
++sscg_io_utils_finalize_output_files (struct sscg_stream **streams);
++
++
++#endif /* _SSCG_IO_UTILS_H */
+diff --git a/include/sscg.h b/include/sscg.h
+index ce9a7916e9432d0843d82af61d56ea7238ded682..2744404c25c68ed905ca621bb955e0c04b33ca81 100644
+--- a/include/sscg.h
++++ b/include/sscg.h
+@@ -27,6 +27,8 @@
+ #include <talloc.h>
+ #include <stdint.h>
+ 
++#include "include/io_utils.h"
++
+ #ifndef _SSCG_H
+ #define _SSCG_H
+ 
+@@ -108,6 +110,13 @@
+     }                                                                         \
+   while (0)
+ 
++
++#define SSCG_CERT_DEFAULT_MODE 0644
++#define SSCG_CERT_DEFAULT_MODE_HELP _ ("0644")
++#define SSCG_KEY_DEFAULT_MODE 0600
++#define SSCG_KEY_DEFAULT_MODE_HELP _ ("0600")
++
++
+ enum sscg_verbosity
+ {
+   SSCG_QUIET = -1,
+@@ -116,6 +125,75 @@ enum sscg_verbosity
+   SSCG_DEBUG
+ };
+ 
++extern int verbosity;
++
++const char *sscg_get_verbosity_name (enum sscg_verbosity);
++
++#define SSCG_LOG(_level, _format, ...)                                        \
++  do                                                                          \
++    {                                                                         \
++      if (verbosity >= _level)                                                \
++        {                                                                     \
++          printf ("%s", sscg_get_verbosity_name (_level));                    \
++          printf (_format, ##__VA_ARGS__);                                    \
++        }                                                                     \
++    }                                                                         \
++  while (0)
++
++#define SSCG_ERROR(_format, ...)                                              \
++  do                                                                          \
++    {                                                                         \
++      if (verbosity > SSCG_QUIET)                                             \
++        {                                                                     \
++          fprintf (stderr, "ERROR: ");                                        \
++          fprintf (stderr, _format, ##__VA_ARGS__);                           \
++        }                                                                     \
++    }                                                                         \
++  while (0)
++
++
++enum sscg_file_type
++{
++  SSCG_FILE_TYPE_UNKNOWN = -1,
++  SSCG_FILE_TYPE_CA,
++  SSCG_FILE_TYPE_CA_KEY,
++  SSCG_FILE_TYPE_SVC,
++  SSCG_FILE_TYPE_SVC_KEY,
++  SSCG_FILE_TYPE_CLIENT,
++  SSCG_FILE_TYPE_CLIENT_KEY,
++  SSCG_FILE_TYPE_CRL,
++  SSCG_FILE_TYPE_DHPARAMS,
++
++  SSCG_NUM_FILE_TYPES
++};
++
++#define SSCG_FILE_TYPE_KEYS                                                   \
++  ((1 << SSCG_FILE_TYPE_CA_KEY) | (1 << SSCG_FILE_TYPE_SVC_KEY) |             \
++   (1 << SSCG_FILE_TYPE_CLIENT_KEY))
++
++#define SSCG_FILE_TYPE_SVC_TYPES                                              \
++  ((1 << SSCG_FILE_TYPE_SVC) | (1 << SSCG_FILE_TYPE_SVC_KEY))
++
++#define SSCG_FILE_TYPE_CLIENT_TYPES                                           \
++  ((1 << SSCG_FILE_TYPE_CLIENT) | (1 << SSCG_FILE_TYPE_CLIENT_KEY))
++
++#define SSCG_FILE_TYPE_CA_TYPES                                               \
++  ((1 << SSCG_FILE_TYPE_CA) | (1 << SSCG_FILE_TYPE_CA_KEY))
++
++const char *
++sscg_get_file_type_name (enum sscg_file_type _type);
++
++#define GET_BIO(_type) sscg_io_utils_get_bio_by_type (options->streams, _type)
++
++#define GET_PATH(_type)                                                       \
++  sscg_io_utils_get_path_by_type (options->streams, _type)
++
++#define ANNOUNCE_WRITE(_type)                                                 \
++  SSCG_LOG (SSCG_DEFAULT,                                                     \
++            "Wrote %s to %s\n",                                               \
++            sscg_get_file_type_name (_type),                                  \
++            GET_PATH (_type));
++
+ struct sscg_options
+ {
+   /* How noisy to be when printing information */
+@@ -149,15 +227,28 @@ struct sscg_options
+   char *ca_key_pass;
+   bool cert_key_pass_prompt;
+   char *cert_key_pass;
++  bool client_key_pass_prompt;
++  char *client_key_pass;
+ 
+   /* Output Files */
+-  char *ca_file;
+-  char *ca_key_file;
+-  char *cert_file;
+-  char *cert_key_file;
++  struct sscg_stream **streams;
++
++  /* Diffie-Hellman Parameters */
++  int dhparams_prime_len;
++  int dhparams_generator;
+ 
+   /* Overwrite the output files */
+   bool overwrite;
+ };
+ 
++
++enum sscg_cert_type
++{
++  SSCG_CERT_TYPE_UNKNOWN = -1,
++  SSCG_CERT_TYPE_SERVER,
++  SSCG_CERT_TYPE_CLIENT,
++
++  SSCG_NUM_CERT_TYPES
++};
++
+ #endif /* _SSCG_H */
+diff --git a/meson.build b/meson.build
+index c7b08ed3d6dff686f08a90ca869ba5881a9e8aaa..eb339ea8c768adab6d576736fbe476b83529e78d 100644
+--- a/meson.build
++++ b/meson.build
+@@ -52,21 +52,30 @@ endif
+ 
+ has_get_sec_level = cc.has_function(
+     'SSL_CTX_get_security_level',
+-    dependencies: [ ssl])
++    dependencies: [ ssl ])
++
++has_generator_3 = cc.has_header_symbol(
++    'openssl/dh.h',
++    'DH_GENERATOR_3',
++    dependencies: [ ssl ])
+ 
+ sscg_lib_srcs = [
+     'src/authority.c',
+     'src/bignum.c',
++    'src/cert.c',
++    'src/dhparams.c',
++    'src/io_utils.c',
+     'src/key.c',
+-    'src/service.c',
+     'src/x509.c',
+ ]
+ 
+ sscg_lib_hdrs = [
+     'include/authority.h',
+     'include/bignum.h',
++    'include/cert.h',
++    'include/dhparams.h',
++    'include/io_utils.h',
+     'include/key.h',
+-    'include/service.h',
+     'include/x509.h',
+ ]
+ 
+@@ -140,6 +149,42 @@ init_bignum_test = executable(
+ )
+ test('init_bignum_test', init_bignum_test)
+ 
++dhparams_test = executable(
++    'dhparams_test',
++    'test/dhparams_test.c',
++    link_with : sscg_lib,
++    dependencies: [],
++    install : false
++)
++
++# Test generating 512-bit, 1024-bit and 2048-bit DH params with multiple
++# generators. 4096-bit and larger takes over ten minutes, so it's excluded from
++# the test suite by default.
++prime_lengths = [ 512, 1024 ]
++dhparam_timeout = 120
++
++if get_option('run_slow_tests')
++    prime_lengths = prime_lengths + [ 2048, 4096 ]
++    dhparam_timeout = 900
++endif
++
++generators = [ 2, 5 ]
++
++if (has_generator_3)
++    generators += [ 3 ]
++endif
++
++foreach prime_len : prime_lengths
++    foreach g : generators
++        test('dhparams_test_' + prime_len.to_string() + '_' + g.to_string(),
++             dhparams_test,
++             args: [ prime_len.to_string(), g.to_string() ],
++             timeout: dhparam_timeout)
++    endforeach
++endforeach
++
++
++
+ cdata = configuration_data()
+ cdata.set_quoted('PACKAGE_VERSION', meson.project_version())
+ cdata.set('HAVE_SSL_CTX_GET_SECURITY_LEVEL', has_get_sec_level)
+diff --git a/meson_options.txt b/meson_options.txt
+new file mode 100644
+index 0000000000000000000000000000000000000000..1d8ff959b3c3d3f2aa6dc928ba38efeedfbe5c96
+--- /dev/null
++++ b/meson_options.txt
+@@ -0,0 +1,24 @@
++# This file is part of sscg.
++#
++# sscg is free software: you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation, either version 3 of the License, or
++# (at your option) any later version.
++#
++# sscg is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++
++# You should have received a copy of the GNU General Public License
++# along with sscg.  If not, see <http://www.gnu.org/licenses/>.
++#
++# Copyright 2019 by Stephen Gallagher <sgallagh@redhat.com>
++
++# Generating 4096-bit Diffie-Hellman parameters can take over ten minutes on a
++# fast system. We skip testing it by default.
++
++# Some tests take a long time (dozens of seconds or even minutes)
++# For general development, we will skip them and run them only in the CI
++# environment.
++option('run_slow_tests', type : 'boolean', value : false)
+diff --git a/src/cert.c b/src/cert.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..0377d1357a3881a9705fcb09fdfe2a9c78cc5ed4
+--- /dev/null
++++ b/src/cert.c
+@@ -0,0 +1,191 @@
++/*
++    This file is part of sscg.
++
++    sscg is free software: you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation, either version 3 of the License, or
++    (at your option) any later version.
++
++    sscg is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with sscg.  If not, see <http://www.gnu.org/licenses/>.
++
++    Copyright 2017 by Stephen Gallagher <sgallagh@redhat.com>
++*/
++
++
++#include "include/sscg.h"
++#include "include/cert.h"
++#include "include/x509.h"
++#include "include/key.h"
++
++int
++create_cert (TALLOC_CTX *mem_ctx,
++             const struct sscg_options *options,
++             struct sscg_x509_cert *ca_cert,
++             struct sscg_evp_pkey *ca_key,
++             enum sscg_cert_type type,
++             struct sscg_x509_cert **_cert,
++             struct sscg_evp_pkey **_key)
++{
++  int ret;
++  size_t i;
++  struct sscg_bignum *e;
++  struct sscg_bignum *serial;
++  struct sscg_cert_info *certinfo;
++  struct sscg_x509_req *csr;
++  struct sscg_evp_pkey *pkey;
++  struct sscg_x509_cert *cert;
++  X509_EXTENSION *ex = NULL;
++  EXTENDED_KEY_USAGE *extended;
++  TALLOC_CTX *tmp_ctx = NULL;
++
++  tmp_ctx = talloc_new (NULL);
++  CHECK_MEM (tmp_ctx);
++
++  /* create a serial number for this certificate */
++  ret = sscg_generate_serial (tmp_ctx, &serial);
++  CHECK_OK (ret);
++
++  certinfo = sscg_cert_info_new (tmp_ctx, options->hash_fn);
++  CHECK_MEM (certinfo);
++
++  /* Populate cert_info from options */
++  certinfo->country = talloc_strdup (certinfo, options->country);
++  CHECK_MEM (certinfo->country);
++
++  certinfo->state = talloc_strdup (certinfo, options->state);
++  CHECK_MEM (certinfo->state);
++
++  certinfo->locality = talloc_strdup (certinfo, options->locality);
++  CHECK_MEM (certinfo->locality);
++
++  certinfo->org = talloc_strdup (certinfo, options->org);
++  CHECK_MEM (certinfo->org);
++
++  certinfo->org_unit = talloc_strdup (certinfo, options->org_unit);
++  CHECK_MEM (certinfo->org_unit);
++
++  certinfo->email = talloc_strdup (certinfo, options->email);
++  CHECK_MEM (certinfo->email);
++
++  certinfo->cn = talloc_strdup (certinfo, options->hostname);
++  CHECK_MEM (certinfo->cn);
++
++  if (options->subject_alt_names)
++    {
++      for (i = 0; options->subject_alt_names[i]; i++)
++        {
++          certinfo->subject_alt_names = talloc_realloc (
++            certinfo, certinfo->subject_alt_names, char *, i + 2);
++          CHECK_MEM (certinfo->subject_alt_names);
++
++          certinfo->subject_alt_names[i] = talloc_strdup (
++            certinfo->subject_alt_names, options->subject_alt_names[i]);
++          CHECK_MEM (certinfo->subject_alt_names[i]);
++
++          /* Add a NULL terminator to the end */
++          certinfo->subject_alt_names[i + 1] = NULL;
++        }
++    }
++
++  /* Ensure that this certificate may not sign other certificates */
++  /* Add key extensions */
++  ex = X509V3_EXT_conf_nid (
++    NULL, NULL, NID_key_usage, "critical,digitalSignature,keyEncipherment");
++  CHECK_MEM (ex);
++  sk_X509_EXTENSION_push (certinfo->extensions, ex);
++
++  extended = sk_ASN1_OBJECT_new_null ();
++
++  switch (type)
++    {
++    case SSCG_CERT_TYPE_SERVER:
++      sk_ASN1_OBJECT_push (extended, OBJ_nid2obj (NID_server_auth));
++      break;
++
++    case SSCG_CERT_TYPE_CLIENT:
++      sk_ASN1_OBJECT_push (extended, OBJ_nid2obj (NID_client_auth));
++      break;
++
++    default:
++      fprintf (stdout, "Unknown certificate type!");
++      ret = EINVAL;
++      goto done;
++    }
++
++
++  ex = X509V3_EXT_i2d (NID_ext_key_usage, 0, extended);
++  sk_ASN1_OBJECT_pop_free (extended, ASN1_OBJECT_free);
++  sk_X509_EXTENSION_push (certinfo->extensions, ex);
++
++  /* Mark it as not a CA */
++  ex = X509V3_EXT_conf_nid (NULL, NULL, NID_basic_constraints, "CA:FALSE");
++  CHECK_MEM (ex);
++  sk_X509_EXTENSION_push (certinfo->extensions, ex);
++
++  /* Use an exponent value of RSA F4 aka 0x10001 (65537) */
++  ret = sscg_init_bignum (tmp_ctx, RSA_F4, &e);
++  CHECK_OK (ret);
++
++  /* Generate an RSA keypair for this CA */
++  if (options->verbosity >= SSCG_VERBOSE)
++    {
++      fprintf (stdout, "Generating RSA key for certificate.\n");
++    }
++  /* TODO: support DSA keys as well */
++  ret = sscg_generate_rsa_key (tmp_ctx, options->key_strength, e, &pkey);
++  CHECK_OK (ret);
++
++  /* Create a certificate signing request for the private CA */
++  if (options->verbosity >= SSCG_VERBOSE)
++    {
++      fprintf (stdout, "Generating CSR for certificate.\n");
++    }
++  ret = sscg_x509v3_csr_new (tmp_ctx, certinfo, pkey, &csr);
++  CHECK_OK (ret);
++
++  /* Finalize the CSR */
++  ret = sscg_x509v3_csr_finalize (certinfo, pkey, csr);
++  CHECK_OK (ret);
++
++  if (options->verbosity >= SSCG_DEBUG)
++    {
++      const char *tempcert =
++        SSCG_CERT_TYPE_SERVER ? "./debug-service.csr" : "debug-client.csr";
++
++      fprintf (stderr, "DEBUG: Writing certificate CSR to %s\n", tempcert);
++      BIO *csr_out = BIO_new_file (tempcert, "w");
++      int sslret = PEM_write_bio_X509_REQ (csr_out, csr->x509_req);
++      CHECK_SSL (sslret, PEM_write_bio_X509_REQ);
++    }
++
++  /* Sign the certificate */
++
++  if (options->verbosity >= SSCG_VERBOSE)
++    {
++      fprintf (stdout, "Signing CSR for certificate. \n");
++    }
++
++  ret = sscg_sign_x509_csr (tmp_ctx,
++                            csr,
++                            serial,
++                            options->lifetime,
++                            ca_cert,
++                            ca_key,
++                            options->hash_fn,
++                            &cert);
++  CHECK_OK (ret);
++
++  *_cert = talloc_steal (mem_ctx, cert);
++  *_key = talloc_steal (mem_ctx, pkey);
++
++  ret = EOK;
++done:
++  talloc_free (tmp_ctx);
++  return ret;
++}
+diff --git a/src/dhparams.c b/src/dhparams.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..f9b64629709beb857a948a7f2f42eb805d76c557
+--- /dev/null
++++ b/src/dhparams.c
+@@ -0,0 +1,146 @@
++/*
++    This file is part of sscg.
++
++    sscg is free software: you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation, either version 3 of the License, or
++    (at your option) any later version.
++
++    sscg is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with sscg.  If not, see <http://www.gnu.org/licenses/>.
++
++    Copyright 2019 by Stephen Gallagher <sgallagh@redhat.com>
++*/
++
++#include <assert.h>
++
++#include "include/sscg.h"
++#include "include/dhparams.h"
++
++
++static int
++_sscg_dhparams_destructor (TALLOC_CTX *ctx);
++
++static int
++dh_cb (int p, int n, BN_GENCB *cb);
++
++int
++create_dhparams (TALLOC_CTX *mem_ctx,
++                 enum sscg_verbosity verbosity,
++                 int prime_len,
++                 int generator,
++                 struct sscg_dhparams **_dhparams)
++{
++  int ret;
++  struct sscg_dhparams *dhparams = NULL;
++  TALLOC_CTX *tmp_ctx = NULL;
++
++  /* First validate the input */
++  assert (_dhparams && !*_dhparams);
++
++  if (prime_len <= 0)
++    {
++      fprintf (stderr, "Prime length must be a positive integer");
++      ret = ERANGE;
++      goto done;
++    }
++
++  if (generator <= 0)
++    {
++      fprintf (stderr, "Generator must be a positive integer");
++      ret = ERANGE;
++      goto done;
++    }
++
++  tmp_ctx = talloc_new (NULL);
++  CHECK_MEM (tmp_ctx);
++
++  dhparams = talloc_zero (tmp_ctx, struct sscg_dhparams);
++  CHECK_MEM (dhparams);
++
++  dhparams->prime_len = prime_len;
++  dhparams->generator = generator;
++  talloc_set_destructor ((TALLOC_CTX *)dhparams, _sscg_dhparams_destructor);
++
++  if (verbosity >= SSCG_DEFAULT)
++    {
++      fprintf (stdout,
++               "Generating DH parameters of length %d and generator %d. "
++               "This will take a long time.\n",
++               dhparams->prime_len,
++               dhparams->generator);
++    }
++
++  dhparams->dh = DH_new ();
++
++  if (verbosity >= SSCG_VERBOSE)
++    {
++#if OPENSSL_VERSION_NUMBER < 0x10100000L
++      dhparams->cb = talloc_zero (dhparams, BN_GENCB);
++#else
++      dhparams->cb = BN_GENCB_new ();
++#endif
++      if (dhparams->cb == NULL)
++        {
++          ERR_print_errors_fp (stderr);
++          ret = ENOMEM;
++          goto done;
++        }
++
++      BN_GENCB_set (dhparams->cb, dh_cb, NULL);
++    }
++
++  if (!DH_generate_parameters_ex (
++        dhparams->dh, dhparams->prime_len, dhparams->generator, dhparams->cb))
++    {
++      ERR_print_errors_fp (stderr);
++      ret = EIO;
++      goto done;
++    }
++
++  ret = EOK;
++  *_dhparams = talloc_steal (mem_ctx, dhparams);
++
++done:
++  talloc_free (tmp_ctx);
++  return ret;
++}
++
++static int
++_sscg_dhparams_destructor (TALLOC_CTX *ctx)
++{
++  struct sscg_dhparams *params =
++    talloc_get_type_abort (ctx, struct sscg_dhparams);
++
++  if (params->dh != NULL)
++    {
++      DH_free (params->dh);
++      params->dh = NULL;
++    }
++
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L
++  if (params->cb != NULL)
++    {
++      BN_GENCB_free (params->cb);
++      params->cb = NULL;
++    }
++#endif
++
++  return 0;
++}
++
++static int
++dh_cb (int p, int n, BN_GENCB *cb)
++{
++  static const char symbols[] = ".+*\n";
++  char c = (p >= 0 && (size_t)p < sizeof (symbols) - 1) ? symbols[p] : '?';
++
++  fprintf (stdout, "%c", c);
++
++  return 1;
++}
+diff --git a/src/io_utils.c b/src/io_utils.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..809a1da0e455afa0dba0796a5f7ac406742328a1
+--- /dev/null
++++ b/src/io_utils.c
+@@ -0,0 +1,424 @@
++/*
++    This file is part of sscg.
++
++    sscg is free software: you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation, either version 3 of the License, or
++    (at your option) any later version.
++
++    sscg is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with sscg.  If not, see <http://www.gnu.org/licenses/>.
++
++    Copyright 2019 by Stephen Gallagher <sgallagh@redhat.com>
++*/
++
++
++#include <assert.h>
++#include <path_utils.h>
++#include <string.h>
++#include <talloc.h>
++
++#include "include/io_utils.h"
++#include "include/sscg.h"
++
++int
++sscg_normalize_path (TALLOC_CTX *mem_ctx,
++                     const char *path,
++                     char **_normalized_path)
++{
++  int ret;
++  char *normalized_path = NULL;
++
++  TALLOC_CTX *tmp_ctx = talloc_new (NULL);
++  CHECK_MEM (tmp_ctx);
++
++  normalized_path = talloc_zero_array (tmp_ctx, char, PATH_MAX);
++  CHECK_MEM (normalized_path);
++
++  ret = make_normalized_absolute_path (normalized_path, PATH_MAX, path);
++  CHECK_OK (ret);
++
++  *_normalized_path = talloc_strdup (mem_ctx, normalized_path);
++  CHECK_MEM (*_normalized_path);
++
++  ret = EOK;
++
++done:
++  talloc_free (normalized_path);
++  talloc_free (tmp_ctx);
++  return ret;
++}
++
++
++static int
++sscg_stream_destructor (TALLOC_CTX *ptr)
++{
++  struct sscg_stream *stream = talloc_get_type_abort (ptr, struct sscg_stream);
++
++  BIO_free (stream->bio);
++
++  return 0;
++}
++
++
++struct sscg_stream *
++sscg_io_utils_get_stream_by_path (struct sscg_stream **streams,
++                                  const char *normalized_path)
++{
++  struct sscg_stream *stream = NULL;
++
++  /* First see if this path already exists in the list */
++  for (int i = 0; (stream = streams[i]) && i < SSCG_NUM_FILE_TYPES; i++)
++    {
++      if (strcmp (normalized_path, stream->path) == 0)
++        break;
++    }
++
++  return stream;
++}
++
++
++struct sscg_stream *
++sscg_io_utils_get_stream_by_type (struct sscg_stream **streams,
++                                  enum sscg_file_type filetype)
++{
++  struct sscg_stream *stream = NULL;
++
++  /* First see if this path already exists in the list */
++  for (int i = 0; (stream = streams[i]) && i < SSCG_NUM_FILE_TYPES; i++)
++    {
++      SSCG_LOG (SSCG_DEBUG,
++                "Checking for 0x%.4x in 0x%.4x\n",
++                (1 << filetype),
++                stream->filetypes);
++      if (stream->filetypes & (1 << filetype))
++        {
++          SSCG_LOG (SSCG_DEBUG,
++                    "Found file type %s in %s\n",
++                    sscg_get_file_type_name (filetype),
++                    stream->path);
++          break;
++        }
++    }
++
++  if (!stream)
++    SSCG_LOG (SSCG_DEBUG,
++              "Could not locate file type: %s. Skipping.\n",
++              sscg_get_file_type_name (filetype));
++
++  return stream;
++}
++
++
++BIO *
++sscg_io_utils_get_bio_by_type (struct sscg_stream **streams,
++                               enum sscg_file_type filetype)
++{
++  struct sscg_stream *_tmp_stream =
++    sscg_io_utils_get_stream_by_type (streams, filetype);
++
++  if (_tmp_stream)
++    {
++      return _tmp_stream->bio;
++    }
++
++  return NULL;
++}
++
++
++const char *
++sscg_io_utils_get_path_by_type (struct sscg_stream **streams,
++                                enum sscg_file_type filetype)
++{
++  struct sscg_stream *_tmp_stream =
++    sscg_io_utils_get_stream_by_type (streams, filetype);
++
++  if (_tmp_stream)
++    {
++      return _tmp_stream->path;
++    }
++
++  return NULL;
++}
++
++
++int
++sscg_io_utils_add_output_file (struct sscg_stream **streams,
++                               enum sscg_file_type filetype,
++                               const char *path,
++                               int mode)
++{
++  int ret, i;
++  TALLOC_CTX *tmp_ctx = NULL;
++  struct sscg_stream *stream = NULL;
++  char *normalized_path = NULL;
++
++  /* If we haven't been passed a path, just return; it's probably an optional
++   * output file
++   */
++  if (path == NULL)
++    {
++      SSCG_LOG (SSCG_DEBUG,
++                "Got a NULL path with filetype: %s\n",
++                sscg_get_file_type_name (filetype));
++      return EOK;
++    }
++
++  tmp_ctx = talloc_new (NULL);
++  CHECK_MEM (tmp_ctx);
++
++  /* Get the normalized version of the path */
++  ret = sscg_normalize_path (tmp_ctx, path, &normalized_path);
++  CHECK_OK (ret);
++
++  SSCG_LOG (SSCG_DEBUG,
++            "%s file path: %s\n",
++            sscg_get_file_type_name (filetype),
++            normalized_path);
++
++  /* First see if this path already exists in the list */
++  stream = sscg_io_utils_get_stream_by_path (streams, normalized_path);
++
++  if (stream == NULL)
++    {
++      /* The path wasn't found, so open it and create it */
++
++      /* First advance the index to the end */
++      for (i = 0; streams[i]; i++)
++        ;
++
++      /* This should never happen. The streams array should always be
++       * sized to the maximum number of known types. If we are asked to add
++       * more entries to the array than we have known file types, it must be
++       * due to a bug.
++       */
++      assert (i < SSCG_NUM_FILE_TYPES);
++
++      stream = talloc_zero (tmp_ctx, struct sscg_stream);
++      CHECK_MEM (stream);
++      talloc_set_destructor ((TALLOC_CTX *)stream, sscg_stream_destructor);
++
++      stream->path = talloc_steal (stream, normalized_path);
++      CHECK_MEM (stream->path);
++
++      streams[i] = talloc_steal (streams, stream);
++    }
++
++  /* Always set the mode to the most-restrictive one requested */
++  SSCG_LOG (SSCG_DEBUG, "Requested mode: %o\n", mode);
++  if (stream->mode)
++    stream->mode &= mode;
++  else
++    stream->mode = mode;
++  SSCG_LOG (SSCG_DEBUG, "Actual mode: %o\n", stream->mode);
++
++  /* Add the file type */
++  stream->filetypes |= (1 << filetype);
++
++  ret = EOK;
++
++done:
++  talloc_free (tmp_ctx);
++  return ret;
++}
++
++
++enum io_utils_errors
++{
++  IO_UTILS_OK = 0,
++  IO_UTILS_TOOMANYKEYS,
++  IO_UTILS_DHPARAMS_NON_EXCLUSIVE,
++  IO_UTILS_CRL_NON_EXCLUSIVE,
++  IO_UTILS_SVC_UNMATCHED,
++  IO_UTILS_CLIENT_UNMATCHED,
++  IO_UTILS_CA_UNMATCHED
++};
++
++static enum io_utils_errors
++io_utils_validate (struct sscg_stream **streams)
++{
++  enum io_utils_errors ret;
++  struct sscg_stream *stream = NULL;
++  int keybits;
++  int allbits = 0;
++
++  for (int i = 0; (stream = streams[i]) && i < SSCG_NUM_FILE_TYPES; i++)
++    {
++      SSCG_LOG (SSCG_DEBUG, "filetypes: 0x%.4x\n", stream->filetypes);
++
++      allbits |= stream->filetypes;
++
++      /* No file may contain two different private keys */
++      /* First check if any private keys are in this file */
++      if ((keybits = stream->filetypes & SSCG_FILE_TYPE_KEYS))
++        {
++          /* Next check if there is exactly one private key in the remainder.
++           * The following bitwise magic checks whether the value is exactly a
++           * power of two (meaning only one bit is set). If the result is
++           * nonzero, more than one bit was set and we have been asked to
++           * include multiple keys into the same file.
++           */
++          if (keybits & (keybits - 1))
++            {
++              ret = IO_UTILS_TOOMANYKEYS;
++              goto done;
++            }
++        }
++
++      /* The dhparams file may only contain dhparams */
++      if ((stream->filetypes & (1 << SSCG_FILE_TYPE_DHPARAMS)) &&
++          (stream->filetypes ^ (1 << SSCG_FILE_TYPE_DHPARAMS)))
++        {
++          ret = IO_UTILS_DHPARAMS_NON_EXCLUSIVE;
++          goto done;
++        }
++
++      /* The CRL file may only contain certificate revocations */
++      if ((stream->filetypes & (1 << SSCG_FILE_TYPE_CRL)) &&
++          (stream->filetypes ^ (1 << SSCG_FILE_TYPE_CRL)))
++        {
++          ret = IO_UTILS_CRL_NON_EXCLUSIVE;
++          goto done;
++        }
++    }
++
++  SSCG_LOG (SSCG_DEBUG, "allbits: 0x%.4x\n", allbits);
++
++  /* If the public or private key is present for the service cert, the other
++   * must be present also
++   */
++  if ((allbits & SSCG_FILE_TYPE_SVC_TYPES) &&
++      ((allbits & SSCG_FILE_TYPE_SVC_TYPES) != SSCG_FILE_TYPE_SVC_TYPES))
++    {
++      ret = IO_UTILS_SVC_UNMATCHED;
++      goto done;
++    }
++
++  /* If the public or private key is present for the client cert, the other
++   * must be present also
++   */
++  if ((allbits & SSCG_FILE_TYPE_CLIENT_TYPES) &&
++      ((allbits & SSCG_FILE_TYPE_CLIENT_TYPES) != SSCG_FILE_TYPE_CLIENT_TYPES))
++    {
++      ret = IO_UTILS_CLIENT_UNMATCHED;
++      goto done;
++    }
++
++  /* If the private key is present for the CA cert, the public key must be
++   * present also
++   */
++  if ((allbits & (1 << SSCG_FILE_TYPE_CA_KEY)) &&
++      !(allbits & (1 << SSCG_FILE_TYPE_CA)))
++    {
++      ret = IO_UTILS_CA_UNMATCHED;
++      goto done;
++    }
++
++
++  ret = IO_UTILS_OK;
++
++done:
++  return ret;
++}
++
++
++int
++sscg_io_utils_open_output_files (struct sscg_stream **streams, bool overwrite)
++{
++  int ret;
++  TALLOC_CTX *tmp_ctx = NULL;
++  enum io_utils_errors validation_result;
++  char *create_mode = NULL;
++  struct sscg_stream *stream = NULL;
++
++  validation_result = io_utils_validate (streams);
++  switch (validation_result)
++    {
++    case IO_UTILS_TOOMANYKEYS:
++      SSCG_ERROR ("Attempted to output multiple keys to the same file.\n");
++      ret = EINVAL;
++      goto done;
++
++    case IO_UTILS_CRL_NON_EXCLUSIVE:
++      SSCG_ERROR ("The CRL file may not include other content.\n");
++      ret = EINVAL;
++      goto done;
++
++    case IO_UTILS_DHPARAMS_NON_EXCLUSIVE:
++      SSCG_ERROR ("The dhparams file may not include other content.\n");
++      ret = EINVAL;
++      goto done;
++
++    case IO_UTILS_SVC_UNMATCHED:
++      SSCG_ERROR (
++        "The service certificate must have both public and private key "
++        "locations specified.\n");
++      ret = EINVAL;
++      goto done;
++
++    case IO_UTILS_CLIENT_UNMATCHED:
++      SSCG_ERROR (
++        "The client certificate must have both public and private key "
++        "locations specified.\n");
++      ret = EINVAL;
++      goto done;
++
++    case IO_UTILS_CA_UNMATCHED:
++      SSCG_ERROR (
++        "The CA certificate must have a public key location specified.\n");
++      ret = EINVAL;
++      goto done;
++
++    case IO_UTILS_OK: break;
++    }
++
++  tmp_ctx = talloc_new (NULL);
++  CHECK_MEM (tmp_ctx);
++
++  if (overwrite)
++    create_mode = talloc_strdup (tmp_ctx, "w");
++  else
++    create_mode = talloc_strdup (tmp_ctx, "wx");
++  CHECK_MEM (create_mode);
++
++  for (int i = 0; (stream = streams[i]) && i < SSCG_NUM_FILE_TYPES; i++)
++    {
++      SSCG_LOG (SSCG_DEBUG, "Opening %s\n", stream->path);
++      stream->bio = BIO_new_file (stream->path, create_mode);
++      CHECK_BIO (stream->bio, stream->path);
++    }
++
++  ret = EOK;
++done:
++  talloc_free (tmp_ctx);
++  return ret;
++}
++
++
++int
++sscg_io_utils_finalize_output_files (struct sscg_stream **streams)
++{
++  struct sscg_stream *stream = NULL;
++  FILE *fp;
++
++  for (int i = 0; (stream = streams[i]) && i < SSCG_NUM_FILE_TYPES; i++)
++    {
++      /* Set the final permissions mode */
++      SSCG_LOG (SSCG_DEBUG,
++                "Setting %s file permissions to %o\n",
++                stream->path,
++                stream->mode);
++      BIO_get_fp (stream->bio, &fp);
++
++      errno = 0;
++      if (fchmod (fileno (fp), stream->mode) != 0)
++        return errno;
++    }
++
++  return EOK;
++}
+diff --git a/src/service.c b/src/service.c
+deleted file mode 100644
+index 34c976dbe905528000b181c24d1fa95da3cd1377..0000000000000000000000000000000000000000
+--- a/src/service.c
++++ /dev/null
+@@ -1,164 +0,0 @@
+-/*
+-    This file is part of sscg.
+-
+-    sscg is free software: you can redistribute it and/or modify
+-    it under the terms of the GNU General Public License as published by
+-    the Free Software Foundation, either version 3 of the License, or
+-    (at your option) any later version.
+-
+-    sscg is distributed in the hope that it will be useful,
+-    but WITHOUT ANY WARRANTY; without even the implied warranty of
+-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-    GNU General Public License for more details.
+-
+-    You should have received a copy of the GNU General Public License
+-    along with sscg.  If not, see <http://www.gnu.org/licenses/>.
+-
+-    Copyright 2017 by Stephen Gallagher <sgallagh@redhat.com>
+-*/
+-
+-
+-#include "include/sscg.h"
+-#include "include/service.h"
+-#include "include/x509.h"
+-#include "include/key.h"
+-
+-int
+-create_service_cert (TALLOC_CTX *mem_ctx,
+-                     const struct sscg_options *options,
+-                     struct sscg_x509_cert *ca_cert,
+-                     struct sscg_evp_pkey *ca_key,
+-                     struct sscg_x509_cert **_svc_cert,
+-                     struct sscg_evp_pkey **_svc_key)
+-{
+-  int ret;
+-  size_t i;
+-  struct sscg_bignum *e;
+-  struct sscg_bignum *serial;
+-  struct sscg_cert_info *svc_certinfo;
+-  struct sscg_x509_req *csr;
+-  struct sscg_evp_pkey *pkey;
+-  struct sscg_x509_cert *cert;
+-  X509_EXTENSION *ex = NULL;
+-  TALLOC_CTX *tmp_ctx = NULL;
+-
+-  tmp_ctx = talloc_new (NULL);
+-  CHECK_MEM (tmp_ctx);
+-
+-  /* create a serial number for this certificate */
+-  ret = sscg_generate_serial (tmp_ctx, &serial);
+-  CHECK_OK (ret);
+-
+-  svc_certinfo = sscg_cert_info_new (tmp_ctx, options->hash_fn);
+-  CHECK_MEM (svc_certinfo);
+-
+-  /* Populate cert_info from options */
+-  svc_certinfo->country = talloc_strdup (svc_certinfo, options->country);
+-  CHECK_MEM (svc_certinfo->country);
+-
+-  svc_certinfo->state = talloc_strdup (svc_certinfo, options->state);
+-  CHECK_MEM (svc_certinfo->state);
+-
+-  svc_certinfo->locality = talloc_strdup (svc_certinfo, options->locality);
+-  CHECK_MEM (svc_certinfo->locality);
+-
+-  svc_certinfo->org = talloc_strdup (svc_certinfo, options->org);
+-  CHECK_MEM (svc_certinfo->org);
+-
+-  svc_certinfo->org_unit = talloc_strdup (svc_certinfo, options->org_unit);
+-  CHECK_MEM (svc_certinfo->org_unit);
+-
+-  svc_certinfo->email = talloc_strdup (svc_certinfo, options->email);
+-  CHECK_MEM (svc_certinfo->email);
+-
+-  svc_certinfo->cn = talloc_strdup (svc_certinfo, options->hostname);
+-  CHECK_MEM (svc_certinfo->cn);
+-
+-  if (options->subject_alt_names)
+-    {
+-      for (i = 0; options->subject_alt_names[i]; i++)
+-        {
+-          svc_certinfo->subject_alt_names = talloc_realloc (
+-            svc_certinfo, svc_certinfo->subject_alt_names, char *, i + 2);
+-          CHECK_MEM (svc_certinfo->subject_alt_names);
+-
+-          svc_certinfo->subject_alt_names[i] = talloc_strdup (
+-            svc_certinfo->subject_alt_names, options->subject_alt_names[i]);
+-          CHECK_MEM (svc_certinfo->subject_alt_names[i]);
+-
+-          /* Add a NULL terminator to the end */
+-          svc_certinfo->subject_alt_names[i + 1] = NULL;
+-        }
+-    }
+-
+-  /* Ensure that this certificate may not sign other certificates */
+-  /* Add key extensions */
+-  ex = X509V3_EXT_conf_nid (
+-    NULL, NULL, NID_key_usage, "critical,digitalSignature,keyEncipherment");
+-  CHECK_MEM (ex);
+-  sk_X509_EXTENSION_push (svc_certinfo->extensions, ex);
+-
+-  /* Mark it as not a CA */
+-  ex = X509V3_EXT_conf_nid (NULL, NULL, NID_basic_constraints, "CA:FALSE");
+-  CHECK_MEM (ex);
+-  sk_X509_EXTENSION_push (svc_certinfo->extensions, ex);
+-
+-  /* Use an exponent value of RSA F4 aka 0x10001 (65537) */
+-  ret = sscg_init_bignum (tmp_ctx, RSA_F4, &e);
+-  CHECK_OK (ret);
+-
+-  /* Generate an RSA keypair for this CA */
+-  if (options->verbosity >= SSCG_VERBOSE)
+-    {
+-      fprintf (stdout, "Generating RSA key for service certificate.\n");
+-    }
+-  /* TODO: support DSA keys as well */
+-  ret = sscg_generate_rsa_key (tmp_ctx, options->key_strength, e, &pkey);
+-  CHECK_OK (ret);
+-
+-  /* Create a certificate signing request for the private CA */
+-  if (options->verbosity >= SSCG_VERBOSE)
+-    {
+-      fprintf (stdout, "Generating CSR for service certificate.\n");
+-    }
+-  ret = sscg_x509v3_csr_new (tmp_ctx, svc_certinfo, pkey, &csr);
+-  CHECK_OK (ret);
+-
+-  /* Finalize the CSR */
+-  ret = sscg_x509v3_csr_finalize (svc_certinfo, pkey, csr);
+-  CHECK_OK (ret);
+-
+-  if (options->verbosity >= SSCG_DEBUG)
+-    {
+-      fprintf (stderr,
+-               "DEBUG: Writing service certificate CSR to ./debug-svc.csr\n");
+-      BIO *svc_csr_out = BIO_new_file ("./debug-svc.csr", "w");
+-      int sslret = PEM_write_bio_X509_REQ (svc_csr_out, csr->x509_req);
+-      CHECK_SSL (sslret, PEM_write_bio_X509_REQ);
+-    }
+-
+-  /* Sign the certificate */
+-
+-  if (options->verbosity >= SSCG_VERBOSE)
+-    {
+-      fprintf (stdout, "Signing CSR for service certificate. \n");
+-    }
+-
+-  ret = sscg_sign_x509_csr (tmp_ctx,
+-                            csr,
+-                            serial,
+-                            options->lifetime,
+-                            ca_cert,
+-                            ca_key,
+-                            options->hash_fn,
+-                            &cert);
+-  CHECK_OK (ret);
+-
+-  *_svc_cert = talloc_steal (mem_ctx, cert);
+-  *_svc_key = talloc_steal (mem_ctx, pkey);
+-
+-  ret = EOK;
+-done:
+-  talloc_free (tmp_ctx);
+-  return ret;
+-}
+diff --git a/src/sscg.c b/src/sscg.c
+index a02e4df66c6cf9ec1865f425b4a15da82fbfdc72..470af815d91f5170a1e8fe00006dbaee4d07b209 100644
+--- a/src/sscg.c
++++ b/src/sscg.c
+@@ -32,7 +32,12 @@
+ #include "config.h"
+ #include "include/sscg.h"
+ #include "include/authority.h"
+-#include "include/service.h"
++#include "include/cert.h"
++#include "include/dhparams.h"
++#include "include/io_utils.h"
++
++
++int verbosity;
+ 
+ 
+ /* Same as OpenSSL CLI */
+@@ -58,6 +63,8 @@ set_default_options (struct sscg_options *opts)
+   int security_level = get_security_level ();
+ 
+   opts->lifetime = 3650;
++  opts->dhparams_prime_len = 2048;
++  opts->dhparams_generator = 2;
+ 
+   /* Select the default key strength based on the system security level
+    * See:
+@@ -132,51 +139,6 @@ print_options (struct sscg_options *opts)
+   fprintf (stdout, "=================\n");
+ }
+ 
+-static int
+-_sscg_normalize_path (TALLOC_CTX *mem_ctx,
+-                      const char *path,
+-                      const char *path_default,
+-                      char **_normalized_path)
+-{
+-  int ret;
+-  char *orig_path = NULL;
+-  char *normalized_path = NULL;
+-
+-  TALLOC_CTX *tmp_ctx = talloc_new (NULL);
+-  CHECK_MEM (tmp_ctx);
+-
+-  if (path)
+-    {
+-      orig_path = talloc_strdup (tmp_ctx, path);
+-    }
+-  else
+-    {
+-      if (!path_default)
+-        {
+-          /* If no default is set and no path was provided,
+-             * return NULL */
+-          *_normalized_path = NULL;
+-          ret = EOK;
+-          goto done;
+-        }
+-      orig_path = talloc_strdup (tmp_ctx, path_default);
+-      CHECK_MEM (orig_path);
+-    }
+-
+-  normalized_path = talloc_zero_array (tmp_ctx, char, PATH_MAX);
+-  CHECK_MEM (normalized_path);
+-
+-  ret = make_normalized_absolute_path (normalized_path, PATH_MAX, orig_path);
+-  CHECK_OK (ret);
+-
+-  *_normalized_path = talloc_steal (mem_ctx, normalized_path);
+-  ret = EOK;
+-
+-done:
+-  talloc_free (tmp_ctx);
+-  return ret;
+-}
+-
+ 
+ /* This function takes a copy of a string into a talloc hierarchy and memsets
+  * the original string to zeroes to avoid leaking it when that memory is freed.
+@@ -251,6 +213,53 @@ sscg_read_pw_file (TALLOC_CTX *mem_ctx, char *path)
+ }
+ 
+ 
++const char *
++sscg_get_verbosity_name (enum sscg_verbosity type)
++{
++  switch (type)
++    {
++    case SSCG_DEFAULT:
++    case SSCG_VERBOSE: return "";
++
++    case SSCG_DEBUG: return "DEBUG: ";
++
++    default: break;
++    }
++
++  /* If it wasn't one of these, we have a bug */
++  return "Unknown Verbosity (bug):";
++}
++
++
++const char *
++sscg_get_file_type_name (enum sscg_file_type type)
++{
++  switch (type)
++    {
++    case SSCG_FILE_TYPE_CA: return "CA certificate";
++
++    case SSCG_FILE_TYPE_CA_KEY: return "CA certificate key";
++
++    case SSCG_FILE_TYPE_SVC: return "service certificate";
++
++    case SSCG_FILE_TYPE_SVC_KEY: return "service certificate key";
++
++    case SSCG_FILE_TYPE_CLIENT: return "client auth certificate";
++
++    case SSCG_FILE_TYPE_CLIENT_KEY: return "client auth certificate key";
++
++    case SSCG_FILE_TYPE_CRL: return "certificate revocation list";
++
++    case SSCG_FILE_TYPE_DHPARAMS: return "Diffie-Hellman parameters";
++
++    default: break;
++    }
++
++  /* If it wasn't one of these, we have a bug */
++  return "Unknown (bug)";
++}
++
++
+ int
+ main (int argc, const char **argv)
+ {
+@@ -274,36 +283,48 @@ main (int argc, const char **argv)
+   char *ca_key_file = NULL;
+   char *cert_file = NULL;
+   char *cert_key_file = NULL;
++  char *client_file = NULL;
++  char *client_key_file = NULL;
++  char *dhparams_file = NULL;
+ 
+-  int ca_mode = 0644;
+-  int ca_key_mode = 0600;
++  int ca_mode = SSCG_CERT_DEFAULT_MODE;
++  int ca_key_mode = SSCG_KEY_DEFAULT_MODE;
+   char *ca_key_password = NULL;
+   char *ca_key_passfile = NULL;
+ 
+-  int cert_mode = 0644;
+-  int cert_key_mode = 0600;
++  int crl_mode = SSCG_CERT_DEFAULT_MODE;
++  char *crl_file = NULL;
++
++  int cert_mode = SSCG_CERT_DEFAULT_MODE;
++  int cert_key_mode = SSCG_KEY_DEFAULT_MODE;
+   char *cert_key_password = NULL;
+   char *cert_key_passfile = NULL;
+ 
+-  char *create_mode = NULL;
++  int client_mode = SSCG_CERT_DEFAULT_MODE;
++  int client_key_mode = SSCG_KEY_DEFAULT_MODE;
++  char *client_key_password = NULL;
++  char *client_key_passfile = NULL;
+ 
+   struct sscg_x509_cert *cacert;
+   struct sscg_evp_pkey *cakey;
+   struct sscg_x509_cert *svc_cert;
+   struct sscg_evp_pkey *svc_key;
++  struct sscg_x509_cert *client_cert;
++  struct sscg_evp_pkey *client_key;
+ 
+-  BIO *ca_out = NULL;
+-  BIO *ca_key_out = NULL;
+-  BIO *cert_out = NULL;
+-  BIO *cert_key_out = NULL;
+-
+-  FILE *fp;
++  int dhparams_mode = SSCG_CERT_DEFAULT_MODE;
++  struct sscg_dhparams *dhparams = NULL;
+ 
+   /* Always use umask 0577 for generating certificates and keys
+        This means that it's opened as write-only by the effective
+        user. */
+   umask (0577);
+ 
++#if OPENSSL_VERSION_NUMBER < 0x10100000L
++  /* In OpenSSL <1.1.0, we need to initialize the library. */
++  OpenSSL_add_all_algorithms ();
++#endif
++
+   TALLOC_CTX *main_ctx = talloc_new (NULL);
+   if (!main_ctx)
+     {
+@@ -315,6 +336,9 @@ main (int argc, const char **argv)
+   CHECK_MEM (options);
+   talloc_set_destructor ((TALLOC_CTX *)options, sscg_options_destructor);
+ 
++  options->streams =
++    talloc_zero_array (options, struct sscg_stream *, SSCG_NUM_FILE_TYPES);
++
+   ret = set_default_options (options);
+   if (ret != EOK)
+     goto done;
+@@ -323,101 +347,144 @@ main (int argc, const char **argv)
+     talloc_asprintf (main_ctx, "%d or larger", options->minimum_key_strength);
+ 
+   options->verbosity = SSCG_DEFAULT;
++  // clang-format off
+   struct poptOption long_options[] = {
+-    POPT_AUTOHELP{ "quiet",
+-                   'q',
+-                   POPT_ARG_VAL,
+-                   &options->verbosity,
+-                   SSCG_QUIET,
+-                   _ ("Display no output unless there is an error."),
+-                   NULL },
+-    { "verbose",
++    POPT_AUTOHELP
++
++    {
++      "quiet",
++      'q',
++      POPT_ARG_VAL,
++      &options->verbosity,
++      SSCG_QUIET,
++       ("Display no output unless there is an error."),
++      NULL
++    },
++
++    {
++      "verbose",
+       'v',
+       POPT_ARG_VAL,
+       &options->verbosity,
+       SSCG_VERBOSE,
+       _ ("Display progress messages."),
+-      NULL },
+-    { "debug",
++      NULL
++    },
++
++    {
++      "debug",
+       'd',
+       POPT_ARG_VAL,
+       &options->verbosity,
+       SSCG_DEBUG,
+       _ ("Enable logging of debug messages. Implies verbose. Warning! "
+          "This will print private key information to the screen!"),
+-      NULL },
+-    { "version",
++      NULL
++    },
++
++    {
++      "version",
+       'V',
+       POPT_ARG_NONE,
+       &options->print_version,
+       0,
+       _ ("Display the version number and exit."),
+-      NULL },
+-    { "force",
++      NULL
++    },
++
++    {
++      "force",
+       'f',
+       POPT_ARG_NONE,
+       &options->overwrite,
+       0,
+       _ ("Overwrite any pre-existing files in the requested locations"),
+-      NULL },
+-    { "lifetime",
++      NULL
++    },
++
++    {
++      "lifetime",
+       '\0',
+       POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT,
+       &options->lifetime,
+       0,
+       _ ("Certificate lifetime (days)."),
+-      _ ("1-3650") },
+-    { "country",
++      _ ("1-3650")
++    },
++
++    {
++      "country",
+       '\0',
+       POPT_ARG_STRING,
+       &country,
+       0,
+       _ ("Certificate DN: Country (C). (default: \"US\")"),
+-      _ ("US, CZ, etc.") },
+-    { "state",
++      _ ("US, CZ, etc.")
++    },
++
++    {
++      "state",
+       '\0',
+       POPT_ARG_STRING,
+       &state,
+       0,
+       _ ("Certificate DN: State or Province (ST)."),
+-      _ ("Massachusetts, British Columbia, etc.") },
+-    { "locality",
++      _ ("Massachusetts, British Columbia, etc.")
++    },
++
++    {
++      "locality",
+       '\0',
+       POPT_ARG_STRING,
+       &locality,
+       0,
+       _ ("Certificate DN: Locality (L)."),
+-      _ ("Westford, Paris, etc.") },
+-    { "organization",
++      _ ("Westford, Paris, etc.")
++    },
++
++    {
++      "organization",
+       '\0',
+       POPT_ARG_STRING,
+       &organization,
+       0,
+       _ ("Certificate DN: Organization (O). (default: \"Unspecified\")"),
+-      _ ("My Company") },
+-    { "organizational-unit",
++      _ ("My Company")
++    },
++
++    {
++      "organizational-unit",
+       '\0',
+       POPT_ARG_STRING,
+       &organizational_unit,
+       0,
+       _ ("Certificate DN: Organizational Unit (OU)."),
+-      _ ("Engineering, etc.") },
+-    { "email",
++      _ ("Engineering, etc.")
++    },
++
++    {
++      "email",
+       '\0',
+       POPT_ARG_STRING,
+       &email,
+       0,
+       _ ("Certificate DN: Email Address (Email)."),
+-      _ ("myname@example.com") },
+-    { "hostname",
++      _ ("myname@example.com")
++    },
++
++    {
++      "hostname",
+       '\0',
+       POPT_ARG_STRING,
+       &hostname,
+       0,
+       _ ("The valid hostname of the certificate. Must be an FQDN. (default: "
+          "current system FQDN)"),
+-      _ ("server.example.com") },
+-    { "subject-alt-name",
++      _ ("server.example.com")
++    },
++
++    {
++      "subject-alt-name",
+       '\0',
+       POPT_ARG_ARGV,
+       &alternative_names,
+@@ -427,7 +494,9 @@ main (int argc, const char **argv)
+          "supported by RFC 5280 such as "
+          "IP:xxx.xxx.xxx.xxx/yyy.yyy.yyy.yyy "
+          "May be specified multiple times."),
+-      _ ("alt.example.com") },
++      _ ("alt.example.com")
++    },
++
+     {
+       "package",
+       '\0',
+@@ -437,7 +506,9 @@ main (int argc, const char **argv)
+       _ ("Unused. Retained for compatibility with earlier versions of sscg."),
+       NULL,
+     },
+-    { "key-strength",
++
++    {
++      "key-strength",
+       '\0',
+       POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT,
+       &options->key_strength,
+@@ -453,6 +524,7 @@ main (int argc, const char **argv)
+       _ ("Hashing algorithm to use for signing."),
+       _ ("{sha256,sha384,sha512}"),
+     },
++
+     {
+       "cipher-alg",
+       '\0',
+@@ -473,15 +545,17 @@ main (int argc, const char **argv)
+          "\"./ca.crt\")"),
+       NULL,
+     },
++
+     {
+       "ca-mode",
+       '\0',
+       POPT_ARG_INT,
+       &ca_mode,
+       0,
+-      _ ("File mode of the created CA certificate. (default: 0644)"),
+-      _ ("0644"),
++      _ ("File mode of the created CA certificate."),
++      SSCG_CERT_DEFAULT_MODE_HELP,
+     },
++
+     {
+       "ca-key-file",
+       '\0',
+@@ -492,15 +566,17 @@ main (int argc, const char **argv)
+          "the key will be destroyed rather than written to the disk."),
+       NULL,
+     },
++
+     {
+       "ca-key-mode",
+       '\0',
+       POPT_ARG_INT,
+       &ca_key_mode,
+       0,
+-      _ ("File mode of the created CA key. (default: 0600)"),
+-      _ ("0600"),
++      _ ("File mode of the created CA key."),
++      SSCG_KEY_DEFAULT_MODE_HELP,
+     },
++
+     {
+       "ca-key-password",
+       '\0',
+@@ -534,6 +610,28 @@ main (int argc, const char **argv)
+       NULL
+     },
+ 
++    {
++      "crl-file",
++      '\0',
++      POPT_ARG_STRING,
++      &crl_file,
++      0,
++      _ ("Path where an (empty) Certificate Revocation List file will be "
++         "created, for applications that expect such a file to exist. If "
++         "unspecified, no such file will be created."),
++      NULL
++    },
++
++    {
++      "crl-mode",
++      '\0',
++      POPT_ARG_INT,
++      &crl_mode,
++      0,
++      _ ("File mode of the created Certificate Revocation List."),
++      SSCG_CERT_DEFAULT_MODE_HELP,
++    },
++
+     {
+       "cert-file",
+       '\0',
+@@ -544,15 +642,17 @@ main (int argc, const char **argv)
+          "(default \"./service.pem\")"),
+       NULL,
+     },
++
+     {
+       "cert-mode",
+       '\0',
+       POPT_ARG_INT,
+       &cert_mode,
+       0,
+-      _ ("File mode of the created certificate. (default: 0644)"),
+-      _ ("0644"),
++      _ ("File mode of the created certificate."),
++      SSCG_CERT_DEFAULT_MODE_HELP,
+     },
++
+     {
+       "cert-key-file",
+       '\0',
+@@ -563,15 +663,17 @@ main (int argc, const char **argv)
+          "(default \"service-key.pem\")"),
+       NULL,
+     },
++
+     {
+       "cert-key-mode",
+       '\0',
+       POPT_ARG_INT,
+       &cert_key_mode,
+       0,
+-      _ ("File mode of the created certificate key. (default: 0600)"),
+-      _ ("0600"),
++      _ ("File mode of the created certificate key."),
++      SSCG_KEY_DEFAULT_MODE_HELP,
+     },
++
+     {
+       "cert-key-password",
+       'p',
+@@ -605,8 +707,115 @@ main (int argc, const char **argv)
+       NULL
+     },
+ 
++    {
++      "client-file",
++      '\0',
++      POPT_ARG_STRING,
++      &client_file,
++      0,
++      _ ("Path where a client authentication certificate will be stored."),
++      NULL
++    },
++    {
++      "client-mode",
++      '\0',
++      POPT_ARG_INT,
++      &client_mode,
++      0,
++      _ ("File mode of the created certificate."),
++      SSCG_CERT_DEFAULT_MODE_HELP,
++    },
++
++    {
++      "client-key-file",
++      '\0',
++      POPT_ARG_STRING,
++      &client_key_file,
++      0,
++      _ ("Path where the client's private key will be stored. "
++         "(default is client-file with a .key suffix, if "
++         "--client-file was passed, otherwise this file will not "
++         "be generated.)"),
++      NULL,
++    },
++
++    {
++      "client-key-mode",
++      '\0',
++      POPT_ARG_INT,
++      &client_key_mode,
++      0,
++      _ ("File mode of the created certificate key."),
++      SSCG_KEY_DEFAULT_MODE_HELP,
++    },
++
++    {
++      "client-key-password",
++      '\0',
++      POPT_ARG_STRING,
++      &client_key_password,
++      0,
++      _ ("Provide a password for the client key file. Note that this will be "
++         "visible in the process table for all users, so this flag should be "
++         "used for testing purposes only. Use --client-keypassfile or "
++         "--client-key-password-prompt for secure password entry."),
++      NULL
++    },
++
++    {
++      "client-key-passfile",
++      '\0',
++      POPT_ARG_STRING,
++      &client_key_passfile,
++      0,
++      _ ("A file containing the password to encrypt the client key file."),
++      NULL
++    },
++
++    {
++      "client-key-password-prompt",
++      '\0',
++      POPT_ARG_NONE,
++      &options->client_key_pass_prompt,
++      0,
++      _ ("Prompt to enter a password for the client key file."),
++      NULL
++    },
++
++    {
++      "dhparams-file",
++      '\0',
++      POPT_ARG_STRING,
++      &dhparams_file,
++      0,
++      _("A file to contain a set of generated Diffie-Hellman parameters. "
++        "If unspecified, no such file will be created."),
++      NULL
++    },
++
++    {
++      "dhparams-prime-len",
++      '\0',
++      POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT,
++      &options->dhparams_prime_len,
++      0,
++      _ ("The length of the prime number to generate for dhparams, in bits."),
++      NULL
++    },
++
++    {
++      "dhparams-generator",
++      '\0',
++      POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT,
++      &options->dhparams_generator,
++      0,
++      _ ("The generator value for dhparams."),
++      _("{2,3,5}")
++    },
++
+     POPT_TABLEEND
+   };
++  // clang-format on
+ 
+   pc = poptGetContext (argv[0], argc, argv, long_options, 0);
+   while ((opt = poptGetNextOpt (pc)) != -1)
+@@ -630,6 +839,8 @@ main (int argc, const char **argv)
+       return 0;
+     }
+ 
++  verbosity = options->verbosity;
++
+   /* Process the Subject information */
+ 
+   if (country)
+@@ -788,6 +999,22 @@ main (int argc, const char **argv)
+         }
+     }
+ 
++  if (client_key_password)
++    {
++      options->client_key_pass =
++        sscg_secure_string_steal (options, client_key_password);
++    }
++  else if (client_key_passfile)
++    {
++      options->client_key_pass =
++        sscg_read_pw_file (options, client_key_passfile);
++      if (!options->client_key_pass)
++        {
++          fprintf (
++            stderr, "Failed to read passphrase from %s", client_key_passfile);
++          goto done;
++        }
++    }
+ 
+   if (options->key_strength < options->minimum_key_strength)
+     {
+@@ -822,86 +1049,115 @@ main (int argc, const char **argv)
+   if (options->verbosity >= SSCG_VERBOSE)
+     print_options (options);
+ 
+-  /* Get the paths of the output files */
+-  ret = _sscg_normalize_path (options, ca_file, "./ca.crt", &options->ca_file);
++  /* Prepare the output files */
++  ret = sscg_io_utils_add_output_file (options->streams,
++                                       SSCG_FILE_TYPE_CA,
++                                       ca_file ? ca_file : "./ca.crt",
++                                       ca_mode);
+   CHECK_OK (ret);
+ 
+-  ret =
+-    _sscg_normalize_path (options, ca_key_file, NULL, &options->ca_key_file);
++  ret = sscg_io_utils_add_output_file (
++    options->streams, SSCG_FILE_TYPE_CA_KEY, ca_key_file, ca_key_mode);
+   CHECK_OK (ret);
+-  if (options->verbosity >= SSCG_DEBUG)
+-    {
+-      fprintf (stdout,
+-               "DEBUG: CA Key file path: %s\n",
+-               options->ca_key_file ? options->ca_key_file : "(N/A)");
+-    }
+-
+-  ret = _sscg_normalize_path (
+-    options, cert_file, "./service.pem", &options->cert_file);
++
++  ret = sscg_io_utils_add_output_file (options->streams,
++                                       SSCG_FILE_TYPE_SVC,
++                                       cert_file ? cert_file : "./service.pem",
++                                       cert_mode);
++  CHECK_OK (ret);
++
++  ret = sscg_io_utils_add_output_file (options->streams,
++                                       SSCG_FILE_TYPE_SVC_KEY,
++                                       cert_key_file ? cert_key_file :
++                                                       "./service-key.pem",
++                                       cert_key_mode);
++  CHECK_OK (ret);
++
++
++  ret = sscg_io_utils_add_output_file (
++    options->streams, SSCG_FILE_TYPE_CLIENT, client_file, client_mode);
++  CHECK_OK (ret);
++
++
++  ret = sscg_io_utils_add_output_file (options->streams,
++                                       SSCG_FILE_TYPE_CLIENT_KEY,
++                                       client_key_file ? client_key_file :
++                                                         client_file,
++                                       client_key_mode);
++  CHECK_OK (ret);
++
++  ret = sscg_io_utils_add_output_file (
++    options->streams, SSCG_FILE_TYPE_CRL, crl_file, crl_mode);
+   CHECK_OK (ret);
+ 
+-  ret = _sscg_normalize_path (
+-    options, cert_key_file, "./service-key.pem", &options->cert_key_file);
++  ret = sscg_io_utils_add_output_file (
++    options->streams, SSCG_FILE_TYPE_DHPARAMS, dhparams_file, dhparams_mode);
+   CHECK_OK (ret);
+ 
+   poptFreeContext (pc);
+ 
+-  /* Validate the file paths */
++  /* Validate and open the file paths */
++  ret = sscg_io_utils_open_output_files (options->streams, options->overwrite);
++  CHECK_OK (ret);
+ 
+-  /* Only one key can exist in a single file */
+-  if (options->ca_key_file &&
+-      strcmp (options->ca_key_file, options->cert_key_file) == 0)
+-    {
+-      fprintf (stderr,
+-               "Certificate key and CA key may not be in the same file.\n");
+-      ret = EINVAL;
+-      goto done;
+-    }
+-
+-  /* The CA key must not be in the same file as the service cert */
+-  if (options->ca_key_file &&
+-      strcmp (options->ca_key_file, options->cert_file) == 0)
+-    {
+-      fprintf (
+-        stderr,
+-        "CA key and service certificate may not be in the same file.\n");
+-      ret = EINVAL;
+-      goto done;
+-    }
+ 
+   /* Generate the private CA for the certificate */
+   ret = create_private_CA (main_ctx, options, &cacert, &cakey);
+   CHECK_OK (ret);
+ 
+   /* Generate the service certificate and sign it with the private CA */
+-  ret = create_service_cert (
+-    main_ctx, options, cacert, cakey, &svc_cert, &svc_key);
++  ret = create_cert (main_ctx,
++                     options,
++                     cacert,
++                     cakey,
++                     SSCG_CERT_TYPE_SERVER,
++                     &svc_cert,
++                     &svc_key);
+   CHECK_OK (ret);
+ 
++  /* If requested, generate the client auth certificate and sign it with the
++   * private CA.
++   */
++  if (GET_BIO (SSCG_FILE_TYPE_CLIENT))
++    {
++      ret = create_cert (main_ctx,
++                         options,
++                         cacert,
++                         cakey,
++                         SSCG_CERT_TYPE_CLIENT,
++                         &client_cert,
++                         &client_key);
++      CHECK_OK (ret);
++    }
++
+ 
+   /* ==== Output the final files ==== */
+ 
+-  /* Set the file-creation mode */
+-  if (options->overwrite)
+-    {
+-      create_mode = talloc_strdup (main_ctx, "w");
+-    }
+-  else
+-    {
+-      create_mode = talloc_strdup (main_ctx, "wx");
+-    }
+-  CHECK_MEM (create_mode);
+ 
+-  /* Create certificate private key file */
+-  if (options->verbosity >= SSCG_DEFAULT)
++  /* Write private keys first */
++
++  if (GET_BIO (SSCG_FILE_TYPE_CLIENT_KEY))
+     {
+-      fprintf (
+-        stdout, "Writing svc private key to %s \n", options->cert_key_file);
++      /* This function has a default mechanism for prompting for the
++       * password if it is passed a cipher and gets a NULL password.
++       *
++       * Only pass the cipher if we have a password or were instructed
++       * to prompt for one.
++       */
++      sret = PEM_write_bio_PrivateKey (
++        GET_BIO (SSCG_FILE_TYPE_CLIENT_KEY),
++        client_key->evp_pkey,
++        options->client_key_pass_prompt || options->client_key_pass ?
++          options->cipher :
++          NULL,
++        (unsigned char *)options->client_key_pass,
++        options->client_key_pass ? strlen (options->client_key_pass) : 0,
++        NULL,
++        NULL);
++      CHECK_SSL (sret, PEM_write_bio_PrivateKey (svc));
++      ANNOUNCE_WRITE (SSCG_FILE_TYPE_SVC_KEY);
+     }
+ 
+-  cert_key_out = BIO_new_file (options->cert_key_file, create_mode);
+-  CHECK_BIO (cert_key_out, options->cert_key_file);
+-
+   /* This function has a default mechanism for prompting for the
+    * password if it is passed a cipher and gets a NULL password.
+    *
+@@ -909,7 +1165,7 @@ main (int argc, const char **argv)
+    * to prompt for one.
+    */
+   sret = PEM_write_bio_PrivateKey (
+-    cert_key_out,
++    GET_BIO (SSCG_FILE_TYPE_SVC_KEY),
+     svc_key->evp_pkey,
+     options->cert_key_pass_prompt || options->cert_key_pass ? options->cipher :
+                                                               NULL,
+@@ -918,83 +1174,11 @@ main (int argc, const char **argv)
+     NULL,
+     NULL);
+   CHECK_SSL (sret, PEM_write_bio_PrivateKey (svc));
+-  BIO_get_fp (cert_key_out, &fp);
+-
+-  if (options->verbosity >= SSCG_DEBUG)
+-    {
+-      fprintf (stdout,
+-               "DEBUG: Setting svc key file permissions to %o\n",
+-               cert_key_mode);
+-    }
+-  fchmod (fileno (fp), cert_key_mode);
+-
+-  BIO_free (cert_key_out);
+-  cert_key_out = NULL;
+-
+-
+-  /* Create service public certificate */
+-  if (options->verbosity >= SSCG_DEFAULT)
+-    {
+-      fprintf (stdout,
+-               "Writing service public certificate to %s\n",
+-               options->cert_file);
+-    }
+-  if (strcmp (options->cert_key_file, options->cert_file) == 0)
+-    {
+-      cert_out = BIO_new_file (options->cert_file, "a");
+-    }
+-  else
+-    {
+-      cert_out = BIO_new_file (options->cert_file, create_mode);
+-    }
+-  CHECK_BIO (cert_out, options->cert_file);
+-
+-  sret = PEM_write_bio_X509 (cert_out, svc_cert->certificate);
+-  CHECK_SSL (sret, PEM_write_bio_X509 (svc));
+-  BIO_get_fp (cert_out, &fp);
+-
+-  /* If this file matches the keyfile, do not set its permissions */
+-  if (strcmp (options->cert_file, options->cert_key_file) == 0)
+-    {
+-      if (options->verbosity >= SSCG_DEBUG)
+-        {
+-          fprintf (stdout,
+-                   "DEBUG: Not setting service cert file permissions: "
+-                   "superseded by the key\n");
+-        }
+-    }
+-  else
+-    {
+-      if (options->verbosity >= SSCG_DEBUG)
+-        {
+-          fprintf (stdout,
+-                   "DEBUG: Setting service cert file permissions to %o\n",
+-                   cert_mode);
+-        }
+-      fchmod (fileno (fp), cert_mode);
+-    }
+-  BIO_free (cert_out);
+-  cert_out = NULL;
+-
++  ANNOUNCE_WRITE (SSCG_FILE_TYPE_SVC_KEY);
+ 
+   /* Create CA private key, if requested */
+-  if (options->ca_key_file)
++  if (GET_BIO (SSCG_FILE_TYPE_CA_KEY))
+     {
+-      if (options->verbosity >= SSCG_DEFAULT)
+-        {
+-          fprintf (
+-            stdout, "Writing CA private key to %s\n", options->ca_key_file);
+-        }
+-      if (strcmp (options->ca_file, options->ca_key_file) == 0)
+-        {
+-          ca_key_out = BIO_new_file (options->ca_key_file, "a");
+-        }
+-      else
+-        {
+-          ca_key_out = BIO_new_file (options->ca_key_file, create_mode);
+-        }
+-      CHECK_BIO (ca_key_out, options->ca_key_file);
+-
+       /* This function has a default mechanism for prompting for the
+        * password if it is passed a cipher and gets a NULL password.
+        *
+@@ -1002,7 +1186,7 @@ main (int argc, const char **argv)
+        * to prompt for one.
+        */
+       sret = PEM_write_bio_PrivateKey (
+-        ca_key_out,
++        GET_BIO (SSCG_FILE_TYPE_CA_KEY),
+         cakey->evp_pkey,
+         options->ca_key_pass_prompt || options->ca_key_pass ? options->cipher :
+                                                               NULL,
+@@ -1011,73 +1195,80 @@ main (int argc, const char **argv)
+         NULL,
+         NULL);
+       CHECK_SSL (sret, PEM_write_bio_PrivateKey (CA));
+-      BIO_get_fp (ca_key_out, &fp);
+-      if (options->verbosity >= SSCG_DEBUG)
+-        {
+-          fprintf (stdout,
+-                   "DEBUG: Setting CA key file permissions to %o\n",
+-                   ca_key_mode);
+-        }
+-      fchmod (fileno (fp), ca_key_mode);
+-      BIO_free (ca_key_out);
+-      ca_key_out = NULL;
++      ANNOUNCE_WRITE (SSCG_FILE_TYPE_CA_KEY);
+     }
+ 
++  /* Public keys come next, in chain order */
++
++  /* Start with the client certificate */
++  if (GET_BIO (SSCG_FILE_TYPE_CLIENT))
++    {
++      sret = PEM_write_bio_X509 (GET_BIO (SSCG_FILE_TYPE_CLIENT),
++                                 client_cert->certificate);
++      CHECK_SSL (sret, PEM_write_bio_X509 (client));
++      ANNOUNCE_WRITE (SSCG_FILE_TYPE_CLIENT);
++    }
++
++  /* Create service public certificate */
++  sret =
++    PEM_write_bio_X509 (GET_BIO (SSCG_FILE_TYPE_SVC), svc_cert->certificate);
++  CHECK_SSL (sret, PEM_write_bio_X509 (svc));
++  ANNOUNCE_WRITE (SSCG_FILE_TYPE_SVC);
++
+ 
+   /* Create CA public certificate */
+-  if (options->verbosity >= SSCG_DEFAULT)
+-    {
+-      fprintf (
+-        stdout, "Writing CA public certificate to %s\n", options->ca_file);
+-    }
+-  if (strcmp (options->ca_file, options->cert_file) == 0)
+-    {
+-      ca_out = BIO_new_file (options->ca_file, "a");
+-    }
+-  else
+-    {
+-      ca_out = BIO_new_file (options->ca_file, create_mode);
+-    }
+-  CHECK_BIO (ca_out, options->ca_file);
+-
+-  sret = PEM_write_bio_X509 (ca_out, cacert->certificate);
++  struct sscg_stream *stream =
++    sscg_io_utils_get_stream_by_type (options->streams, SSCG_FILE_TYPE_CA);
++  sret = PEM_write_bio_X509 (stream->bio, cacert->certificate);
+   CHECK_SSL (sret, PEM_write_bio_X509 (CA));
+-  BIO_get_fp (ca_out, &fp);
+-  /* If this file matches the keyfile, do not set its permissions */
+-  if (options->ca_key_file &&
+-      strcmp (options->ca_file, options->ca_key_file) == 0)
++  ANNOUNCE_WRITE (SSCG_FILE_TYPE_CA);
++
++
++  /* Then write any non-certificate files */
++
++  /* Create CRL file */
++  if (GET_BIO (SSCG_FILE_TYPE_CRL))
+     {
+-      if (options->verbosity >= SSCG_DEBUG)
+-        {
+-          fprintf (
+-            stdout,
+-            "DEBUG: Not setting CA file permissions: superseded by a key\n");
+-        }
++      /* The CRL file is left intentionally blank, so do nothing here. The
++       * file was created as empty, so it will just be closed and have its
++       * permissions set later.
++       */
++      ANNOUNCE_WRITE (SSCG_FILE_TYPE_CRL);
+     }
+-  else
++
++
++  /* Create DH parameters file */
++  if (GET_BIO (SSCG_FILE_TYPE_DHPARAMS))
+     {
+-      if (options->verbosity >= SSCG_DEBUG)
+-        {
+-          fprintf (
+-            stdout, "DEBUG: Setting CA file permissions to %o\n", ca_mode);
+-        }
+-      fchmod (fileno (fp), ca_mode);
++      /* Open the file before generating the parameters. This avoids wasting
++       * the time to generate them if the destination is not writable.
++       */
++
++      ret = create_dhparams (main_ctx,
++                             options->verbosity,
++                             options->dhparams_prime_len,
++                             options->dhparams_generator,
++                             &dhparams);
++      CHECK_OK (ret);
++
++      /* Export the DH parameters to the file */
++      sret = PEM_write_bio_DHparams (GET_BIO (SSCG_FILE_TYPE_DHPARAMS),
++                                     dhparams->dh);
++      CHECK_SSL (sret, PEM_write_bio_DHparams ());
++      ANNOUNCE_WRITE (SSCG_FILE_TYPE_DHPARAMS);
+     }
+-  BIO_free (cert_out);
+-  cert_out = NULL;
+ 
+ 
++  /* Set the final file permissions */
++  sscg_io_utils_finalize_output_files (options->streams);
++
+   ret = EOK;
++
+ done:
+-  BIO_free (cert_key_out);
+-  BIO_free (cert_out);
+-  BIO_free (ca_key_out);
+-  BIO_free (ca_out);
+-
+   talloc_zfree (main_ctx);
+   if (ret != EOK)
+     {
+-      fprintf (stderr, "%s\n", strerror (ret));
++      SSCG_ERROR ("%s\n", strerror (ret));
+     }
+   return ret;
+ }
+diff --git a/test/dhparams_test.c b/test/dhparams_test.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..b054b40ea73ca98870836bd89ea10677be8fb075
+--- /dev/null
++++ b/test/dhparams_test.c
+@@ -0,0 +1,106 @@
++/*
++    This file is part of sscg.
++
++    sscg is free software: you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation, either version 3 of the License, or
++    (at your option) any later version.
++
++    sscg is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with sscg.  If not, see <http://www.gnu.org/licenses/>.
++
++    Copyright 2019 by Stephen Gallagher <sgallagh@redhat.com>
++*/
++
++#include <errno.h>
++#include <stdio.h>
++#include <string.h>
++#include <talloc.h>
++#include <openssl/err.h>
++
++#include "include/dhparams.h"
++
++int
++main (int argc, char **argv)
++{
++  int ret, sret, prime_len, generator;
++  struct sscg_dhparams *params = NULL;
++  TALLOC_CTX *main_ctx = NULL;
++
++  if (getenv ("SSCG_SKIP_DHPARAMS"))
++    {
++      /* Skip this test */
++      return 77;
++    }
++
++  errno = 0;
++  prime_len = strtol (argv[1], NULL, 0);
++  if (errno)
++    {
++      fprintf (stderr, "Prime length was not a valid integer.");
++      ret = errno;
++      goto done;
++    }
++
++  errno = 0;
++  generator = strtol (argv[2], NULL, 0);
++  if (errno)
++    {
++      fprintf (stderr, "Generator was not a valid integer.");
++      ret = errno;
++      goto done;
++    }
++
++  main_ctx = talloc_new (NULL);
++
++  ret = create_dhparams (main_ctx, SSCG_DEBUG, prime_len, generator, &params);
++  if (ret != EOK)
++    {
++      fprintf (stderr,
++               "Could not generate DH parameters: [%s]",
++               ERR_error_string (ERR_get_error (), NULL));
++      goto done;
++    }
++
++  if (!DH_check (params->dh, &sret))
++    {
++      ERR_print_errors_fp (stderr);
++      goto done;
++    }
++  if (sret & DH_CHECK_P_NOT_PRIME)
++    fprintf (stderr, "p value is not prime\n");
++  if (sret & DH_CHECK_P_NOT_SAFE_PRIME)
++    fprintf (stderr, "p value is not a safe prime\n");
++  if (sret & DH_CHECK_Q_NOT_PRIME)
++    fprintf (stderr, "q value is not a prime\n");
++  if (sret & DH_CHECK_INVALID_Q_VALUE)
++    fprintf (stderr, "q value is invalid\n");
++  if (sret & DH_CHECK_INVALID_J_VALUE)
++    fprintf (stderr, "j value is invalid\n");
++  if (sret & DH_UNABLE_TO_CHECK_GENERATOR)
++    fprintf (stderr, "unable to check the generator value\n");
++  if (sret & DH_NOT_SUITABLE_GENERATOR)
++    fprintf (stderr, "the g value is not a generator\n");
++
++  if (sret != 0)
++    {
++      /*
++       * We have generated parameters but DH_check() indicates they are
++       * invalid! This should never happen!
++       */
++      fprintf (stderr, "ERROR: Invalid parameters generated\n");
++      ret = EIO;
++      goto done;
++    }
++
++  ret = EOK;
++
++done:
++  talloc_free (main_ctx);
++  return ret;
++}
+-- 
+2.23.0
+
diff --git a/SOURCES/0008-Fix-client-cert-issues-found-by-CI-tests.patch b/SOURCES/0008-Fix-client-cert-issues-found-by-CI-tests.patch
new file mode 100644
index 0000000..152464a
--- /dev/null
+++ b/SOURCES/0008-Fix-client-cert-issues-found-by-CI-tests.patch
@@ -0,0 +1,98 @@
+From 8afa0ce578ecd5cc3a397707fdb163cc169b9bd1 Mon Sep 17 00:00:00 2001
+From: Stephen Gallagher <sgallagh@redhat.com>
+Date: Fri, 13 Dec 2019 08:25:01 -0500
+Subject: [PATCH 8/8] Fix client-cert issues found by CI tests
+
+Resolves: rhbz#1720667
+
+Better error message for client certs without public key file
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Fix memory leak in sscg_sign_x509_csr()
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Address clang-analyzer warning
+
+clang-analyzer determined that it was possible for the GET_BIO()
+return value to have changed between conditional creation of the
+client certificate and writing it out. This patch stores the result
+of the lookup so it's certain to be consistent.
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+---
+ src/io_utils.c | 4 ++--
+ src/sscg.c     | 8 +++++---
+ src/x509.c     | 1 +
+ 3 files changed, 8 insertions(+), 5 deletions(-)
+
+diff --git a/src/io_utils.c b/src/io_utils.c
+index 809a1da0e455afa0dba0796a5f7ac406742328a1..a2502afb20f4bcb536428f3528900c2bb06997f5 100644
+--- a/src/io_utils.c
++++ b/src/io_utils.c
+@@ -363,8 +363,8 @@ sscg_io_utils_open_output_files (struct sscg_stream **streams, bool overwrite)
+ 
+     case IO_UTILS_CLIENT_UNMATCHED:
+       SSCG_ERROR (
+-        "The client certificate must have both public and private key "
+-        "locations specified.\n");
++        "The client certificate must have the public key location "
++        "specified.\n");
+       ret = EINVAL;
+       goto done;
+ 
+diff --git a/src/sscg.c b/src/sscg.c
+index 470af815d91f5170a1e8fe00006dbaee4d07b209..f34a43b83e562d0bd7da9a77e25911762db83693 100644
+--- a/src/sscg.c
++++ b/src/sscg.c
+@@ -300,6 +300,7 @@ main (int argc, const char **argv)
+   char *cert_key_password = NULL;
+   char *cert_key_passfile = NULL;
+ 
++  bool build_client_cert = false;
+   int client_mode = SSCG_CERT_DEFAULT_MODE;
+   int client_key_mode = SSCG_KEY_DEFAULT_MODE;
+   char *client_key_password = NULL;
+@@ -1118,7 +1119,8 @@ main (int argc, const char **argv)
+   /* If requested, generate the client auth certificate and sign it with the
+    * private CA.
+    */
+-  if (GET_BIO (SSCG_FILE_TYPE_CLIENT))
++  build_client_cert = !!(GET_BIO (SSCG_FILE_TYPE_CLIENT));
++  if (build_client_cert)
+     {
+       ret = create_cert (main_ctx,
+                          options,
+@@ -1136,7 +1138,7 @@ main (int argc, const char **argv)
+ 
+   /* Write private keys first */
+ 
+-  if (GET_BIO (SSCG_FILE_TYPE_CLIENT_KEY))
++  if (build_client_cert)
+     {
+       /* This function has a default mechanism for prompting for the
+        * password if it is passed a cipher and gets a NULL password.
+@@ -1201,7 +1203,7 @@ main (int argc, const char **argv)
+   /* Public keys come next, in chain order */
+ 
+   /* Start with the client certificate */
+-  if (GET_BIO (SSCG_FILE_TYPE_CLIENT))
++  if (build_client_cert)
+     {
+       sret = PEM_write_bio_X509 (GET_BIO (SSCG_FILE_TYPE_CLIENT),
+                                  client_cert->certificate);
+diff --git a/src/x509.c b/src/x509.c
+index 18f0627bc64e7cb503a9e81c36dbe726186d1144..c173f539791fbbc51e52e6b121e587dca43924d4 100644
+--- a/src/x509.c
++++ b/src/x509.c
+@@ -482,5 +482,6 @@ done:
+       *_cert = talloc_steal (mem_ctx, scert);
+     }
+   X509_NAME_free (subject);
++  talloc_free(tmp_ctx);
+   return ret;
+ }
+-- 
+2.23.0
+
diff --git a/SOURCES/0009-Fix-help-message-for-client-key-file.patch b/SOURCES/0009-Fix-help-message-for-client-key-file.patch
new file mode 100644
index 0000000..04e0777
--- /dev/null
+++ b/SOURCES/0009-Fix-help-message-for-client-key-file.patch
@@ -0,0 +1,36 @@
+From fa6be1a9bbc8c5d42a248e398e3aac08078e311e Mon Sep 17 00:00:00 2001
+From: Stephen Gallagher <sgallagh@redhat.com>
+Date: Fri, 13 Dec 2019 11:51:43 -0500
+Subject: [PATCH 9/9] Fix help message for --client-key-file
+
+Resolves: rhbz#1720667
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Further clarify --client-key-file help message
+
+Resolves: rhbz#1720667
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+---
+ src/sscg.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/src/sscg.c b/src/sscg.c
+index f34a43b83e562d0bd7da9a77e25911762db83693..4d009a67488e83c4332f58ee52f7d6ea72a8ddbd 100644
+--- a/src/sscg.c
++++ b/src/sscg.c
+@@ -734,9 +734,7 @@ main (int argc, const char **argv)
+       &client_key_file,
+       0,
+       _ ("Path where the client's private key will be stored. "
+-         "(default is client-file with a .key suffix, if "
+-         "--client-file was passed, otherwise this file will not "
+-         "be generated.)"),
++         "(default is the client-file)"),
+       NULL,
+     },
+ 
+-- 
+2.24.1
+
diff --git a/SOURCES/0010-Better-validation-of-command-line-arguments.patch b/SOURCES/0010-Better-validation-of-command-line-arguments.patch
new file mode 100644
index 0000000..7e934e9
--- /dev/null
+++ b/SOURCES/0010-Better-validation-of-command-line-arguments.patch
@@ -0,0 +1,920 @@
+From 87530e9ebc872761c06506f3cb6a4fa5c494a614 Mon Sep 17 00:00:00 2001
+From: Stephen Gallagher <sgallagh@redhat.com>
+Date: Tue, 7 Jan 2020 14:32:01 -0500
+Subject: [PATCH 10/10] Better validation of command line arguments
+
+Check that key passphrases are within 4-1023 characters
+
+OpenSSL CLI tools cannot handle files with passphrases outside this
+range.
+
+Resolves: rhbz#1784441
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Output private keys with 2048 iteration count
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Rework passphrase handling
+
+Handle passphrases as part of the sscg_stream for a file. This will
+allow us to check for relevance as well as reducing code duplication.
+
+Resolves: rhbz#1784443
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Fix wrong x509 version in CSR
+
+This was, fortunately, not causing any problems because the signing
+process resulted in the certificates being generated with the
+correct version. It's best to be correct anyway.
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Fix memory leaks
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Fix alignment issue with popt
+
+The boolean values need to be explicitly defined as int because
+a bool may not be aligned properly. It was working prior to some
+recent changes by lucky accident.
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Prevent uninitialized read error
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Add missing newline for error message
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Fix OpenSSL 1.0 support
+
+The symbol UI_F_UI_SET_RESULT changed to UI_F_UI_SET_RESULT_EX in
+OpenSSL 1.1, but no other semantics changed that we care about.
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Fix formatting
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Fix missing error check
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+
+Read long password files properly
+
+Long passphrase files may require more than a single call to BIO_read()
+to gather the whole string.
+
+Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
+---
+ include/io_utils.h |  37 ++++++-
+ include/key.h      |   6 +-
+ include/sscg.h     |  42 +++++---
+ include/x509.h     |   6 +-
+ meson.build        |   1 +
+ src/io_utils.c     | 199 ++++++++++++++++++++++++++++++++++++-
+ src/sscg.c         | 239 +++++++--------------------------------------
+ src/x509.c         |   6 +-
+ 8 files changed, 310 insertions(+), 226 deletions(-)
+
+diff --git a/include/io_utils.h b/include/io_utils.h
+index 6a89a476b3d982447b6603153c6765835cd67464..907097c7ff1f7ae3c3adf35d0dfba0f5763dc8c0 100644
+--- a/include/io_utils.h
++++ b/include/io_utils.h
+@@ -24,6 +24,7 @@
+ #include <stdbool.h>
+ #include <talloc.h>
+ 
++#include "include/key.h"
+ #include "include/sscg.h"
+ 
+ 
+@@ -33,6 +34,9 @@ struct sscg_stream
+   char *path;
+   int mode;
+   int filetypes;
++
++  bool pass_prompt;
++  char *passphrase;
+ };
+ 
+ 
+@@ -69,8 +73,6 @@ sscg_io_utils_get_path_by_type (struct sscg_stream **streams,
+  * @path: The path to the file on disk.
+  * @mode: The filesystem mode this file should have when written to disk.
+  * See chmod(1) for the possible values.
+- * @overwrite: If true, replace any existing file at @normalized_path. If
+- * false, opening will fail if it already exists and return an error.
+  *
+  * Prepares all output filenames to be opened. Files are not created until
+  * sscg_io_utils_open_output_files() is called.
+@@ -82,9 +84,40 @@ sscg_io_utils_add_output_file (struct sscg_stream **streams,
+                                int mode);
+ 
+ 
++/**
++ * sscg_io_utils_add_output_key:
++ * @streams: The array of streams from the sscg_options
++ * @filetype:
++ * @path: The path to the file on disk.
++ * @mode: The filesystem mode this file should have when written to disk.
++ * See chmod(1) for the possible values.
++ * @pass_prompt: Whether the user should be prompted to enter a passphrase
++ * interactively.
++ * @passphrase: The passphrase supplied at the command line.
++ * @passfile: The path to a file containing the passphrase.
++ *
++ * Prepares all output filenames to be opened. Files are not created until
++ * sscg_io_utils_open_output_files() is called.
++ */
++int
++sscg_io_utils_add_output_key (struct sscg_stream **streams,
++                              enum sscg_file_type filetype,
++                              const char *path,
++                              int mode,
++                              bool pass_prompt,
++                              char *passphrase,
++                              char *passfile);
++
++
+ int
+ sscg_io_utils_open_output_files (struct sscg_stream **streams, bool overwrite);
+ 
++int
++sscg_io_utils_write_privatekey (struct sscg_stream **streams,
++                                enum sscg_file_type filetype,
++                                struct sscg_evp_pkey *key,
++                                struct sscg_options *options);
++
+ /* If this function fails, some of the output files may be left as 0400 */
+ int
+ sscg_io_utils_finalize_output_files (struct sscg_stream **streams);
+diff --git a/include/key.h b/include/key.h
+index ef871d6937e2fc805a445d6686263b023a38eaaa..4c32cad04950ee7fd75ec4144147eb919280c00a 100644
+--- a/include/key.h
++++ b/include/key.h
+@@ -17,15 +17,15 @@
+     Copyright 2017 by Stephen Gallagher <sgallagh@redhat.com>
+ */
+ 
++#ifndef _SSCG_KEY_H
++#define _SSCG_KEY_H
++
+ #include <openssl/rsa.h>
+ #include <openssl/evp.h>
+ 
+ #include "include/sscg.h"
+ #include "include/bignum.h"
+ 
+-#ifndef _SSCG_KEY_H
+-#define _SSCG_KEY_H
+-
+ struct sscg_evp_pkey
+ {
+   EVP_PKEY *evp_pkey;
+diff --git a/include/sscg.h b/include/sscg.h
+index 2744404c25c68ed905ca621bb955e0c04b33ca81..96b78152ccc492deafbbc61eb98702562a8fe5e6 100644
+--- a/include/sscg.h
++++ b/include/sscg.h
+@@ -20,17 +20,18 @@
+ /* This is a master header file that should be included by all
+    sscg source files. */
+ 
++
++#ifndef _SSCG_H
++#define _SSCG_H
++
+ #include <errno.h>
+ #include <openssl/ssl.h>
+ #include <openssl/err.h>
++#include <openssl/ui.h>
+ #include <stdbool.h>
+ #include <talloc.h>
+ #include <stdint.h>
+ 
+-#include "include/io_utils.h"
+-
+-#ifndef _SSCG_H
+-#define _SSCG_H
+ 
+ /* TODO: implement internationalization */
+ 
+@@ -81,15 +82,34 @@
+     }                                                                         \
+   while (0)
+ 
++/* The function changed in 1.1, but the library and reason names did not */
++#ifndef UI_F_UI_SET_RESULT_EX
++#define UI_F_UI_SET_RESULT_EX UI_F_UI_SET_RESULT
++#endif
++
+ #define CHECK_SSL(_sslret, _fn)                                               \
+   do                                                                          \
+     {                                                                         \
+       if (_sslret != 1)                                                       \
+         {                                                                     \
+           /* Get information about error from OpenSSL */                      \
++          unsigned long _ssl_error = ERR_get_error ();                        \
++          if ((ERR_GET_LIB (_ssl_error) == ERR_LIB_UI) &&                     \
++              (ERR_GET_FUNC (_ssl_error) == UI_F_UI_SET_RESULT_EX) &&         \
++              ((ERR_GET_REASON (_ssl_error) == UI_R_RESULT_TOO_LARGE) ||      \
++               (ERR_GET_REASON (_ssl_error) == UI_R_RESULT_TOO_SMALL)))       \
++            {                                                                 \
++              fprintf (                                                       \
++                stderr,                                                       \
++                "Passphrases must be between %d and %d characters. \n",       \
++                SSCG_MIN_KEY_PASS_LEN,                                        \
++                SSCG_MAX_KEY_PASS_LEN);                                       \
++              ret = EINVAL;                                                   \
++              goto done;                                                      \
++            }                                                                 \
+           fprintf (stderr,                                                    \
+                    "Error occurred in " #_fn ": [%s].\n",                     \
+-                   ERR_error_string (ERR_get_error (), NULL));                \
++                   ERR_error_string (_ssl_error, NULL));                      \
+           ret = EIO;                                                          \
+           goto done;                                                          \
+         }                                                                     \
+@@ -223,12 +243,9 @@ struct sscg_options
+   const EVP_CIPHER *cipher;
+   const EVP_MD *hash_fn;
+ 
+-  bool ca_key_pass_prompt;
+-  char *ca_key_pass;
+-  bool cert_key_pass_prompt;
+-  char *cert_key_pass;
+-  bool client_key_pass_prompt;
+-  char *client_key_pass;
++  int ca_key_pass_prompt;
++  int cert_key_pass_prompt;
++  int client_key_pass_prompt;
+ 
+   /* Output Files */
+   struct sscg_stream **streams;
+@@ -251,4 +268,7 @@ enum sscg_cert_type
+   SSCG_NUM_CERT_TYPES
+ };
+ 
++#define SSCG_MIN_KEY_PASS_LEN 4
++#define SSCG_MAX_KEY_PASS_LEN 1023
++
+ #endif /* _SSCG_H */
+diff --git a/include/x509.h b/include/x509.h
+index 865cd0018d3ea77915cd86349e333ae6f4de2af0..cc7e498d06c4d2e503d7d8748dfd5386f9ad0794 100644
+--- a/include/x509.h
++++ b/include/x509.h
+@@ -17,6 +17,9 @@
+     Copyright 2017 by Stephen Gallagher <sgallagh@redhat.com>
+ */
+ 
++#ifndef _SSCG_X509_H
++#define _SSCG_X509_H
++
+ #include <openssl/x509.h>
+ #include <openssl/x509v3.h>
+ 
+@@ -24,9 +27,6 @@
+ #include "include/bignum.h"
+ #include "include/key.h"
+ 
+-#ifndef _SSCG_X509_H
+-#define _SSCG_X509_H
+-
+ struct sscg_cert_info
+ {
+   /* === Input Data === */
+diff --git a/meson.build b/meson.build
+index eb339ea8c768adab6d576736fbe476b83529e78d..3d8937ce73dc84f652f6fdad461a1468a532f0f2 100644
+--- a/meson.build
++++ b/meson.build
+@@ -76,6 +76,7 @@ sscg_lib_hdrs = [
+     'include/dhparams.h',
+     'include/io_utils.h',
+     'include/key.h',
++    'include/sscg.h',
+     'include/x509.h',
+ ]
+ 
+diff --git a/src/io_utils.c b/src/io_utils.c
+index a2502afb20f4bcb536428f3528900c2bb06997f5..1b8bc41c3849acbe4657ae14dfe55e3010957129 100644
+--- a/src/io_utils.c
++++ b/src/io_utils.c
+@@ -24,8 +24,14 @@
+ #include <talloc.h>
+ 
+ #include "include/io_utils.h"
++#include "include/key.h"
+ #include "include/sscg.h"
+ 
++
++/* Same as OpenSSL CLI */
++#define MAX_PW_LEN 1024
++
++
+ int
+ sscg_normalize_path (TALLOC_CTX *mem_ctx,
+                      const char *path,
+@@ -62,6 +68,12 @@ sscg_stream_destructor (TALLOC_CTX *ptr)
+ 
+   BIO_free (stream->bio);
+ 
++  /* Zero out the memory before freeing it so we don't leak passwords */
++  if (stream->passphrase)
++    {
++      memset (stream->passphrase, 0, strnlen (stream->passphrase, MAX_PW_LEN));
++    }
++
+   return 0;
+ }
+ 
+@@ -147,11 +159,101 @@ sscg_io_utils_get_path_by_type (struct sscg_stream **streams,
+ }
+ 
+ 
++/* This function takes a copy of a string into a talloc hierarchy and memsets
++ * the original string to zeroes to avoid leaking it when that memory is freed.
++ */
++static char *
++sscg_secure_string_steal (TALLOC_CTX *mem_ctx, char *src)
++{
++  char *dest = talloc_strdup (mem_ctx, src);
++
++  memset ((void *)src, 0, strlen (src));
++
++  return dest;
++}
++
++
++static int
++validate_passphrase (struct sscg_stream *stream)
++{
++  /* Ignore non-key types */
++  if (!(stream->filetypes & SSCG_FILE_TYPE_KEYS))
++    return EOK;
++
++  /* Ignore unset passwords; these will be prompted for when writing out the
++   * key file
++   */
++  if (!stream->passphrase)
++    return EOK;
++
++  size_t pass_len = strnlen (stream->passphrase, SSCG_MAX_KEY_PASS_LEN + 1);
++
++  if ((pass_len < SSCG_MIN_KEY_PASS_LEN) || (pass_len > SSCG_MAX_KEY_PASS_LEN))
++    {
++      SSCG_ERROR ("Passphrases must be between %d and %d characters. \n",
++                  SSCG_MIN_KEY_PASS_LEN,
++                  SSCG_MAX_KEY_PASS_LEN);
++      return EINVAL;
++    }
++  return EOK;
++}
++
++
++static char *
++sscg_read_pw_file (TALLOC_CTX *mem_ctx, char *path)
++{
++  int i;
++  BIO *pwdbio = NULL;
++  char tpass[MAX_PW_LEN + 1];
++  int offset = 0;
++  char *tmp = NULL;
++  char *password = NULL;
++
++  pwdbio = BIO_new_file (path, "r");
++  if (pwdbio == NULL)
++    {
++      fprintf (stderr, "Can't open file %s\n", path);
++      return NULL;
++    }
++
++  /* Read up to one more character than the MAX_PW_LEN */
++  for (offset = 0;
++       (i = BIO_read (pwdbio, tpass + offset, MAX_PW_LEN + 1 - offset)) > 0 &&
++       offset < (MAX_PW_LEN + 1);
++       offset += i)
++    ;
++
++  tpass[MAX_PW_LEN] = '\0';
++
++  BIO_free_all (pwdbio);
++  pwdbio = NULL;
++
++  if (i < 0)
++    {
++      fprintf (stderr, "Error reading password from BIO\n");
++      return NULL;
++    }
++
++  tmp = strchr (tpass, '\n');
++  if (tmp != NULL)
++    *tmp = 0;
++
++  password = talloc_strdup (mem_ctx, tpass);
++
++  memset (tpass, 0, MAX_PW_LEN + 1);
++
++  return password;
++}
++
++
+ int
+-sscg_io_utils_add_output_file (struct sscg_stream **streams,
+-                               enum sscg_file_type filetype,
+-                               const char *path,
+-                               int mode)
++sscg_io_utils_add_output_key (struct sscg_stream **streams,
++                              enum sscg_file_type filetype,
++                              const char *path,
++                              int mode,
++                              bool pass_prompt,
++                              char *passphrase,
++                              char *passfile)
+ {
+   int ret, i;
+   TALLOC_CTX *tmp_ctx = NULL;
+@@ -163,6 +265,22 @@ sscg_io_utils_add_output_file (struct sscg_stream **streams,
+    */
+   if (path == NULL)
+     {
++      if (pass_prompt)
++        {
++          SSCG_ERROR (
++            "Passphrase prompt requested for %s, but no file path provided.\n",
++            sscg_get_file_type_name (filetype));
++          return EINVAL;
++        }
++
++      if (passphrase)
++        {
++          SSCG_ERROR (
++            "Passphrase provided for %s, but no file path provided.\n",
++            sscg_get_file_type_name (filetype));
++          return EINVAL;
++        }
++
+       SSCG_LOG (SSCG_DEBUG,
+                 "Got a NULL path with filetype: %s\n",
+                 sscg_get_file_type_name (filetype));
+@@ -220,6 +338,31 @@ sscg_io_utils_add_output_file (struct sscg_stream **streams,
+   /* Add the file type */
+   stream->filetypes |= (1 << filetype);
+ 
++
++  /* Set the password options */
++  stream->pass_prompt = pass_prompt;
++
++  if (passphrase)
++    {
++      stream->passphrase = sscg_secure_string_steal (stream, passphrase);
++      ret = validate_passphrase (stream);
++      if (ret != EOK)
++        goto done;
++    }
++  else if (passfile)
++    {
++      stream->passphrase = sscg_read_pw_file (stream, passfile);
++      if (!stream->passphrase)
++        {
++          fprintf (stderr, "Failed to read passphrase from %s", passfile);
++          ret = EIO;
++          goto done;
++        }
++    }
++  ret = validate_passphrase (stream);
++  if (ret != EOK)
++    goto done;
++
+   ret = EOK;
+ 
+ done:
+@@ -228,6 +371,17 @@ done:
+ }
+ 
+ 
++int
++sscg_io_utils_add_output_file (struct sscg_stream **streams,
++                               enum sscg_file_type filetype,
++                               const char *path,
++                               int mode)
++{
++  return sscg_io_utils_add_output_key (
++    streams, filetype, path, mode, false, NULL, NULL);
++}
++
++
+ enum io_utils_errors
+ {
+   IO_UTILS_OK = 0,
+@@ -400,6 +554,43 @@ done:
+ }
+ 
+ 
++int
++sscg_io_utils_write_privatekey (struct sscg_stream **streams,
++                                enum sscg_file_type filetype,
++                                struct sscg_evp_pkey *key,
++                                struct sscg_options *options)
++{
++  int ret, sret;
++
++  struct sscg_stream *stream =
++    sscg_io_utils_get_stream_by_type (streams, filetype);
++  if (stream)
++    {
++      /* This function has a default mechanism for prompting for the
++       * password if it is passed a cipher and gets a NULL password.
++       *
++       * Only pass the cipher if we have a password or were instructed
++       * to prompt for one.
++       */
++      sret = PEM_write_bio_PKCS8PrivateKey (
++        stream->bio,
++        key->evp_pkey,
++        stream->pass_prompt || stream->passphrase ? options->cipher : NULL,
++        stream->passphrase,
++        stream->passphrase ? strlen (stream->passphrase) : 0,
++        NULL,
++        NULL);
++      CHECK_SSL (sret, PEM_write_bio_PKCS8PrivateKey);
++      ANNOUNCE_WRITE (filetype);
++    }
++
++  ret = EOK;
++
++done:
++  return ret;
++}
++
++
+ int
+ sscg_io_utils_finalize_output_files (struct sscg_stream **streams)
+ {
+diff --git a/src/sscg.c b/src/sscg.c
+index 4d009a67488e83c4332f58ee52f7d6ea72a8ddbd..96a9be1232d890590e97c126f8f4a78d571d7247 100644
+--- a/src/sscg.c
++++ b/src/sscg.c
+@@ -18,6 +18,7 @@
+ */
+ 
+ #define _GNU_SOURCE
++#include <assert.h>
+ #include <popt.h>
+ #include <stdlib.h>
+ #include <stdio.h>
+@@ -40,9 +41,6 @@
+ int verbosity;
+ 
+ 
+-/* Same as OpenSSL CLI */
+-#define MAX_PW_LEN 1024
+-
+ static int
+ get_security_level (void)
+ {
+@@ -140,79 +138,6 @@ print_options (struct sscg_options *opts)
+ }
+ 
+ 
+-/* This function takes a copy of a string into a talloc hierarchy and memsets
+- * the original string to zeroes to avoid leaking it when that memory is freed.
+- */
+-static char *
+-sscg_secure_string_steal (TALLOC_CTX *mem_ctx, char *src)
+-{
+-  char *dest = talloc_strdup (mem_ctx, src);
+-
+-  memset (src, 0, strlen (src));
+-
+-  return dest;
+-}
+-
+-
+-static int
+-sscg_options_destructor (TALLOC_CTX *opts)
+-{
+-  struct sscg_options *options =
+-    talloc_get_type_abort (opts, struct sscg_options);
+-
+-  /* Zero out the memory before freeing it so we don't leak passwords */
+-  if (options->ca_key_pass)
+-    {
+-      memset (options->ca_key_pass, 0, strlen (options->ca_key_pass));
+-    }
+-
+-  if (options->cert_key_pass)
+-    {
+-      memset (options->cert_key_pass, 0, strlen (options->cert_key_pass));
+-    }
+-
+-  return 0;
+-}
+-
+-
+-static char *
+-sscg_read_pw_file (TALLOC_CTX *mem_ctx, char *path)
+-{
+-  int i;
+-  BIO *pwdbio = NULL;
+-  char tpass[MAX_PW_LEN];
+-  char *tmp = NULL;
+-  char *password = NULL;
+-
+-  pwdbio = BIO_new_file (path, "r");
+-  if (pwdbio == NULL)
+-    {
+-      fprintf (stderr, "Can't open file %s\n", path);
+-      return NULL;
+-    }
+-
+-  i = BIO_gets (pwdbio, tpass, MAX_PW_LEN);
+-  BIO_free_all (pwdbio);
+-  pwdbio = NULL;
+-
+-  if (i <= 0)
+-    {
+-      fprintf (stderr, "Error reading password from BIO\n");
+-      return NULL;
+-    }
+-
+-  tmp = strchr (tpass, '\n');
+-  if (tmp != NULL)
+-    *tmp = 0;
+-
+-  password = talloc_strdup (mem_ctx, tpass);
+-
+-  memset (tpass, 0, MAX_PW_LEN);
+-
+-  return password;
+-}
+-
+-
+ const char *
+ sscg_get_verbosity_name (enum sscg_verbosity type)
+ {
+@@ -310,12 +235,14 @@ main (int argc, const char **argv)
+   struct sscg_evp_pkey *cakey;
+   struct sscg_x509_cert *svc_cert;
+   struct sscg_evp_pkey *svc_key;
+-  struct sscg_x509_cert *client_cert;
+-  struct sscg_evp_pkey *client_key;
++  struct sscg_x509_cert *client_cert = NULL;
++  struct sscg_evp_pkey *client_key = NULL;
+ 
+   int dhparams_mode = SSCG_CERT_DEFAULT_MODE;
+   struct sscg_dhparams *dhparams = NULL;
+ 
++  struct sscg_stream *stream = NULL;
++
+   /* Always use umask 0577 for generating certificates and keys
+        This means that it's opened as write-only by the effective
+        user. */
+@@ -335,7 +262,6 @@ main (int argc, const char **argv)
+ 
+   options = talloc_zero (main_ctx, struct sscg_options);
+   CHECK_MEM (options);
+-  talloc_set_destructor ((TALLOC_CTX *)options, sscg_options_destructor);
+ 
+   options->streams =
+     talloc_zero_array (options, struct sscg_stream *, SSCG_NUM_FILE_TYPES);
+@@ -965,56 +891,6 @@ main (int argc, const char **argv)
+         }
+     }
+ 
+-  /* Password handling */
+-  if (ca_key_password)
+-    {
+-      options->ca_key_pass =
+-        sscg_secure_string_steal (options, ca_key_password);
+-    }
+-  else if (ca_key_passfile)
+-    {
+-      options->ca_key_pass = sscg_read_pw_file (options, ca_key_passfile);
+-      if (!options->ca_key_pass)
+-        {
+-          fprintf (
+-            stderr, "Failed to read passphrase from %s", ca_key_passfile);
+-          goto done;
+-        }
+-    }
+-
+-  if (cert_key_password)
+-    {
+-      options->cert_key_pass =
+-        sscg_secure_string_steal (options, cert_key_password);
+-    }
+-  else if (cert_key_passfile)
+-    {
+-      options->cert_key_pass = sscg_read_pw_file (options, cert_key_passfile);
+-      if (!options->cert_key_pass)
+-        {
+-          fprintf (
+-            stderr, "Failed to read passphrase from %s", cert_key_passfile);
+-          goto done;
+-        }
+-    }
+-
+-  if (client_key_password)
+-    {
+-      options->client_key_pass =
+-        sscg_secure_string_steal (options, client_key_password);
+-    }
+-  else if (client_key_passfile)
+-    {
+-      options->client_key_pass =
+-        sscg_read_pw_file (options, client_key_passfile);
+-      if (!options->client_key_pass)
+-        {
+-          fprintf (
+-            stderr, "Failed to read passphrase from %s", client_key_passfile);
+-          goto done;
+-        }
+-    }
+-
+   if (options->key_strength < options->minimum_key_strength)
+     {
+       fprintf (stderr,
+@@ -1055,8 +931,13 @@ main (int argc, const char **argv)
+                                        ca_mode);
+   CHECK_OK (ret);
+ 
+-  ret = sscg_io_utils_add_output_file (
+-    options->streams, SSCG_FILE_TYPE_CA_KEY, ca_key_file, ca_key_mode);
++  ret = sscg_io_utils_add_output_key (options->streams,
++                                      SSCG_FILE_TYPE_CA_KEY,
++                                      ca_key_file,
++                                      ca_key_mode,
++                                      options->ca_key_pass_prompt,
++                                      ca_key_password,
++                                      ca_key_passfile);
+   CHECK_OK (ret);
+ 
+   ret = sscg_io_utils_add_output_file (options->streams,
+@@ -1065,11 +946,14 @@ main (int argc, const char **argv)
+                                        cert_mode);
+   CHECK_OK (ret);
+ 
+-  ret = sscg_io_utils_add_output_file (options->streams,
+-                                       SSCG_FILE_TYPE_SVC_KEY,
+-                                       cert_key_file ? cert_key_file :
+-                                                       "./service-key.pem",
+-                                       cert_key_mode);
++  ret = sscg_io_utils_add_output_key (options->streams,
++                                      SSCG_FILE_TYPE_SVC_KEY,
++                                      cert_key_file ? cert_key_file :
++                                                      "./service-key.pem",
++                                      cert_key_mode,
++                                      options->cert_key_pass_prompt,
++                                      cert_key_password,
++                                      cert_key_passfile);
+   CHECK_OK (ret);
+ 
+ 
+@@ -1078,11 +962,14 @@ main (int argc, const char **argv)
+   CHECK_OK (ret);
+ 
+ 
+-  ret = sscg_io_utils_add_output_file (options->streams,
+-                                       SSCG_FILE_TYPE_CLIENT_KEY,
+-                                       client_key_file ? client_key_file :
+-                                                         client_file,
+-                                       client_key_mode);
++  ret = sscg_io_utils_add_output_key (options->streams,
++                                      SSCG_FILE_TYPE_CLIENT_KEY,
++                                      client_key_file ? client_key_file :
++                                                        client_file,
++                                      client_key_mode,
++                                      options->client_key_pass_prompt,
++                                      client_key_password,
++                                      client_key_passfile);
+   CHECK_OK (ret);
+ 
+   ret = sscg_io_utils_add_output_file (
+@@ -1136,67 +1023,17 @@ main (int argc, const char **argv)
+ 
+   /* Write private keys first */
+ 
+-  if (build_client_cert)
+-    {
+-      /* This function has a default mechanism for prompting for the
+-       * password if it is passed a cipher and gets a NULL password.
+-       *
+-       * Only pass the cipher if we have a password or were instructed
+-       * to prompt for one.
+-       */
+-      sret = PEM_write_bio_PrivateKey (
+-        GET_BIO (SSCG_FILE_TYPE_CLIENT_KEY),
+-        client_key->evp_pkey,
+-        options->client_key_pass_prompt || options->client_key_pass ?
+-          options->cipher :
+-          NULL,
+-        (unsigned char *)options->client_key_pass,
+-        options->client_key_pass ? strlen (options->client_key_pass) : 0,
+-        NULL,
+-        NULL);
+-      CHECK_SSL (sret, PEM_write_bio_PrivateKey (svc));
+-      ANNOUNCE_WRITE (SSCG_FILE_TYPE_SVC_KEY);
+-    }
++  ret = sscg_io_utils_write_privatekey (
++    options->streams, SSCG_FILE_TYPE_CLIENT_KEY, client_key, options);
++  CHECK_OK (ret);
+ 
+-  /* This function has a default mechanism for prompting for the
+-   * password if it is passed a cipher and gets a NULL password.
+-   *
+-   * Only pass the cipher if we have a password or were instructed
+-   * to prompt for one.
+-   */
+-  sret = PEM_write_bio_PrivateKey (
+-    GET_BIO (SSCG_FILE_TYPE_SVC_KEY),
+-    svc_key->evp_pkey,
+-    options->cert_key_pass_prompt || options->cert_key_pass ? options->cipher :
+-                                                              NULL,
+-    (unsigned char *)options->cert_key_pass,
+-    options->cert_key_pass ? strlen (options->cert_key_pass) : 0,
+-    NULL,
+-    NULL);
+-  CHECK_SSL (sret, PEM_write_bio_PrivateKey (svc));
+-  ANNOUNCE_WRITE (SSCG_FILE_TYPE_SVC_KEY);
++  ret = sscg_io_utils_write_privatekey (
++    options->streams, SSCG_FILE_TYPE_SVC_KEY, svc_key, options);
++  CHECK_OK (ret);
+ 
+-  /* Create CA private key, if requested */
+-  if (GET_BIO (SSCG_FILE_TYPE_CA_KEY))
+-    {
+-      /* This function has a default mechanism for prompting for the
+-       * password if it is passed a cipher and gets a NULL password.
+-       *
+-       * Only pass the cipher if we have a password or were instructed
+-       * to prompt for one.
+-       */
+-      sret = PEM_write_bio_PrivateKey (
+-        GET_BIO (SSCG_FILE_TYPE_CA_KEY),
+-        cakey->evp_pkey,
+-        options->ca_key_pass_prompt || options->ca_key_pass ? options->cipher :
+-                                                              NULL,
+-        (unsigned char *)options->ca_key_pass,
+-        options->ca_key_pass ? strlen (options->ca_key_pass) : 0,
+-        NULL,
+-        NULL);
+-      CHECK_SSL (sret, PEM_write_bio_PrivateKey (CA));
+-      ANNOUNCE_WRITE (SSCG_FILE_TYPE_CA_KEY);
+-    }
++  ret = sscg_io_utils_write_privatekey (
++    options->streams, SSCG_FILE_TYPE_CA_KEY, cakey, options);
++  CHECK_OK (ret);
+ 
+   /* Public keys come next, in chain order */
+ 
+@@ -1217,7 +1054,7 @@ main (int argc, const char **argv)
+ 
+ 
+   /* Create CA public certificate */
+-  struct sscg_stream *stream =
++  stream =
+     sscg_io_utils_get_stream_by_type (options->streams, SSCG_FILE_TYPE_CA);
+   sret = PEM_write_bio_X509 (stream->bio, cacert->certificate);
+   CHECK_SSL (sret, PEM_write_bio_X509 (CA));
+diff --git a/src/x509.c b/src/x509.c
+index c173f539791fbbc51e52e6b121e587dca43924d4..42315d42d1e03460a8121e1592d8e7fcc0fef1df 100644
+--- a/src/x509.c
++++ b/src/x509.c
+@@ -72,7 +72,7 @@ _sscg_certinfo_destructor (TALLOC_CTX *ctx)
+   struct sscg_cert_info *certinfo =
+     talloc_get_type_abort (ctx, struct sscg_cert_info);
+ 
+-  sk_X509_EXTENSION_free (certinfo->extensions);
++  sk_X509_EXTENSION_pop_free (certinfo->extensions, X509_EXTENSION_free);
+ 
+   return 0;
+ }
+@@ -155,7 +155,7 @@ sscg_x509v3_csr_new (TALLOC_CTX *mem_ctx,
+   talloc_set_destructor ((TALLOC_CTX *)csr, _sscg_csr_destructor);
+ 
+   /* We will generate only x509v3 certificates */
+-  sslret = X509_REQ_set_version (csr->x509_req, 3);
++  sslret = X509_REQ_set_version (csr->x509_req, 2);
+   CHECK_SSL (sslret, X509_REQ_set_version);
+ 
+   subject = X509_REQ_get_subject_name (csr->x509_req);
+@@ -461,6 +461,8 @@ sscg_sign_x509_csr (TALLOC_CTX *mem_ctx,
+         }
+       sslret = X509_add_ext (cert, ext, -1);
+       CHECK_SSL (sslret, X509_add_ext);
++
++      X509_EXTENSION_free (ext);
+     }
+ 
+   /* Sign the new certificate */
+-- 
+2.24.1
+
diff --git a/SPECS/sscg.spec b/SPECS/sscg.spec
index 12b96cf..235e7fb 100644
--- a/SPECS/sscg.spec
+++ b/SPECS/sscg.spec
@@ -9,7 +9,7 @@
 
 Name:           sscg
 Version:        2.3.3
-Release:        6%{?dist}
+Release:        14%{?dist}
 Summary:        Simple SSL certificate generator
 
 License:        BSD
@@ -34,6 +34,18 @@ Patch0002: 0002-Adjust-defaults-based-on-system-security-level.patch
 Patch0003: 0003-Adjust-hash-defaults-based-on-system-security-level.patch
 Patch0004: 0004-Properly-check-all-return-values.patch
 
+# RHBZ #1717880
+Patch0005: 0005-Add-password-support-for-private-keys.patch
+Patch0006: 0006-Allow-specifying-keyfile-password-by-file.patch
+
+# RHBZ #1720667
+Patch0007: 0007-Add-support-for-client-certificates-and-dhparams.patch
+Patch0008: 0008-Fix-client-cert-issues-found-by-CI-tests.patch
+Patch0009: 0009-Fix-help-message-for-client-key-file.patch
+
+# RHBZ #1784441 and 1784443
+Patch0010: 0010-Better-validation-of-command-line-arguments.patch
+
 %description
 A utility to aid in the creation of more secure "self-signed"
 certificates. The certificates created by this tool are generated in a
@@ -67,6 +79,37 @@ false signatures from the service certificate.
 %{_mandir}/man8/%{name}.8*
 
 %changelog
+* Tue Jan 21 2020 Stephen Gallagher <sgallagh@redhat.com> - 2.3.3-14
+- Properly handling reading long passphrase files.
+
+* Tue Jan 21 2020 Stephen Gallagher <sgallagh@redhat.com> - 2.3.3-13
+- Fix missing error check for --*-key-passfile
+
+* Thu Jan 09 2020 Stephen Gallagher <sgallagh@redhat.com> - 2.3.3-12
+- Improve validation of command-line arguments
+- Resolves: rhbz#1784441
+- Resolves: rhbz#1784443
+
+* Tue Jan 07 2020 Stephen Gallagher <sgallagh@redhat.com> - 2.3.3-11
+- Further improve --client-key-file help message
+- Resolves: rhbz#1720667
+
+* Fri Dec 13 2019 Stephen Gallagher <sgallagh@redhat.com> - 2.3.3-10
+- Fix incorrect help message
+- Resolves: rhbz#1720667
+
+* Fri Dec 13 2019 Stephen Gallagher <sgallagh@redhat.com> - 2.3.3-9
+- Fix null-dereference and memory leak issues with client certs
+- Resolves: rhbz#1720667
+
+* Wed Dec 11 2019 Stephen Gallagher <sgallagh@redhat.com> - 2.3.3-8
+- Add support for generating client authentication certificates
+- Resolves: rhbz#1720667
+
+* Fri Nov 01 2019 Stephen Gallagher <sgallagh@redhat.com> - 2.3.3-7
+- Add support for password-protecting the private key files
+- Resolves: rhbz#1717880
+
 * Wed Nov 28 2018 Stephen Gallagher <sgallagh@redhat.com> - 2.3.3-6
 - Fixes for issues detected by automated testing.
 - Resolves: rhbz#1653323