1 /*
2  * Copyright (c) 2015-2021 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
compare_pin(wget_hpkp_pin * p1,wget_hpkp_pin * p2)47 static int compare_pin(wget_hpkp_pin *p1, wget_hpkp_pin *p2)
48 {
49 	int n;
50 
51 	if ((n = strcmp(p1->hash_type, p2->hash_type)))
52 		return n;
53 
54 	if (p1->pinsize < p2->pinsize)
55 		return -1;
56 
57 	if (p1->pinsize > p2->pinsize)
58 		return 1;
59 
60 	return memcmp(p1->pin, p2->pin, p1->pinsize);
61 }
62 
hpkp_pin_free(void * pin)63 static void hpkp_pin_free(void *pin)
64 {
65 	wget_hpkp_pin *p = pin;
66 
67 	if (p) {
68 		xfree(p->hash_type);
69 		xfree(p->pin);
70 		xfree(p->pin_b64);
71 		xfree(p);
72 	}
73 }
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  */
wget_hpkp_pin_add(wget_hpkp * hpkp,const char * pin_type,const char * pin_b64)82 void wget_hpkp_pin_add(wget_hpkp *hpkp, const char *pin_type, const char *pin_b64)
83 {
84 	wget_hpkp_pin *pin = wget_calloc(1, sizeof(wget_hpkp_pin));
85 	size_t len_b64 = strlen(pin_b64);
86 
87 	pin->hash_type = wget_strdup(pin_type);
88 	pin->pin_b64 = wget_strdup(pin_b64);
89 	pin->pin = (unsigned char *)wget_base64_decode_alloc(pin_b64, len_b64, &pin->pinsize);
90 
91 	if (!hpkp->pins) {
92 		hpkp->pins = wget_vector_create(5, (wget_vector_compare_fn *) compare_pin);
93 		wget_vector_set_destructor(hpkp->pins, hpkp_pin_free);
94 	}
95 
96 	wget_vector_add(hpkp->pins, pin);
97 }
98 
99 /**
100  * \param[in] hpkp An HPKP database entry
101  *
102  * Free hpkp_t instance created by wget_hpkp_new()
103  * It can be used as destructor function in vectors and hashmaps.
104  * If `hpkp` is NULL this function does nothing.
105  */
wget_hpkp_free(wget_hpkp * hpkp)106 void wget_hpkp_free(wget_hpkp *hpkp)
107 {
108 	if (hpkp) {
109 		xfree(hpkp->host);
110 		wget_vector_free(&hpkp->pins);
111 		xfree(hpkp);
112 	}
113 }
114 
115 /*
116  * TODO HPKP: wget_hpkp_new() should get an IRI rather than a string, and check by itself
117  * whether it is HTTPS, not an IP literal, etc.
118  *
119  * This is also applicable to HSTS.
120  */
121 /**
122  * \return A newly allocated and initialized HPKP structure
123  *
124  * Creates a new HPKP structure initialized with the given values.
125  */
wget_hpkp_new(void)126 wget_hpkp *wget_hpkp_new(void)
127 {
128 	wget_hpkp *hpkp = wget_calloc(1, sizeof(wget_hpkp));
129 
130 	hpkp->created = time(NULL);
131 
132 	return hpkp;
133 }
134 
135 /**
136  * \param[in] hpkp An HPKP database entry
137  * \param[in] host Hostname of the web server
138  *
139  * Sets the hostname of the web server into given HPKP database entry.
140  */
wget_hpkp_set_host(wget_hpkp * hpkp,const char * host)141 void wget_hpkp_set_host(wget_hpkp *hpkp, const char *host)
142 {
143 	xfree(hpkp->host);
144 	hpkp->host = wget_strdup(host);
145 }
146 
147 /**
148  * \param[in] hpkp An HPKP database entry
149  * \param[in] maxage Maximum time the entry is valid (in seconds)
150  *
151  * Sets the maximum time the HPKP entry is valid.
152  * Corresponds to `max-age` directive in `Public-Key-Pins` HTTP response header.
153  */
wget_hpkp_set_maxage(wget_hpkp * hpkp,int64_t maxage)154 void wget_hpkp_set_maxage(wget_hpkp *hpkp, int64_t maxage)
155 {
156 	int64_t now;
157 
158 	// avoid integer overflow here
159 	if (maxage <= 0 || maxage >= INT64_MAX / 2 || (now = time(NULL)) < 0 || now >= INT64_MAX / 2) {
160 		hpkp->maxage = 0;
161 		hpkp->expires = 0;
162 	} else {
163 		hpkp->maxage = maxage;
164 		hpkp->expires = now + maxage;
165 	}
166 }
167 
168 /**
169  * \param[in] hpkp An HPKP database entry
170  * \param[in] include_subdomains Nonzero if this entry is also valid for all subdomains, zero otherwise.
171  *
172  * Sets whether the entry is also valid for all subdomains.
173  * Corresponds to the optional `includeSubDomains` directive in `Public-Key-Pins` HTTP response header.
174  */
wget_hpkp_set_include_subdomains(wget_hpkp * hpkp,bool include_subdomains)175 void wget_hpkp_set_include_subdomains(wget_hpkp *hpkp, bool include_subdomains)
176 {
177 	hpkp->include_subdomains = include_subdomains;
178 }
179 
180 /**
181  * \param[in] hpkp An HPKP database entry
182  * \return The number of public key hashes added.
183  *
184  * Gets the number of public key hashes added to the given HPKP database entry.
185  */
wget_hpkp_get_n_pins(wget_hpkp * hpkp)186 int wget_hpkp_get_n_pins(wget_hpkp *hpkp)
187 {
188 	return wget_vector_size(hpkp->pins);
189 }
190 
191 /**
192  * \param[in] hpkp An HPKP database entry
193  * \param[out] pin_types An array of pointers where hash types will be stored.
194  * \param[out] pins_b64 An array of pointers where the public keys in base64 format will be stored
195  *
196  * Gets all the public key hashes added to the given HPKP database entry.
197  *
198  * The size of the arrays used must be at least one returned by \ref wget_hpkp_get_n_pins "wget_hpkp_get_n_pins()".
199  */
wget_hpkp_get_pins_b64(wget_hpkp * hpkp,const char ** pin_types,const char ** pins_b64)200 void wget_hpkp_get_pins_b64(wget_hpkp *hpkp, const char **pin_types, const char **pins_b64)
201 {
202 	int i, n_pins;
203 
204 	n_pins = wget_vector_size(hpkp->pins);
205 
206 	for (i = 0; i < n_pins; i++) {
207 		wget_hpkp_pin *pin = (wget_hpkp_pin *) wget_vector_get(hpkp->pins, i);
208 		pin_types[i] = pin->hash_type;
209 		pins_b64[i] = pin->pin_b64;
210 	}
211 }
212 
213 /**
214  * \param[in] hpkp An HPKP database entry
215  * \param[out] pin_types An array of pointers where hash types will be stored.
216  * \param[out] sizes An array of sizes where pin sizes will be stored.
217  * \param[out] pins An array of pointers where the public keys in binary format will be stored
218  *
219  * Gets all the public key hashes added to the given HPKP database entry.
220  *
221  * The size of the arrays used must be at least one returned by \ref wget_hpkp_get_n_pins "wget_hpkp_get_n_pins()".
222  */
wget_hpkp_get_pins(wget_hpkp * hpkp,const char ** pin_types,size_t * sizes,const void ** pins)223 void wget_hpkp_get_pins(wget_hpkp *hpkp, const char **pin_types, size_t *sizes, const void **pins)
224 {
225 	int i, n_pins;
226 
227 	n_pins = wget_vector_size(hpkp->pins);
228 
229 	for (i = 0; i < n_pins; i++) {
230 		wget_hpkp_pin *pin = (wget_hpkp_pin *) wget_vector_get(hpkp->pins, i);
231 		pin_types[i] = pin->hash_type;
232 		sizes[i] = pin->pinsize;
233 		pins[i] = pin->pin;
234 	}
235 }
236 
237 /**
238  * \param[in] hpkp An HPKP database entry
239  * \return The hostname this entry is valid for
240  *
241  * Gets the hostname this entry is valid for, as set by \ref wget_hpkp_set_host "wget_hpkp_set_host()"
242  */
wget_hpkp_get_host(wget_hpkp * hpkp)243 const char * wget_hpkp_get_host(wget_hpkp *hpkp)
244 {
245 	return hpkp->host;
246 }
247 
248 /**
249  * \param[in] hpkp An HPKP database entry
250  * \return The maximum time (in seconds) the entry is valid
251  *
252  * Gets the maximum time this entry is valid for, as set by \ref wget_hpkp_set_maxage "wget_hpkp_set_maxage()"
253  */
wget_hpkp_get_maxage(wget_hpkp * hpkp)254 int64_t wget_hpkp_get_maxage(wget_hpkp *hpkp)
255 {
256 	return hpkp->maxage;
257 }
258 
259 /**
260  * \param[in] hpkp An HPKP database entry
261  * \return `true` if the HPKP entry is also valid for all subdomains, `false` otherwise
262  *
263  * Gets whether the HPKP database entry is also valid for the subdomains.
264  */
wget_hpkp_get_include_subdomains(wget_hpkp * hpkp)265 bool wget_hpkp_get_include_subdomains(wget_hpkp *hpkp)
266 {
267 	return hpkp->include_subdomains;
268 }
269 
270 /**@}*/
271