static void PRINTFLIKE(4, 5) dane_incompat(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter, int errtype, const char *fmt,...) { va_list ap; va_start(ap, fmt); if (tls->level == TLS_LEV_DANE) { tls->level = (errtype == DANE_UNUSABLE) ? TLS_LEV_ENCRYPT : TLS_LEV_MAY; if (errtype == NONDANE_CONFIG) vmsg_warn(fmt, ap); else if (msg_verbose) vmsg_info(fmt, ap); } else { /* dane-only */ if (errtype == NONDANE_CONFIG) { vmsg_warn(fmt, ap); MARK_INVALID(tls->why, &tls->level); } else { tls->level = TLS_LEV_INVALID; vdsb_simple(tls->why, "4.7.5", fmt, ap); } } va_end(ap); }
static void tls_site_lookup(SMTP_TLS_POLICY *tls, int *site_level, const char *site_name, const char *site_class) { const char *lookup; /* * Look up a non-default policy. In case of multiple lookup results, the * precedence order is a permutation of the TLS enforcement level order: * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more * specific policy including NONE, otherwise we choose the stronger * enforcement level. */ if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) { if (!strcasecmp(lookup, "NONE")) { /* NONE overrides MAY or NOTFOUND. */ if (*site_level <= TLS_LEV_MAY) *site_level = TLS_LEV_NONE; } else if (!strcasecmp(lookup, "MAY")) { /* MAY overrides NOTFOUND but not NONE. */ if (*site_level < TLS_LEV_NONE) *site_level = TLS_LEV_MAY; } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) { if (*site_level < TLS_LEV_ENCRYPT) *site_level = TLS_LEV_ENCRYPT; } else if (!strcasecmp(lookup, "MUST")) { if (*site_level < TLS_LEV_VERIFY) *site_level = TLS_LEV_VERIFY; } else { msg_warn("%s: unknown TLS policy '%s' for %s %s", tls_per_site->title, lookup, site_class, site_name); MARK_INVALID(tls->why, site_level); return; } } else if (tls_per_site->error) { msg_warn("%s: %s \"%s\": per-site table lookup error", tls_per_site->title, site_class, site_name); dsb_simple(tls->why, "4.3.0", "Temporary lookup error"); *site_level = TLS_LEV_INVALID; return; } return; }
/* * Verifies the given certificate against a certificate list of * trusted CAs. * * Returns only 0 or 1. If 1 it means that the certificate * was successfuly verified. * * 'flags': an OR of the gnutls_certificate_verify_flags enumeration. * * Output will hold some extra information about the verification * procedure. */ static unsigned verify_crt(gnutls_x509_crt_t cert, const gnutls_x509_crt_t * trusted_cas, int tcas_size, unsigned int flags, unsigned int *output, verify_state_st *vparams, unsigned end_cert) { gnutls_datum_t cert_signed_data = { NULL, 0 }; gnutls_datum_t cert_signature = { NULL, 0 }; gnutls_x509_crt_t issuer = NULL; int issuer_version, hash_algo; unsigned result = 1; const mac_entry_st * me; unsigned int out = 0, usage; int sigalg, ret; if (output) *output = 0; if (vparams->max_path == 0) { MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE); /* bail immediately, to avoid inconistency */ goto cleanup; } vparams->max_path--; if (tcas_size >= 1) issuer = find_issuer(cert, trusted_cas, tcas_size); ret = _gnutls_x509_get_signed_data(cert->cert, &cert->der, "tbsCertificate", &cert_signed_data); if (ret < 0) { MARK_INVALID(0); cert_signed_data.data = NULL; } ret = _gnutls_x509_get_signature(cert->cert, "signature", &cert_signature); if (ret < 0) { MARK_INVALID(0); cert_signature.data = NULL; } ret = _gnutls_x509_get_signature_algorithm(cert->cert, "signatureAlgorithm.algorithm"); if (ret < 0) { MARK_INVALID(0); } sigalg = ret; /* issuer is not in trusted certificate * authorities. */ if (issuer == NULL) { MARK_INVALID(GNUTLS_CERT_SIGNER_NOT_FOUND); } else { if (vparams->nc != NULL) { /* append the issuer's constraints */ ret = gnutls_x509_crt_get_name_constraints(issuer, vparams->nc, GNUTLS_NAME_CONSTRAINTS_FLAG_APPEND, NULL); if (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE); goto nc_done; } /* only check name constraints in server certificates, not CAs */ if (end_cert != 0) { ret = gnutls_x509_name_constraints_check_crt(vparams->nc, GNUTLS_SAN_DNSNAME, cert); if (ret == 0) { MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE); goto nc_done; } ret = gnutls_x509_name_constraints_check_crt(vparams->nc, GNUTLS_SAN_RFC822NAME, cert); if (ret == 0) { MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE); goto nc_done; } ret = gnutls_x509_name_constraints_check_crt(vparams->nc, GNUTLS_SAN_DN, cert); if (ret == 0) { MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE); goto nc_done; } ret = gnutls_x509_name_constraints_check_crt(vparams->nc, GNUTLS_SAN_URI, cert); if (ret == 0) { MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE); goto nc_done; } ret = gnutls_x509_name_constraints_check_crt(vparams->nc, GNUTLS_SAN_IPADDRESS, cert); if (ret == 0) { MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE); goto nc_done; } } } nc_done: if (vparams->tls_feat != NULL) { /* append the issuer's constraints */ ret = gnutls_x509_crt_get_tlsfeatures(issuer, vparams->tls_feat, GNUTLS_EXT_FLAG_APPEND, NULL); if (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE); goto feat_done; } ret = gnutls_x509_tlsfeatures_check_crt(vparams->tls_feat, cert); if (ret == 0) { MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE); goto feat_done; } } feat_done: issuer_version = gnutls_x509_crt_get_version(issuer); if (issuer_version < 0) { MARK_INVALID(0); } else if (!(flags & GNUTLS_VERIFY_DISABLE_CA_SIGN) && ((flags & GNUTLS_VERIFY_DO_NOT_ALLOW_X509_V1_CA_CRT) || issuer_version != 1)) { if (check_if_ca(cert, issuer, &vparams->max_path, flags) != 1) { MARK_INVALID(GNUTLS_CERT_SIGNER_NOT_CA); } ret = gnutls_x509_crt_get_key_usage(issuer, &usage, NULL); if (ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { if (ret < 0) { MARK_INVALID(0); } else if (!(usage & GNUTLS_KEY_KEY_CERT_SIGN)) { MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE); } } } if (sigalg >= 0) { hash_algo = gnutls_sign_get_hash_algorithm(sigalg); me = mac_to_entry(hash_algo); } else { me = NULL; } if (me == NULL) { MARK_INVALID(0); } else if (cert_signed_data.data != NULL && cert_signature.data != NULL) { ret = _gnutls_x509_verify_data(me, &cert_signed_data, &cert_signature, issuer); if (ret == GNUTLS_E_PK_SIG_VERIFY_FAILED) { MARK_INVALID(GNUTLS_CERT_SIGNATURE_FAILURE); } else if (ret < 0) { MARK_INVALID(0); } } } /* we always check the issuer for unsupported critical extensions */ if (issuer && check_for_unknown_exts(issuer) != 0) { if (!(flags & GNUTLS_VERIFY_IGNORE_UNKNOWN_CRIT_EXTENSIONS)) { MARK_INVALID(GNUTLS_CERT_UNKNOWN_CRIT_EXTENSIONS); } } /* we only check the end-certificate for critical extensions; that * way do not perform this check twice on the certificates when * verifying a large list */ if (end_cert && check_for_unknown_exts(cert) != 0) { if (!(flags & GNUTLS_VERIFY_IGNORE_UNKNOWN_CRIT_EXTENSIONS)) { MARK_INVALID(GNUTLS_CERT_UNKNOWN_CRIT_EXTENSIONS); } } if (sigalg >= 0) { if (is_level_acceptable(cert, issuer, sigalg, flags) == 0) { MARK_INVALID(GNUTLS_CERT_INSECURE_ALGORITHM); } /* If the certificate is not self signed check if the algorithms * used are secure. If the certificate is self signed it doesn't * really matter. */ if (gnutls_sign_is_secure(sigalg) == 0 && _gnutls_is_broken_sig_allowed(sigalg, flags) == 0 && is_issuer(cert, cert) == 0) { MARK_INVALID(GNUTLS_CERT_INSECURE_ALGORITHM); } } /* Check activation/expiration times */ if (!(flags & GNUTLS_VERIFY_DISABLE_TIME_CHECKS)) { /* check the time of the issuer first */ if (issuer != NULL && !(flags & GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS)) { out |= check_time_status(issuer, vparams->now); if (out != 0) { gnutls_assert(); result = 0; } } out |= check_time_status(cert, vparams->now); if (out != 0) { gnutls_assert(); result = 0; } } cleanup: if (output) *output |= out; if (vparams->func) { if (result == 0) { out |= GNUTLS_CERT_INVALID; } vparams->func(cert, issuer, NULL, out); } _gnutls_free_datum(&cert_signed_data); _gnutls_free_datum(&cert_signature); return result; }
static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter) { TLS_DANE *dane; if (!iter->port) { msg_warn("%s: the \"dane\" security level is invalid for delivery via" " unix-domain sockets", STR(iter->dest)); MARK_INVALID(tls->why, &tls->level); return; } if (!tls_dane_avail()) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: %s configured, but no requisite library support", STR(iter->dest), policy_name(tls->level)); return; } if (!(smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) || smtp_dns_support != SMTP_DNS_DNSSEC) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: %s configured with dnssec lookups disabled", STR(iter->dest), policy_name(tls->level)); return; } /* * If we ignore MX lookup errors, we also ignore DNSSEC security problems * and thus avoid any reasonable expectation that we get the right DANE * key material. */ if (smtp_mode && var_ign_mx_lookup_err) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: %s configured with MX lookup errors ignored", STR(iter->dest), policy_name(tls->level)); return; } /* * This is not optional, code in tls_dane.c assumes that the nexthop * qname is already an fqdn. If we're using these flags to go from qname * to rname, the assumption is invalid. Likewise we cannot add the qname * to certificate name checks, ... */ if (smtp_dns_res_opt & (RES_DEFNAMES | RES_DNSRCH)) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: dns resolver options incompatible with %s TLS", STR(iter->dest), policy_name(tls->level)); return; } /* When the MX name is present and insecure, DANE does not apply. */ if (iter->mx && !iter->mx->dnssec_valid) { dane_incompat(tls, iter, NONDANE_DEST, "non DNSSEC destination"); return; } /* When TLSA lookups fail, we defer the message */ if ((dane = tls_dane_resolve(iter->port, "tcp", iter->rr, var_smtp_tls_force_tlsa)) == 0) { tls->level = TLS_LEV_INVALID; dsb_simple(tls->why, "4.7.5", "TLSA lookup error for %s:%u", STR(iter->host), ntohs(iter->port)); return; } if (tls_dane_notfound(dane)) { dane_incompat(tls, iter, NONDANE_DEST, "no TLSA records found"); tls_dane_free(dane); return; } /* * Some TLSA records found, but none usable, per * * https://tools.ietf.org/html/draft-ietf-dane-srv-02#section-4 * * we MUST use TLS, and SHALL use full PKIX certificate checks. The latter * would be unwise for SMTP: no human present to "click ok" and risk of * non-delivery in most cases exceeds risk of interception. * * We also have a form of Goedel's incompleteness theorem in play: any list * of public root CA certs is either incomplete or inconsistent (for any * given verifier some of the CAs are surely not trustworthy). */ if (tls_dane_unusable(dane)) { dane_incompat(tls, iter, DANE_UNUSABLE, "TLSA records unusable"); tls_dane_free(dane); return; } /* * With DANE trust anchors, peername matching is not configurable. */ if (TLS_DANE_HASTA(dane)) { tls->matchargv = argv_alloc(2); argv_add(tls->matchargv, dane->base_domain, ARGV_END); if (iter->mx) { if (strcmp(iter->mx->qname, iter->mx->rname) == 0) argv_add(tls->matchargv, iter->mx->qname, ARGV_END); else argv_add(tls->matchargv, iter->mx->rname, iter->mx->qname, ARGV_END); } } else if (!TLS_DANE_HASEE(dane)) msg_panic("empty DANE match list"); tls->dane = dane; tls->level = TLS_LEV_DANE; return; }
static void *policy_create(const char *unused_key, void *context) { SMTP_ITERATOR *iter = (SMTP_ITERATOR *) context; int site_level; const char *dest = STR(iter->dest); const char *host = STR(iter->host); /* * Prepare a pristine policy object. */ SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) mymalloc(sizeof(*tls)); smtp_tls_policy_init(tls, dsb_create()); /* * Compute the per-site TLS enforcement level. For compatibility with the * original TLS patch, this algorithm is gives equal precedence to host * and next-hop policies. */ tls->level = global_tls_level(); site_level = TLS_LEV_NOTFOUND; if (tls_policy) { tls_policy_lookup(tls, &site_level, dest, "next-hop destination"); } else if (tls_per_site) { tls_site_lookup(tls, &site_level, dest, "next-hop destination"); if (site_level != TLS_LEV_INVALID && strcasecmp(dest, host) != 0) tls_site_lookup(tls, &site_level, host, "server hostname"); /* * Override a wild-card per-site policy with a more specific global * policy. * * With the original TLS patch, 1) a per-site ENCRYPT could not override * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy * produced inconsistent results: it changed a global VERIFY into * NONE, while producing MAY with all weaker global policy settings. * * With the current implementation, a combined per-site (NONE+MAY) * consistently overrides global policy with NONE, and global policy * can override only a per-site MAY wildcard. That is, specific * policies consistently override wildcard policies, and * (non-wildcard) per-site policies consistently override global * policies. */ if (site_level == TLS_LEV_MAY && tls->level > TLS_LEV_MAY) site_level = tls->level; } switch (site_level) { default: tls->level = site_level; case TLS_LEV_NOTFOUND: break; case TLS_LEV_INVALID: return ((void *) tls); } /* * DANE initialization may change the security level to something else, * so do this early, so that we use the right level below. Note that * "dane-only" changes to "dane" once we obtain the requisite TLSA * records. */ if (tls->level == TLS_LEV_DANE || tls->level == TLS_LEV_DANE_ONLY) dane_init(tls, iter); if (tls->level == TLS_LEV_INVALID) return ((void *) tls); /* * Use main.cf protocols setting if not set in per-destination table. */ if (tls->level > TLS_LEV_NONE && tls->protocols == 0) tls->protocols = mystrdup((tls->level == TLS_LEV_MAY) ? var_smtp_tls_proto : var_smtp_tls_mand_proto); /* * Compute cipher grade (if set in per-destination table, else * set_cipher() uses main.cf settings) and security level dependent * cipher exclusion list. */ set_cipher_grade(tls); /* * Use main.cf cert_match setting if not set in per-destination table. */ switch (tls->level) { case TLS_LEV_INVALID: case TLS_LEV_NONE: case TLS_LEV_MAY: case TLS_LEV_ENCRYPT: case TLS_LEV_DANE: break; case TLS_LEV_FPRINT: if (tls->dane == 0) tls->dane = tls_dane_alloc(); if (!TLS_DANE_HASEE(tls->dane)) { tls_dane_add_ee_digests(tls->dane, var_smtp_tls_fpt_dgst, var_smtp_tls_fpt_cmatch, "\t\n\r, "); if (!TLS_DANE_HASEE(tls->dane)) { msg_warn("nexthop domain %s: configured at fingerprint " "security level, but with no fingerprints to match.", dest); MARK_INVALID(tls->why, &tls->level); return ((void *) tls); } } break; case TLS_LEV_VERIFY: case TLS_LEV_SECURE: if (tls->matchargv == 0) tls->matchargv = argv_split(tls->level == TLS_LEV_VERIFY ? var_smtp_tls_vfy_cmatch : var_smtp_tls_sec_cmatch, "\t\n\r, :"); if (*var_smtp_tls_tafile) { if (tls->dane == 0) tls->dane = tls_dane_alloc(); if (!TLS_DANE_HASTA(tls->dane) && !load_tas(tls->dane, var_smtp_tls_tafile)) { MARK_INVALID(tls->why, &tls->level); return ((void *) tls); } } break; default: msg_panic("unexpected TLS security level: %d", tls->level); } if (msg_verbose && tls->level != global_tls_level()) msg_info("%s TLS level: %s", "effective", policy_name(tls->level)); return ((void *) tls); }
static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level, const char *site_name, const char *site_class) { const char *lookup; char *policy; char *saved_policy; char *tok; const char *err; char *name; char *val; static VSTRING *cbuf; #undef FREE_RETURN #define FREE_RETURN do { myfree(saved_policy); return; } while (0) #define INVALID_RETURN(why, levelp) do { \ MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0) #define WHERE \ STR(vstring_sprintf(cbuf, "%s, %s \"%s\"", \ tls_policy->title, site_class, site_name)) if (cbuf == 0) cbuf = vstring_alloc(10); if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { if (tls_policy->error) { msg_warn("%s: policy table lookup error", WHERE); MARK_INVALID(tls->why, site_level); } return; } saved_policy = policy = mystrdup(lookup); if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { msg_warn("%s: invalid empty policy", WHERE); INVALID_RETURN(tls->why, site_level); } *site_level = tls_level_lookup(tok); if (*site_level == TLS_LEV_INVALID) { /* tls_level_lookup() logs no warning. */ msg_warn("%s: invalid security level \"%s\"", WHERE, tok); INVALID_RETURN(tls->why, site_level); } /* * Warn about ignored attributes when TLS is disabled. */ if (*site_level < TLS_LEV_MAY) { while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", WHERE, tok); FREE_RETURN; } /* * Errors in attributes may have security consequences, don't ignore * errors that can degrade security. */ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) { if ((err = split_nameval(tok, &name, &val)) != 0) { msg_warn("%s: malformed attribute/value pair \"%s\": %s", WHERE, tok, err); INVALID_RETURN(tls->why, site_level); } /* Only one instance per policy. */ if (!strcasecmp(name, "ciphers")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } if (tls->grade) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->grade = mystrdup(val); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "protocols")) { if (tls->protocols) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->protocols = mystrdup(val); continue; } /* Multiple instances per policy. */ if (!strcasecmp(name, "match")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } switch (*site_level) { default: msg_warn("%s: attribute \"%s\" invalid at security level " "\"%s\"", WHERE, name, policy_name(*site_level)); INVALID_RETURN(tls->why, site_level); break; case TLS_LEV_FPRINT: if (!tls->dane) tls->dane = tls_dane_alloc(); tls_dane_add_ee_digests(tls->dane, var_smtp_tls_fpt_dgst, val, "|"); break; case TLS_LEV_VERIFY: case TLS_LEV_SECURE: if (tls->matchargv == 0) tls->matchargv = argv_split(val, ":"); else argv_split_append(tls->matchargv, val, ":"); break; } continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "exclude")) { if (tls->exclusions) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->exclusions = vstring_strcpy(vstring_alloc(10), val); continue; } /* Multiple instances per policy. */ if (!strcasecmp(name, "tafile")) { /* Only makes sense if we're using CA-based trust */ if (!TLS_MUST_PKIX(*site_level)) { msg_warn("%s: attribute \"%s\" invalid at security level" " \"%s\"", WHERE, name, policy_name(*site_level)); INVALID_RETURN(tls->why, site_level); } if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } if (!tls->dane) tls->dane = tls_dane_alloc(); if (!tls_dane_load_trustfile(tls->dane, val)) { INVALID_RETURN(tls->why, site_level); } continue; } msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); INVALID_RETURN(tls->why, site_level); } FREE_RETURN; }