sscg/SOURCES/0007-Add-support-for-client...

2652 lines
74 KiB
Diff

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