1 /*
2  * Copyright (C) 2016, 2017 Red Hat, Inc.
3  *
4  * Author: Nikos Mavrogiannopoulos
5  *
6  * This file is part of GnuTLS.
7  *
8  * The GnuTLS is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation; either version 2.1 of
11  * the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program.  If not, see <https://www.gnu.org/licenses/>
20  *
21  */
22 
23 #include "gnutls_int.h"
24 #include "errors.h"
25 #include "str.h"
26 #include <uninorm.h>
27 #include <unistr.h>
28 #include <unictype.h>
29 
30 /* rfc5892#section-2.6 exceptions
31  */
is_allowed_exception(uint32_t ch)32 inline static int is_allowed_exception(uint32_t ch)
33 {
34 	switch (ch) {
35 		case 0xB7:
36 		case 0x0375:
37 		case 0x05F3:
38 		case 0x05F4:
39 		case 0x30FB:
40 		case 0x0660:
41 		case 0x0661:
42 		case 0x0662:
43 		case 0x0663:
44 		case 0x0664:
45 		case 0x0665:
46 		case 0x0666:
47 		case 0x0667:
48 		case 0x0668:
49 		case 0x0669:
50 		case 0x06F0:
51 		case 0x06F1:
52 		case 0x06F2:
53 		case 0x06F3:
54 		case 0x06F4:
55 		case 0x06F5:
56 		case 0x06F6:
57 		case 0x06F7:
58 		case 0x06F8:
59 		case 0x06F9:
60 		case 0x0640:
61 		case 0x07FA:
62 		case 0x302E:
63 		case 0x302F:
64 		case 0x3031:
65 		case 0x3032:
66 		case 0x3033:
67 		case 0x3034:
68 		case 0x3035:
69 		case 0x303B:
70 			return 0; /* disallowed */
71 		case 0xDF:
72 		case 0x03C2:
73 		case 0x06FD:
74 		case 0x06FE:
75 		case 0x0F0B:
76 		case 0x3007:
77 			return 1; /* allowed */
78 		default:
79 			return -1; /* not exception */
80 	}
81 }
82 
83 /* Checks whether the provided string is in the valid set of FreeFormClass (RFC7564
84  * as an RFC7613 requirement), and converts all spaces to the ASCII-space. */
check_for_valid_freeformclass(uint32_t * ucs4,unsigned ucs4_size)85 static int check_for_valid_freeformclass(uint32_t *ucs4, unsigned ucs4_size)
86 {
87 	unsigned i;
88 	int rc;
89 	uint32_t tmp[4];
90 	size_t tmp_size;
91 	uint32_t *nrm;
92 	uc_general_category_t cat;
93 	unsigned is_invalid;
94 
95 	/* make the union of Valid categories, excluding any invalid (i.e., control) */
96 	cat = uc_general_category_or(UC_CATEGORY_Ll, UC_CATEGORY_Lu); /* LetterDigits */
97 	cat = uc_general_category_or(cat, UC_CATEGORY_Lo);
98 	cat = uc_general_category_or(cat, UC_CATEGORY_Nd);
99 	cat = uc_general_category_or(cat, UC_CATEGORY_Lm);
100 	cat = uc_general_category_or(cat, UC_CATEGORY_Mn);
101 	cat = uc_general_category_or(cat, UC_CATEGORY_Mc);
102 	cat = uc_general_category_or(cat, UC_CATEGORY_Lt); /* OtherLetterDigits */
103 	cat = uc_general_category_or(cat, UC_CATEGORY_Nl);
104 	cat = uc_general_category_or(cat, UC_CATEGORY_No);
105 	cat = uc_general_category_or(cat, UC_CATEGORY_Me);
106 	cat = uc_general_category_or(cat, UC_CATEGORY_Sm); /* Symbols */
107 	cat = uc_general_category_or(cat, UC_CATEGORY_Sc);
108 	cat = uc_general_category_or(cat, UC_CATEGORY_So);
109 	cat = uc_general_category_or(cat, UC_CATEGORY_Sk);
110 	cat = uc_general_category_or(cat, UC_CATEGORY_Pc); /* Punctuation */
111 	cat = uc_general_category_or(cat, UC_CATEGORY_Pd);
112 	cat = uc_general_category_or(cat, UC_CATEGORY_Ps);
113 	cat = uc_general_category_or(cat, UC_CATEGORY_Pe);
114 	cat = uc_general_category_or(cat, UC_CATEGORY_Pi);
115 	cat = uc_general_category_or(cat, UC_CATEGORY_Pf);
116 	cat = uc_general_category_or(cat, UC_CATEGORY_Po);
117 	cat = uc_general_category_or(cat, UC_CATEGORY_Zs); /* Spaces */
118 	cat = uc_general_category_and_not(cat, UC_CATEGORY_Cc); /* Not in Control */
119 
120 	/* check for being in the allowed sets in rfc7564#section-4.3 */
121 	for (i=0;i<ucs4_size;i++) {
122 		is_invalid = 0;
123 
124 		/* Disallowed
125 		   o  Old Hangul Jamo characters, i.e., the OldHangulJamo ("I") category
126 		      (not handled in this code)
127 
128 		   o  Control characters, i.e., the Controls ("L") category
129 
130 		   o  Ignorable characters, i.e., the PrecisIgnorableProperties ("M")
131 		 */
132 		if (uc_is_property_default_ignorable_code_point(ucs4[i]) ||
133 		    uc_is_property_not_a_character(ucs4[i])) {
134 			return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_STRING);
135 		}
136 
137 
138 		/* Contextual rules - we do not implement them / we reject chars from these sets
139 		   o  A number of characters from the Exceptions ("F") category defined
140 
141 		   o  Joining characters, i.e., the JoinControl ("H") category defined
142 		 */
143 		rc = is_allowed_exception(ucs4[i]);
144 		if (rc == 0 || uc_is_property_join_control(ucs4[i]))
145 			return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_STRING);
146 
147 		if (rc == 1) /* exceptionally allowed, continue */
148 			continue;
149 
150 
151 		/* Replace all spaces; an RFC7613 requirement
152 		 */
153 		if (uc_is_general_category(ucs4[i], UC_CATEGORY_Zs)) /* replace */
154 			ucs4[i] = 0x20;
155 
156 		/* Valid */
157 		if ((ucs4[i] < 0x21 || ucs4[i] > 0x7E) && !uc_is_general_category(ucs4[i], cat))
158 			is_invalid = 1;
159 
160 		/* HasCompat */
161 		if (is_invalid) {
162 			tmp_size = sizeof(tmp)/sizeof(tmp[0]);
163 			nrm = u32_normalize(UNINORM_NFKC, &ucs4[i], 1, tmp, &tmp_size);
164 			if (nrm == NULL || (tmp_size == 1 && nrm[0] == ucs4[i]))
165 				return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_STRING);
166 		}
167 	}
168 
169 	return 0;
170 }
171 
172 
173 /**
174  * gnutls_utf8_password_normalize:
175  * @password: contain the UTF-8 formatted password
176  * @plen: the length of the provided password
177  * @out: the result in an null-terminated allocated string
178  * @flags: should be zero
179  *
180  * This function will convert the provided UTF-8 password according
181  * to the normalization rules in RFC7613.
182  *
183  * If the flag %GNUTLS_UTF8_IGNORE_ERRS is specified, any UTF-8 encoding
184  * errors will be ignored, and in that case the output will be a copy of the input.
185  *
186  * Returns: %GNUTLS_E_INVALID_UTF8_STRING on invalid UTF-8 data, or 0 on success.
187  *
188  * Since: 3.5.7
189  **/
gnutls_utf8_password_normalize(const unsigned char * password,unsigned plen,gnutls_datum_t * out,unsigned flags)190 int gnutls_utf8_password_normalize(const unsigned char *password, unsigned plen,
191 				   gnutls_datum_t *out, unsigned flags)
192 {
193 	size_t ucs4_size = 0, nrm_size = 0;
194 	size_t final_size = 0;
195 	uint8_t *final = NULL;
196 	uint32_t *ucs4 = NULL;
197 	uint32_t *nrm = NULL;
198 	uint8_t *nrmu8 = NULL;
199 	int ret;
200 
201 	if (plen == 0) {
202 		out->data = (uint8_t*)gnutls_strdup("");
203 		out->size = 0;
204 		if (out->data == NULL)
205 			return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
206 		return 0;
207 	}
208 
209 	/* check for invalid UTF-8 */
210 	if (u8_check((uint8_t*)password, plen) != NULL) {
211 		gnutls_assert();
212 		if (flags & GNUTLS_UTF8_IGNORE_ERRS) {
213  raw_copy:
214 			out->data = gnutls_malloc(plen+1);
215 			if (out->data == NULL)
216 				return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
217 			out->size = plen;
218 			memcpy(out->data, password, plen);
219 			out->data[plen] = 0;
220 			return 0;
221 		} else {
222 			return GNUTLS_E_INVALID_UTF8_STRING;
223 		}
224 	}
225 
226 	/* convert to UTF-32 */
227 	ucs4 = u8_to_u32((uint8_t*)password, plen, NULL, &ucs4_size);
228 	if (ucs4 == NULL) {
229 		gnutls_assert();
230 		ret = GNUTLS_E_PARSING_ERROR;
231 		goto fail;
232 	}
233 
234 	ret = check_for_valid_freeformclass(ucs4, ucs4_size);
235 	if (ret < 0) {
236 		gnutls_assert();
237 		if (flags & GNUTLS_UTF8_IGNORE_ERRS) {
238 			free(ucs4);
239 			goto raw_copy;
240 		}
241 		if (ret == GNUTLS_E_INVALID_UTF8_STRING)
242 			ret = GNUTLS_E_INVALID_PASSWORD_STRING;
243 		goto fail;
244 	}
245 
246 	/* normalize to NFC */
247 	nrm = u32_normalize(UNINORM_NFC, ucs4, ucs4_size, NULL, &nrm_size);
248 	if (nrm == NULL) {
249 		gnutls_assert();
250 		ret = GNUTLS_E_INVALID_PASSWORD_STRING;
251 		goto fail;
252 	}
253 
254 	/* convert back to UTF-8 */
255 	final_size = 0;
256 	nrmu8 = u32_to_u8(nrm, nrm_size, NULL, &final_size);
257 	if (nrmu8 == NULL) {
258 		gnutls_assert();
259 		ret = GNUTLS_E_INVALID_PASSWORD_STRING;
260 		goto fail;
261 	}
262 
263 	/* copy to output with null terminator */
264 	final = gnutls_malloc(final_size+1);
265 	if (final == NULL) {
266 		gnutls_assert();
267 		ret = GNUTLS_E_MEMORY_ERROR;
268 		goto fail;
269 	}
270 
271 	memcpy(final, nrmu8, final_size);
272 	final[final_size] = 0;
273 
274 	free(ucs4);
275 	free(nrm);
276 	free(nrmu8);
277 
278 	out->data = final;
279 	out->size = final_size;
280 
281 	return 0;
282 
283  fail:
284 	gnutls_free(final);
285 	free(ucs4);
286 	free(nrm);
287 	free(nrmu8);
288 	return ret;
289 }
290 
291