Dillo v3.2.0-151-g90488cbf
Loading...
Searching...
No Matches
hsts.c
Go to the documentation of this file.
1/*
2 * File: hsts.c
3 * HTTP Strict Transport Security
4 *
5 * Copyright 2015 corvid
6 * Copyright (C) 2023 Rodrigo Arias Mallo <rodarima@gmail.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 */
14
19/* To preload hosts, as of 2015, chromium is the list keeper:
20 * https://src.chromium.org/viewvc/chrome/trunk/src/net/http/transport_security_state_static.json
21 * although mozilla's is easier to work from (and they trim it based on
22 * criteria such as max-age must be at least some number of months)
23 * https://mxr.mozilla.org/mozilla-central/source/security/manager/ssl/nsSTSPreloadList.inc?raw=1
24 */
25
26#include <time.h>
27#include <errno.h>
28#include <limits.h> /* for INT_MAX */
29#include <stdlib.h> /* for strtol */
30
31#include "hsts.h"
32#include "msg.h"
33#include "dlib/dlib.h" /* dIsspace */
34#include "IO/tls.h"
35
36typedef struct {
37 char *host;
38 time_t expires_at;
39 bool_t subdomains;
40} HstsData_t;
41
42/* When there is difficulty in representing future dates, use the (by far)
43 * most likely latest representable time of January 19, 2038.
44 */
46static Dlist *domains;
47
48static void Hsts_free_policy(HstsData_t *p)
49{
50 dFree(p->host);
51 dFree(p);
52}
53
55{
57 HstsData_t *policy;
58 int i, n = dList_length(domains);
59
60 for (i = 0; i < n; i++) {
61 policy = dList_nth_data(domains, i);
62 Hsts_free_policy(policy);
63 }
65 }
66}
67
71static int Domain_node_domain_str_cmp(const void *v1, const void *v2)
72{
73 const HstsData_t *node = v1;
74 const char *host = v2;
75
76 return dStrAsciiCasecmp(node->host, host);
77}
78
79static HstsData_t *Hsts_get_policy(const char *host)
80{
82}
83
84static void Hsts_remove_policy(HstsData_t *policy)
85{
86 if (policy) {
87 _MSG("HSTS: removed policy for %s\n", policy->host);
88 Hsts_free_policy(policy);
89 dList_remove(domains, policy);
90 }
91}
92
96static time_t Hsts_future_time(long seconds_from_now)
97{
98 time_t ret, now = time(NULL);
99 struct tm *tm = gmtime(&now);
100
101 if (seconds_from_now > INT_MAX - tm->tm_sec)
102 tm->tm_sec = INT_MAX;
103 else
104 tm->tm_sec += seconds_from_now;
105
106 ret = mktime(tm);
107 if (ret == (time_t) -1)
109
110 return ret;
111}
112
116static int Domain_node_cmp(const void *v1, const void *v2)
117{
118 const HstsData_t *node1 = v1, *node2 = v2;
119
120 return dStrAsciiCasecmp(node1->host, node2->host);
121}
122
123static void Hsts_set_policy(const char *host, long max_age, bool_t subdomains)
124{
125 time_t exp = Hsts_future_time(max_age);
126 HstsData_t *policy = Hsts_get_policy(host);
127
128 _MSG("HSTS: %s %s%s: until %s", (policy ? "modify" : "add"), host,
129 (subdomains ? " and subdomains" : ""), ctime(&exp));
130
131 if (policy == NULL) {
132 policy = dNew0(HstsData_t, 1);
133 policy->host = dStrdup(host);
135 }
136 policy->subdomains = subdomains;
137 policy->expires_at = exp;
138}
139
143static char *Hsts_parse_attr(const char **header_str)
144{
145 const char *str;
146 uint_t len;
147
148 while (dIsspace(**header_str))
149 (*header_str)++;
150
151 str = *header_str;
152 /* find '=' at end of attr, ';' after attr/val pair, '\0' end of string */
153 len = strcspn(str, "=;");
154 *header_str += len;
155
156 while (len && (str[len - 1] == ' ' || str[len - 1] == '\t'))
157 len--;
158 return dStrndup(str, len);
159}
160
164static char *Hsts_parse_value(const char **header_str)
165{
166 uint_t len;
167 const char *str;
168
169 if (**header_str == '=') {
170 (*header_str)++;
171 while (dIsspace(**header_str))
172 (*header_str)++;
173
174 str = *header_str;
175 /* finds ';' after attr/val pair or '\0' at end of string */
176 len = strcspn(str, ";");
177 *header_str += len;
178
179 while (len && (str[len - 1] == ' ' || str[len - 1] == '\t'))
180 len--;
181 } else {
182 str = *header_str;
183 len = 0;
184 }
185 return dStrndup(str, len);
186}
187
191static void Hsts_eat_value(const char **str)
192{
193 if (**str == '=')
194 *str += strcspn(*str, ";");
195}
196
200void a_Hsts_set(const char *header, const DilloUrl *url)
201{
202 long max_age = 0;
203 const char *host = URL_HOST(url);
204 bool_t max_age_valid = FALSE, subdomains = FALSE;
205
206 _MSG("HSTS header for %s: %s\n", host, header);
207
208 if (!a_Tls_certificate_is_clean(url)) {
209 /* RFC 6797 gives rationale in section 14.3. */
210 _MSG("But there were certificate warnings, so ignore it (!)\n");
211 return;
212 }
213
214 /* Iterate until there is nothing left of the string */
215 while (*header) {
216 char *attr;
217 char *value;
218
219 /* Get attribute */
220 attr = Hsts_parse_attr(&header);
221
222 /* Get the value for the attribute and store it */
223 if (dStrAsciiCasecmp(attr, "max-age") == 0) {
224 value = Hsts_parse_value(&header);
225 if (dIsdigit(*value)) {
226 errno = 0;
227 max_age = strtol(value, NULL, 10);
228 if (errno == ERANGE)
229 max_age = INT_MAX;
230 max_age_valid = TRUE;
231 }
232 dFree(value);
233 } else if (dStrAsciiCasecmp(attr, "includeSubDomains") == 0) {
234 subdomains = TRUE;
235 Hsts_eat_value(&header);
236 } else if (dStrAsciiCasecmp(attr, "preload") == 0) {
237 /* 'preload' is not part of the RFC, but what does google care for
238 * standards? They require that 'preload' be specified by a domain
239 * in order to be added to their preload list.
240 */
241 } else {
242 MSG("HSTS: header contains unknown attribute: '%s'\n", attr);
243 Hsts_eat_value(&header);
244 }
245
246 dFree(attr);
247
248 if (*header == ';')
249 header++;
250 }
251 if (max_age_valid) {
252 if (max_age > 0)
253 Hsts_set_policy(host, max_age, subdomains);
254 else
256 }
257}
258
259static bool_t Hsts_expired(HstsData_t *policy)
260{
261 time_t now = time(NULL);
262 bool_t ret = (now > policy->expires_at) ? TRUE : FALSE;
263
264 if (ret) {
265 _MSG("HSTS: expired\n");
266 }
267 return ret;
268}
269
271{
272 bool_t ret = FALSE;
273
274 if (host) {
275 HstsData_t *policy = Hsts_get_policy(host);
276
277 if (policy) {
278 _MSG("HSTS: matched host %s\n", host);
279 if (Hsts_expired(policy))
280 Hsts_remove_policy(policy);
281 else
282 ret = TRUE;
283 }
284 if (!ret) {
285 const char *domain_str;
286
287 for (domain_str = strchr(host+1, '.');
288 domain_str != NULL && *domain_str;
289 domain_str = strchr(domain_str+1, '.')) {
290 policy = Hsts_get_policy(domain_str+1);
291
292 if (policy && policy->subdomains) {
293 _MSG("HSTS: matched %s under %s subdomain rule\n", host,
294 policy->host);
295 if (Hsts_expired(policy)) {
296 Hsts_remove_policy(policy);
297 } else {
298 ret = TRUE;
299 break;
300 }
301 }
302 }
303 }
304 }
305 return ret;
306}
307
308static void Hsts_preload(FILE *stream)
309{
310 const int LINE_MAXLEN = 4096;
311 const long ONE_YEAR = 60 * 60 * 24 * 365;
312
313 char *rc, *subdomains;
314 char line[LINE_MAXLEN];
315 char domain[LINE_MAXLEN];
316
317 /* Get all lines in the file */
318 while (!feof(stream)) {
319 line[0] = '\0';
320 rc = fgets(line, LINE_MAXLEN, stream);
321 if (!rc && ferror(stream)) {
322 MSG_WARN("HSTS: Error while reading preload entries: %s\n",
323 dStrerror(errno));
324 return; /* bail out */
325 }
326
327 /* Remove leading and trailing whitespace */
328 dStrstrip(line);
329
330 if (line[0] != '\0' && line[0] != '#') {
331 int i = 0, j = 0;
332
333 /* Get the domain */
334 while (line[i] != '\0' && !dIsspace(line[i]))
335 domain[j++] = line[i++];
336 domain[j] = '\0';
337
338 /* Skip past whitespace */
339 while (dIsspace(line[i]))
340 i++;
341
342 subdomains = line + i;
343
344 if (dStrAsciiCasecmp(subdomains, "true") == 0)
345 Hsts_set_policy(domain, ONE_YEAR, TRUE);
346 else if (dStrAsciiCasecmp(subdomains, "false") == 0)
347 Hsts_set_policy(domain, ONE_YEAR, FALSE);
348 else {
349 MSG_WARN("HSTS: format of line not recognized. Ignoring '%s'.\n",
350 line);
351 }
352 }
353 }
354}
355
356void a_Hsts_init(FILE *preload_file)
357{
359 struct tm future_tm = {7, 14, 3, 19, 0, 138, 0, 0, 0, 0, 0};
360
361 hsts_latest_representable_time = mktime(&future_tm);
362 domains = dList_new(32);
363
364 if (preload_file) {
365 Hsts_preload(preload_file);
366 fclose(preload_file);
367 }
368 }
369}
370
#define _MSG(...)
Definition bookmarks.c:44
#define MSG(...)
Definition bookmarks.c:45
unsigned int uint_t
Definition d_size.h:20
unsigned char bool_t
Definition d_size.h:21
void dList_insert_sorted(Dlist *lp, void *data, dCompareFunc func)
Insert an element into a sorted list.
Definition dlib.c:796
void dFree(void *mem)
Definition dlib.c:67
int dStrAsciiCasecmp(const char *s1, const char *s2)
Definition dlib.c:202
char * dStrstrip(char *s)
Remove leading and trailing whitespace.
Definition dlib.c:121
char * dStrdup(const char *s)
Definition dlib.c:76
Dlist * dList_new(int size)
Create a new empty list.
Definition dlib.c:575
int dList_length(Dlist *lp)
For completing the ADT.
Definition dlib.c:640
void * dList_nth_data(Dlist *lp, int n0)
Return the nth data item, NULL when not found or 'n0' is out of range.
Definition dlib.c:689
char * dStrndup(const char *s, size_t sz)
Definition dlib.c:87
void * dList_find_sorted(Dlist *lp, const void *data, dCompareFunc func)
Search a sorted list.
Definition dlib.c:823
void dList_free(Dlist *lp)
Free a list (not its elements)
Definition dlib.c:591
void dList_remove(Dlist *lp, const void *data)
Definition dlib.c:668
#define dStrerror
Definition dlib.h:124
static int dIsdigit(unsigned char c)
Definition dlib.h:50
#define dNew0(type, count)
Definition dlib.h:80
static int dIsspace(unsigned char c)
Definition dlib.h:53
#define TRUE
Definition dlib.h:36
#define FALSE
Definition dlib.h:32
#define LINE_MAXLEN
Definition cookies.c:78
static void Hsts_preload(FILE *stream)
Definition hsts.c:308
bool_t a_Hsts_require_https(const char *host)
Definition hsts.c:270
static time_t Hsts_future_time(long seconds_from_now)
Return the time_t for a future time.
Definition hsts.c:96
static int Domain_node_cmp(const void *v1, const void *v2)
Compare function for searching domains.
Definition hsts.c:116
static void Hsts_eat_value(const char **str)
Advance past any value.
Definition hsts.c:191
static time_t hsts_latest_representable_time
Definition hsts.c:45
void a_Hsts_freeall(void)
Definition hsts.c:54
void a_Hsts_set(const char *header, const DilloUrl *url)
The response for this url had an HSTS header, so let's take action.
Definition hsts.c:200
static void Hsts_set_policy(const char *host, long max_age, bool_t subdomains)
Definition hsts.c:123
static char * Hsts_parse_value(const char **header_str)
Get the value in *header_str.
Definition hsts.c:164
static void Hsts_free_policy(HstsData_t *p)
Definition hsts.c:48
static HstsData_t * Hsts_get_policy(const char *host)
Definition hsts.c:79
static Dlist * domains
Definition hsts.c:46
static void Hsts_remove_policy(HstsData_t *policy)
Definition hsts.c:84
static char * Hsts_parse_attr(const char **header_str)
Read the next attribute.
Definition hsts.c:143
static bool_t Hsts_expired(HstsData_t *policy)
Definition hsts.c:259
static int Domain_node_domain_str_cmp(const void *v1, const void *v2)
Compare function for searching a domain node by domain string.
Definition hsts.c:71
void a_Hsts_init(FILE *preload_file)
Definition hsts.c:356
#define MSG_WARN(...)
Definition msg.h:26
DilloPrefs prefs
Global Data.
Definition prefs.c:33
bool_t http_strict_transport_security
Definition prefs.h:106
Definition url.h:88
Definition dlib.h:161
int a_Tls_certificate_is_clean(const DilloUrl *url)
Did everything seem proper with the certificate – no warnings to click through?.
Definition tls.c:101
#define URL_HOST(u)
Definition url.h:75