diff options
author | beck <> | 2022-06-29 08:56:44 +0000 |
---|---|---|
committer | beck <> | 2022-06-29 08:56:44 +0000 |
commit | 6d7ca2b2aebbc58b83cbb3b71398deeb7acbfed6 (patch) | |
tree | b366ef947fe73670b1c21ffc3ffba4ed862616d2 /src | |
parent | 1de11e7e8f780f3c0123b481a3ecf4d8ae835e20 (diff) | |
download | openbsd-6d7ca2b2aebbc58b83cbb3b71398deeb7acbfed6.tar.gz openbsd-6d7ca2b2aebbc58b83cbb3b71398deeb7acbfed6.tar.bz2 openbsd-6d7ca2b2aebbc58b83cbb3b71398deeb7acbfed6.zip |
Refactor asn1 time parsing to use CBS - enforce valid times in ASN.1 parsing.
While we're here enforce valid days for months and leap years.
Inspired by same in boringssl.
ok jsing@
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/libcrypto/asn1/a_time_tm.c | 210 | ||||
-rw-r--r-- | src/lib/libcrypto/asn1/asn1_locl.h | 4 | ||||
-rw-r--r-- | src/lib/libcrypto/asn1/tasn_dec.c | 9 |
3 files changed, 155 insertions, 68 deletions
diff --git a/src/lib/libcrypto/asn1/a_time_tm.c b/src/lib/libcrypto/asn1/a_time_tm.c index 23e2ce4b4c..9ddae82768 100644 --- a/src/lib/libcrypto/asn1/a_time_tm.c +++ b/src/lib/libcrypto/asn1/a_time_tm.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: a_time_tm.c,v 1.21 2022/06/27 13:54:57 beck Exp $ */ | 1 | /* $OpenBSD: a_time_tm.c,v 1.22 2022/06/29 08:56:44 beck Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2015 Bob Beck <beck@openbsd.org> | 3 | * Copyright (c) 2015 Bob Beck <beck@openbsd.org> |
4 | * | 4 | * |
@@ -24,6 +24,7 @@ | |||
24 | #include <openssl/asn1t.h> | 24 | #include <openssl/asn1t.h> |
25 | #include <openssl/err.h> | 25 | #include <openssl/err.h> |
26 | 26 | ||
27 | #include "bytestring.h" | ||
27 | #include "o_time.h" | 28 | #include "o_time.h" |
28 | 29 | ||
29 | #define RFC5280 0 | 30 | #define RFC5280 0 |
@@ -173,6 +174,137 @@ tm_to_rfc5280_time(struct tm *tm, ASN1_TIME *atime) | |||
173 | return (tm_to_gentime(tm, atime)); | 174 | return (tm_to_gentime(tm, atime)); |
174 | } | 175 | } |
175 | 176 | ||
177 | |||
178 | static int | ||
179 | cbs_get_two_digit_value(CBS *cbs, int *out) | ||
180 | { | ||
181 | uint8_t first_digit, second_digit; | ||
182 | |||
183 | if (!CBS_get_u8(cbs, &first_digit)) | ||
184 | return 0; | ||
185 | if (!isdigit(first_digit)) | ||
186 | return 0; | ||
187 | if (!CBS_get_u8(cbs, &second_digit)) | ||
188 | return 0; | ||
189 | if (!isdigit(second_digit)) | ||
190 | return 0; | ||
191 | |||
192 | *out = (first_digit - '0') * 10 + (second_digit - '0'); | ||
193 | |||
194 | return 1; | ||
195 | } | ||
196 | |||
197 | static int | ||
198 | is_valid_day(int year, int month, int day) | ||
199 | { | ||
200 | if (day < 1) | ||
201 | return 0; | ||
202 | switch (month) { | ||
203 | case 1: | ||
204 | case 3: | ||
205 | case 5: | ||
206 | case 7: | ||
207 | case 8: | ||
208 | case 10: | ||
209 | case 12: | ||
210 | return day <= 31; | ||
211 | case 4: | ||
212 | case 6: | ||
213 | case 9: | ||
214 | case 11: | ||
215 | return day <= 30; | ||
216 | case 2: | ||
217 | if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) | ||
218 | return day <= 29; | ||
219 | else | ||
220 | return day <= 28; | ||
221 | default: | ||
222 | return 0; | ||
223 | } | ||
224 | } | ||
225 | |||
226 | /* | ||
227 | * asn1_time_parse_cbs returns one if |cbs| is a valid DER-encoded, ASN.1 Time | ||
228 | * body within the limitations imposed by RFC 5280, or zero otherwise. The time | ||
229 | * is expected to parse as a Generalized Time if is_gentime is true, and as a | ||
230 | * UTC Time otherwise. If |out_tm| is non-NULL, |*out_tm| will be zeroed, and | ||
231 | * then set to the corresponding time in UTC. This function does not compute | ||
232 | * |out_tm->tm_wday| or |out_tm->tm_yday|. |cbs| is not consumed. | ||
233 | */ | ||
234 | int | ||
235 | asn1_time_parse_cbs(const CBS *cbs, int is_gentime, struct tm *out_tm) | ||
236 | { | ||
237 | int year, month, day, hour, min, sec, val; | ||
238 | CBS copy; | ||
239 | uint8_t tz; | ||
240 | |||
241 | CBS_dup(cbs, ©); | ||
242 | |||
243 | if (is_gentime) { | ||
244 | if (!cbs_get_two_digit_value(©, &val)) | ||
245 | return 0; | ||
246 | year = val * 100; | ||
247 | if (!cbs_get_two_digit_value(©, &val)) | ||
248 | return 0; | ||
249 | year += val; | ||
250 | } else { | ||
251 | year = 1900; | ||
252 | if (!cbs_get_two_digit_value(©, &val)) | ||
253 | return 0; | ||
254 | year += val; | ||
255 | if (year < 1950) | ||
256 | year += 100; | ||
257 | if (year >= 2050) | ||
258 | return 0; /* A Generalized time must be used. */ | ||
259 | } | ||
260 | |||
261 | if (!cbs_get_two_digit_value(©, &month)) | ||
262 | return 0; | ||
263 | if (month < 1 || month > 12) | ||
264 | return 0; /* Reject invalid months. */ | ||
265 | |||
266 | if (!cbs_get_two_digit_value(©, &day)) | ||
267 | return 0; | ||
268 | if (!is_valid_day(year, month, day)) | ||
269 | return 0; /* Reject invalid days. */ | ||
270 | |||
271 | if (!cbs_get_two_digit_value(©, &hour)) | ||
272 | return 0; | ||
273 | if (hour > 23) | ||
274 | return 0; /* Reject invalid hours. */ | ||
275 | |||
276 | if (!cbs_get_two_digit_value(©, &min)) | ||
277 | return 0; | ||
278 | if (min > 59) | ||
279 | return 0; /* Reject invalid minutes. */ | ||
280 | |||
281 | if (!cbs_get_two_digit_value(©, &sec)) | ||
282 | return 0; | ||
283 | if (sec > 59) | ||
284 | return 0; /* Reject invalid seconds. Leap seconds are invalid. */ | ||
285 | |||
286 | if (!CBS_get_u8(©, &tz)) | ||
287 | return 0; | ||
288 | if ( tz != 'Z') | ||
289 | return 0; /* Reject anything but Z on the end. */ | ||
290 | |||
291 | if (CBS_len(©) != 0) | ||
292 | return 0; /* Reject invalid lengths. */ | ||
293 | |||
294 | if (out_tm != NULL) { | ||
295 | memset(out_tm, 0, sizeof(*out_tm)); | ||
296 | /* Fill in the tm fields corresponding to what we validated. */ | ||
297 | out_tm->tm_year = year - 1900; | ||
298 | out_tm->tm_mon = month - 1; | ||
299 | out_tm->tm_mday = day; | ||
300 | out_tm->tm_hour = hour; | ||
301 | out_tm->tm_min = min; | ||
302 | out_tm->tm_sec = sec; | ||
303 | } | ||
304 | |||
305 | return 1; | ||
306 | } | ||
307 | |||
176 | /* | 308 | /* |
177 | * Parse an RFC 5280 format ASN.1 time string. | 309 | * Parse an RFC 5280 format ASN.1 time string. |
178 | * | 310 | * |
@@ -188,83 +320,29 @@ tm_to_rfc5280_time(struct tm *tm, ASN1_TIME *atime) | |||
188 | * | 320 | * |
189 | * Fills in *tm with the corresponding time if tm is non NULL. | 321 | * Fills in *tm with the corresponding time if tm is non NULL. |
190 | */ | 322 | */ |
191 | #define ATOI2(ar) ((ar) += 2, ((ar)[-2] - '0') * 10 + ((ar)[-1] - '0')) | ||
192 | int | 323 | int |
193 | ASN1_time_parse(const char *bytes, size_t len, struct tm *tm, int mode) | 324 | ASN1_time_parse(const char *bytes, size_t len, struct tm *tm, int mode) |
194 | { | 325 | { |
195 | size_t i; | 326 | struct tm tml, *tmp = tm ? tm : &tml; |
196 | int type = 0; | 327 | int type = 0; |
197 | struct tm ltm; | 328 | CBS cbs; |
198 | struct tm *lt; | ||
199 | const char *p; | ||
200 | 329 | ||
201 | if (bytes == NULL) | 330 | if (bytes == NULL) |
202 | return (-1); | 331 | return (-1); |
203 | 332 | ||
204 | /* Constrain to valid lengths. */ | 333 | CBS_init(&cbs, bytes, len); |
205 | if (len != UTCTIME_LENGTH && len != GENTIME_LENGTH) | ||
206 | return (-1); | ||
207 | |||
208 | lt = tm; | ||
209 | if (lt == NULL) | ||
210 | lt = <m; | ||
211 | memset(lt, 0, sizeof(*lt)); | ||
212 | 334 | ||
213 | /* Timezone is required and must be GMT (Zulu). */ | 335 | if (CBS_len(&cbs) == UTCTIME_LENGTH) |
214 | if (bytes[len - 1] != 'Z') | 336 | type = V_ASN1_UTCTIME; |
215 | return (-1); | 337 | if (CBS_len(&cbs) == GENTIME_LENGTH) |
216 | |||
217 | /* Make sure everything else is digits. */ | ||
218 | for (i = 0; i < len - 1; i++) { | ||
219 | if (isdigit((unsigned char)bytes[i])) | ||
220 | continue; | ||
221 | return (-1); | ||
222 | } | ||
223 | |||
224 | /* | ||
225 | * Validate and convert the time | ||
226 | */ | ||
227 | p = bytes; | ||
228 | switch (len) { | ||
229 | case GENTIME_LENGTH: | ||
230 | if (mode == V_ASN1_UTCTIME) | ||
231 | return (-1); | ||
232 | lt->tm_year = (ATOI2(p) * 100) - 1900; /* cc */ | ||
233 | type = V_ASN1_GENERALIZEDTIME; | 338 | type = V_ASN1_GENERALIZEDTIME; |
234 | /* FALLTHROUGH */ | 339 | if (asn1_time_parse_cbs(&cbs, type == V_ASN1_GENERALIZEDTIME, tmp)) { |
235 | case UTCTIME_LENGTH: | 340 | if (mode != 0 && mode != type) |
236 | if (type == 0) { | 341 | return -1; |
237 | if (mode == V_ASN1_GENERALIZEDTIME) | 342 | return type; |
238 | return (-1); | ||
239 | type = V_ASN1_UTCTIME; | ||
240 | } | ||
241 | lt->tm_year += ATOI2(p); /* yy */ | ||
242 | if (type == V_ASN1_UTCTIME) { | ||
243 | if (lt->tm_year < 50) | ||
244 | lt->tm_year += 100; | ||
245 | } | ||
246 | lt->tm_mon = ATOI2(p) - 1; /* mm */ | ||
247 | if (lt->tm_mon < 0 || lt->tm_mon > 11) | ||
248 | return (-1); | ||
249 | lt->tm_mday = ATOI2(p); /* dd */ | ||
250 | if (lt->tm_mday < 1 || lt->tm_mday > 31) | ||
251 | return (-1); | ||
252 | lt->tm_hour = ATOI2(p); /* HH */ | ||
253 | if (lt->tm_hour < 0 || lt->tm_hour > 23) | ||
254 | return (-1); | ||
255 | lt->tm_min = ATOI2(p); /* MM */ | ||
256 | if (lt->tm_min < 0 || lt->tm_min > 59) | ||
257 | return (-1); | ||
258 | lt->tm_sec = ATOI2(p); /* SS */ | ||
259 | /* Leap second 60 is not accepted. Reconsider later? */ | ||
260 | if (lt->tm_sec < 0 || lt->tm_sec > 59) | ||
261 | return (-1); | ||
262 | break; | ||
263 | default: | ||
264 | return (-1); | ||
265 | } | 343 | } |
266 | 344 | ||
267 | return (type); | 345 | return -1; |
268 | } | 346 | } |
269 | 347 | ||
270 | /* | 348 | /* |
diff --git a/src/lib/libcrypto/asn1/asn1_locl.h b/src/lib/libcrypto/asn1/asn1_locl.h index a0a1842d99..ec853250f2 100644 --- a/src/lib/libcrypto/asn1/asn1_locl.h +++ b/src/lib/libcrypto/asn1/asn1_locl.h | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: asn1_locl.h,v 1.34 2022/06/27 12:36:05 tb Exp $ */ | 1 | /* $OpenBSD: asn1_locl.h,v 1.35 2022/06/29 08:56:44 beck Exp $ */ |
2 | /* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL | 2 | /* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL |
3 | * project 2006. | 3 | * project 2006. |
4 | */ | 4 | */ |
@@ -219,4 +219,6 @@ int i2t_ASN1_OBJECT_internal(const ASN1_OBJECT *aobj, char *buf, int buf_len, | |||
219 | int no_name); | 219 | int no_name); |
220 | ASN1_OBJECT *t2i_ASN1_OBJECT_internal(const char *oid); | 220 | ASN1_OBJECT *t2i_ASN1_OBJECT_internal(const char *oid); |
221 | 221 | ||
222 | int asn1_time_parse_cbs(const CBS *cbs, int is_gentime, struct tm *out_tm); | ||
223 | |||
222 | __END_HIDDEN_DECLS | 224 | __END_HIDDEN_DECLS |
diff --git a/src/lib/libcrypto/asn1/tasn_dec.c b/src/lib/libcrypto/asn1/tasn_dec.c index 375425a9f2..235685484c 100644 --- a/src/lib/libcrypto/asn1/tasn_dec.c +++ b/src/lib/libcrypto/asn1/tasn_dec.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: tasn_dec.c,v 1.77 2022/06/25 17:43:56 jsing Exp $ */ | 1 | /* $OpenBSD: tasn_dec.c,v 1.78 2022/06/29 08:56:44 beck Exp $ */ |
2 | /* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL | 2 | /* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL |
3 | * project 2000. | 3 | * project 2000. |
4 | */ | 4 | */ |
@@ -353,6 +353,13 @@ asn1_c2i_primitive(ASN1_VALUE **pval, CBS *content, int utype, const ASN1_ITEM * | |||
353 | ASN1error(ASN1_R_UNIVERSALSTRING_IS_WRONG_LENGTH); | 353 | ASN1error(ASN1_R_UNIVERSALSTRING_IS_WRONG_LENGTH); |
354 | goto err; | 354 | goto err; |
355 | } | 355 | } |
356 | if (utype == V_ASN1_UTCTIME || utype == V_ASN1_GENERALIZEDTIME) { | ||
357 | if (!asn1_time_parse_cbs(content, | ||
358 | utype == V_ASN1_GENERALIZEDTIME, NULL)) { | ||
359 | ASN1error(ASN1_R_INVALID_TIME_FORMAT); | ||
360 | goto err; | ||
361 | } | ||
362 | } | ||
356 | /* All based on ASN1_STRING and handled the same way. */ | 363 | /* All based on ASN1_STRING and handled the same way. */ |
357 | if (*pval == NULL) { | 364 | if (*pval == NULL) { |
358 | if ((stmp = ASN1_STRING_type_new(utype)) == NULL) { | 365 | if ((stmp = ASN1_STRING_type_new(utype)) == NULL) { |