/*++ /* NAME /* tls_verify 3 /* SUMMARY /* peer name and peer certificate verification /* SYNOPSIS /* #define TLS_INTERNAL /* #include /* /* int tls_verify_certificate_callback(ok, ctx, int flags) /* int ok; /* X509_STORE_CTX *ctx; /* int flags; /* DESCRIPTION /* tls_verify_callback() is called several times (directly or /* indirectly) from crypto/x509/x509_vfy.c. It is called as /* a final check, and if it returns "0", the handshake is /* immediately shut down and the connection fails. /* /* Postfix/TLS has two modes, the "opportunistic" mode and /* the "enforce" mode: /* /* In the "opportunistic" mode we never want the connection /* to fail just because there is something wrong with the /* peer's certificate. After all, we would have sent or received /* the mail even if TLS weren't available. Therefore the /* return value is always "1". /* /* The SMTP client or server may require TLS (e.g. to protect /* passwords), while peer certificates are optional. In this /* case we must return "1" even when we are unhappy with the /* peer certificate. Only when peer certificates are required, /* certificate verification failure will result in immediate /* termination (return 0). /* /* The SMTP client will attempt to verify the server hostname /* against the names listed in the server certificate. When /* a hostname match is required, the verification fails /* on certificate verification or hostname mis-match errors. /* When no hostname match is required, hostname verification /* failures are logged but they do not affect the TLS handshake /* or the SMTP session. /* /* The rules for peer name wild-card matching differ between /* RFC 2818 (HTTP over TLS) and RFC 2830 (LDAP over TLS), while /* RFC RFC3207 (SMTP over TLS) does not specify a rule at all. /* Postfix uses a restrictive match algorithm. One asterisk /* ('*') is allowed as the left-most component of a wild-card /* certificate name; it matches the left-most component of /* the peer hostname. /* /* Another area where RFCs aren't always explicit is the /* handling of dNSNames in peer certificates. RFC 3207 (SMTP /* over TLS) does not mention dNSNames. Postfix follows the /* strict rules in RFC 2818 (HTTP over TLS), section 3.1: The /* Subject Alternative Name/dNSName has precedence over /* CommonName. If at least one dNSName is provided, Postfix /* verifies those against the peer hostname and ignores the /* CommonName, otherwise Postfix verifies the CommonName /* against the peer hostname. /* /* The only error condition not handled inside the OpenSSL /* library is the case of a too-long certificate chain. We /* test for this condition only if "ok = 1", that is, if /* verification didn't fail because of some earlier problem. /* /* Arguments: /* .IP ok /* Result of prior verification: non-zero means success. In /* order to reduce the noise level, some tests or error reports /* are disabled when verification failed because of some /* earlier problem. /* .IP ctx /* TLS client or server context. This also specifies the /* TLScontext with enforcement options. /* .IP flags /* .RS /* .IP TLS_VERIFY_PEERNAME /* Verify the peer hostname against the names listed /* in the peer certificate. The peer hostname is specified /* via the ctx argument. /* .RE /* LICENSE /* .ad /* .fi /* This software is free. You can do with it whatever you want. /* The original author kindly requests that you acknowledge /* the use of his software. /* AUTHOR(S) /* Originally written by: /* Lutz Jaenicke /* BTU Cottbus /* Allgemeine Elektrotechnik /* Universitaetsplatz 3-4 /* D-03044 Cottbus, Germany /* /* Updated by: /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /*--*/ /* System library. */ #include #ifdef USE_TLS #include #ifdef STRCASECMP_IN_STRINGS_H #include #endif /* Utility library. */ #include /* TLS library. */ #define TLS_INTERNAL #include /* match_hostname - match hostname against pattern */ static int match_hostname(const char *pattern, const char *hostname) { char *peername_left; return (strcasecmp(hostname, pattern) == 0 || (pattern[0] == '*' && pattern[1] == '.' && pattern[2] != 0 && (peername_left = strchr(hostname, '.')) != 0 && strcasecmp(peername_left + 1, pattern + 2) == 0)); } /* tls_verify_certificate_callback - verify peer certificate info */ int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx, int flags) { char buf[1024]; X509 *err_cert; int err; int depth; int verify_depth; SSL *con; TLScontext_t *TLScontext; /* Adapted from OpenSSL apps/s_cb.c */ err_cert = X509_STORE_CTX_get_current_cert(ctx); err = X509_STORE_CTX_get_error(ctx); depth = X509_STORE_CTX_get_error_depth(ctx); con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); TLScontext = SSL_get_ex_data(con, TLScontext_index); X509_NAME_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf)); if (TLScontext->log_level >= 2) msg_info("certificate verification depth=%d subject=%s", depth, buf); /* * Test for a too long certificate chain, because that error condition is * not handled by the OpenSSL library. */ verify_depth = SSL_get_verify_depth(con); if (ok && (verify_depth >= 0) && (depth > verify_depth)) { ok = 0; err = X509_V_ERR_CERT_CHAIN_TOO_LONG; X509_STORE_CTX_set_error(ctx, err); } if (!ok) { msg_info("certificate verification failed for %s: num=%d:%s", TLScontext->peername_save, err, X509_verify_cert_error_string(err)); } /* * Match the peer hostname against the names listed in the peer * certificate. */ if (ok && (depth == 0) && (flags & TLS_VERIFY_PEERNAME)) { int i, r; int hostname_matched; int dNSName_found; STACK_OF(GENERAL_NAME) * gens; /* * Verify the name(s) in the peer certificate against the peer * hostname. Log peer hostname/certificate mis-matches. If a match is * required but fails, bail out with a verification error. */ hostname_matched = dNSName_found = 0; gens = X509_get_ext_d2i(err_cert, NID_subject_alt_name, 0, 0); if (gens) { for (i = 0, r = sk_GENERAL_NAME_num(gens); i < r; ++i) { const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i); if (gn->type == GEN_DNS) { dNSName_found++; if ((hostname_matched = match_hostname((char *) gn->d.ia5->data, TLScontext->peername_save))) break; } } sk_GENERAL_NAME_free(gens); } if (dNSName_found) { if (!hostname_matched) msg_info("certificate peer name verification failed for %s: " "%d dNSNames in certificate found, but none matches", TLScontext->peername_save, dNSName_found); } else { buf[0] = '\0'; if (!X509_NAME_get_text_by_NID(X509_get_subject_name(err_cert), NID_commonName, buf, sizeof(buf))) { msg_info("certificate peer name verification failed for %s:" "cannot parse subject CommonName", TLScontext->peername_save); tls_print_errors(); } else { hostname_matched = match_hostname(buf, TLScontext->peername_save); if (!hostname_matched) msg_info("certificate peer name verification failed for %s:" " CommonName mis-match: %s", TLScontext->peername_save, buf); } } if (!hostname_matched) { if (TLScontext->enforce_verify_errors && TLScontext->enforce_CN) { err = X509_V_ERR_CERT_REJECTED; X509_STORE_CTX_set_error(ctx, err); msg_info("certificate peer name verification failed for %s:" " hostname mismatch", TLScontext->peername_save); ok = 0; } } else TLScontext->hostname_matched = 1; } /* * Other causes for verification failure. */ switch (ctx->error) { case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, sizeof(buf)); msg_info("certificate verification failed for %s:" "issuer %s certificate unavailable", TLScontext->peername_save, buf); break; case X509_V_ERR_CERT_NOT_YET_VALID: case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: msg_info("certificate verification failed for %s:" "certificate not yet valid", TLScontext->peername_save); break; case X509_V_ERR_CERT_HAS_EXPIRED: case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: msg_info("certificate verification failed for %s:" "certificate has expired", TLScontext->peername_save); break; } if (TLScontext->log_level >= 2) msg_info("verify return: %d", ok); /* * Never fail in case of opportunistic mode. */ if (TLScontext->enforce_verify_errors) return (ok); else return (1); } #endif