ws: Automatically renew auto-created expired certificates

TLS certificates should not be valid for longer than two years [1], and
some browsers (in particular, Safari) are being really picky about it.

So reduce the lifetime for our self-signed (previously 100 years) or
sscg (previously 10 years) certificates to 1 year, and automatically
renew them when they are expired.

[1] https://www.globalsign.com/en/blog/ssl-certificate-validity-capped-at-maximum-two-years

Fixes #14121
Closes #14293
This commit is contained in:
Martin Pitt 2020-07-01 09:21:53 +02:00 committed by Martin Pitt
parent d4395c469f
commit 3230d799f2
4 changed files with 90 additions and 5 deletions

View File

@ -58,7 +58,10 @@
<listitem><para>Manage Cockpit's SSL certificate. If used without options will check if cockpit has a valid certificate without making any changes.</para>
<para>
<option>--ensure</option> Ensure that a certificate exists and can be loaded. Certificate will be created if it does not already exist.
<option>--ensure</option> Ensure that a certificate exists and can be loaded. If none exists, create a <code>0-self-signed.cert</code>
certificate, using <ulink url="https://github.com/sgallagher/sscg">sscg</ulink> (if available) or
<ulink url="https://linux.die.net/man/1/openssl">openssl</ulink>. That self-signed default certificate will be automatically
renewed when it expires.
</para>
<para>

View File

@ -156,7 +156,7 @@ openssl_make_dummy_cert (const gchar *key_file,
const gchar *argv[] = {
"openssl",
"req", "-x509",
"-days", "36500",
"-days", "365",
"-newkey", "rsa:2048",
"-keyout", key_file,
"-keyform", "PEM",
@ -210,7 +210,7 @@ sscg_make_dummy_cert (const gchar *key_file,
const gchar *argv[] = {
"sscg", "--quiet",
"--lifetime", "3650",
"--lifetime", "365",
"--key-strength", "2048",
"--cert-key-file", key_file,
"--cert-file", cert_file,

View File

@ -147,7 +147,8 @@ out:
static int
ensure_certificate (const gchar *user,
const gchar *group,
const gchar *selinux)
const gchar *selinux,
gboolean check_expired)
{
GError *error = NULL;
GTlsCertificate *certificate = NULL;
@ -170,6 +171,20 @@ ensure_certificate (const gchar *user,
goto out;
}
/* auto-renew our expired auto-created certificates */
/* must specify ca due to https://gitlab.gnome.org/GNOME/glib-networking/-/issues/139 */
if (check_expired && g_str_has_suffix (path, "/0-self-signed.cert") &&
g_tls_certificate_verify (certificate, NULL, certificate) & G_TLS_CERTIFICATE_EXPIRED)
{
int r;
g_message ("Automatically created certificate %s is expired, renewing it", path);
r = g_unlink (path);
if (r == 0)
ensure_certificate (user, group, selinux, FALSE); /* avoid infinite recursion */
else
g_warning ("Failed to remove %s: %m", path);
}
ret = set_cert_attributes (path, user, group, selinux);
out:
@ -311,7 +326,7 @@ cockpit_remotectl_certificate (int argc,
if (pem_files)
ret = cockpit_certificate_combine (pem_files, user, group, selinux);
else if (ensure)
ret = ensure_certificate (user, group, selinux);
ret = ensure_certificate (user, group, selinux, TRUE);
else
ret = locate_certificate ();
}

View File

@ -200,6 +200,63 @@ test_valid_selfsigned (TestCase *test,
g_assert_cmpint (g_tls_certificate_verify (certificate, NULL, certificate), ==, 0);
}
static void
test_refresh_expired (TestCase *test,
gconstpointer data)
{
GError *error = NULL;
g_autofree gchar *oldpath = g_build_filename (test->cert_dir, "alice-expired.cert", NULL);
g_autofree gchar *selfsigned_path = g_build_filename (test->cert_dir, "0-self-signed.cert", NULL);
g_autoptr(GTlsCertificate) certificate = NULL;
char *argv[] = { "certificate", "--user", (gchar *) g_get_user_name (), "--ensure", NULL };
int ret;
if (!openssl_path)
{
g_test_skip ("openssl not available");
return;
}
/* The call in setup() just created a combined certificate out of alice-expired.
* Rename it to pretend it was a self-signed one */
g_assert_cmpint (g_rename (oldpath, selfsigned_path), ==, 0);
/* sanity check: cert should be expired */
certificate = g_tls_certificate_new_from_file (selfsigned_path, &error);
g_assert_no_error (error);
g_assert_cmpint (g_tls_certificate_verify (certificate, NULL, certificate), ==, G_TLS_CERTIFICATE_EXPIRED);
/* call with --ensure again, refreshes the cert */
ret = cockpit_remotectl_certificate (4, argv);
g_assert_cmpint (ret, ==, 0);
/* now it's a valid certificate again */
test_valid_selfsigned (test, data);
}
static void
test_keep_custom_expired (TestCase *test,
gconstpointer data)
{
GError *error = NULL;
g_autofree gchar *path = g_build_filename (test->cert_dir, "alice-expired.cert", NULL);
g_autofree gchar *orig_content = NULL;
g_autofree gchar *new_content = NULL;
char *argv[] = { "certificate", "--user", (gchar *) g_get_user_name (), "--ensure", NULL };
int ret;
/* The call in setup() just created a combined certificate out of alice-expired */
g_file_get_contents (path, &orig_content, NULL, &error);
g_assert_no_error (error);
/* call with --ensure again; this is a custom certificate, should *not* be touched */
ret = cockpit_remotectl_certificate (4, argv);
g_assert_cmpint (ret, ==, 0);
g_file_get_contents (path, &new_content, NULL, &error);
g_assert_no_error (error);
g_assert_cmpstr (orig_content, ==, new_content);
}
static void
test_failure (TestCase *test,
gconstpointer data)
@ -226,6 +283,8 @@ const gchar *invalid_files1[] = { SRCDIR "/src/ws/mock-config/cockpit/cockpit.co
const gchar *invalid_files2[] = { SRCDIR "/src/bridge/mock-server.crt",
SRCDIR "/src/bridge/mock-client.crt", NULL };
const gchar *invalid_files3[] = { SRCDIR "/src/bridge/mock-client.key", NULL };
const gchar *expired_files[] = { SRCDIR "/src/tls/ca/alice-expired.pem",
SRCDIR "/src/tls/ca/alice.key", NULL };
/* test both possible orders in combined files: certs|key and key|certs */
#define combined_key_first SRCDIR "/src/ws/mock-combined.crt"
@ -272,6 +331,10 @@ static const TestFixture fixture_create = {
.needs_openssl = TRUE,
};
static const TestFixture fixture_expired = {
.files = expired_files,
};
static const TestFixture fixture_create_no_permission = {
.expected_message = "Couldn't create temporary file*Permission denied",
.files = no_files,
@ -315,6 +378,10 @@ main (int argc,
setup, test_valid_selfsigned, teardown);
g_test_add ("/remotectl-certificate/create-no-permission", TestCase, &fixture_create_no_permission,
setup, test_failure, teardown);
g_test_add ("/remotectl-certificate/refresh-expired", TestCase, &fixture_expired,
setup, test_refresh_expired, teardown);
g_test_add ("/remotectl-certificate/keep-custom-expired", TestCase, &fixture_expired,
setup, test_keep_custom_expired, teardown);
g_test_add ("/remotectl-certificate/load-combined-key-first", TestCase, &fixture_preinstall_combined_key_first,
setup, test_success, teardown);
g_test_add ("/remotectl-certificate/load-combined-key-last", TestCase, &fixture_preinstall_combined_key_last,