suricata
detect-tls-cert-validity.c
Go to the documentation of this file.
1/* Copyright (C) 2015-2020 Open Information Security Foundation
2 *
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
5 * Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
18/**
19 * \file
20 *
21 * \author Mats Klepsland <mats.klepsland@gmail.com>
22 *
23 * Implements tls certificate validity keywords
24 */
25
26#include "suricata-common.h"
27#include "threads.h"
28#include "decode.h"
29#include "detect.h"
30
31#include "detect-parse.h"
32#include "detect-engine.h"
33#include "detect-engine-mpm.h"
34#include "detect-content.h"
35#include "detect-pcre.h"
37
38#include "flow.h"
39#include "flow-util.h"
40#include "flow-var.h"
41
42#include "stream-tcp.h"
43
44#include "app-layer.h"
45#include "app-layer-ssl.h"
46
47#include "util-time.h"
48#include "util-unittest.h"
50
51/**
52 * [tls_notbefore|tls_notafter]:[<|>]<date string>[<><date string>];
53 */
54#define PARSE_REGEX "^\\s*(<|>)?\\s*([ -:TW0-9]+)\\s*(?:(<>)\\s*([ -:TW0-9]+))?\\s*$"
55static DetectParseRegex parse_regex;
56
57static int DetectTlsValidityMatch (DetectEngineThreadCtx *, Flow *,
58 uint8_t, void *, void *, const Signature *,
59 const SigMatchCtx *);
60
61static time_t DateStringToEpoch (char *);
62static DetectTlsValidityData *DetectTlsValidityParse (const char *);
63static int DetectTlsExpiredSetup (DetectEngineCtx *, Signature *s, const char *str);
64static int DetectTlsValidSetup (DetectEngineCtx *, Signature *s, const char *str);
65static int DetectTlsNotBeforeSetup (DetectEngineCtx *, Signature *s, const char *str);
66static int DetectTlsNotAfterSetup (DetectEngineCtx *, Signature *s, const char *str);
67static int DetectTlsValiditySetup (DetectEngineCtx *, Signature *s, const char *str, uint8_t);
68#ifdef UNITTESTS
69static void TlsNotBeforeRegisterTests(void);
70static void TlsNotAfterRegisterTests(void);
71static void TlsExpiredRegisterTests(void);
72static void TlsValidRegisterTests(void);
73#endif /* UNITTESTS */
74static void DetectTlsValidityFree(DetectEngineCtx *, void *);
75static int g_tls_validity_buffer_id = 0;
76
77/**
78 * \brief Registration function for tls validity keywords.
79 */
81{
82 sigmatch_table[DETECT_TLS_NOTBEFORE].name = "tls_cert_notbefore";
83 sigmatch_table[DETECT_TLS_NOTBEFORE].desc = "match TLS certificate notBefore field";
84 sigmatch_table[DETECT_TLS_NOTBEFORE].url = "/rules/tls-keywords.html#tls-cert-notbefore";
85 sigmatch_table[DETECT_TLS_NOTBEFORE].AppLayerTxMatch = DetectTlsValidityMatch;
86 sigmatch_table[DETECT_TLS_NOTBEFORE].Setup = DetectTlsNotBeforeSetup;
87 sigmatch_table[DETECT_TLS_NOTBEFORE].Free = DetectTlsValidityFree;
88#ifdef UNITTESTS
90#endif
91
92 sigmatch_table[DETECT_TLS_NOTAFTER].name = "tls_cert_notafter";
93 sigmatch_table[DETECT_TLS_NOTAFTER].desc = "match TLS certificate notAfter field";
94 sigmatch_table[DETECT_TLS_NOTAFTER].url = "/rules/tls-keywords.html#tls-cert-notafter";
95 sigmatch_table[DETECT_TLS_NOTAFTER].AppLayerTxMatch = DetectTlsValidityMatch;
96 sigmatch_table[DETECT_TLS_NOTAFTER].Setup = DetectTlsNotAfterSetup;
97 sigmatch_table[DETECT_TLS_NOTAFTER].Free = DetectTlsValidityFree;
98#ifdef UNITTESTS
100#endif
101
102 sigmatch_table[DETECT_TLS_EXPIRED].name = "tls_cert_expired";
103 sigmatch_table[DETECT_TLS_EXPIRED].desc = "match expired TLS certificates";
104 sigmatch_table[DETECT_TLS_EXPIRED].url = "/rules/tls-keywords.html#tls-cert-expired";
105 sigmatch_table[DETECT_TLS_EXPIRED].AppLayerTxMatch = DetectTlsValidityMatch;
106 sigmatch_table[DETECT_TLS_EXPIRED].Setup = DetectTlsExpiredSetup;
107 sigmatch_table[DETECT_TLS_EXPIRED].Free = DetectTlsValidityFree;
109#ifdef UNITTESTS
111#endif
112
113 sigmatch_table[DETECT_TLS_VALID].name = "tls_cert_valid";
114 sigmatch_table[DETECT_TLS_VALID].desc = "match valid TLS certificates";
115 sigmatch_table[DETECT_TLS_VALID].url = "/rules/tls-keywords.html#tls-cert-valid";
116 sigmatch_table[DETECT_TLS_VALID].AppLayerTxMatch = DetectTlsValidityMatch;
117 sigmatch_table[DETECT_TLS_VALID].Setup = DetectTlsValidSetup;
118 sigmatch_table[DETECT_TLS_VALID].Free = DetectTlsValidityFree;
120#ifdef UNITTESTS
122#endif
123
125
126 g_tls_validity_buffer_id = DetectBufferTypeGetByName("tls:server_cert_done:generic");
127}
128
129/**
130 * \internal
131 * \brief Function to match validity field in a tls certificate.
132 *
133 * \param t Pointer to thread vars.
134 * \param det_ctx Pointer to the pattern matcher thread.
135 * \param f Pointer to the current flow.
136 * \param flags Flags.
137 * \param state App layer state.
138 * \param s Pointer to the Signature.
139 * \param m Pointer to the sigmatch that we will cast into
140 * DetectTlsValidityData.
141 *
142 * \retval 0 no match.
143 * \retval 1 match.
144 */
145static int DetectTlsValidityMatch (DetectEngineThreadCtx *det_ctx,
146 Flow *f, uint8_t flags, void *state,
147 void *txv, const Signature *s,
148 const SigMatchCtx *ctx)
149{
150 SCEnter();
151
152 SSLState *ssl_state = (SSLState *)state;
153 if (ssl_state == NULL) {
154 SCLogDebug("no tls state, no match");
155 SCReturnInt(0);
156 }
157
158 int ret = 0;
159
160 SSLStateConnp *connp = NULL;
161 if (flags & STREAM_TOSERVER)
162 connp = &ssl_state->client_connp;
163 else
164 connp = &ssl_state->server_connp;
165
167
168 time_t cert_epoch = 0;
170 cert_epoch = connp->cert0_not_before;
171 else if (dd->type == DETECT_TLS_TYPE_NOTAFTER)
172 cert_epoch = connp->cert0_not_after;
173
174 if (cert_epoch == 0)
175 SCReturnInt(0);
176
177 if ((dd->mode & DETECT_TLS_VALIDITY_EQ) && cert_epoch == dd->epoch)
178 ret = 1;
179 else if ((dd->mode & DETECT_TLS_VALIDITY_LT) && cert_epoch <= dd->epoch)
180 ret = 1;
181 else if ((dd->mode & DETECT_TLS_VALIDITY_GT) && cert_epoch >= dd->epoch)
182 ret = 1;
183 else if ((dd->mode & DETECT_TLS_VALIDITY_RA) &&
184 cert_epoch >= dd->epoch && cert_epoch <= dd->epoch2)
185 ret = 1;
186 else if ((dd->mode & DETECT_TLS_VALIDITY_EX) && (time_t)SCTIME_SECS(f->lastts) > cert_epoch)
187 ret = 1;
188 else if ((dd->mode & DETECT_TLS_VALIDITY_VA) && (time_t)SCTIME_SECS(f->lastts) <= cert_epoch)
189 ret = 1;
190
191 SCReturnInt(ret);
192}
193
194/**
195 * \internal
196 * \brief Function to check if string is epoch.
197 *
198 * \param string Date string.
199 *
200 * \retval epoch time on success.
201 * \retval LONG_MIN on failure.
202 */
203static time_t StringIsEpoch (char *string)
204{
205 if (strlen(string) == 0)
206 return LONG_MIN;
207
208 /* We assume that the date string is epoch if it consists of only
209 digits. */
210 char *sp = string;
211 while (*sp) {
212 if (isdigit(*sp++) == 0)
213 return LONG_MIN;
214 }
215
216 return strtol(string, NULL, 10);
217}
218
219/**
220 * \internal
221 * \brief Function to convert date string to epoch.
222 *
223 * \param string Date string.
224 *
225 * \retval epoch on success.
226 * \retval 0 on failure.
227 */
228static time_t DateStringToEpoch (char *string)
229{
230 int r = 0;
231 struct tm tm;
232 const char *patterns[] = {
233 /* ISO 8601 */
234 "%Y-%m",
235 "%Y-%m-%d",
236 "%Y-%m-%d %H",
237 "%Y-%m-%d %H:%M",
238 "%Y-%m-%d %H:%M:%S",
239 "%Y-%m-%dT%H",
240 "%Y-%m-%dT%H:%M",
241 "%Y-%m-%dT%H:%M:%S",
242 "%H:%M",
243 "%H:%M:%S",
244 };
245
246 /* Skip leading whitespace. */
247 while (isspace(*string))
248 string++;
249
250 size_t inlen, oldlen;
251
252 oldlen = inlen = strlen(string);
253
254 /* Skip trailing whitespace */
255 while (inlen > 0 && isspace(string[inlen - 1]))
256 inlen--;
257
258 char tmp[inlen + 1];
259
260 if (inlen < oldlen) {
261 strlcpy(tmp, string, inlen + 1);
262 string = tmp;
263 }
264
265 time_t epoch = StringIsEpoch(string);
266 if (epoch != LONG_MIN) {
267 return epoch;
268 }
269
270 r = SCStringPatternToTime(string, patterns, 10, &tm);
271
272 if (r != 0)
273 return LONG_MIN;
274
275 return SCMkTimeUtc(&tm);
276}
277
278/**
279 * \internal
280 * \brief Function to parse options passed via tls validity keywords.
281 *
282 * \param rawstr Pointer to the user provided options.
283 *
284 * \retval dd pointer to DetectTlsValidityData on success.
285 * \retval NULL on failure.
286 */
287static DetectTlsValidityData *DetectTlsValidityParse (const char *rawstr)
288{
289 DetectTlsValidityData *dd = NULL;
290 char mode[2] = "";
291 char value1[20] = "";
292 char value2[20] = "";
293 char range[3] = "";
294
295 pcre2_match_data *match = NULL;
296 int ret = DetectParsePcreExec(&parse_regex, &match, rawstr, 0, 0);
297 if (ret < 3 || ret > 5) {
298 SCLogError("Parse error %s", rawstr);
299 goto error;
300 }
301
302 size_t pcre2len = sizeof(mode);
303 int res = SC_Pcre2SubstringCopy(match, 1, (PCRE2_UCHAR8 *)mode, &pcre2len);
304 if (res < 0) {
305 SCLogError("pcre2_substring_copy_bynumber failed");
306 goto error;
307 }
308 SCLogDebug("mode \"%s\"", mode);
309
310 pcre2len = sizeof(value1);
311 res = pcre2_substring_copy_bynumber(match, 2, (PCRE2_UCHAR8 *)value1, &pcre2len);
312 if (res < 0) {
313 SCLogError("pcre2_substring_copy_bynumber failed");
314 goto error;
315 }
316 SCLogDebug("value1 \"%s\"", value1);
317
318 if (ret > 3) {
319 pcre2len = sizeof(range);
320 res = pcre2_substring_copy_bynumber(match, 3, (PCRE2_UCHAR8 *)range, &pcre2len);
321 if (res < 0) {
322 SCLogError("pcre2_substring_copy_bynumber failed");
323 goto error;
324 }
325 SCLogDebug("range \"%s\"", range);
326
327 if (ret > 4) {
328 pcre2len = sizeof(value2);
329 res = pcre2_substring_copy_bynumber(match, 4, (PCRE2_UCHAR8 *)value2, &pcre2len);
330 if (res < 0) {
331 SCLogError("pcre2_substring_copy_bynumber failed");
332 goto error;
333 }
334 SCLogDebug("value2 \"%s\"", value2);
335 }
336 }
337
338 dd = SCMalloc(sizeof(DetectTlsValidityData));
339 if (unlikely(dd == NULL))
340 goto error;
341
342 dd->epoch = 0;
343 dd->epoch2 = 0;
344 dd->mode = 0;
345
346 if (strlen(mode) > 0) {
347 if (mode[0] == '<')
349 else if (mode[0] == '>')
351 }
352
353 if (strlen(range) > 0) {
354 if (strcmp("<>", range) == 0)
356 }
357
358 if (strlen(range) != 0 && strlen(mode) != 0) {
359 SCLogError("Range specified but mode also set");
360 goto error;
361 }
362
363 if (dd->mode == 0) {
365 }
366
367 /* set the first value */
368 dd->epoch = DateStringToEpoch(value1);
369 if (dd->epoch == LONG_MIN)
370 goto error;
371
372 /* set the second value if specified */
373 if (strlen(value2) > 0) {
374 if (!(dd->mode & DETECT_TLS_VALIDITY_RA)) {
375 SCLogError("Multiple tls validity values specified but mode is not range");
376 goto error;
377 }
378
379 dd->epoch2 = DateStringToEpoch(value2);
380 if (dd->epoch2 == LONG_MIN)
381 goto error;
382
383 if (dd->epoch2 <= dd->epoch) {
384 SCLogError("Second value in range must not be smaller than the first");
385 goto error;
386 }
387 }
388 pcre2_match_data_free(match);
389 return dd;
390
391error:
392 if (match) {
393 pcre2_match_data_free(match);
394 }
395 if (dd)
396 SCFree(dd);
397 return NULL;
398}
399
400/**
401 * \brief Function to add the parsed tls_cert_expired into the current signature.
402 *
403 * \param de_ctx Pointer to the Detection Engine Context.
404 * \param s Pointer to the Current Signature.
405 * \param rawstr Pointer to the user provided flags options.
406 *
407 * \retval 0 on Success.
408 * \retval -1 on Failure.
409 */
410static int DetectTlsExpiredSetup (DetectEngineCtx *de_ctx, Signature *s,
411 const char *rawstr)
412{
413 DetectTlsValidityData *dd = NULL;
414
415 SCLogDebug("\'%s\'", rawstr);
416
418 return -1;
419
420 dd = SCCalloc(1, sizeof(DetectTlsValidityData));
421 if (dd == NULL) {
422 SCLogError("Allocation \'%s\' failed", rawstr);
423 goto error;
424 }
425
426 /* okay so far so good, lets get this into a SigMatch
427 * and put it in the Signature. */
428
431 dd->epoch = 0;
432 dd->epoch2 = 0;
433
435 g_tls_validity_buffer_id) == NULL) {
436 goto error;
437 }
438 return 0;
439
440error:
441 DetectTlsValidityFree(de_ctx, dd);
442 return -1;
443}
444
445/**
446 * \brief Function to add the parsed tls_cert_valid into the current signature.
447 *
448 * \param de_ctx Pointer to the Detection Engine Context.
449 * \param s Pointer to the Current Signature.
450 * \param rawstr Pointer to the user provided flags options.
451 *
452 * \retval 0 on Success.
453 * \retval -1 on Failure.
454 */
455static int DetectTlsValidSetup (DetectEngineCtx *de_ctx, Signature *s,
456 const char *rawstr)
457{
458 DetectTlsValidityData *dd = NULL;
459
460 SCLogDebug("\'%s\'", rawstr);
461
463 return -1;
464
465 dd = SCCalloc(1, sizeof(DetectTlsValidityData));
466 if (dd == NULL) {
467 SCLogError("Allocation \'%s\' failed", rawstr);
468 goto error;
469 }
470
471 /* okay so far so good, lets get this into a SigMatch
472 * and put it in the Signature. */
473
476 dd->epoch = 0;
477 dd->epoch2 = 0;
478
480 de_ctx, s, DETECT_TLS_VALID, (SigMatchCtx *)dd, g_tls_validity_buffer_id) == NULL) {
481 goto error;
482 }
483 return 0;
484
485error:
486 DetectTlsValidityFree(de_ctx, dd);
487 return -1;
488}
489
490/**
491 * \brief Function to add the parsed tls_notbefore into the current signature.
492 *
493 * \param de_ctx Pointer to the Detection Engine Context.
494 * \param s Pointer to the Current Signature.
495 * \param rawstr Pointer to the user provided flags options.
496 *
497 * \retval 0 on Success.
498 * \retval -1 on Failure.
499 */
500static int DetectTlsNotBeforeSetup (DetectEngineCtx *de_ctx, Signature *s,
501 const char *rawstr)
502{
504 int r = DetectTlsValiditySetup(de_ctx, s, rawstr, type);
505
506 SCReturnInt(r);
507}
508
509/**
510 * \brief Function to add the parsed tls_notafter into the current signature.
511 *
512 * \param de_ctx Pointer to the Detection Engine Context.
513 * \param s Pointer to the Current Signature.
514 * \param rawstr Pointer to the user provided flags options.
515 *
516 * \retval 0 on Success.
517 * \retval -1 on Failure.
518 */
519static int DetectTlsNotAfterSetup (DetectEngineCtx *de_ctx, Signature *s,
520 const char *rawstr)
521{
523 int r = DetectTlsValiditySetup(de_ctx, s, rawstr, type);
524
525 SCReturnInt(r);
526}
527
528/**
529 * \brief Function to add the parsed tls validity field into the current signature.
530 *
531 * \param de_ctx Pointer to the Detection Engine Context.
532 * \param s Pointer to the Current Signature.
533 * \param rawstr Pointer to the user provided flags options.
534 * \param type Defines if this is notBefore or notAfter.
535 *
536 * \retval 0 on Success.
537 * \retval -1 on Failure.
538 */
539static int DetectTlsValiditySetup (DetectEngineCtx *de_ctx, Signature *s,
540 const char *rawstr, uint8_t type)
541{
542 DetectTlsValidityData *dd = NULL;
543
544 SCLogDebug("\'%s\'", rawstr);
545
547 return -1;
548
549 dd = DetectTlsValidityParse(rawstr);
550 if (dd == NULL) {
551 SCLogError("Parsing \'%s\' failed", rawstr);
552 goto error;
553 }
554
555 /* okay so far so good, lets get this into a SigMatch
556 * and put it in the Signature. */
557
560 }
561 else if (type == DETECT_TLS_TYPE_NOTAFTER) {
563 }
564 else {
565 goto error;
566 }
567
569 g_tls_validity_buffer_id) == NULL) {
570 goto error;
571 }
572 return 0;
573
574error:
575 DetectTlsValidityFree(de_ctx, dd);
576 return -1;
577}
578
579/**
580 * \internal
581 * \brief Function to free memory associated with DetectTlsValidityData.
582 *
583 * \param de_ptr Pointer to DetectTlsValidityData.
584 */
585void DetectTlsValidityFree(DetectEngineCtx *de_ctx, void *de_ptr)
586{
588 if (dd)
589 SCFree(dd);
590}
591
592#ifdef UNITTESTS
594#endif
@ ALPROTO_TLS
uint8_t flags
Definition decode-gre.h:0
uint16_t type
@ DETECT_TLS_EXPIRED
@ DETECT_TLS_NOTBEFORE
@ DETECT_TLS_NOTAFTER
int DetectBufferTypeGetByName(const char *name)
void DetectSetupParseRegexes(const char *parse_str, DetectParseRegex *detect_parse)
int SCDetectSignatureSetAppProto(Signature *s, AppProto alproto)
int DetectParsePcreExec(DetectParseRegex *parse_regex, pcre2_match_data **match, const char *str, int start_offset, int options)
int SC_Pcre2SubstringCopy(pcre2_match_data *match_data, uint32_t number, PCRE2_UCHAR *buffer, PCRE2_SIZE *bufflen)
SigMatch * SCSigMatchAppendSMToList(DetectEngineCtx *de_ctx, Signature *s, uint16_t type, SigMatchCtx *ctx, const int list)
Append a SigMatch to the list type.
SigTableElmt * sigmatch_table
void DetectTlsValidityRegister(void)
Registration function for tls validity keywords.
#define PARSE_REGEX
#define DETECT_TLS_VALIDITY_EQ
#define DETECT_TLS_VALIDITY_GT
#define DETECT_TLS_VALIDITY_LT
#define DETECT_TLS_VALIDITY_VA
#define DETECT_TLS_TYPE_NOTBEFORE
#define DETECT_TLS_TYPE_NOTAFTER
#define DETECT_TLS_VALIDITY_EX
#define DETECT_TLS_VALIDITY_RA
#define SIGMATCH_NOOPT
Definition detect.h:1651
DetectEngineCtx * de_ctx
struct Thresholds ctx
main detection engine ctx
Definition detect.h:932
Flow data structure.
Definition flow.h:356
SCTime_t lastts
Definition flow.h:410
int64_t cert0_not_after
int64_t cert0_not_before
SSLv[2.0|3.[0|1|2|3]] state structure.
SSLStateConnp server_connp
SSLStateConnp client_connp
Used to start a pointer to SigMatch context Should never be dereferenced without casting to something...
Definition detect.h:351
const char * url
Definition detect.h:1462
int(* Setup)(DetectEngineCtx *, Signature *, const char *)
Definition detect.h:1441
void(* Free)(DetectEngineCtx *, void *)
Definition detect.h:1446
uint16_t flags
Definition detect.h:1450
const char * desc
Definition detect.h:1461
int(* AppLayerTxMatch)(DetectEngineThreadCtx *, Flow *, uint8_t flags, void *alstate, void *txv, const Signature *, const SigMatchCtx *)
Definition detect.h:1424
void(* RegisterTests)(void)
Definition detect.h:1448
const char * name
Definition detect.h:1459
Signature container.
Definition detect.h:668
#define str(s)
size_t strlcpy(char *dst, const char *src, size_t siz)
void TlsExpiredRegisterTests(void)
Register unit tests for tls_cert_expired.
void TlsValidRegisterTests(void)
Register unit tests for tls_cert_valid.
void TlsNotBeforeRegisterTests(void)
Register unit tests for tls_cert_notbefore.
void TlsNotAfterRegisterTests(void)
Register unit tests for tls_cert_notafter.
#define SCEnter(...)
Definition util-debug.h:277
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCReturnInt(x)
Definition util-debug.h:281
#define SCLogError(...)
Macro used to log ERROR messages.
Definition util-debug.h:267
#define SCMalloc(sz)
Definition util-mem.h:47
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
#define unlikely(expr)
int SCStringPatternToTime(char *string, const char **patterns, int num_patterns, struct tm *tp)
Parse a date string based on specified patterns.
Definition util-time.c:485
time_t SCMkTimeUtc(struct tm *tp)
Convert broken-down time to seconds since Unix epoch.
Definition util-time.c:442
#define SCTIME_SECS(t)
Definition util-time.h:57