Coverage Report

Created: 2025-01-28 06:58

/src/wget2/libwget/hpkp.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2015-2024 Free Software Foundation, Inc.
3
 *
4
 * This file is part of libwget.
5
 *
6
 * Libwget is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * Libwget is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with libwget.  If not, see <https://www.gnu.org/licenses/>.
18
 *
19
 *
20
 * HTTP Public Key Pinning (HPKP)
21
 */
22
23
#include <config.h>
24
25
#include <wget.h>
26
#include <string.h>
27
#include <stddef.h>
28
#include <ctype.h>
29
#include <sys/stat.h>
30
#include <limits.h>
31
#include "private.h"
32
#include "hpkp.h"
33
34
/**
35
 * \file
36
 * \brief HTTP Public Key Pinning (RFC 7469) routines
37
 * \defgroup libwget-hpkp HTTP Public Key Pinning (RFC 7469) routines
38
 * @{
39
 *
40
 * This is an implementation of RFC 7469.
41
 */
42
43
/*
44
 * Compare function for SPKI hashes. Returns 0 if they're equal.
45
 */
46
WGET_GCC_NONNULL_ALL
47
static int compare_pin(wget_hpkp_pin *p1, wget_hpkp_pin *p2)
48
4.41k
{
49
4.41k
  int n;
50
51
4.41k
  if ((n = strcmp(p1->hash_type, p2->hash_type)))
52
1.45k
    return n;
53
54
2.96k
  if (p1->pinsize < p2->pinsize)
55
42
    return -1;
56
57
2.92k
  if (p1->pinsize > p2->pinsize)
58
96
    return 1;
59
60
2.82k
  return memcmp(p1->pin, p2->pin, p1->pinsize);
61
2.92k
}
62
63
static void hpkp_pin_free(void *pin)
64
6.86k
{
65
6.86k
  wget_hpkp_pin *p = pin;
66
67
6.86k
  if (p) {
68
6.86k
    xfree(p->hash_type);
69
6.86k
    xfree(p->pin);
70
6.86k
    xfree(p->pin_b64);
71
6.86k
    xfree(p);
72
6.86k
  }
73
6.86k
}
74
75
/**
76
 * \param[in] hpkp An HPKP database entry
77
 * \param[in] pin_type The type of hash supplied, e.g. "sha256"
78
 * \param[in] pin_b64 The public key hash in base64 format
79
 *
80
 * Adds a public key hash to HPKP database entry.
81
 */
82
void wget_hpkp_pin_add(wget_hpkp *hpkp, const char *pin_type, const char *pin_b64)
83
6.86k
{
84
6.86k
  wget_hpkp_pin *pin = wget_calloc(1, sizeof(wget_hpkp_pin));
85
6.86k
  if (!pin)
86
0
    return;
87
88
6.86k
  size_t len_b64 = strlen(pin_b64);
89
90
6.86k
  pin->hash_type = wget_strdup(pin_type);
91
6.86k
  pin->pin_b64 = wget_strdup(pin_b64);
92
6.86k
  pin->pin = (unsigned char *) wget_base64_decode_alloc(pin_b64, len_b64, &pin->pinsize);
93
94
6.86k
  if (!hpkp->pins) {
95
1.86k
    hpkp->pins = wget_vector_create(5, (wget_vector_compare_fn *) compare_pin);
96
1.86k
    wget_vector_set_destructor(hpkp->pins, hpkp_pin_free);
97
1.86k
  }
98
99
6.86k
  wget_vector_add(hpkp->pins, pin);
100
6.86k
}
101
102
/**
103
 * \param[in] hpkp An HPKP database entry
104
 *
105
 * Free hpkp_t instance created by wget_hpkp_new()
106
 * It can be used as destructor function in vectors and hashmaps.
107
 * If `hpkp` is NULL this function does nothing.
108
 */
109
void wget_hpkp_free(wget_hpkp *hpkp)
110
8.61k
{
111
8.61k
  if (hpkp) {
112
3.05k
    xfree(hpkp->host);
113
3.05k
    wget_vector_free(&hpkp->pins);
114
3.05k
    xfree(hpkp);
115
3.05k
  }
116
8.61k
}
117
118
/*
119
 * TODO HPKP: wget_hpkp_new() should get an IRI rather than a string, and check by itself
120
 * whether it is HTTPS, not an IP literal, etc.
121
 *
122
 * This is also applicable to HSTS.
123
 */
124
/**
125
 * \return A newly allocated and initialized HPKP structure
126
 *
127
 * Creates a new HPKP structure initialized with the given values.
128
 */
129
wget_hpkp *wget_hpkp_new(void)
130
3.05k
{
131
3.05k
  wget_hpkp *hpkp = wget_calloc(1, sizeof(wget_hpkp));
132
133
3.05k
  if (hpkp)
134
3.05k
    hpkp->created = time(NULL);
135
136
3.05k
  return hpkp;
137
3.05k
}
138
139
/**
140
 * \param[in] hpkp An HPKP database entry
141
 * \param[in] host Hostname of the web server
142
 *
143
 * Sets the hostname of the web server into given HPKP database entry.
144
 */
145
void wget_hpkp_set_host(wget_hpkp *hpkp, const char *host)
146
0
{
147
0
  xfree(hpkp->host);
148
0
  hpkp->host = wget_strdup(host);
149
0
}
150
151
/**
152
 * \param[in] hpkp An HPKP database entry
153
 * \param[in] maxage Maximum time the entry is valid (in seconds)
154
 *
155
 * Sets the maximum time the HPKP entry is valid.
156
 * Corresponds to `max-age` directive in `Public-Key-Pins` HTTP response header.
157
 */
158
void wget_hpkp_set_maxage(wget_hpkp *hpkp, int64_t maxage)
159
783
{
160
783
  int64_t now;
161
162
  // avoid integer overflow here
163
783
  if (maxage <= 0 || maxage >= INT64_MAX / 2 || (now = time(NULL)) < 0 || now >= INT64_MAX / 2) {
164
474
    hpkp->maxage = 0;
165
474
    hpkp->expires = 0;
166
474
  } else {
167
309
    hpkp->maxage = maxage;
168
309
    hpkp->expires = now + maxage;
169
309
  }
170
783
}
171
172
/**
173
 * \param[in] hpkp An HPKP database entry
174
 * \param[in] include_subdomains Nonzero if this entry is also valid for all subdomains, zero otherwise.
175
 *
176
 * Sets whether the entry is also valid for all subdomains.
177
 * Corresponds to the optional `includeSubDomains` directive in `Public-Key-Pins` HTTP response header.
178
 */
179
void wget_hpkp_set_include_subdomains(wget_hpkp *hpkp, bool include_subdomains)
180
1.35k
{
181
1.35k
  hpkp->include_subdomains = include_subdomains;
182
1.35k
}
183
184
/**
185
 * \param[in] hpkp An HPKP database entry
186
 * \return The number of public key hashes added.
187
 *
188
 * Gets the number of public key hashes added to the given HPKP database entry.
189
 */
190
int wget_hpkp_get_n_pins(wget_hpkp *hpkp)
191
0
{
192
0
  return wget_vector_size(hpkp->pins);
193
0
}
194
195
/**
196
 * \param[in] hpkp An HPKP database entry
197
 * \param[out] pin_types An array of pointers where hash types will be stored.
198
 * \param[out] pins_b64 An array of pointers where the public keys in base64 format will be stored
199
 *
200
 * Gets all the public key hashes added to the given HPKP database entry.
201
 *
202
 * The size of the arrays used must be at least one returned by \ref wget_hpkp_get_n_pins "wget_hpkp_get_n_pins()".
203
 */
204
void wget_hpkp_get_pins_b64(wget_hpkp *hpkp, const char **pin_types, const char **pins_b64)
205
0
{
206
0
  int i, n_pins;
207
208
0
  n_pins = wget_vector_size(hpkp->pins);
209
210
0
  for (i = 0; i < n_pins; i++) {
211
0
    wget_hpkp_pin *pin = (wget_hpkp_pin *) wget_vector_get(hpkp->pins, i);
212
0
    if (!pin)
213
0
      continue;
214
0
    pin_types[i] = pin->hash_type;
215
0
    pins_b64[i] = pin->pin_b64;
216
0
  }
217
0
}
218
219
/**
220
 * \param[in] hpkp An HPKP database entry
221
 * \param[out] pin_types An array of pointers where hash types will be stored.
222
 * \param[out] sizes An array of sizes where pin sizes will be stored.
223
 * \param[out] pins An array of pointers where the public keys in binary format will be stored
224
 *
225
 * Gets all the public key hashes added to the given HPKP database entry.
226
 *
227
 * The size of the arrays used must be at least one returned by \ref wget_hpkp_get_n_pins "wget_hpkp_get_n_pins()".
228
 */
229
void wget_hpkp_get_pins(wget_hpkp *hpkp, const char **pin_types, size_t *sizes, const void **pins)
230
0
{
231
0
  int i, n_pins;
232
233
0
  n_pins = wget_vector_size(hpkp->pins);
234
235
0
  for (i = 0; i < n_pins; i++) {
236
0
    wget_hpkp_pin *pin = (wget_hpkp_pin *) wget_vector_get(hpkp->pins, i);
237
0
    if (!pin)
238
0
      continue;
239
0
    pin_types[i] = pin->hash_type;
240
0
    sizes[i] = pin->pinsize;
241
0
    pins[i] = pin->pin;
242
0
  }
243
0
}
244
245
/**
246
 * \param[in] hpkp An HPKP database entry
247
 * \return The hostname this entry is valid for
248
 *
249
 * Gets the hostname this entry is valid for, as set by \ref wget_hpkp_set_host "wget_hpkp_set_host()"
250
 */
251
const char * wget_hpkp_get_host(wget_hpkp *hpkp)
252
0
{
253
0
  return hpkp->host;
254
0
}
255
256
/**
257
 * \param[in] hpkp An HPKP database entry
258
 * \return The maximum time (in seconds) the entry is valid
259
 *
260
 * Gets the maximum time this entry is valid for, as set by \ref wget_hpkp_set_maxage "wget_hpkp_set_maxage()"
261
 */
262
int64_t wget_hpkp_get_maxage(wget_hpkp *hpkp)
263
0
{
264
0
  return hpkp->maxage;
265
0
}
266
267
/**
268
 * \param[in] hpkp An HPKP database entry
269
 * \return `true` if the HPKP entry is also valid for all subdomains, `false` otherwise
270
 *
271
 * Gets whether the HPKP database entry is also valid for the subdomains.
272
 */
273
bool wget_hpkp_get_include_subdomains(wget_hpkp *hpkp)
274
0
{
275
0
  return hpkp->include_subdomains;
276
0
}
277
278
/**@}*/