/* $OpenBSD: a_time_tm.c,v 1.5 2015/10/08 02:26:31 beck Exp $ */ /* * Copyright (c) 2015 Bob Beck * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "o_time.h" #include "asn1_locl.h" char * gentime_string_from_tm(struct tm *tm) { char *ret = NULL; int year; year = tm->tm_year + 1900; if (year < 0 || year > 9999) return (NULL); if (asprintf(&ret, "%04u%02u%02u%02u%02u%02uZ", year, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec) == -1) ret = NULL; return (ret); } char * utctime_string_from_tm(struct tm *tm) { char *ret = NULL; if (tm->tm_year >= 150 || tm->tm_year < 50) return (NULL); if (asprintf(&ret, "%02u%02u%02u%02u%02u%02uZ", tm->tm_year % 100, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec) == -1) ret = NULL; return (ret); } /* * Parse an ASN.1 time string. * * mode must be: * 0 if we expect to parse a time as specified in RFC 5280 from an * X509 certificate. * V_ASN1_UTCTIME if we wish to parse a legacy ASN1 UTC time. * V_ASN1_GENERALIZEDTIME if we wish to parse a legacy ASN1 Generalized time. * * Returns: * -1 if the string was invalid. * V_ASN1_UTCTIME if the string validated as a UTC time string. * V_ASN1_GENERALIZEDTIME if the string validated as a Generalized time string. * * Fills in *tm with the corresponding time if tm is non NULL. */ #define RFC5280 0 #define ATOI2(ar) ((ar) += 2, ((ar)[-2] - '0') * 10 + ((ar)[-1] - '0')) int asn1_time_parse(const char *bytes, size_t len, struct tm *tm, int mode) { char *p, *buf = NULL, *dot = NULL, *tz = NULL; int i, offset = 0, noseconds = 0, type = 0, ret = -1; struct tm ltm; struct tm *lt; size_t tlen; char tzc; if (bytes == NULL) goto err; if (len > INT_MAX) goto err; /* Constrain the RFC5280 case within min/max valid lengths. */ if (mode == RFC5280 && (len < 13 || len > 15)) goto err; if ((buf = strndup(bytes, len)) == NULL) goto err; lt = tm; if (lt == NULL) { time_t t = time(NULL); lt = gmtime_r(&t, <m); if (lt == NULL) goto err; } /* * Find position of the optional fractional seconds, and the * start of the timezone, while ensuring everything else is * digits. */ for (i = 0; i < len; i++) { char *t = buf + i; if (isdigit((unsigned char)*t)) continue; if (*t == '.' && dot == NULL && tz == NULL) { dot = t; continue; } if ((*t == 'Z' || *t == '+' || *t == '-') && tz == NULL) { tz = t; continue; } goto err; } /* * Timezone is required. For the non-RFC case it may be * either Z or +- HHMM, but for RFC5280 it may be only Z. */ if (tz == NULL) goto err; tzc = *tz; *tz++ = '\0'; if (tzc == 'Z') { if (*tz != '\0') goto err; } else if (mode != RFC5280 && (tzc == '+' || tzc == '-') && strlen(tz) == 4) { int hours = ATOI2(tz); int mins = ATOI2(tz); if (hours < 0 || hours > 12 || mins < 0 || mins > 59) goto err; offset = hours * 3600 + mins * 60; if (tzc == '-') offset = -offset; } else goto err; if (offset != 0) { /* XXX - yuck - OPENSSL_gmtime_adj should go away */ if (!OPENSSL_gmtime_adj(lt, 0, offset)) goto err; } /* * We only allow fractional seconds to be present if we are in * the non-RFC case of a Generalized time. RFC 5280 forbids * fractional seconds. */ if (dot != NULL) { if (mode != V_ASN1_GENERALIZEDTIME) goto err; *dot++ = '\0'; if (!isdigit((unsigned char)*dot)) goto err; } /* * Validate and convert the time */ p = buf; tlen = strlen(buf); switch (tlen) { case 14: lt->tm_year = (ATOI2(p) * 100) - 1900; /* cc */ if (mode != RFC5280 && mode != V_ASN1_GENERALIZEDTIME) goto err; type = V_ASN1_GENERALIZEDTIME; /* FALLTHROUGH */ case 12: if (type == 0 && mode == V_ASN1_GENERALIZEDTIME) { /* * In the non-RFC case of a Generalized time * seconds may not have been provided. RFC * 5280 mandates that seconds must be present. */ noseconds = 1; lt->tm_year = (ATOI2(p) * 100) - 1900; /* cc */ type = V_ASN1_GENERALIZEDTIME; } /* FALLTHROUGH */ case 10: if (type == 0) { /* * At this point we must have a UTC time. * In the RFC 5280 case it must have the * seconds present. In the non-RFC case * may have no seconds. */ if (mode == V_ASN1_GENERALIZEDTIME) goto err; if (tlen == 10) { if (mode != V_ASN1_UTCTIME) goto err; noseconds = 1; } type = V_ASN1_UTCTIME; } lt->tm_year += ATOI2(p); /* yy */ if (type == V_ASN1_UTCTIME) { if (lt->tm_year < 50) lt->tm_year += 100; } lt->tm_mon = ATOI2(p) - 1; /* mm */ if (lt->tm_mon < 0 || lt->tm_mon > 11) goto err; lt->tm_mday = ATOI2(p); /* dd */ if (lt->tm_mday < 1 || lt->tm_mday > 31) goto err; lt->tm_hour = ATOI2(p); /* HH */ if (lt->tm_hour < 0 || lt->tm_hour > 23) goto err; lt->tm_min = ATOI2(p); /* MM */ if (lt->tm_hour < 0 || lt->tm_min > 59) goto err; lt->tm_sec = 0; /* SS */ if (noseconds) break; lt->tm_sec = ATOI2(p); /* Leap second 60 is not accepted. Reconsider later? */ if (lt->tm_hour < 0 || lt->tm_sec > 59) goto err; break; default: goto err; } /* RFC 5280 section 4.1.2.5 */ if (mode == RFC5280) { if (lt->tm_year < 150 && type != V_ASN1_UTCTIME) goto err; if (lt->tm_year >= 150 && type != V_ASN1_GENERALIZEDTIME) goto err; } ret = type; err: free(buf); return (ret); }