]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/ErrorDetail.cc
Maintenance: update --with-gnutls detection (#1685)
[thirdparty/squid.git] / src / security / ErrorDetail.cc
1 /*
2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 #include "squid.h"
10 #include "base/IoManip.h"
11 #include "error/SysErrorDetail.h"
12 #include "html/Quoting.h"
13 #include "sbuf/SBuf.h"
14 #include "sbuf/Stream.h"
15 #include "security/Certificate.h"
16 #include "security/ErrorDetail.h"
17 #include "security/forward.h"
18 #include "security/Io.h"
19 #include "util.h"
20
21 #if USE_OPENSSL
22 #include "ssl/ErrorDetailManager.h"
23 #elif HAVE_LIBGNUTLS
24 #if HAVE_GNUTLS_GNUTLS_H
25 #include <gnutls/gnutls.h>
26 #endif
27 #endif
28 #include <map>
29 #include <optional>
30
31 namespace Security {
32
33 // we use std::map to optimize search; TODO: Use std::unordered_map instead?
34 typedef std::map<ErrorCode, const char *> ErrorCodeNames;
35 static const ErrorCodeNames TheErrorCodeNames = {
36 { SQUID_TLS_ERR_ACCEPT,
37 "SQUID_TLS_ERR_ACCEPT"
38 },
39 { SQUID_TLS_ERR_CONNECT,
40 "SQUID_TLS_ERR_CONNECT"
41 },
42 { SQUID_X509_V_ERR_INFINITE_VALIDATION,
43 "SQUID_X509_V_ERR_INFINITE_VALIDATION"
44 },
45 { SQUID_X509_V_ERR_CERT_CHANGE,
46 "SQUID_X509_V_ERR_CERT_CHANGE"
47 },
48 { SQUID_X509_V_ERR_DOMAIN_MISMATCH,
49 "SQUID_X509_V_ERR_DOMAIN_MISMATCH"
50 },
51 #if USE_OPENSSL
52 { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT,
53 "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT"
54 },
55 { X509_V_ERR_UNABLE_TO_GET_CRL,
56 "X509_V_ERR_UNABLE_TO_GET_CRL"
57 },
58 { X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE,
59 "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE"
60 },
61 { X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE,
62 "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE"
63 },
64 { X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY,
65 "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"
66 },
67 { X509_V_ERR_CERT_SIGNATURE_FAILURE,
68 "X509_V_ERR_CERT_SIGNATURE_FAILURE"
69 },
70 { X509_V_ERR_CRL_SIGNATURE_FAILURE,
71 "X509_V_ERR_CRL_SIGNATURE_FAILURE"
72 },
73 { X509_V_ERR_CERT_NOT_YET_VALID,
74 "X509_V_ERR_CERT_NOT_YET_VALID"
75 },
76 { X509_V_ERR_CERT_HAS_EXPIRED,
77 "X509_V_ERR_CERT_HAS_EXPIRED"
78 },
79 { X509_V_ERR_CRL_NOT_YET_VALID,
80 "X509_V_ERR_CRL_NOT_YET_VALID"
81 },
82 { X509_V_ERR_CRL_HAS_EXPIRED,
83 "X509_V_ERR_CRL_HAS_EXPIRED"
84 },
85 { X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD,
86 "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD"
87 },
88 { X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD,
89 "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD"
90 },
91 { X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD,
92 "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD"
93 },
94 { X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD,
95 "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD"
96 },
97 { X509_V_ERR_OUT_OF_MEM,
98 "X509_V_ERR_OUT_OF_MEM"
99 },
100 { X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
101 "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
102 },
103 { X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
104 "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN"
105 },
106 { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
107 "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
108 },
109 { X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,
110 "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
111 },
112 { X509_V_ERR_CERT_CHAIN_TOO_LONG,
113 "X509_V_ERR_CERT_CHAIN_TOO_LONG"
114 },
115 { X509_V_ERR_CERT_REVOKED,
116 "X509_V_ERR_CERT_REVOKED"
117 },
118 { X509_V_ERR_INVALID_CA,
119 "X509_V_ERR_INVALID_CA"
120 },
121 { X509_V_ERR_PATH_LENGTH_EXCEEDED,
122 "X509_V_ERR_PATH_LENGTH_EXCEEDED"
123 },
124 { X509_V_ERR_INVALID_PURPOSE,
125 "X509_V_ERR_INVALID_PURPOSE"
126 },
127 { X509_V_ERR_CERT_UNTRUSTED,
128 "X509_V_ERR_CERT_UNTRUSTED"
129 },
130 { X509_V_ERR_CERT_REJECTED,
131 "X509_V_ERR_CERT_REJECTED"
132 },
133 { X509_V_ERR_SUBJECT_ISSUER_MISMATCH,
134 "X509_V_ERR_SUBJECT_ISSUER_MISMATCH"
135 },
136 { X509_V_ERR_AKID_SKID_MISMATCH,
137 "X509_V_ERR_AKID_SKID_MISMATCH"
138 },
139 { X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH,
140 "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH"
141 },
142 { X509_V_ERR_KEYUSAGE_NO_CERTSIGN,
143 "X509_V_ERR_KEYUSAGE_NO_CERTSIGN"
144 },
145 #if defined(X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER)
146 {
147 X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER, // 33
148 "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER"
149 },
150 #endif
151 #if defined(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION)
152 {
153 X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, // 34
154 "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION"
155 },
156 #endif
157 #if defined(X509_V_ERR_KEYUSAGE_NO_CRL_SIGN)
158 {
159 X509_V_ERR_KEYUSAGE_NO_CRL_SIGN, // 35
160 "X509_V_ERR_KEYUSAGE_NO_CRL_SIGN"
161 },
162 #endif
163 #if defined(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION)
164 {
165 X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION, // 36
166 "X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION"
167 },
168 #endif
169 #if defined(X509_V_ERR_INVALID_NON_CA)
170 {
171 X509_V_ERR_INVALID_NON_CA, // 37
172 "X509_V_ERR_INVALID_NON_CA"
173 },
174 #endif
175 #if defined(X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED)
176 {
177 X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED, // 38
178 "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED"
179 },
180 #endif
181 #if defined(X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE)
182 {
183 X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE, // 39
184 "X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE"
185 },
186 #endif
187 #if defined(X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED)
188 {
189 X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED, // 40
190 "X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED"
191 },
192 #endif
193 #if defined(X509_V_ERR_INVALID_EXTENSION)
194 {
195 X509_V_ERR_INVALID_EXTENSION, // 41
196 "X509_V_ERR_INVALID_EXTENSION"
197 },
198 #endif
199 #if defined(X509_V_ERR_INVALID_POLICY_EXTENSION)
200 {
201 X509_V_ERR_INVALID_POLICY_EXTENSION, // 42
202 "X509_V_ERR_INVALID_POLICY_EXTENSION"
203 },
204 #endif
205 #if defined(X509_V_ERR_NO_EXPLICIT_POLICY)
206 {
207 X509_V_ERR_NO_EXPLICIT_POLICY, // 43
208 "X509_V_ERR_NO_EXPLICIT_POLICY"
209 },
210 #endif
211 #if defined(X509_V_ERR_DIFFERENT_CRL_SCOPE)
212 {
213 X509_V_ERR_DIFFERENT_CRL_SCOPE, // 44
214 "X509_V_ERR_DIFFERENT_CRL_SCOPE"
215 },
216 #endif
217 #if defined(X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE)
218 {
219 X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE, // 45
220 "X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE"
221 },
222 #endif
223 #if defined(X509_V_ERR_UNNESTED_RESOURCE)
224 {
225 X509_V_ERR_UNNESTED_RESOURCE, // 46
226 "X509_V_ERR_UNNESTED_RESOURCE"
227 },
228 #endif
229 #if defined(X509_V_ERR_PERMITTED_VIOLATION)
230 {
231 X509_V_ERR_PERMITTED_VIOLATION, // 47
232 "X509_V_ERR_PERMITTED_VIOLATION"
233 },
234 #endif
235 #if defined(X509_V_ERR_EXCLUDED_VIOLATION)
236 {
237 X509_V_ERR_EXCLUDED_VIOLATION, // 48
238 "X509_V_ERR_EXCLUDED_VIOLATION"
239 },
240 #endif
241 #if defined(X509_V_ERR_SUBTREE_MINMAX)
242 {
243 X509_V_ERR_SUBTREE_MINMAX, // 49
244 "X509_V_ERR_SUBTREE_MINMAX"
245 },
246 #endif
247 { X509_V_ERR_APPLICATION_VERIFICATION, // 50
248 "X509_V_ERR_APPLICATION_VERIFICATION"
249 },
250 #if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE)
251 {
252 X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE, // 51
253 "X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE"
254 },
255 #endif
256 #if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX)
257 {
258 X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX, // 52
259 "X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX"
260 },
261 #endif
262 #if defined(X509_V_ERR_UNSUPPORTED_NAME_SYNTAX)
263 {
264 X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, // 53
265 "X509_V_ERR_UNSUPPORTED_NAME_SYNTAX"
266 },
267 #endif
268 #if defined(X509_V_ERR_CRL_PATH_VALIDATION_ERROR)
269 {
270 X509_V_ERR_CRL_PATH_VALIDATION_ERROR, // 54
271 "X509_V_ERR_CRL_PATH_VALIDATION_ERROR"
272 },
273 #endif
274 #if defined(X509_V_ERR_PATH_LOOP)
275 {
276 X509_V_ERR_PATH_LOOP, // 55
277 "X509_V_ERR_PATH_LOOP"
278 },
279 #endif
280 #if defined(X509_V_ERR_SUITE_B_INVALID_VERSION)
281 {
282 X509_V_ERR_SUITE_B_INVALID_VERSION, // 56
283 "X509_V_ERR_SUITE_B_INVALID_VERSION"
284 },
285 #endif
286 #if defined(X509_V_ERR_SUITE_B_INVALID_ALGORITHM)
287 {
288 X509_V_ERR_SUITE_B_INVALID_ALGORITHM, // 57
289 "X509_V_ERR_SUITE_B_INVALID_ALGORITHM"
290 },
291 #endif
292 #if defined(X509_V_ERR_SUITE_B_INVALID_CURVE)
293 {
294 X509_V_ERR_SUITE_B_INVALID_CURVE, // 58
295 "X509_V_ERR_SUITE_B_INVALID_CURVE"
296 },
297 #endif
298 #if defined(X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM)
299 {
300 X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM, // 59
301 "X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM"
302 },
303 #endif
304 #if defined(X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED)
305 {
306 X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED, // 60
307 "X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED"
308 },
309 #endif
310 #if defined(X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256)
311 {
312 X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256, // 61
313 "X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256"
314 },
315 #endif
316 #if defined(X509_V_ERR_HOSTNAME_MISMATCH)
317 {
318 X509_V_ERR_HOSTNAME_MISMATCH, // 62
319 "X509_V_ERR_HOSTNAME_MISMATCH"
320 },
321 #endif
322 #if defined(X509_V_ERR_EMAIL_MISMATCH)
323 {
324 X509_V_ERR_EMAIL_MISMATCH, // 63
325 "X509_V_ERR_EMAIL_MISMATCH"
326 },
327 #endif
328 #if defined(X509_V_ERR_IP_ADDRESS_MISMATCH)
329 {
330 X509_V_ERR_IP_ADDRESS_MISMATCH, // 64
331 "X509_V_ERR_IP_ADDRESS_MISMATCH"
332 },
333 #endif
334 #if defined(X509_V_ERR_DANE_NO_MATCH)
335 {
336 X509_V_ERR_DANE_NO_MATCH, // 65
337 "X509_V_ERR_DANE_NO_MATCH"
338 },
339 #endif
340 #if defined(X509_V_ERR_EE_KEY_TOO_SMALL)
341 {
342 X509_V_ERR_EE_KEY_TOO_SMALL, // 66
343 "X509_V_ERR_EE_KEY_TOO_SMALL"
344 },
345 #endif
346 #if defined(X509_V_ERR_CA_KEY_TOO_SMALL)
347 {
348 X509_V_ERR_CA_KEY_TOO_SMALL, // 67
349 "X509_V_ERR_CA_KEY_TOO_SMALL"
350 },
351 #endif
352 #if defined(X509_V_ERR_CA_MD_TOO_WEAK)
353 {
354 X509_V_ERR_CA_MD_TOO_WEAK, // 68
355 "X509_V_ERR_CA_MD_TOO_WEAK"
356 },
357 #endif
358 #if defined(X509_V_ERR_INVALID_CALL)
359 {
360 X509_V_ERR_INVALID_CALL, // 69
361 "X509_V_ERR_INVALID_CALL"
362 },
363 #endif
364 #if defined(X509_V_ERR_STORE_LOOKUP)
365 {
366 X509_V_ERR_STORE_LOOKUP, // 70
367 "X509_V_ERR_STORE_LOOKUP"
368 },
369 #endif
370 #if defined(X509_V_ERR_NO_VALID_SCTS)
371 {
372 X509_V_ERR_NO_VALID_SCTS, // 71
373 "X509_V_ERR_NO_VALID_SCTS"
374 },
375 #endif
376 #if defined(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION)
377 {
378 X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION, // 72
379 "X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION"
380 },
381 #endif
382 #if defined(X509_V_ERR_OCSP_VERIFY_NEEDED)
383 {
384 X509_V_ERR_OCSP_VERIFY_NEEDED, // 73
385 "X509_V_ERR_OCSP_VERIFY_NEEDED"
386 },
387 #endif
388 #if defined(X509_V_ERR_OCSP_VERIFY_FAILED)
389 {
390 X509_V_ERR_OCSP_VERIFY_FAILED, // 74
391 "X509_V_ERR_OCSP_VERIFY_FAILED"
392 },
393 #endif
394 #if defined(X509_V_ERR_OCSP_CERT_UNKNOWN)
395 {
396 X509_V_ERR_OCSP_CERT_UNKNOWN, // 75
397 "X509_V_ERR_OCSP_CERT_UNKNOWN"
398 },
399 #endif
400 {
401 SSL_ERROR_NONE,
402 "SSL_ERROR_NONE"
403 },
404 #endif // USE_OPENSSL
405 };
406
407 } // namespace Security
408
409 Security::ErrorCode
410 Security::ErrorCodeFromName(const char *name)
411 {
412 static auto TheCmp = [](const char *a, const char *b) {return strcmp(a, b) < 0;};
413 static std::map<const char *, ErrorCode, decltype(TheCmp)> TheErrorCodeByNameIndx(TheCmp);
414 if (TheErrorCodeByNameIndx.empty()) {
415 for (const auto &i: TheErrorCodeNames)
416 TheErrorCodeByNameIndx.insert(std::make_pair(i.second, i.first));
417
418 // redirector to support legacy error translations
419 TheErrorCodeByNameIndx.insert(std::make_pair("SQUID_ERR_SSL_HANDSHAKE", SQUID_TLS_ERR_CONNECT));
420 }
421
422 const auto it = TheErrorCodeByNameIndx.find(name);
423 if (it != TheErrorCodeByNameIndx.end())
424 return it->second;
425
426 return 0;
427 }
428
429 const char *
430 Security::ErrorNameFromCode(const ErrorCode err, const bool prefixRawCode)
431 {
432 const auto it = TheErrorCodeNames.find(err);
433 if (it != TheErrorCodeNames.end())
434 return it->second;
435
436 static char tmpBuffer[128];
437 snprintf(tmpBuffer, sizeof(tmpBuffer), "%s%d",
438 (prefixRawCode ? "SSL_ERR=" : ""), static_cast<int>(err));
439 return tmpBuffer;
440 }
441
442 /* Security::ErrorDetail */
443
444 /// helper constructor implementing the logic shared by the two public ones
445 Security::ErrorDetail::ErrorDetail(const ErrorCode err, const int aSysErrorNo):
446 error_no(err),
447 // We could restrict errno(3) collection to cases where the TLS library
448 // explicitly talks about the errno being set, but correctly detecting those
449 // cases is difficult. We simplify in hope that all other cases will either
450 // have a useful errno or a zero errno.
451 sysErrorNo(aSysErrorNo)
452 {
453 #if USE_OPENSSL
454 /// Extract and remember errors stored internally by the TLS library.
455 if ((lib_error_no = ERR_get_error())) {
456 debugs(83, 7, "got 0x" << asHex(lib_error_no));
457 // more errors may be stacked
458 // TODO: Save/detail all stacked errors by always flushing stale ones.
459 ForgetErrors();
460 }
461 #else
462 // other libraries return errors explicitly instead of auto-storing them
463 #endif
464 }
465
466 Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const CertPointer &cert, const CertPointer &broken, const char *aReason):
467 ErrorDetail(anErrorCode, 0)
468 {
469 errReason = aReason;
470 peer_cert = cert;
471 broken_cert = broken ? broken : cert;
472 }
473
474 #if USE_OPENSSL
475 Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const int anIoErrorNo, const int aSysErrorNo):
476 ErrorDetail(anErrorCode, aSysErrorNo)
477 {
478 ioErrorNo = anIoErrorNo;
479 }
480
481 #elif HAVE_LIBGNUTLS
482 Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const LibErrorCode aLibErrorNo, const int aSysErrorNo):
483 ErrorDetail(anErrorCode, aSysErrorNo)
484 {
485 lib_error_no = aLibErrorNo;
486 }
487 #endif
488
489 void
490 Security::ErrorDetail::setPeerCertificate(const CertPointer &cert)
491 {
492 assert(cert);
493 assert(!peer_cert);
494 assert(!broken_cert);
495 peer_cert = cert;
496 // unlike the constructor, the supplied certificate is not a broken_cert
497 }
498
499 SBuf
500 Security::ErrorDetail::brief() const
501 {
502 SBufStream os;
503
504 printErrorCode(os);
505
506 if (lib_error_no) {
507 #if USE_OPENSSL
508 // TODO: Log ERR_error_string_n() instead, despite length, whitespace?
509 // Example: `error:1408F09C:SSL routines:ssl3_get_record:http request`.
510 os << "+TLS_LIB_ERR=" << asHex(lib_error_no).upperCase();
511 #elif HAVE_LIBGNUTLS
512 os << '+' << gnutls_strerror_name(lib_error_no);
513 #endif
514 }
515
516 #if USE_OPENSSL
517 // TODO: Consider logging long but human-friendly names (e.g.,
518 // SSL_ERROR_SYSCALL).
519 if (ioErrorNo)
520 os << "+TLS_IO_ERR=" << ioErrorNo;
521 #endif
522
523 if (sysErrorNo) {
524 os << '+' << SysErrorDetail::Brief(sysErrorNo);
525 }
526
527 if (broken_cert)
528 os << "+broken_cert";
529
530 return os.buf();
531 }
532
533 SBuf
534 Security::ErrorDetail::verbose(const HttpRequestPointer &request) const
535 {
536 std::optional<SBuf> customFormat;
537 #if USE_OPENSSL
538 if (const auto errorDetail = Ssl::ErrorDetailsManager::GetInstance().findDetail(error_no, request))
539 customFormat = errorDetail->detail;
540 #else
541 (void)request;
542 #endif
543 auto format = customFormat ? customFormat->c_str() : "SSL handshake error (%err_name)";
544
545 SBufStream os;
546 assert(format);
547 auto remainder = format;
548 while (auto p = strchr(remainder, '%')) {
549 os.write(remainder, p - remainder);
550 const auto formattingCodeLen = convertErrorCodeToDescription(++p, os);
551 if (!formattingCodeLen)
552 os << '%';
553 remainder = p + formattingCodeLen;
554 }
555 os << remainder;
556 return os.buf();
557 }
558
559 /// textual representation of the subject of the broken certificate
560 void
561 Security::ErrorDetail::printSubject(std::ostream &os) const
562 {
563 if (broken_cert) {
564 auto buf = SubjectName(*broken_cert);
565 if (!buf.isEmpty()) {
566 // TODO: Convert html_quote() into an std::ostream manipulator.
567 // quote to avoid possible html code injection through
568 // certificate subject
569 os << html_quote(buf.c_str());
570 return;
571 }
572 }
573 os << "[Not available]";
574 }
575
576 #if USE_OPENSSL
577 /// a helper class to print CNs extracted using Ssl::matchX509CommonNames()
578 class CommonNamesPrinter
579 {
580 public:
581 explicit CommonNamesPrinter(std::ostream &os): os_(os) {}
582
583 /// Ssl::matchX509CommonNames() visitor that reports the given name (if any)
584 static int PrintName(void *, ASN1_STRING *);
585
586 /// whether any names have been printed so far
587 bool printed = false;
588
589 private:
590 void printName(const ASN1_STRING *);
591
592 std::ostream &os_; ///< destination for printed names
593 };
594
595 int
596 CommonNamesPrinter::PrintName(void * const printer, ASN1_STRING * const name)
597 {
598 assert(printer);
599 static_cast<CommonNamesPrinter*>(printer)->printName(name);
600 return 1;
601 }
602
603 /// prints an HTML-quoted version of the given common name (as a part of the
604 /// printed names list)
605 void
606 CommonNamesPrinter::printName(const ASN1_STRING * const name)
607 {
608 if (name && name->length) {
609 if (printed)
610 os_ << ", ";
611
612 // TODO: Convert html_quote() into an std::ostream manipulator accepting (buf, n).
613 SBuf buf(reinterpret_cast<const char *>(name->data), name->length);
614 os_ << html_quote(buf.c_str());
615
616 printed = true;
617 }
618 }
619 #endif // USE_OPENSSL
620
621 /// a list of the broken certificates CN and alternate names
622 void
623 Security::ErrorDetail::printCommonName(std::ostream &os) const
624 {
625 #if USE_OPENSSL
626 if (broken_cert.get()) {
627 CommonNamesPrinter printer(os);
628 Ssl::matchX509CommonNames(broken_cert.get(), &printer, printer.PrintName);
629 if (printer.printed)
630 return;
631 }
632 #endif // USE_OPENSSL
633 os << "[Not available]";
634 }
635
636 /// the issuer of the broken certificate
637 void
638 Security::ErrorDetail::printCaName(std::ostream &os) const
639 {
640 if (broken_cert) {
641 auto buf = IssuerName(*broken_cert);
642 if (!buf.isEmpty()) {
643 // quote to avoid possible html code injection through
644 // certificate issuer subject
645 os << html_quote(buf.c_str());
646 return;
647 }
648 }
649 os << "[Not available]";
650 }
651
652 /// textual representation of the "not before" field of the broken certificate
653 void
654 Security::ErrorDetail::printNotBefore(std::ostream &os) const
655 {
656 #if USE_OPENSSL
657 if (broken_cert.get()) {
658 if (const auto tm = X509_getm_notBefore(broken_cert.get())) {
659 // TODO: Add and use an ASN1_TIME printing operator instead.
660 static char tmpBuffer[256]; // A temporary buffer
661 Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer));
662 os << tmpBuffer;
663 return;
664 }
665 }
666 #endif // USE_OPENSSL
667 os << "[Not available]";
668 }
669
670 /// textual representation of the "not after" field of the broken certificate
671 void
672 Security::ErrorDetail::printNotAfter(std::ostream &os) const
673 {
674 #if USE_OPENSSL
675 if (broken_cert.get()) {
676 if (const auto tm = X509_getm_notAfter(broken_cert.get())) {
677 // XXX: Reduce code duplication.
678 static char tmpBuffer[256]; // A temporary buffer
679 Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer));
680 os << tmpBuffer;
681 return;
682 }
683 }
684 #endif // USE_OPENSSL
685 os << "[Not available]";
686 }
687
688 /// textual representation of error_no
689 void
690 Security::ErrorDetail::printErrorCode(std::ostream &os) const
691 {
692 #if USE_OPENSSL
693 // try detailEntry first because it is faster
694 if (detailEntry) {
695 os << detailEntry->name;
696 return;
697 }
698 #endif
699 os << ErrorNameFromCode(error_no);
700 }
701
702 /// short description of error_no
703 void
704 Security::ErrorDetail::printErrorDescription(std::ostream &os) const
705 {
706 if (!error_no) {
707 os << "[No Error]";
708 return;
709 }
710
711 #if USE_OPENSSL
712 if (detailEntry) {
713 os << detailEntry->descr;
714 return;
715 }
716 #endif
717
718 os << "[Not available]";
719 }
720
721 /// textual representation of lib_error_no
722 void
723 Security::ErrorDetail::printErrorLibError(std::ostream &os) const
724 {
725 if (errReason.size() > 0)
726 os << errReason;
727 else if (lib_error_no)
728 os << ErrorString(lib_error_no);
729 else
730 os << "[No Error]";
731 }
732
733 /**
734 * Converts the code to a string value. Supported formatting codes are:
735 *
736 * Error meta information:
737 * %err_name: The name of a high-level SSL error (e.g., X509_V_ERR_*)
738 * %ssl_error_descr: A short description of the SSL error
739 * %ssl_lib_error: human-readable low-level error string by ErrorString()
740 *
741 * Certificate information extracted from broken (not necessarily peer!) cert
742 * %ssl_cn: The comma-separated list of common and alternate names
743 * %ssl_subject: The certificate subject
744 * %ssl_ca_name: The certificate issuer name
745 * %ssl_notbefore: The certificate "not before" field
746 * %ssl_notafter: The certificate "not after" field
747 *
748 \returns the length of the code (the number of characters to be replaced by value)
749 \retval 0 for unsupported codes
750 */
751 size_t
752 Security::ErrorDetail::convertErrorCodeToDescription(const char * const code, std::ostream &os) const
753 {
754 using PartDescriber = void (ErrorDetail::*)(std::ostream &os) const;
755 static const std::map<const char*, PartDescriber> PartDescriberByCode = {
756 {"ssl_subject", &ErrorDetail::printSubject},
757 {"ssl_ca_name", &ErrorDetail::printCaName},
758 {"ssl_cn", &ErrorDetail::printCommonName},
759 {"ssl_notbefore", &ErrorDetail::printNotBefore},
760 {"ssl_notafter", &ErrorDetail::printNotAfter},
761 {"err_name", &ErrorDetail::printErrorCode},
762 {"ssl_error_descr", &ErrorDetail::printErrorDescription},
763 {"ssl_lib_error", &ErrorDetail::printErrorLibError}
764 };
765
766 // We can refactor the map to find matches without looping, but that
767 // requires a "starts with" comparison function -- `code` length is unknown.
768 for (const auto &pair: PartDescriberByCode) {
769 const auto len = strlen(pair.first);
770 if (strncmp(code, pair.first, len) == 0) {
771 const auto method = pair.second;
772 (this->*method)(os);
773 return len;
774 }
775 }
776
777 // TODO: Support logformat %codes.
778 return 0;
779 }
780