/src/gnutls/lib/x509/ip.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2007-2016 Free Software Foundation, Inc. |
3 | | * Copyright (C) 2015-2016 Red Hat, Inc. |
4 | | * |
5 | | * Author: Simon Josefsson, Nikos Mavrogiannopoulos, Martin Ukrop |
6 | | * |
7 | | * This file is part of GnuTLS. |
8 | | * |
9 | | * The GnuTLS is free software; you can redistribute it and/or |
10 | | * modify it under the terms of the GNU Lesser General Public License |
11 | | * as published by the Free Software Foundation; either version 2.1 of |
12 | | * the License, or (at your option) any later version. |
13 | | * |
14 | | * This library is distributed in the hope that it will be useful, but |
15 | | * WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 | | * Lesser General Public License for more details. |
18 | | * |
19 | | * You should have received a copy of the GNU Lesser General Public License |
20 | | * along with this program. If not, see <https://www.gnu.org/licenses/> |
21 | | * |
22 | | */ |
23 | | |
24 | | #include "gnutls_int.h" |
25 | | #include "ip.h" |
26 | | #include <gnutls/x509.h> |
27 | | #include <arpa/inet.h> |
28 | | |
29 | | /*- |
30 | | * _gnutls_mask_to_prefix: |
31 | | * @mask: CIDR mask |
32 | | * @mask_size: number of bytes in @mask |
33 | | * |
34 | | * Check for mask validity (form of 1*0*) and return its prefix numerically. |
35 | | * |
36 | | * Returns: Length of 1-prefix (0 -- mask_size*8), -1 in case of invalid mask |
37 | | -*/ |
38 | | int _gnutls_mask_to_prefix(const unsigned char *mask, unsigned mask_size) |
39 | 0 | { |
40 | 0 | unsigned i, prefix_length = 0; |
41 | 0 | for (i = 0; i < mask_size; i++) { |
42 | 0 | if (mask[i] == 0xFF) { |
43 | 0 | prefix_length += 8; |
44 | 0 | } else { |
45 | 0 | switch (mask[i]) { |
46 | 0 | case 0xFE: |
47 | 0 | prefix_length += 7; |
48 | 0 | break; |
49 | 0 | case 0xFC: |
50 | 0 | prefix_length += 6; |
51 | 0 | break; |
52 | 0 | case 0xF8: |
53 | 0 | prefix_length += 5; |
54 | 0 | break; |
55 | 0 | case 0xF0: |
56 | 0 | prefix_length += 4; |
57 | 0 | break; |
58 | 0 | case 0xE0: |
59 | 0 | prefix_length += 3; |
60 | 0 | break; |
61 | 0 | case 0xC0: |
62 | 0 | prefix_length += 2; |
63 | 0 | break; |
64 | 0 | case 0x80: |
65 | 0 | prefix_length += 1; |
66 | 0 | break; |
67 | 0 | case 0x00: |
68 | 0 | break; |
69 | 0 | default: |
70 | 0 | return -1; |
71 | 0 | } |
72 | 0 | break; |
73 | 0 | } |
74 | 0 | } |
75 | 0 | i++; |
76 | | // mask is invalid, if there follows something else than 0x00 |
77 | 0 | for (; i < mask_size; i++) { |
78 | 0 | if (mask[i] != 0) |
79 | 0 | return -1; |
80 | 0 | } |
81 | 0 | return prefix_length; |
82 | 0 | } |
83 | | |
84 | | /*- |
85 | | * _gnutls_ip_to_string: |
86 | | * @_ip: IP address (RFC5280 format) |
87 | | * @ip_size: Size of @_ip (4 or 16) |
88 | | * @out: Resulting string |
89 | | * @out_size: Size of @out |
90 | | * |
91 | | * Transform IP address into human-readable string. |
92 | | * @string must be already allocated and |
93 | | * at least 16/48 bytes for IPv4/v6 address respectively. |
94 | | * |
95 | | * Returns: Address of result string. |
96 | | -*/ |
97 | | const char *_gnutls_ip_to_string(const void *_ip, unsigned int ip_size, |
98 | | char *out, unsigned int out_size) |
99 | 0 | { |
100 | 0 | if (ip_size != 4 && ip_size != 16) { |
101 | 0 | gnutls_assert(); |
102 | 0 | return NULL; |
103 | 0 | } |
104 | | |
105 | 0 | if (ip_size == 4 && out_size < 16) { |
106 | 0 | gnutls_assert(); |
107 | 0 | return NULL; |
108 | 0 | } |
109 | | |
110 | 0 | if (ip_size == 16 && out_size < 48) { |
111 | 0 | gnutls_assert(); |
112 | 0 | return NULL; |
113 | 0 | } |
114 | | |
115 | 0 | if (ip_size == 4) |
116 | 0 | return inet_ntop(AF_INET, _ip, out, out_size); |
117 | 0 | else |
118 | 0 | return inet_ntop(AF_INET6, _ip, out, out_size); |
119 | 0 | } |
120 | | |
121 | | /*- |
122 | | * _gnutls_cidr_to_string: |
123 | | * @_ip: CIDR range (RFC5280 format) |
124 | | * @ip_size: Size of @_ip (8 or 32) |
125 | | * @out: Resulting string |
126 | | * @out_size: Size of @out |
127 | | * |
128 | | * Transform CIDR IP address range into human-readable string. |
129 | | * The input @_ip must be in RFC5280 format (IP address in network byte |
130 | | * order, followed by its network mask which is 4 bytes in IPv4 and |
131 | | * 16 bytes in IPv6). @string must be already allocated and |
132 | | * at least 33/97 bytes for IPv4/v6 address respectively. |
133 | | * |
134 | | * Returns: Address of result string. |
135 | | -*/ |
136 | | const char *_gnutls_cidr_to_string(const void *_ip, unsigned int ip_size, |
137 | | char *out, unsigned int out_size) |
138 | 0 | { |
139 | 0 | const unsigned char *ip = _ip; |
140 | 0 | char tmp[64]; |
141 | 0 | const char *p; |
142 | |
|
143 | 0 | if (ip_size != 8 && ip_size != 32) { |
144 | 0 | gnutls_assert(); |
145 | 0 | return NULL; |
146 | 0 | } |
147 | | |
148 | 0 | if (ip_size == 8) { |
149 | 0 | p = inet_ntop(AF_INET, ip, tmp, sizeof(tmp)); |
150 | |
|
151 | 0 | if (p) |
152 | 0 | snprintf(out, out_size, "%s/%d", tmp, |
153 | 0 | _gnutls_mask_to_prefix(ip + 4, 4)); |
154 | 0 | } else { |
155 | 0 | p = inet_ntop(AF_INET6, ip, tmp, sizeof(tmp)); |
156 | |
|
157 | 0 | if (p) |
158 | 0 | snprintf(out, out_size, "%s/%d", tmp, |
159 | 0 | _gnutls_mask_to_prefix(ip + 16, 16)); |
160 | 0 | } |
161 | |
|
162 | 0 | if (p == NULL) |
163 | 0 | return NULL; |
164 | | |
165 | 0 | return out; |
166 | 0 | } |
167 | | |
168 | | static void prefix_to_mask(unsigned prefix, unsigned char *mask, |
169 | | size_t mask_size) |
170 | 0 | { |
171 | 0 | int i; |
172 | 0 | unsigned j; |
173 | 0 | memset(mask, 0, mask_size); |
174 | |
|
175 | 0 | for (i = prefix, j = 0; i > 0 && j < mask_size; i -= 8, j++) { |
176 | 0 | if (i >= 8) { |
177 | 0 | mask[j] = 0xff; |
178 | 0 | } else { |
179 | 0 | mask[j] = (unsigned long)(0xffU << (8 - i)); |
180 | 0 | } |
181 | 0 | } |
182 | 0 | } |
183 | | |
184 | | /*- |
185 | | * _gnutls_mask_ip: |
186 | | * @ip: IP of @ipsize bytes |
187 | | * @mask: netmask of @ipsize bytes |
188 | | * @ipsize: IP length (4 or 16) |
189 | | * |
190 | | * Mask given IP in place according to the given mask. |
191 | | * |
192 | | * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value. |
193 | | -*/ |
194 | | int _gnutls_mask_ip(unsigned char *ip, const unsigned char *mask, |
195 | | unsigned ipsize) |
196 | 0 | { |
197 | 0 | unsigned i; |
198 | |
|
199 | 0 | if (ipsize != 4 && ipsize != 16) |
200 | 0 | return GNUTLS_E_MALFORMED_CIDR; |
201 | 0 | for (i = 0; i < ipsize; i++) |
202 | 0 | ip[i] &= mask[i]; |
203 | 0 | return GNUTLS_E_SUCCESS; |
204 | 0 | } |
205 | | |
206 | | /** |
207 | | * gnutls_x509_cidr_to_rfc5280: |
208 | | * @cidr: CIDR in RFC4632 format (IP/prefix), null-terminated |
209 | | * @cidr_rfc5280: CIDR range converted to RFC5280 format |
210 | | * |
211 | | * This function will convert text CIDR range with prefix (such as '10.0.0.0/8') |
212 | | * to RFC5280 (IP address in network byte order followed by its network mask). |
213 | | * Works for both IPv4 and IPv6. |
214 | | * |
215 | | * The resulting object is directly usable for IP name constraints usage, |
216 | | * for example in functions %gnutls_x509_name_constraints_add_permitted |
217 | | * or %gnutls_x509_name_constraints_add_excluded. |
218 | | * |
219 | | * The data in datum needs to be deallocated using gnutls_free(). |
220 | | * |
221 | | * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value. |
222 | | * |
223 | | * Since: 3.5.4 |
224 | | */ |
225 | | int gnutls_x509_cidr_to_rfc5280(const char *cidr, gnutls_datum_t *cidr_rfc5280) |
226 | 0 | { |
227 | 0 | unsigned iplength, prefix; |
228 | 0 | int ret; |
229 | 0 | char *p; |
230 | 0 | char *p_end = NULL; |
231 | 0 | char *cidr_tmp; |
232 | |
|
233 | 0 | p = strchr(cidr, '/'); |
234 | 0 | if (p != NULL) { |
235 | 0 | prefix = strtol(p + 1, &p_end, 10); |
236 | 0 | if (prefix == 0 && p_end == p + 1) { |
237 | 0 | _gnutls_debug_log( |
238 | 0 | "Cannot parse prefix given in CIDR %s\n", cidr); |
239 | 0 | gnutls_assert(); |
240 | 0 | return GNUTLS_E_MALFORMED_CIDR; |
241 | 0 | } |
242 | 0 | unsigned length = p - cidr + 1; |
243 | 0 | cidr_tmp = gnutls_malloc(length); |
244 | 0 | if (cidr_tmp == NULL) { |
245 | 0 | return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); |
246 | 0 | } |
247 | 0 | memcpy(cidr_tmp, cidr, length); |
248 | 0 | cidr_tmp[length - 1] = 0; |
249 | 0 | } else { |
250 | 0 | _gnutls_debug_log("No prefix given in CIDR %s\n", cidr); |
251 | 0 | gnutls_assert(); |
252 | 0 | return GNUTLS_E_MALFORMED_CIDR; |
253 | 0 | } |
254 | | |
255 | 0 | if (strchr(cidr, ':') != 0) { /* IPv6 */ |
256 | 0 | iplength = 16; |
257 | 0 | } else { /* IPv4 */ |
258 | 0 | iplength = 4; |
259 | 0 | } |
260 | 0 | cidr_rfc5280->size = 2 * iplength; |
261 | |
|
262 | 0 | if (prefix > iplength * 8) { |
263 | 0 | _gnutls_debug_log("Invalid prefix given in CIDR %s (%d)\n", |
264 | 0 | cidr, prefix); |
265 | 0 | ret = gnutls_assert_val(GNUTLS_E_MALFORMED_CIDR); |
266 | 0 | goto cleanup; |
267 | 0 | } |
268 | | |
269 | 0 | cidr_rfc5280->data = gnutls_malloc(cidr_rfc5280->size); |
270 | 0 | if (cidr_rfc5280->data == NULL) { |
271 | 0 | ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); |
272 | 0 | goto cleanup; |
273 | 0 | } |
274 | | |
275 | 0 | ret = inet_pton(iplength == 4 ? AF_INET : AF_INET6, cidr_tmp, |
276 | 0 | cidr_rfc5280->data); |
277 | 0 | if (ret == 0) { |
278 | 0 | _gnutls_debug_log("Cannot parse IP from CIDR %s\n", cidr_tmp); |
279 | 0 | ret = gnutls_assert_val(GNUTLS_E_MALFORMED_CIDR); |
280 | 0 | goto cleanup; |
281 | 0 | } |
282 | | |
283 | 0 | prefix_to_mask(prefix, &cidr_rfc5280->data[iplength], iplength); |
284 | 0 | _gnutls_mask_ip(cidr_rfc5280->data, &cidr_rfc5280->data[iplength], |
285 | 0 | iplength); |
286 | |
|
287 | 0 | ret = GNUTLS_E_SUCCESS; |
288 | |
|
289 | 0 | cleanup: |
290 | 0 | gnutls_free(cidr_tmp); |
291 | 0 | return ret; |
292 | 0 | } |