Dillo v3.1.1-119-g140d9ebd
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 <ctype.h> /* for isspace */
30#include <stdlib.h> /* for strtol */
31
32#include "hsts.h"
33#include "msg.h"
34#include "../dlib/dlib.h"
35#include "IO/tls.h"
36
37typedef struct {
38 char *host;
39 time_t expires_at;
40 bool_t subdomains;
41} HstsData_t;
42
43/* When there is difficulty in representing future dates, use the (by far)
44 * most likely latest representable time of January 19, 2038.
45 */
47static Dlist *domains;
48
49static void Hsts_free_policy(HstsData_t *p)
50{
51 dFree(p->host);
52 dFree(p);
53}
54
56{
58 HstsData_t *policy;
59 int i, n = dList_length(domains);
60
61 for (i = 0; i < n; i++) {
62 policy = dList_nth_data(domains, i);
63 Hsts_free_policy(policy);
64 }
66 }
67}
68
72static int Domain_node_domain_str_cmp(const void *v1, const void *v2)
73{
74 const HstsData_t *node = v1;
75 const char *host = v2;
76
77 return dStrAsciiCasecmp(node->host, host);
78}
79
80static HstsData_t *Hsts_get_policy(const char *host)
81{
83}
84
85static void Hsts_remove_policy(HstsData_t *policy)
86{
87 if (policy) {
88 _MSG("HSTS: removed policy for %s\n", policy->host);
89 Hsts_free_policy(policy);
90 dList_remove(domains, policy);
91 }
92}
93
97static time_t Hsts_future_time(long seconds_from_now)
98{
99 time_t ret, now = time(NULL);
100 struct tm *tm = gmtime(&now);
101
102 if (seconds_from_now > INT_MAX - tm->tm_sec)
103 tm->tm_sec = INT_MAX;
104 else
105 tm->tm_sec += seconds_from_now;
106
107 ret = mktime(tm);
108 if (ret == (time_t) -1)
110
111 return ret;
112}
113
117static int Domain_node_cmp(const void *v1, const void *v2)
118{
119 const HstsData_t *node1 = v1, *node2 = v2;
120
121 return dStrAsciiCasecmp(node1->host, node2->host);
122}
123
124static void Hsts_set_policy(const char *host, long max_age, bool_t subdomains)
125{
126 time_t exp = Hsts_future_time(max_age);
127 HstsData_t *policy = Hsts_get_policy(host);
128
129 _MSG("HSTS: %s %s%s: until %s", (policy ? "modify" : "add"), host,
130 (subdomains ? " and subdomains" : ""), ctime(&exp));
131
132 if (policy == NULL) {
133 policy = dNew0(HstsData_t, 1);
134 policy->host = dStrdup(host);
136 }
137 policy->subdomains = subdomains;
138 policy->expires_at = exp;
139}
140
144static char *Hsts_parse_attr(const char **header_str)
145{
146 const char *str;
147 uint_t len;
148
149 while (dIsspace(**header_str))
150 (*header_str)++;
151
152 str = *header_str;
153 /* find '=' at end of attr, ';' after attr/val pair, '\0' end of string */
154 len = strcspn(str, "=;");
155 *header_str += len;
156
157 while (len && (str[len - 1] == ' ' || str[len - 1] == '\t'))
158 len--;
159 return dStrndup(str, len);
160}
161
165static char *Hsts_parse_value(const char **header_str)
166{
167 uint_t len;
168 const char *str;
169
170 if (**header_str == '=') {
171 (*header_str)++;
172 while (dIsspace(**header_str))
173 (*header_str)++;
174
175 str = *header_str;
176 /* finds ';' after attr/val pair or '\0' at end of string */
177 len = strcspn(str, ";");
178 *header_str += len;
179
180 while (len && (str[len - 1] == ' ' || str[len - 1] == '\t'))
181 len--;
182 } else {
183 str = *header_str;
184 len = 0;
185 }
186 return dStrndup(str, len);
187}
188
192static void Hsts_eat_value(const char **str)
193{
194 if (**str == '=')
195 *str += strcspn(*str, ";");
196}
197
201void a_Hsts_set(const char *header, const DilloUrl *url)
202{
203 long max_age = 0;
204 const char *host = URL_HOST(url);
205 bool_t max_age_valid = FALSE, subdomains = FALSE;
206
207 _MSG("HSTS header for %s: %s\n", host, header);
208
209 if (!a_Tls_certificate_is_clean(url)) {
210 /* RFC 6797 gives rationale in section 14.3. */
211 _MSG("But there were certificate warnings, so ignore it (!)\n");
212 return;
213 }
214
215 /* Iterate until there is nothing left of the string */
216 while (*header) {
217 char *attr;
218 char *value;
219
220 /* Get attribute */
221 attr = Hsts_parse_attr(&header);
222
223 /* Get the value for the attribute and store it */
224 if (dStrAsciiCasecmp(attr, "max-age") == 0) {
225 value = Hsts_parse_value(&header);
226 if (isdigit(*value)) {
227 errno = 0;
228 max_age = strtol(value, NULL, 10);
229 if (errno == ERANGE)
230 max_age = INT_MAX;
231 max_age_valid = TRUE;
232 }
233 dFree(value);
234 } else if (dStrAsciiCasecmp(attr, "includeSubDomains") == 0) {
235 subdomains = TRUE;
236 Hsts_eat_value(&header);
237 } else if (dStrAsciiCasecmp(attr, "preload") == 0) {
238 /* 'preload' is not part of the RFC, but what does google care for
239 * standards? They require that 'preload' be specified by a domain
240 * in order to be added to their preload list.
241 */
242 } else {
243 MSG("HSTS: header contains unknown attribute: '%s'\n", attr);
244 Hsts_eat_value(&header);
245 }
246
247 dFree(attr);
248
249 if (*header == ';')
250 header++;
251 }
252 if (max_age_valid) {
253 if (max_age > 0)
254 Hsts_set_policy(host, max_age, subdomains);
255 else
257 }
258}
259
260static bool_t Hsts_expired(HstsData_t *policy)
261{
262 time_t now = time(NULL);
263 bool_t ret = (now > policy->expires_at) ? TRUE : FALSE;
264
265 if (ret) {
266 _MSG("HSTS: expired\n");
267 }
268 return ret;
269}
270
272{
273 bool_t ret = FALSE;
274
275 if (host) {
276 HstsData_t *policy = Hsts_get_policy(host);
277
278 if (policy) {
279 _MSG("HSTS: matched host %s\n", host);
280 if (Hsts_expired(policy))
281 Hsts_remove_policy(policy);
282 else
283 ret = TRUE;
284 }
285 if (!ret) {
286 const char *domain_str;
287
288 for (domain_str = strchr(host+1, '.');
289 domain_str != NULL && *domain_str;
290 domain_str = strchr(domain_str+1, '.')) {
291 policy = Hsts_get_policy(domain_str+1);
292
293 if (policy && policy->subdomains) {
294 _MSG("HSTS: matched %s under %s subdomain rule\n", host,
295 policy->host);
296 if (Hsts_expired(policy)) {
297 Hsts_remove_policy(policy);
298 } else {
299 ret = TRUE;
300 break;
301 }
302 }
303 }
304 }
305 }
306 return ret;
307}
308
309static void Hsts_preload(FILE *stream)
310{
311 const int LINE_MAXLEN = 4096;
312 const long ONE_YEAR = 60 * 60 * 24 * 365;
313
314 char *rc, *subdomains;
315 char line[LINE_MAXLEN];
316 char domain[LINE_MAXLEN];
317
318 /* Get all lines in the file */
319 while (!feof(stream)) {
320 line[0] = '\0';
321 rc = fgets(line, LINE_MAXLEN, stream);
322 if (!rc && ferror(stream)) {
323 MSG_WARN("HSTS: Error while reading preload entries: %s\n",
324 dStrerror(errno));
325 return; /* bail out */
326 }
327
328 /* Remove leading and trailing whitespace */
329 dStrstrip(line);
330
331 if (line[0] != '\0' && line[0] != '#') {
332 int i = 0, j = 0;
333
334 /* Get the domain */
335 while (line[i] != '\0' && !dIsspace(line[i]))
336 domain[j++] = line[i++];
337 domain[j] = '\0';
338
339 /* Skip past whitespace */
340 while (dIsspace(line[i]))
341 i++;
342
343 subdomains = line + i;
344
345 if (dStrAsciiCasecmp(subdomains, "true") == 0)
346 Hsts_set_policy(domain, ONE_YEAR, TRUE);
347 else if (dStrAsciiCasecmp(subdomains, "false") == 0)
348 Hsts_set_policy(domain, ONE_YEAR, FALSE);
349 else {
350 MSG_WARN("HSTS: format of line not recognized. Ignoring '%s'.\n",
351 line);
352 }
353 }
354 }
355}
356
357void a_Hsts_init(FILE *preload_file)
358{
360 struct tm future_tm = {7, 14, 3, 19, 0, 138, 0, 0, 0, 0, 0};
361
362 hsts_latest_representable_time = mktime(&future_tm);
363 domains = dList_new(32);
364
365 if (preload_file) {
366 Hsts_preload(preload_file);
367 fclose(preload_file);
368 }
369 }
370}
371
#define _MSG(...)
Definition bookmarks.c:45
#define MSG(...)
Definition bookmarks.c:46
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:769
void dFree(void *mem)
Definition dlib.c:68
int dStrAsciiCasecmp(const char *s1, const char *s2)
Definition dlib.c:203
char * dStrstrip(char *s)
Remove leading and trailing whitespace.
Definition dlib.c:122
char * dStrdup(const char *s)
Definition dlib.c:77
Dlist * dList_new(int size)
Create a new empty list.
Definition dlib.c:548
int dList_length(Dlist *lp)
For completing the ADT.
Definition dlib.c:613
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:662
char * dStrndup(const char *s, size_t sz)
Definition dlib.c:88
void * dList_find_sorted(Dlist *lp, const void *data, dCompareFunc func)
Search a sorted list.
Definition dlib.c:796
void dList_free(Dlist *lp)
Free a list (not its elements)
Definition dlib.c:564
void dList_remove(Dlist *lp, const void *data)
Definition dlib.c:641
#define dStrerror
Definition dlib.h:95
#define dNew0(type, count)
Definition dlib.h:51
#define dIsspace(c)
Definition dlib.h:33
#define TRUE
Definition dlib.h:23
#define FALSE
Definition dlib.h:19
#define LINE_MAXLEN
Definition cookies.c:79
static void Hsts_preload(FILE *stream)
Definition hsts.c:309
bool_t a_Hsts_require_https(const char *host)
Definition hsts.c:271
static time_t Hsts_future_time(long seconds_from_now)
Return the time_t for a future time.
Definition hsts.c:97
static int Domain_node_cmp(const void *v1, const void *v2)
Compare function for searching domains.
Definition hsts.c:117
static void Hsts_eat_value(const char **str)
Advance past any value.
Definition hsts.c:192
static time_t hsts_latest_representable_time
Definition hsts.c:46
void a_Hsts_freeall(void)
Definition hsts.c:55
void a_Hsts_set(const char *header, const DilloUrl *url)
The reponse for this url had an HSTS header, so let's take action.
Definition hsts.c:201
static void Hsts_set_policy(const char *host, long max_age, bool_t subdomains)
Definition hsts.c:124
static char * Hsts_parse_value(const char **header_str)
Get the value in *header_str.
Definition hsts.c:165
static void Hsts_free_policy(HstsData_t *p)
Definition hsts.c:49
static HstsData_t * Hsts_get_policy(const char *host)
Definition hsts.c:80
static Dlist * domains
Definition hsts.c:47
static void Hsts_remove_policy(HstsData_t *policy)
Definition hsts.c:85
static char * Hsts_parse_attr(const char **header_str)
Read the next attribute.
Definition hsts.c:144
static bool_t Hsts_expired(HstsData_t *policy)
Definition hsts.c:260
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:72
void a_Hsts_init(FILE *preload_file)
Definition hsts.c:357
#define MSG_WARN(...)
Definition msg.h:26
DilloPrefs prefs
Global Data.
Definition prefs.c:33
bool_t http_strict_transport_security
Definition prefs.h:105
Definition url.h:88
Definition dlib.h:131
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