1 /*        $NetBSD: tls_verify.c,v 1.5 2025/02/25 19:15:50 christos Exp $        */
2 
3 /*++
4 /* NAME
5 /*        tls_verify 3
6 /* SUMMARY
7 /*        peer name and peer certificate verification
8 /* SYNOPSIS
9 /*        #define TLS_INTERNAL
10 /*        #include <tls.h>
11 /*
12 /*        int       tls_verify_certificate_callback(ok, ctx)
13 /*        int       ok;
14 /*        X509_STORE_CTX *ctx;
15 /*
16 /*        int     tls_log_verify_error(TLScontext, tlsrpt)
17 /*        TLS_SESS_STATE *TLScontext;
18 /*        struct TLSRPT_WRAPPER *tlsrpt;
19 /*
20 /*        char *tls_peer_CN(peercert, TLScontext)
21 /*        X509   *peercert;
22 /*        TLS_SESS_STATE *TLScontext;
23 /*
24 /*        char *tls_issuer_CN(peercert, TLScontext)
25 /*        X509   *peercert;
26 /*        TLS_SESS_STATE *TLScontext;
27 /* DESCRIPTION
28 /*        tls_verify_certificate_callback() is called several times (directly
29 /*        or indirectly) from crypto/x509/x509_vfy.c. It collects errors
30 /*        and trust information at each element of the trust chain.
31 /*        The last call at depth 0 sets the verification status based
32 /*        on the cumulative winner (lowest depth) of errors vs. trust.
33 /*        We always return 1 (continue the handshake) and handle trust
34 /*        and peer-name verification problems at the application level.
35 /*
36 /*        tls_log_verify_error() (called only when we care about the
37 /*        peer certificate, that is not when opportunistic) logs the
38 /*        reason why the certificate failed to be verified.
39 /*
40 /*        tls_peer_CN() returns the text CommonName for the peer
41 /*        certificate subject, or an empty string if no CommonName was
42 /*        found. The result is allocated with mymalloc() and must be
43 /*        freed by the caller; it contains UTF-8 without non-printable
44 /*        ASCII characters.
45 /*
46 /*        tls_issuer_CN() returns the text CommonName for the peer
47 /*        certificate issuer, or an empty string if no CommonName was
48 /*        found. The result is allocated with mymalloc() and must be
49 /*        freed by the caller; it contains UTF-8 without non-printable
50 /*        ASCII characters.
51 /*
52 /*        Arguments:
53 /* .IP ok
54 /*        Result of prior verification: non-zero means success.  In
55 /*        order to reduce the noise level, some tests or error reports
56 /*        are disabled when verification failed because of some
57 /*        earlier problem.
58 /* .IP ctx
59 /*        SSL application context. This links to the Postfix TLScontext
60 /*        with enforcement and logging options.
61 /* .IP gn
62 /*        An OpenSSL GENERAL_NAME structure holding a DNS subjectAltName
63 /*        to be decoded and checked for validity.
64 /* .IP peercert
65 /*        Server or client X.509 certificate.
66 /* .IP TLScontext
67 /*        Server or client context for warning messages.
68 /* DIAGNOSTICS
69 /*        tls_peer_CN() and tls_issuer_CN() log a warning when 1) the requested
70 /*        information is not available in the specified certificate, 2) the
71 /*        result exceeds a fixed limit, 3) the result contains NUL characters or
72 /*        the result contains non-printable or non-ASCII characters.
73 /* LICENSE
74 /* .ad
75 /* .fi
76 /*        This software is free. You can do with it whatever you want.
77 /*        The original author kindly requests that you acknowledge
78 /*        the use of his software.
79 /* AUTHOR(S)
80 /*        Originally written by:
81 /*        Lutz Jaenicke
82 /*        BTU Cottbus
83 /*        Allgemeine Elektrotechnik
84 /*        Universitaetsplatz 3-4
85 /*        D-03044 Cottbus, Germany
86 /*
87 /*        Updated by:
88 /*        Wietse Venema
89 /*        IBM T.J. Watson Research
90 /*        P.O. Box 704
91 /*        Yorktown Heights, NY 10598, USA
92 /*
93 /*        Victor Duchovni
94 /*        Morgan Stanley
95 /*
96 /*        Wietse Venema
97 /*        porcupine.org
98 /*--*/
99 
100 /* System library. */
101 
102 #include <sys_defs.h>
103 #include <ctype.h>
104 
105 #ifdef USE_TLS
106 #include <string.h>
107 
108 /* Utility library. */
109 
110 #include <msg.h>
111 #include <mymalloc.h>
112 #include <stringops.h>
113 
114 /* TLS library. */
115 
116 #ifdef USE_TLSRPT
117 #include <tlsrpt_wrapper.h>
118 #endif
119 
120 #define TLS_INTERNAL
121 #include <tls.h>
122 
123 /* update_error_state - safely stash away error state */
124 
update_error_state(TLS_SESS_STATE * TLScontext,int depth,X509 * errorcert,int errorcode)125 static void update_error_state(TLS_SESS_STATE *TLScontext, int depth,
126                                              X509 *errorcert, int errorcode)
127 {
128     /* No news is good news */
129     if (TLScontext->errordepth >= 0 && TLScontext->errordepth <= depth)
130           return;
131 
132     /*
133      * The certificate pointer is stable during the verification callback,
134      * but may be freed after the callback returns.  Since we delay error
135      * reporting till later, we bump the refcount so we can rely on it still
136      * being there until later.
137      */
138     if (TLScontext->errorcert != 0)
139           X509_free(TLScontext->errorcert);
140     if (errorcert != 0)
141           X509_up_ref(errorcert);
142     TLScontext->errorcert = errorcert;
143     TLScontext->errorcode = errorcode;
144     TLScontext->errordepth = depth;
145 }
146 
147 /* tls_verify_certificate_callback - verify peer certificate info */
148 
tls_verify_certificate_callback(int ok,X509_STORE_CTX * ctx)149 int     tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
150 {
151     char    buf[CCERT_BUFSIZ];
152     X509   *cert;
153     int     err;
154     int     depth;
155     SSL    *con;
156     TLS_SESS_STATE *TLScontext;
157     EVP_PKEY *rpk = 0;
158 
159     /* May be NULL as of OpenSSL 1.0, thanks for the API change! */
160     cert = X509_STORE_CTX_get_current_cert(ctx);
161     err = X509_STORE_CTX_get_error(ctx);
162     con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
163     TLScontext = SSL_get_ex_data(con, TLScontext_index);
164     depth = X509_STORE_CTX_get_error_depth(ctx);
165 #if OPENSSL_VERSION_PREREQ(3,2)
166     if (cert == 0)
167           rpk = X509_STORE_CTX_get0_rpk(ctx);
168 #endif
169 
170     /*
171      * Transient failures to load the (DNS or synthetic TLSA) trust settings
172      * must poison certificate verification, since otherwise the default
173      * trust store may bless a certificate that would have failed
174      * verification with the preferred trust anchors (or fingerprints).
175      *
176      * Since we unconditionally continue, or in any case if verification is
177      * about to succeed, there is eventually a final depth 0 callback, at
178      * which point we force an "unspecified" error.  The failure to load the
179      * trust settings was logged earlier.
180      */
181     if (TLScontext->must_fail) {
182           if (depth == 0) {
183               X509_STORE_CTX_set_error(ctx, err = X509_V_ERR_UNSPECIFIED);
184               update_error_state(TLScontext, depth, cert, err);
185           }
186           return (1);
187     }
188     if (ok == 0)
189           update_error_state(TLScontext, depth, cert, err);
190 
191     if (TLScontext->log_mask & TLS_LOG_VERBOSE) {
192           if (cert) {
193               X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
194               msg_info("%s: depth=%d verify=%d subject=%s",
195                          TLScontext->namaddr, depth, ok, printable(buf, '?'));
196           } else if (rpk) {
197               msg_info("%s: verify=%d raw public key", TLScontext->namaddr, ok);
198           } else {
199               msg_info("%s: depth=%d verify=%d", TLScontext->namaddr, depth, ok);
200           }
201     }
202     return (1);
203 }
204 
205 /* tls_log_verify_error - Report final verification error status */
206 
tls_log_verify_error(TLS_SESS_STATE * TLScontext,struct TLSRPT_WRAPPER * tlsrpt)207 void    tls_log_verify_error(TLS_SESS_STATE *TLScontext,
208                                            struct TLSRPT_WRAPPER *tlsrpt)
209 {
210     char    buf[CCERT_BUFSIZ];
211     int     err = TLScontext->errorcode;
212     X509   *cert = TLScontext->errorcert;
213     int     depth = TLScontext->errordepth;
214 
215 #ifdef USE_TLSRPT
216     VSTRING *err_vstr = vstring_alloc(100);
217 
218 #define CERT_ERROR_TO_STRING(err) \
219     translit(vstring_str(vstring_strcpy(err_vstr, \
220                                                   X509_verify_cert_error_string(err))), \
221                " ", "_")
222 #endif
223 
224 #define PURPOSE ((depth>0) ? "CA": TLScontext->am_server ? "client": "server")
225 
226     if (err == X509_V_OK)
227           return;
228 
229     /*
230      * If an external policy flagged an error, report that instead.
231      */
232     if (TLScontext->ffail_type) {
233           msg_info("certificate verification failed for %s: "
234                      "external policy failure (%s)",
235                      TLScontext->namaddr, TLScontext->ffail_type);
236 #ifdef USE_TLSRPT
237           if (tlsrpt) {
238               tlsrpt_failure_t failure_type;
239 
240               if ((failure_type = convert_tlsrpt_policy_failure(TLScontext->ffail_type)) < 0)
241                     msg_panic("tls_log_verify_error: unexpected failure_reason: %s",
242                                 TLScontext->ffail_type);
243               trw_report_failure(tlsrpt, failure_type,
244                                       /* additional_info= */ (char *) 0,
245                                       /* failure_reason= */ (char *) 0);
246           }
247 #endif
248           return;
249     }
250 
251     /*
252      * Specific causes for verification failure.
253      */
254     switch (err) {
255     case X509_V_ERR_CERT_UNTRUSTED:
256 
257           /*
258            * We expect the error cert to be the leaf, but it is likely
259            * sufficient to omit it from the log, even less user confusion.
260            */
261           msg_info("certificate verification failed for %s: "
262                      "not trusted by local or TLSA policy", TLScontext->namaddr);
263 #ifdef USE_TLSRPT
264           if (tlsrpt)
265               trw_report_failure(tlsrpt, TLSRPT_CERTIFICATE_NOT_TRUSTED,
266                                       /* additional_info= */ (char *) 0,
267                                       /* failure_code= */ (char *) 0);
268 #endif
269           break;
270     case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
271           msg_info("certificate verification failed for %s: "
272                      "self-signed certificate", TLScontext->namaddr);
273 #ifdef USE_TLSRPT
274           if (tlsrpt)
275               trw_report_failure(tlsrpt, TLSRPT_VALIDATION_FAILURE,
276                                       /* additional_info= */ (char *) 0,
277                                      CERT_ERROR_TO_STRING(err));
278 #endif
279           break;
280     case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
281     case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
282 
283           /*
284            * There is no difference between issuing cert not provided and
285            * provided, but not found in CAfile/CApath. Either way, we don't
286            * trust it.
287            */
288           if (cert)
289               X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
290           else
291               strcpy(buf, "<unknown>");
292           msg_info("certificate verification failed for %s: untrusted issuer %s",
293                      TLScontext->namaddr, printable(buf, '?'));
294 #ifdef USE_TLSRPT
295           if (tlsrpt)
296               trw_report_failure(tlsrpt, TLSRPT_VALIDATION_FAILURE,
297                                       /* additional_info= */ (char *) 0,
298                                      CERT_ERROR_TO_STRING(err));
299 #endif
300           break;
301     case X509_V_ERR_CERT_NOT_YET_VALID:
302     case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
303           msg_info("%s certificate verification failed for %s: certificate not"
304                      " yet valid", PURPOSE, TLScontext->namaddr);
305 #ifdef USE_TLSRPT
306           if (tlsrpt)
307               trw_report_failure(tlsrpt, TLSRPT_VALIDATION_FAILURE,
308                                       /* additional_info= */ (char *) 0,
309                                      CERT_ERROR_TO_STRING(err));
310 #endif
311           break;
312     case X509_V_ERR_CERT_HAS_EXPIRED:
313     case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
314           msg_info("%s certificate verification failed for %s: certificate has"
315                      " expired", PURPOSE, TLScontext->namaddr);
316 #ifdef USE_TLSRPT
317           if (tlsrpt)
318               trw_report_failure(tlsrpt, TLSRPT_CERTIFICATE_EXPIRED,
319                                       /* additional_info= */ (char *) 0,
320                                       /* failure_code= */ (char *) 0);
321 #endif
322           break;
323     case X509_V_ERR_INVALID_PURPOSE:
324           msg_info("certificate verification failed for %s: not designated for "
325                      "use as a %s certificate", TLScontext->namaddr, PURPOSE);
326 #ifdef USE_TLSRPT
327           if (tlsrpt)
328               trw_report_failure(tlsrpt, TLSRPT_VALIDATION_FAILURE,
329                                       /* additional_info= */ (char *) 0,
330                                      CERT_ERROR_TO_STRING(err));
331 #endif
332           break;
333     case X509_V_ERR_CERT_CHAIN_TOO_LONG:
334           msg_info("certificate verification failed for %s: "
335                      "certificate chain longer than limit(%d)",
336                      TLScontext->namaddr, depth - 1);
337 #ifdef USE_TLSRPT
338           if (tlsrpt)
339               trw_report_failure(tlsrpt, TLSRPT_VALIDATION_FAILURE,
340                                       /* additional_info= */ (char *) 0,
341                                      CERT_ERROR_TO_STRING(err));
342 #endif
343           break;
344     default:
345           msg_info("%s certificate verification failed for %s: num=%d:%s",
346                      PURPOSE, TLScontext->namaddr, err,
347                      X509_verify_cert_error_string(err));
348           break;
349     }
350 #ifdef USE_TLSRPT
351     vstring_free(err_vstr);
352 #endif
353 }
354 
355 #ifndef DONT_GRIPE
356 #define DONT_GRIPE 0
357 #define DO_GRIPE 1
358 #endif
359 
360 /* tls_text_name - extract certificate property value by name */
361 
tls_text_name(X509_NAME * name,int nid,const char * label,const TLS_SESS_STATE * TLScontext,int gripe)362 static char *tls_text_name(X509_NAME *name, int nid, const char *label,
363                                       const TLS_SESS_STATE *TLScontext, int gripe)
364 {
365     const char *myname = "tls_text_name";
366     int     pos;
367     X509_NAME_ENTRY *entry;
368     ASN1_STRING *entry_str;
369     int     asn1_type;
370     int     utf8_length;
371     unsigned char *utf8_value;
372     int     ch;
373     unsigned char *cp;
374 
375     if (name == 0 || (pos = X509_NAME_get_index_by_NID(name, nid, -1)) < 0) {
376           if (gripe != DONT_GRIPE) {
377               msg_warn("%s: %s: peer certificate has no %s",
378                          myname, TLScontext->namaddr, label);
379               tls_print_errors();
380           }
381           return (0);
382     }
383 #if 0
384 
385     /*
386      * If the match is required unambiguous, insist that that no other values
387      * be present.
388      */
389     if (X509_NAME_get_index_by_NID(name, nid, pos) >= 0) {
390           msg_warn("%s: %s: multiple %ss in peer certificate",
391                      myname, TLScontext->namaddr, label);
392           return (0);
393     }
394 #endif
395 
396     if ((entry = X509_NAME_get_entry(name, pos)) == 0) {
397           /* This should not happen */
398           msg_warn("%s: %s: error reading peer certificate %s entry",
399                      myname, TLScontext->namaddr, label);
400           tls_print_errors();
401           return (0);
402     }
403     if ((entry_str = X509_NAME_ENTRY_get_data(entry)) == 0) {
404           /* This should not happen */
405           msg_warn("%s: %s: error reading peer certificate %s data",
406                      myname, TLScontext->namaddr, label);
407           tls_print_errors();
408           return (0);
409     }
410 
411     /*
412      * XXX Convert everything into UTF-8. This is a super-set of ASCII, so we
413      * don't have to bother with separate code paths for ASCII-like content.
414      * If the payload is ASCII then we won't waste lots of CPU cycles
415      * converting it into UTF-8. It's up to OpenSSL to do something
416      * reasonable when converting ASCII formats that contain non-ASCII
417      * content.
418      *
419      * XXX Don't bother optimizing the string length error check. It is not
420      * worth the complexity.
421      */
422     asn1_type = ASN1_STRING_type(entry_str);
423     if ((utf8_length = ASN1_STRING_to_UTF8(&utf8_value, entry_str)) < 0) {
424           msg_warn("%s: %s: error decoding peer %s of ASN.1 type=%d",
425                      myname, TLScontext->namaddr, label, asn1_type);
426           tls_print_errors();
427           return (0);
428     }
429 
430     /*
431      * No returns without cleaning up. A good optimizer will replace multiple
432      * blocks of identical code by jumps to just one such block.
433      */
434 #define TLS_TEXT_NAME_RETURN(x) do { \
435           char *__tls_text_name_temp = (x); \
436           OPENSSL_free(utf8_value); \
437           return (__tls_text_name_temp); \
438     } while (0)
439 
440     /*
441      * Remove trailing null characters. They would give false alarms with the
442      * length check and with the embedded null check.
443      */
444 #define TRIM0(s, l) do { while ((l) > 0 && (s)[(l)-1] == 0) --(l); } while (0)
445 
446     TRIM0(utf8_value, utf8_length);
447 
448     /*
449      * Enforce the length limit, because the caller will copy the result into
450      * a fixed-length buffer.
451      */
452     if (utf8_length >= CCERT_BUFSIZ) {
453           msg_warn("%s: %s: peer %s too long: %d",
454                      myname, TLScontext->namaddr, label, utf8_length);
455           TLS_TEXT_NAME_RETURN(0);
456     }
457 
458     /*
459      * Reject embedded nulls in ASCII or UTF-8 names. OpenSSL is responsible
460      * for producing properly-formatted UTF-8.
461      */
462     if (utf8_length != strlen((char *) utf8_value)) {
463           msg_warn("%s: %s: NULL character in peer %s",
464                      myname, TLScontext->namaddr, label);
465           TLS_TEXT_NAME_RETURN(0);
466     }
467 
468     /*
469      * Reject non-printable ASCII characters in UTF-8 content.
470      *
471      * Note: the code below does not find control characters in illegal UTF-8
472      * sequences. It's OpenSSL's job to produce valid UTF-8, and reportedly,
473      * it does validation.
474      */
475     for (cp = utf8_value; (ch = *cp) != 0; cp++) {
476           if (ISASCII(ch) && !ISPRINT(ch)) {
477               msg_warn("%s: %s: non-printable content in peer %s",
478                          myname, TLScontext->namaddr, label);
479               TLS_TEXT_NAME_RETURN(0);
480           }
481     }
482     TLS_TEXT_NAME_RETURN(mystrdup((char *) utf8_value));
483 }
484 
485 /* tls_peer_CN - extract peer common name from certificate */
486 
tls_peer_CN(X509 * peercert,const TLS_SESS_STATE * TLScontext)487 char   *tls_peer_CN(X509 *peercert, const TLS_SESS_STATE *TLScontext)
488 {
489     char   *cn;
490     const char *san;
491 
492     /* Absent a commonName, return a validated DNS-ID SAN */
493     cn = tls_text_name(X509_get_subject_name(peercert), NID_commonName,
494                            "subject CN", TLScontext, DONT_GRIPE);
495     if (cn == 0 && (san = SSL_get0_peername(TLScontext->con)) != 0)
496           cn = mystrdup(san);
497     return (cn ? cn : mystrdup(""));
498 }
499 
500 /* tls_issuer_CN - extract issuer common name from certificate */
501 
tls_issuer_CN(X509 * peer,const TLS_SESS_STATE * TLScontext)502 char   *tls_issuer_CN(X509 *peer, const TLS_SESS_STATE *TLScontext)
503 {
504     X509_NAME *name;
505     char   *cn;
506 
507     name = X509_get_issuer_name(peer);
508 
509     /*
510      * If no issuer CN field, use Organization instead. CA certs without a CN
511      * are common, so we only complain if the organization is also missing.
512      */
513     if ((cn = tls_text_name(name, NID_commonName,
514                                   "issuer CN", TLScontext, DONT_GRIPE)) == 0)
515           cn = tls_text_name(name, NID_organizationName,
516                                  "issuer Organization", TLScontext, DONT_GRIPE);
517     return (cn ? cn : mystrdup(""));
518 }
519 
520 #endif
521