Blob Blame History Raw
From 3523ad7b8b2349ed4ee301b992797902b7288028 Mon Sep 17 00:00:00 2001
From: Trevor Vaughan <tvaughan@onyxpoint.com>
Date: Fri, 23 Feb 2018 16:11:35 -0500
Subject: [PATCH 22/25] Allow configuration of client SCEP algorithms

* Allow users to set `scep_cipher` and `scep_digest` in their CA
configuration. These settings are authoritative and will override
anything from the server.  This was added to support connections to
systems, such as Dogtag, that do not provide a CA capabilities string
and, therefore, are prone to causing incorrect ciphers to be used on the
client side.

* In accordance with the latest SCEP Draft RFC, the default cipher has
been changed to AES-256 and the default digest has been changed to
SHA-256. These were chosen as reasonable defaults for most users and
systems.

* To ease the determination of which configuration file controls what
CA, the output of `getcert list-cas -v` was updated to print a
`config-path` entry which will list the specific configuration
associated with a given CA.

Closes #89
---
 src/getcert.c     |   6 ++
 src/prefs.h       |   5 ++
 src/scepgen-o.c   | 182 ++++++++++++++++++++++++++++++++++++++++++------------
 src/store-files.c |  22 +++++++
 src/store-int.h   |   4 ++
 src/tdbus.h       |   2 +
 src/tdbush.c      | 149 +++++++++++++++++++++++++++++++++++++++++++-
 7 files changed, 331 insertions(+), 39 deletions(-)

diff --git a/src/getcert.c b/src/getcert.c
index 35fd0d6..724d125 100644
--- a/src/getcert.c
+++ b/src/getcert.c
@@ -4157,6 +4157,12 @@ list_cas(const char *argv0, int argc, const char **argv)
 		if ((s != NULL) && (strlen(s) > 0)) {
 			printf(_("\tpost-save command: %s\n"), s);
 		}
+		if (verbose > 0) {
+			printf(_("\tconfig-path: %s\n"),
+			       query_rep_s(bus, cas[i], CM_DBUS_CA_INTERFACE,
+					   "get_config_file_path",
+					   verbose, globals.tctx));
+		}
 	}
 	return 0;
 }
diff --git a/src/prefs.h b/src/prefs.h
index 231aea7..349ec64 100644
--- a/src/prefs.h
+++ b/src/prefs.h
@@ -20,9 +20,12 @@
 
 enum cm_prefs_cipher {
 	cm_prefs_aes128,
+	cm_prefs_aes192,
 	cm_prefs_aes256,
 	cm_prefs_des3,
 	cm_prefs_des,
+	/* This is for the selection logic */
+	cm_prefs_nocipher,
 };
 
 enum cm_prefs_digest {
@@ -31,6 +34,8 @@ enum cm_prefs_digest {
 	cm_prefs_sha512,
 	cm_prefs_sha1,
 	cm_prefs_md5,
+	/* This is for the selection logic */
+	cm_prefs_nodigest,
 };
 
 enum cm_notification_method;
diff --git a/src/scepgen-o.c b/src/scepgen-o.c
index d11e3de..07c2b8b 100644
--- a/src/scepgen-o.c
+++ b/src/scepgen-o.c
@@ -433,49 +433,155 @@ cm_scepgen_o_cooked(struct cm_store_ca *ca, struct cm_store_entry *entry,
 		free(pem);
 		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 	}
-	cipher = cm_prefs_des;
-	for (i = 0;
-	     (ca->cm_ca_capabilities != NULL) &&
-	     (ca->cm_ca_capabilities[i] != NULL);
-	     i++) {
-		capability = ca->cm_ca_capabilities[i];
-		if (strcmp(capability, "DES3") == 0) {
-			cm_log(1, "Server supports DES3, using that.\n");
+
+	char* scep_cipher = ca->cm_ca_scep_cipher;
+	if (scep_cipher != NULL) {
+		/* Force the cipher to whatever is in the configuration */
+		if (strcmp(scep_cipher, "AES256") == 0) {
+			cipher = cm_prefs_aes256;
+		}
+		else if (strcmp(scep_cipher, "AES192") == 0) {
+			cipher = cm_prefs_aes192;
+		}
+		else if (strcmp(scep_cipher, "AES128") == 0) {
+			cipher = cm_prefs_aes128;
+		}
+		else if (strcmp(scep_cipher, "DES3") == 0) {
 			cipher = cm_prefs_des3;
-			break;
-		}
-	}
-	if (cipher == cm_prefs_des) {
-		cm_log(1, "Server does not support DES3, using DES.\n");
-	}
-	pref_digest = cm_prefs_preferred_digest();
-	digest = cm_prefs_md5;
-	for (i = 0;
-	     (ca->cm_ca_capabilities != NULL) &&
-	     (ca->cm_ca_capabilities[i] != NULL);
-	     i++) {
-		capability = ca->cm_ca_capabilities[i];
-		if ((pref_digest == cm_prefs_sha1) &&
-		    (strcmp(capability, "SHA-1") == 0)) {
-			cm_log(1, "Server supports SHA-1, using that.\n");
-			digest = cm_prefs_sha1;
-			break;
 		}
-		if ((pref_digest == cm_prefs_sha256) &&
-		    (strcmp(capability, "SHA-256") == 0)) {
-			cm_log(1, "Server supports SHA-256, using that.\n");
-			digest = cm_prefs_sha256;
-			break;
+		else if (strcmp(scep_cipher, "DES") == 0) {
+			cipher = cm_prefs_des;
 		}
-		if ((pref_digest == cm_prefs_sha512) &&
-		    (strcmp(capability, "SHA-512") == 0)) {
-			cm_log(1, "Server supports SHA-512, using that.\n");
-			digest = cm_prefs_sha512;
-			break;
+		else {
+			cm_log(1, "Option 'scep_cipher' must be one of AES256, AES192, AES128, DES3, or DES. Got '%s'\n", scep_cipher);
+			_exit(1);
+		}
+
+		cm_log(1, "SCEP cipher authoritatively set to: '%s'\n", scep_cipher);
+	}
+	else {
+		cipher = cm_prefs_nocipher;
+		for (i = 0;
+		     (ca->cm_ca_capabilities != NULL) &&
+		     (ca->cm_ca_capabilities[i] != NULL);
+		     i++) {
+			capability = ca->cm_ca_capabilities[i];
+			if ((strcmp(capability, "AES-256") == 0) ||
+				(strcmp(capability, "AES256") == 0)) {
+					cm_log(1, "Server supports AES256, using that.\n");
+					cipher = cm_prefs_aes256;
+					break;
+			}
+			if ((strcmp(capability, "AES-192") == 0) ||
+				(strcmp(capability, "AES192") == 0)) {
+					cm_log(1, "Server supports AES192, using that.\n");
+					cipher = cm_prefs_aes192;
+					break;
+			}
+			if ((strcmp(capability, "AES-128") == 0) ||
+				(strcmp(capability, "AES128") == 0)) {
+					cm_log(1, "Server supports AES128, using that.\n");
+					cipher = cm_prefs_aes128;
+					break;
+			}
+			if (strcmp(capability, "AES") == 0) {
+				cm_log(1, "Server supports AES, using AES256.\n");
+				cipher = cm_prefs_aes256;
+				break;
+			}
+			if (strcmp(capability, "DES3") == 0) {
+				cm_log(1, "Server supports DES3, using that.\n");
+				cipher = cm_prefs_des3;
+				break;
+			}
+			/* This remains for backward compatibility */
+			if (strcmp(capability, "DES") == 0) {
+				cm_log(1, "Server supports DES, using that.\n");
+				cipher = cm_prefs_des;
+				break;
+			}
+		}
+		if (cipher == cm_prefs_nocipher) {
+			/* Per the latest Draft RFC */
+			cm_log(1, "Could not determine supported CA capabilities, using AES256.\n");
+			cipher = cm_prefs_aes256;
 		}
 	}
-	if (digest == cm_prefs_md5) {
-		cm_log(1, "Server does not support better digests, using MD5.\n");
+
+	char* scep_digest = ca->cm_ca_scep_digest;
+	if (scep_digest != NULL) {
+		/* Force the digest to whatever is in the configuration */
+		if (strcmp(scep_digest, "SHA512") == 0) {
+			digest = cm_prefs_sha512;
+		}
+		else if (strcmp(scep_digest, "SHA384") == 0) {
+			digest = cm_prefs_sha384;
+		}
+		else if (strcmp(scep_digest, "SHA256") == 0) {
+			digest = cm_prefs_sha256;
+		}
+		else if (strcmp(scep_digest, "SHA1") == 0) {
+			digest = cm_prefs_sha1;
+		}
+		else if (strcmp(scep_digest, "MD5") == 0) {
+			digest = cm_prefs_md5;
+		}
+		else {
+			cm_log(1, "Option 'scep_digest' must be one of AES256, AES192, AES128, DES3, or DES. Got '%s'\n", scep_digest);
+			_exit(1);
+		}
+
+		cm_log(1, "SCEP digest authoritatively set to: '%s'\n", scep_digest);
+	}
+	else {
+		pref_digest = cm_prefs_preferred_digest();
+		digest = cm_prefs_nodigest;
+		for (i = 0;
+		     (ca->cm_ca_capabilities != NULL) &&
+		     (ca->cm_ca_capabilities[i] != NULL);
+		     i++) {
+			capability = ca->cm_ca_capabilities[i];
+			if ((pref_digest == cm_prefs_sha512) &&
+			    ((strcmp(capability, "SHA-512") == 0) ||
+				(strcmp(capability, "SHA512") == 0))) {
+					cm_log(1, "Server supports SHA-512, using that.\n");
+					digest = cm_prefs_sha512;
+					break;
+			}
+			if ((pref_digest == cm_prefs_sha384) &&
+			    ((strcmp(capability, "SHA-384") == 0) ||
+				(strcmp(capability, "SHA384") == 0))) {
+					cm_log(1, "Server supports SHA-384, using that.\n");
+					digest = cm_prefs_sha384;
+					break;
+			}
+			if ((pref_digest == cm_prefs_sha256) &&
+			    ((strcmp(capability, "SHA-256") == 0) ||
+				(strcmp(capability, "SHA256") == 0))) {
+					cm_log(1, "Server supports SHA-256, using that.\n");
+					digest = cm_prefs_sha256;
+					break;
+			}
+			if ((pref_digest == cm_prefs_sha1) &&
+			    ((strcmp(capability, "SHA-1") == 0) ||
+				(strcmp(capability, "SHA1") == 0))) {
+					cm_log(1, "Server supports SHA-1, using that.\n");
+					digest = cm_prefs_sha1;
+					break;
+			}
+			/* This remains for backward compatibility */
+			if ((pref_digest == cm_prefs_sha1) &&
+			    (strcmp(capability, "MD5") == 0)) {
+				cm_log(1, "Server supports MD5, using that.\n");
+				digest = cm_prefs_md5;
+				break;
+			}
+		}
+		if (digest == cm_prefs_nodigest) {
+			/* Per the latest Draft RFC */
+			cm_log(1, "Could not determine supported CA capabilities, using SHA256.\n");
+			digest = cm_prefs_sha256;
+		}
 	}
 	if (old_cert != NULL) {
 		if (cm_pkcs7_envelope_ias(ca->cm_ca_encryption_cert, cipher,
diff --git a/src/store-files.c b/src/store-files.c
index 977e896..c7195c4 100644
--- a/src/store-files.c
+++ b/src/store-files.c
@@ -206,6 +206,8 @@ enum cm_store_file_field {
 	cm_store_ca_field_other_cert_nssdbs,
 
 	cm_store_ca_field_capabilities,
+	cm_store_ca_field_scep_cipher,
+	cm_store_ca_field_scep_digest,
 	cm_store_ca_field_scep_ca_identifier,
 	cm_store_ca_field_encryption_cert,
 	cm_store_ca_field_encryption_issuer_cert,
@@ -385,6 +387,8 @@ static struct cm_store_file_field_list {
 	{cm_store_ca_field_other_cert_nssdbs, "ca_other_cert_dbs"},
 
 	{cm_store_ca_field_capabilities, "ca_capabilities"},
+	{cm_store_ca_field_scep_cipher, "scep_cipher"},
+	{cm_store_ca_field_scep_digest, "scep_digest"},
 	{cm_store_ca_field_scep_ca_identifier, "scep_ca_identifier"},
 	{cm_store_ca_field_encryption_cert, "ca_encryption_cert"},
 	{cm_store_ca_field_encryption_issuer_cert, "ca_encryption_issuer_cert"},
@@ -725,6 +729,8 @@ cm_store_entry_read(void *parent, const char *filename, FILE *fp)
 			case cm_store_ca_field_other_root_cert_nssdbs:
 			case cm_store_ca_field_other_cert_nssdbs:
 			case cm_store_ca_field_capabilities:
+			case cm_store_ca_field_scep_cipher:
+			case cm_store_ca_field_scep_digest:
 			case cm_store_ca_field_scep_ca_identifier:
 			case cm_store_ca_field_encryption_cert:
 			case cm_store_ca_field_encryption_issuer_cert:
@@ -1523,6 +1529,14 @@ cm_store_ca_read(void *parent, const char *filename, FILE *fp)
 				ret->cm_ca_capabilities =
 					free_if_empty_multi(ret, p);
 				break;
+			case cm_store_ca_field_scep_cipher:
+				ret->cm_ca_scep_cipher =
+					free_if_empty(p);
+				break;
+			case cm_store_ca_field_scep_digest:
+				ret->cm_ca_scep_digest =
+					free_if_empty(p);
+				break;
 			case cm_store_ca_field_scep_ca_identifier:
 				ret->cm_ca_scep_ca_identifier =
 					free_if_empty(p);
@@ -2339,6 +2353,10 @@ cm_store_ca_write(FILE *fp, struct cm_store_ca *ca)
 				 ca->cm_ca_other_cert_store_nssdbs);
 	cm_store_file_write_strs(fp, cm_store_ca_field_capabilities,
 				 ca->cm_ca_capabilities);
+	cm_store_file_write_str(fp, cm_store_ca_field_scep_cipher,
+				ca->cm_ca_scep_cipher);
+	cm_store_file_write_str(fp, cm_store_ca_field_scep_digest,
+				ca->cm_ca_scep_digest);
 	cm_store_file_write_str(fp, cm_store_ca_field_scep_ca_identifier,
 				ca->cm_ca_scep_ca_identifier);
 	cm_store_file_write_str(fp, cm_store_ca_field_encryption_cert,
@@ -2861,6 +2879,10 @@ cm_store_ca_dup(void *parent, struct cm_store_ca *ca)
 
 	ret->cm_ca_capabilities =
 		cm_store_maybe_strdupv(ret, ca->cm_ca_capabilities);
+	ret->cm_ca_scep_cipher =
+		cm_store_maybe_strdup(ret, ca->cm_ca_scep_cipher);
+	ret->cm_ca_scep_digest =
+		cm_store_maybe_strdup(ret, ca->cm_ca_scep_digest);
 	ret->cm_ca_scep_ca_identifier =
 		cm_store_maybe_strdup(ret, ca->cm_ca_scep_ca_identifier);
 	ret->cm_ca_encryption_cert =
diff --git a/src/store-int.h b/src/store-int.h
index 98b37e6..4a40406 100644
--- a/src/store-int.h
+++ b/src/store-int.h
@@ -349,6 +349,10 @@ struct cm_store_ca {
 	char **cm_ca_other_cert_store_nssdbs;
 	/* CA capabilities.  Currently only ever SCEP capabilities. */
 	char **cm_ca_capabilities;
+	/* SCEP Cipher to use. Overrides CA Capabilities */
+	char *cm_ca_scep_cipher;
+	/* SCEP Digest to use. Overrides CA Capabilities */
+	char *cm_ca_scep_digest;
 	/* An SCEP CA identifier, for use in gathering an RA (and possibly a
 	 * CA) certificate. */
 	char *cm_ca_scep_ca_identifier;
diff --git a/src/tdbus.h b/src/tdbus.h
index 7164f11..e63e783 100644
--- a/src/tdbus.h
+++ b/src/tdbus.h
@@ -119,6 +119,8 @@
 #define CM_DBUS_PROP_ROOT_CERTS "root-certs"
 #define CM_DBUS_PROP_OTHER_ROOT_CERTS "root-other-certs"
 #define CM_DBUS_PROP_OTHER_CERTS "other-certs"
+#define CM_DBUS_PROP_SCEP_CIPHER "scep-cipher"
+#define CM_DBUS_PROP_SCEP_DIGEST "scep-digest"
 #define CM_DBUS_PROP_SCEP_CA_IDENTIFIER "scep-ca-identifier"
 #define CM_DBUS_PROP_SCEP_CA_CAPABILITIES "scep-ca-capabilities"
 #define CM_DBUS_PROP_SCEP_RA_CERT "scep-ra-cert"
diff --git a/src/tdbush.c b/src/tdbush.c
index 04fe57e..3ce6c40 100644
--- a/src/tdbush.c
+++ b/src/tdbush.c
@@ -2128,6 +2128,27 @@ ca_get_serial(DBusConnection *conn, DBusMessage *msg,
 	}
 }
 
+/* org.fedorahosted.certonger.ca.get_config_file_path */
+ca_get_config_file_path(DBusConnection *conn, DBusMessage *msg,
+		struct cm_client_info *ci, struct cm_context *ctx)
+{
+	DBusMessage *rep;
+	struct cm_store_ca *ca;
+	ca = get_ca_for_request_message(msg, ctx);
+	if (ca == NULL) {
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	}
+	rep = dbus_message_new_method_return(msg);
+	if (rep != NULL) {
+		cm_tdbusm_set_s(rep, ca->cm_store_private);
+		dbus_connection_send(conn, rep, NULL);
+		dbus_message_unref(rep);
+		return DBUS_HANDLER_RESULT_HANDLED;
+	} else {
+		return send_internal_ca_error(conn, msg);
+	}
+}
+
 /* org.fedorahosted.certonger.ca.refresh */
 static DBusHandlerResult
 ca_refresh(DBusConnection *conn, DBusMessage *msg,
@@ -2262,6 +2283,106 @@ ca_prop_set_external_helper(struct cm_context *ctx, void *parent,
 }
 
 static const char *
+ca_prop_get_scep_cipher(struct cm_context *ctx, void *parent,
+			       void *record, const char *name)
+{
+	struct cm_store_ca *ca = record;
+
+	if (strcmp(name, CM_DBUS_PROP_SCEP_CIPHER) == 0) {
+		if (ca->cm_ca_type != cm_ca_external) {
+			return "";
+		}
+		if (ca->cm_ca_scep_cipher != NULL) {
+			return ca->cm_ca_scep_cipher;
+		} else {
+			return "";
+		}
+	}
+	return NULL;
+}
+
+static void
+ca_prop_set_scep_cipher(struct cm_context *ctx, void *parent,
+			       void *record, const char *name,
+			       const char *new_value)
+{
+	const char *propname[2], *path;
+	struct cm_store_ca *ca = record;
+	enum cm_ca_phase phase;
+
+	if (strcmp(name, CM_DBUS_PROP_SCEP_CIPHER) == 0) {
+		if (ca->cm_ca_type != cm_ca_external) {
+			return;
+		}
+		talloc_free(ca->cm_ca_scep_cipher);
+		ca->cm_ca_scep_cipher = new_value ?
+					       talloc_strdup(ca, new_value) :
+					       NULL;
+		for (phase = 0; phase < cm_ca_phase_invalid; phase++) {
+			cm_restart_ca(ctx, ca->cm_nickname, phase);
+		}
+		propname[0] = CM_DBUS_PROP_SCEP_CIPHER;
+		propname[1] = NULL;
+		path = talloc_asprintf(parent, "%s/%s",
+				       CM_DBUS_CA_PATH,
+				       ca->cm_busname);
+		cm_tdbush_property_emit_changed(ctx, path,
+						CM_DBUS_CA_INTERFACE,
+						propname);
+	}
+}
+
+static const char *
+ca_prop_get_scep_digest(struct cm_context *ctx, void *parent,
+			       void *record, const char *name)
+{
+	struct cm_store_ca *ca = record;
+
+	if (strcmp(name, CM_DBUS_PROP_SCEP_DIGEST) == 0) {
+		if (ca->cm_ca_type != cm_ca_external) {
+			return "";
+		}
+		if (ca->cm_ca_scep_digest != NULL) {
+			return ca->cm_ca_scep_digest;
+		} else {
+			return "";
+		}
+	}
+	return NULL;
+}
+
+static void
+ca_prop_set_scep_digest(struct cm_context *ctx, void *parent,
+			       void *record, const char *name,
+			       const char *new_value)
+{
+	const char *propname[2], *path;
+	struct cm_store_ca *ca = record;
+	enum cm_ca_phase phase;
+
+	if (strcmp(name, CM_DBUS_PROP_SCEP_DIGEST) == 0) {
+		if (ca->cm_ca_type != cm_ca_external) {
+			return;
+		}
+		talloc_free(ca->cm_ca_scep_digest);
+		ca->cm_ca_scep_digest = new_value ?
+					       talloc_strdup(ca, new_value) :
+					       NULL;
+		for (phase = 0; phase < cm_ca_phase_invalid; phase++) {
+			cm_restart_ca(ctx, ca->cm_nickname, phase);
+		}
+		propname[0] = CM_DBUS_PROP_SCEP_DIGEST;
+		propname[1] = NULL;
+		path = talloc_asprintf(parent, "%s/%s",
+				       CM_DBUS_CA_PATH,
+				       ca->cm_busname);
+		cm_tdbush_property_emit_changed(ctx, path,
+						CM_DBUS_CA_INTERFACE,
+						propname);
+	}
+}
+
+static const char *
 ca_prop_get_scep_ca_identifier(struct cm_context *ctx, void *parent,
 			       void *record, const char *name)
 {
@@ -7232,6 +7353,14 @@ cm_tdbush_iface_ca(void)
 	if (ret == NULL) {
 		ret = make_interface(CM_DBUS_CA_INTERFACE,
 				     make_interface_item(cm_tdbush_interface_method,
+							 make_method("get_config_file_path",
+								     ca_get_config_file_path,
+								     make_method_arg("path",
+										     DBUS_TYPE_STRING_AS_STRING,
+										     cm_tdbush_method_arg_out,
+										     NULL),
+								     NULL),
+				     make_interface_item(cm_tdbush_interface_method,
 							 make_method("get_nickname",
 								     ca_get_nickname,
 								     make_method_arg("nickname",
@@ -7483,6 +7612,24 @@ cm_tdbush_iface_ca(void)
 								       NULL, NULL, NULL, NULL, NULL,
 								       NULL),
 				     make_interface_item(cm_tdbush_interface_property,
+							 make_property(CM_DBUS_PROP_SCEP_CIPHER,
+								       cm_tdbush_property_string,
+								       cm_tdbush_property_readwrite,
+								       cm_tdbush_property_special,
+								       0,
+								       ca_prop_get_scep_cipher, NULL, NULL, NULL, NULL,
+								       ca_prop_set_scep_cipher, NULL, NULL, NULL, NULL,
+								       NULL),
+				     make_interface_item(cm_tdbush_interface_property,
+							 make_property(CM_DBUS_PROP_SCEP_DIGEST,
+								       cm_tdbush_property_string,
+								       cm_tdbush_property_readwrite,
+								       cm_tdbush_property_special,
+								       0,
+								       ca_prop_get_scep_digest, NULL, NULL, NULL, NULL,
+								       ca_prop_set_scep_digest, NULL, NULL, NULL, NULL,
+								       NULL),
+				     make_interface_item(cm_tdbush_interface_property,
 							 make_property(CM_DBUS_PROP_SCEP_CA_IDENTIFIER,
 								       cm_tdbush_property_string,
 								       cm_tdbush_property_readwrite,
@@ -7527,7 +7674,7 @@ cm_tdbush_iface_ca(void)
 								       NULL, NULL, NULL, NULL, NULL,
 								       NULL, NULL, NULL, NULL, NULL,
 								       NULL),
-				     NULL))))))))))))))))))))))))))))))))))));
+				     NULL)))))))))))))))))))))))))))))))))))))));
 	}
 	return ret;
 }
-- 
1.8.3.1