summaryrefslogtreecommitdiffstats
path: root/common/compliance.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/compliance.c')
-rw-r--r--common/compliance.c702
1 files changed, 702 insertions, 0 deletions
diff --git a/common/compliance.c b/common/compliance.c
new file mode 100644
index 0000000..eaecee7
--- /dev/null
+++ b/common/compliance.c
@@ -0,0 +1,702 @@
+/* compliance.c - Functions for compliance modi
+ * Copyright (C) 2017 g10 Code GmbH
+ * Copyright (C) 2017 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GnuPG.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of either
+ *
+ * - the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * or
+ *
+ * - the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * or both in parallel, as here.
+ *
+ * This file 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 this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <gcrypt.h>
+
+#include "openpgpdefs.h"
+#include "logging.h"
+#include "util.h"
+#include "i18n.h"
+#include "compliance.h"
+
+static int initialized;
+static int module;
+
+/* This value is used by DSA and RSA checks in addition to the hard
+ * coded length checks. It allows to increase the required key length
+ * using a confue file. */
+static unsigned int min_compliant_rsa_length;
+
+/* Return the address of a compliance cache variable for COMPLIANCE.
+ * If no such variable exists NULL is returned. FOR_RNG returns the
+ * cache variable for the RNG compliance check. */
+static int *
+get_compliance_cache (enum gnupg_compliance_mode compliance, int for_rng)
+{
+ static int r_gnupg = -1, s_gnupg = -1;
+ static int r_rfc4880 = -1, s_rfc4880 = -1;
+ static int r_rfc2440 = -1, s_rfc2440 = -1;
+ static int r_pgp6 = -1, s_pgp6 = -1;
+ static int r_pgp7 = -1, s_pgp7 = -1;
+ static int r_pgp8 = -1, s_pgp8 = -1;
+ static int r_de_vs = -1, s_de_vs = -1;
+
+ int *ptr = NULL;
+
+ switch (compliance)
+ {
+ case CO_GNUPG: ptr = for_rng? &r_gnupg : &s_gnupg ; break;
+ case CO_RFC4880: ptr = for_rng? &r_rfc4880 : &s_rfc4880; break;
+ case CO_RFC2440: ptr = for_rng? &r_rfc2440 : &s_rfc2440; break;
+ case CO_PGP6: ptr = for_rng? &r_pgp6 : &s_pgp6 ; break;
+ case CO_PGP7: ptr = for_rng? &r_pgp7 : &s_pgp7 ; break;
+ case CO_PGP8: ptr = for_rng? &r_pgp8 : &s_pgp8 ; break;
+ case CO_DE_VS: ptr = for_rng? &r_de_vs : &s_de_vs ; break;
+ }
+
+ return ptr;
+}
+
+
+/* Initializes the module. Must be called with the current
+ * GNUPG_MODULE_NAME. Checks a few invariants, and tunes the policies
+ * for the given module. */
+void
+gnupg_initialize_compliance (int gnupg_module_name)
+{
+ log_assert (! initialized);
+
+ /* We accept both OpenPGP-style and gcrypt-style algorithm ids.
+ * Assert that they are compatible. */
+ log_assert ((int) GCRY_PK_RSA == (int) PUBKEY_ALGO_RSA);
+ log_assert ((int) GCRY_PK_RSA_E == (int) PUBKEY_ALGO_RSA_E);
+ log_assert ((int) GCRY_PK_RSA_S == (int) PUBKEY_ALGO_RSA_S);
+ log_assert ((int) GCRY_PK_ELG_E == (int) PUBKEY_ALGO_ELGAMAL_E);
+ log_assert ((int) GCRY_PK_DSA == (int) PUBKEY_ALGO_DSA);
+ log_assert ((int) GCRY_PK_ECC == (int) PUBKEY_ALGO_ECDH);
+ log_assert ((int) GCRY_PK_ELG == (int) PUBKEY_ALGO_ELGAMAL);
+ log_assert ((int) GCRY_CIPHER_NONE == (int) CIPHER_ALGO_NONE);
+ log_assert ((int) GCRY_CIPHER_IDEA == (int) CIPHER_ALGO_IDEA);
+ log_assert ((int) GCRY_CIPHER_3DES == (int) CIPHER_ALGO_3DES);
+ log_assert ((int) GCRY_CIPHER_CAST5 == (int) CIPHER_ALGO_CAST5);
+ log_assert ((int) GCRY_CIPHER_BLOWFISH == (int) CIPHER_ALGO_BLOWFISH);
+ log_assert ((int) GCRY_CIPHER_AES == (int) CIPHER_ALGO_AES);
+ log_assert ((int) GCRY_CIPHER_AES192 == (int) CIPHER_ALGO_AES192);
+ log_assert ((int) GCRY_CIPHER_AES256 == (int) CIPHER_ALGO_AES256);
+ log_assert ((int) GCRY_CIPHER_TWOFISH == (int) CIPHER_ALGO_TWOFISH);
+ log_assert ((int) GCRY_MD_MD5 == (int) DIGEST_ALGO_MD5);
+ log_assert ((int) GCRY_MD_SHA1 == (int) DIGEST_ALGO_SHA1);
+ log_assert ((int) GCRY_MD_RMD160 == (int) DIGEST_ALGO_RMD160);
+ log_assert ((int) GCRY_MD_SHA256 == (int) DIGEST_ALGO_SHA256);
+ log_assert ((int) GCRY_MD_SHA384 == (int) DIGEST_ALGO_SHA384);
+ log_assert ((int) GCRY_MD_SHA512 == (int) DIGEST_ALGO_SHA512);
+ log_assert ((int) GCRY_MD_SHA224 == (int) DIGEST_ALGO_SHA224);
+
+ switch (gnupg_module_name)
+ {
+ case GNUPG_MODULE_NAME_GPGSM:
+ case GNUPG_MODULE_NAME_GPG:
+ break;
+
+ default:
+ log_assert (!"no policies for this module");
+ }
+
+ module = gnupg_module_name;
+ initialized = 1;
+}
+
+/* Return true if ALGO with a key of KEYLENGTH is compliant to the
+ * given COMPLIANCE mode. If KEY is not NULL, various bits of
+ * information will be extracted from it. If CURVENAME is not NULL, it
+ * is assumed to be the already computed. ALGO may be either an
+ * OpenPGP-style pubkey_algo_t, or a gcrypt-style enum gcry_pk_algos,
+ * both are compatible from the point of view of this function. */
+int
+gnupg_pk_is_compliant (enum gnupg_compliance_mode compliance, int algo,
+ unsigned int algo_flags,
+ gcry_mpi_t key[], unsigned int keylength,
+ const char *curvename)
+{
+ enum { is_rsa, is_dsa, is_elg, is_ecc } algotype;
+ int result = 0;
+
+ if (! initialized)
+ return 0;
+
+ switch (algo)
+ {
+ case PUBKEY_ALGO_RSA:
+ case PUBKEY_ALGO_RSA_E:
+ case PUBKEY_ALGO_RSA_S:
+ algotype = is_rsa;
+ break;
+
+ case PUBKEY_ALGO_DSA:
+ algotype = is_dsa;
+ break;
+
+ case PUBKEY_ALGO_ELGAMAL_E:
+ algotype = is_elg;
+ break;
+
+ case PUBKEY_ALGO_ECDH:
+ case PUBKEY_ALGO_ECDSA:
+ case PUBKEY_ALGO_EDDSA:
+ algotype = is_ecc;
+ break;
+
+ case PUBKEY_ALGO_ELGAMAL:
+ return 0; /* Signing with Elgamal is not at all supported. */
+
+ default: /* Unknown. */
+ return 0;
+ }
+
+ if (compliance == CO_DE_VS)
+ {
+ char *curve = NULL;
+
+ switch (algotype)
+ {
+ case is_elg:
+ result = 0;
+ break;
+
+ case is_rsa:
+ result = ((keylength == 2048
+ || keylength == 3072
+ || keylength == 4096)
+ && keylength >= min_compliant_rsa_length);
+ /* Although rsaPSS was not part of the original evaluation
+ * we got word that we can claim compliance. */
+ (void)algo_flags;
+ break;
+
+ case is_dsa:
+ if (key)
+ {
+ size_t P = gcry_mpi_get_nbits (key[0]);
+ size_t Q = gcry_mpi_get_nbits (key[1]);
+ result = (Q == 256
+ && (P == 2048 || P == 3072)
+ && P >= min_compliant_rsa_length);
+ }
+ break;
+
+ case is_ecc:
+ if (!curvename && key)
+ {
+ curve = openpgp_oid_to_str (key[0]);
+ curvename = openpgp_oid_to_curve (curve, 0);
+ if (!curvename)
+ curvename = curve;
+ }
+
+ result = (curvename
+ && (algo == PUBKEY_ALGO_ECDH
+ || algo == PUBKEY_ALGO_ECDSA)
+ && (!strcmp (curvename, "brainpoolP256r1")
+ || !strcmp (curvename, "brainpoolP384r1")
+ || !strcmp (curvename, "brainpoolP512r1")));
+ break;
+
+ default:
+ result = 0;
+ }
+ xfree (curve);
+ }
+ else
+ {
+ result = 1; /* Assume compliance. */
+ }
+
+ return result;
+}
+
+
+/* Return true if ALGO with the given KEYLENGTH is allowed in the
+ * given COMPLIANCE mode. USE specifies for which use case the
+ * predicate is evaluated. This way policies can be strict in what
+ * they produce, and liberal in what they accept. */
+int
+gnupg_pk_is_allowed (enum gnupg_compliance_mode compliance,
+ enum pk_use_case use, int algo,
+ unsigned int algo_flags, gcry_mpi_t key[],
+ unsigned int keylength, const char *curvename)
+{
+ int result = 0;
+
+ if (! initialized)
+ return 1;
+
+ switch (compliance)
+ {
+ case CO_DE_VS:
+ switch (algo)
+ {
+ case PUBKEY_ALGO_RSA:
+ case PUBKEY_ALGO_RSA_E:
+ case PUBKEY_ALGO_RSA_S:
+ switch (use)
+ {
+ case PK_USE_DECRYPTION:
+ case PK_USE_VERIFICATION:
+ result = 1;
+ break;
+ case PK_USE_ENCRYPTION:
+ case PK_USE_SIGNING:
+ result = ((keylength == 2048
+ || keylength == 3072
+ || keylength == 4096)
+ && keylength >= min_compliant_rsa_length);
+ break;
+ default:
+ log_assert (!"reached");
+ }
+ (void)algo_flags;
+ break;
+
+ case PUBKEY_ALGO_DSA:
+ if (use == PK_USE_VERIFICATION)
+ result = 1;
+ else if (use == PK_USE_SIGNING && key)
+ {
+ size_t P = gcry_mpi_get_nbits (key[0]);
+ size_t Q = gcry_mpi_get_nbits (key[1]);
+ result = (Q == 256
+ && (P == 2048 || P == 3072)
+ && keylength >= min_compliant_rsa_length);
+ }
+ break;
+
+ case PUBKEY_ALGO_ELGAMAL:
+ case PUBKEY_ALGO_ELGAMAL_E:
+ result = (use == PK_USE_DECRYPTION);
+ break;
+
+ case PUBKEY_ALGO_ECDH:
+ if (use == PK_USE_DECRYPTION)
+ result = 1;
+ else if (use == PK_USE_ENCRYPTION)
+ {
+ char *curve = NULL;
+
+ if (!curvename && key)
+ {
+ curve = openpgp_oid_to_str (key[0]);
+ curvename = openpgp_oid_to_curve (curve, 0);
+ if (!curvename)
+ curvename = curve;
+ }
+
+ result = (curvename
+ && (!strcmp (curvename, "brainpoolP256r1")
+ || !strcmp (curvename, "brainpoolP384r1")
+ || !strcmp (curvename, "brainpoolP512r1")));
+
+ xfree (curve);
+ }
+ break;
+
+ case PUBKEY_ALGO_ECDSA:
+ if (use == PK_USE_VERIFICATION)
+ result = 1;
+ else
+ {
+ char *curve = NULL;
+
+ if (! curvename && key)
+ {
+ curve = openpgp_oid_to_str (key[0]);
+ curvename = openpgp_oid_to_curve (curve, 0);
+ if (!curvename)
+ curvename = curve;
+ }
+
+ result = (use == PK_USE_SIGNING
+ && curvename
+ && (!strcmp (curvename, "brainpoolP256r1")
+ || !strcmp (curvename, "brainpoolP384r1")
+ || !strcmp (curvename, "brainpoolP512r1")));
+ xfree (curve);
+ }
+ break;
+
+
+ case PUBKEY_ALGO_EDDSA:
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ /* The default policy is to allow all algorithms. */
+ result = 1;
+ }
+
+ return result;
+}
+
+
+/* Return true if (CIPHER, MODE) is compliant to the given COMPLIANCE mode. */
+int
+gnupg_cipher_is_compliant (enum gnupg_compliance_mode compliance,
+ cipher_algo_t cipher,
+ enum gcry_cipher_modes mode)
+{
+ if (! initialized)
+ return 0;
+
+ switch (compliance)
+ {
+ case CO_DE_VS:
+ switch (cipher)
+ {
+ case CIPHER_ALGO_AES:
+ case CIPHER_ALGO_AES192:
+ case CIPHER_ALGO_AES256:
+ case CIPHER_ALGO_3DES:
+ switch (module)
+ {
+ case GNUPG_MODULE_NAME_GPG:
+ return mode == GCRY_CIPHER_MODE_CFB;
+ case GNUPG_MODULE_NAME_GPGSM:
+ return mode == GCRY_CIPHER_MODE_CBC;
+ }
+ log_assert (!"reached");
+
+ default:
+ return 0;
+ }
+ log_assert (!"reached");
+
+ default:
+ return 0;
+ }
+
+ log_assert (!"reached");
+}
+
+
+/* Return true if CIPHER is allowed in the given COMPLIANCE mode. If
+ * PRODUCER is true, the predicate is evaluated for the producer, if
+ * false for the consumer. This way policies can be strict in what
+ * they produce, and liberal in what they accept. */
+int
+gnupg_cipher_is_allowed (enum gnupg_compliance_mode compliance, int producer,
+ cipher_algo_t cipher,
+ enum gcry_cipher_modes mode)
+{
+ if (! initialized)
+ return 1;
+
+ switch (compliance)
+ {
+ case CO_DE_VS:
+ switch (cipher)
+ {
+ case CIPHER_ALGO_AES:
+ case CIPHER_ALGO_AES192:
+ case CIPHER_ALGO_AES256:
+ case CIPHER_ALGO_3DES:
+ switch (module)
+ {
+ case GNUPG_MODULE_NAME_GPG:
+ return (mode == GCRY_CIPHER_MODE_NONE
+ || mode == GCRY_CIPHER_MODE_CFB);
+ case GNUPG_MODULE_NAME_GPGSM:
+ return (mode == GCRY_CIPHER_MODE_NONE
+ || mode == GCRY_CIPHER_MODE_CBC
+ || (mode == GCRY_CIPHER_MODE_GCM && !producer));
+ }
+ log_assert (!"reached");
+
+ case CIPHER_ALGO_BLOWFISH:
+ case CIPHER_ALGO_CAMELLIA128:
+ case CIPHER_ALGO_CAMELLIA192:
+ case CIPHER_ALGO_CAMELLIA256:
+ case CIPHER_ALGO_CAST5:
+ case CIPHER_ALGO_IDEA:
+ case CIPHER_ALGO_TWOFISH:
+ return (module == GNUPG_MODULE_NAME_GPG
+ && (mode == GCRY_CIPHER_MODE_NONE
+ || mode == GCRY_CIPHER_MODE_CFB)
+ && ! producer);
+ default:
+ return 0;
+ }
+ log_assert (!"reached");
+
+ default:
+ /* The default policy is to allow all algorithms. */
+ return 1;
+ }
+
+ log_assert (!"reached");
+}
+
+
+/* Return true if DIGEST is compliant to the given COMPLIANCE mode. */
+int
+gnupg_digest_is_compliant (enum gnupg_compliance_mode compliance,
+ digest_algo_t digest)
+{
+ if (! initialized)
+ return 0;
+
+ switch (compliance)
+ {
+ case CO_DE_VS:
+ switch (digest)
+ {
+ case DIGEST_ALGO_SHA256:
+ case DIGEST_ALGO_SHA384:
+ case DIGEST_ALGO_SHA512:
+ return 1;
+ default:
+ return 0;
+ }
+ log_assert (!"reached");
+
+ default:
+ return 0;
+ }
+
+ log_assert (!"reached");
+}
+
+
+/* Return true if DIGEST is allowed in the given COMPLIANCE mode. If
+ * PRODUCER is true, the predicate is evaluated for the producer, if
+ * false for the consumer. This way policies can be strict in what
+ * they produce, and liberal in what they accept. */
+int
+gnupg_digest_is_allowed (enum gnupg_compliance_mode compliance, int producer,
+ digest_algo_t digest)
+{
+ if (! initialized)
+ return 1;
+
+ switch (compliance)
+ {
+ case CO_DE_VS:
+ switch (digest)
+ {
+ case DIGEST_ALGO_SHA256:
+ case DIGEST_ALGO_SHA384:
+ case DIGEST_ALGO_SHA512:
+ return 1;
+ case DIGEST_ALGO_SHA1:
+ case DIGEST_ALGO_SHA224:
+ case DIGEST_ALGO_RMD160:
+ return ! producer;
+ case DIGEST_ALGO_MD5:
+ return ! producer && module == GNUPG_MODULE_NAME_GPGSM;
+ default:
+ return 0;
+ }
+ log_assert (!"reached");
+
+ default:
+ /* The default policy is to allow all algorithms. */
+ return 1;
+ }
+
+ log_assert (!"reached");
+}
+
+
+/* Return True if the random number generator is compliant in
+ * COMPLIANCE mode. */
+int
+gnupg_rng_is_compliant (enum gnupg_compliance_mode compliance)
+{
+ int *result;
+ int res;
+
+ result = get_compliance_cache (compliance, 1);
+
+ if (result && *result != -1)
+ res = *result; /* Use cached result. */
+ else if (compliance == CO_DE_VS)
+ {
+ /* We also check whether the library is at all compliant. */
+ res = gnupg_gcrypt_is_compliant (compliance);
+
+ /* In DE_VS mode under Windows we also require that the JENT RNG
+ * is active. Check it here. */
+#ifdef HAVE_W32_SYSTEM
+ if (res == 1)
+ {
+ char *buf;
+ char *fields[5];
+
+ buf = gcry_get_config (0, "rng-type");
+ if (buf
+ && split_fields_colon (buf, fields, DIM (fields)) >= 5
+ && atoi (fields[4]) > 0)
+ ; /* Field 5 > 0 := Jent is active. */
+ else
+ result = 0; /* Force non-compliance. */
+ gcry_free (buf);
+ }
+#endif /*HAVE_W32_SYSTEM*/
+ }
+ else
+ res = 1;
+
+ if (result)
+ *result = res;
+
+ return res;
+}
+
+
+/* Return true if the used Libgcrypt is compliant in COMPLIANCE
+ * mode. */
+int
+gnupg_gcrypt_is_compliant (enum gnupg_compliance_mode compliance)
+{
+ int *result;
+ int res;
+
+ result = get_compliance_cache (compliance, 0);
+
+ if (result && *result != -1)
+ res = *result; /* Use cached result. */
+ else if (compliance == CO_DE_VS)
+ {
+ int is19orlater = !!gcry_check_version ("1.9.0");
+
+ /* A compliant version of GnuPG requires Libgcrypt >= 1.8.1 and
+ * less than 1.9.0. Version 1.9.0 requires a re-evaluation and
+ * can thus not be used for de-vs. */
+ if (gcry_check_version ("1.8.1") && !is19orlater)
+ res = 1; /* Compliant version of Libgcrypt. */
+ else if (is19orlater)
+ {
+ /* Libgcrypt might be nice enough to tell us whether it is
+ * compliant. */
+ char *buf;
+ char *fields[3];
+
+ buf = gcry_get_config (0, "compliance");
+ if (buf
+ && split_fields_colon (buf, fields, DIM (fields)) >= 2
+ && strstr (fields[1], "de-vs"))
+ res = 1; /* Compliant. */
+ else
+ res = 0; /* Non-compliant. */
+ gcry_free (buf);
+ }
+ else
+ res = 0; /* Non-compliant version of Libgcrypt. */
+ }
+ else
+ res = 1;
+
+ if (result)
+ *result = res;
+
+ return res;
+}
+
+
+const char *
+gnupg_status_compliance_flag (enum gnupg_compliance_mode compliance)
+{
+ switch (compliance)
+ {
+ case CO_GNUPG:
+ return "8";
+ case CO_RFC4880:
+ case CO_RFC2440:
+ case CO_PGP6:
+ case CO_PGP7:
+ case CO_PGP8:
+ log_assert (!"no status code assigned for this compliance mode");
+ case CO_DE_VS:
+ return "23";
+ }
+ log_assert (!"invalid compliance mode");
+}
+
+
+/* Parse the value of --compliance. Returns the value corresponding
+ * to the given STRING according to OPTIONS of size LENGTH, or -1
+ * indicating that the lookup was unsuccessful, or the list of options
+ * was printed. If quiet is false, an additional hint to use 'help'
+ * is printed on unsuccessful lookups. */
+int
+gnupg_parse_compliance_option (const char *string,
+ struct gnupg_compliance_option options[],
+ size_t length,
+ int quiet)
+{
+ size_t i;
+
+ if (! ascii_strcasecmp (string, "help"))
+ {
+ log_info (_("valid values for option '%s':\n"), "--compliance");
+ for (i = 0; i < length; i++)
+ log_info (" %s\n", options[i].keyword);
+ return -1;
+ }
+
+ for (i = 0; i < length; i++)
+ if (! ascii_strcasecmp (string, options[i].keyword))
+ return options[i].value;
+
+ log_error (_("invalid value for option '%s'\n"), "--compliance");
+ if (! quiet)
+ log_info (_("(use \"help\" to list choices)\n"));
+ return -1;
+}
+
+
+/* Return the command line option for the given COMPLIANCE mode. */
+const char *
+gnupg_compliance_option_string (enum gnupg_compliance_mode compliance)
+{
+ switch (compliance)
+ {
+ case CO_GNUPG: return "--compliance=gnupg";
+ case CO_RFC4880: return "--compliance=openpgp";
+ case CO_RFC2440: return "--compliance=rfc2440";
+ case CO_PGP6: return "--compliance=pgp6";
+ case CO_PGP7: return "--compliance=pgp7";
+ case CO_PGP8: return "--compliance=pgp8";
+ case CO_DE_VS: return "--compliance=de-vs";
+ }
+
+ log_assert (!"invalid compliance mode");
+}
+
+
+/* Set additional infos for example taken from config files at startup. */
+void
+gnupg_set_compliance_extra_info (unsigned int min_rsa)
+{
+ min_compliant_rsa_length = min_rsa;
+}