summaryrefslogtreecommitdiff
path: root/src/lib/libcrypto/asn1/a_time_posix.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/libcrypto/asn1/a_time_posix.c')
-rw-r--r--src/lib/libcrypto/asn1/a_time_posix.c272
1 files changed, 272 insertions, 0 deletions
diff --git a/src/lib/libcrypto/asn1/a_time_posix.c b/src/lib/libcrypto/asn1/a_time_posix.c
new file mode 100644
index 0000000000..a2c9988575
--- /dev/null
+++ b/src/lib/libcrypto/asn1/a_time_posix.c
@@ -0,0 +1,272 @@
1/*
2 * Copyright (c) 2022, Google Inc.
3 * Copyright (c) 2022, Bob Beck <beck@obtuse.com>
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
12 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
14 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
15 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18/*
19 * Time conversion to/from POSIX time_t and struct tm, with no support
20 * for time zones other than UTC
21 */
22
23#include <inttypes.h>
24#include <limits.h>
25#include <string.h>
26#include <time.h>
27
28#define SECS_PER_HOUR (int64_t)(60 * 60)
29#define SECS_PER_DAY (int64_t)(24 * SECS_PER_HOUR)
30
31/*
32 * Is a year/month/day combination valid, in the range from year 0000
33 * to 9999?
34 */
35static int
36is_valid_date(int year, int month, int day)
37{
38 int days_in_month;
39 if (day < 1 || month < 1 || year < 0 || year > 9999)
40 return 0;
41 switch (month) {
42 case 1:
43 case 3:
44 case 5:
45 case 7:
46 case 8:
47 case 10:
48 case 12:
49 days_in_month = 31;
50 break;
51 case 4:
52 case 6:
53 case 9:
54 case 11:
55 days_in_month = 30;
56 break;
57 case 2:
58 if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
59 days_in_month = 29;
60 else
61 days_in_month = 28;
62 break;
63 default:
64 return 0;
65 }
66 return day <= days_in_month;
67}
68
69/*
70 * Is a time valid? Leap seconds of 60 are not considered valid, as
71 * the POSIX time in seconds does not include them.
72 */
73static int
74is_valid_time(int hours, int minutes, int seconds)
75{
76 return hours >= 0 && minutes >= 0 && seconds >= 0 && hours <= 23 &&
77 minutes <= 59 && seconds <= 59;
78}
79
80/* Is a int64 time representing a time within our expected range? */
81static int
82is_valid_epoch_time(int64_t time)
83{
84 /* 0000-01-01 00:00:00 UTC to 9999-12-31 23:59:59 UTC */
85 return (int64_t)-62167219200 <= time && time <= (int64_t)253402300799;
86}
87
88/*
89 * Inspired by algorithms presented in
90 * https://howardhinnant.github.io/date_algorithms.html
91 * (Public Domain)
92 */
93static int
94posix_time_from_utc(int year, int month, int day, int hours, int minutes,
95 int seconds, int64_t *out_time)
96{
97 int64_t era, year_of_era, day_of_year, day_of_era, posix_days;
98
99 if (!is_valid_date(year, month, day) ||
100 !is_valid_time(hours, minutes, seconds))
101 return 0;
102 if (month <= 2)
103 year--; /* Start years on Mar 1, so leap days end a year. */
104
105 /* At this point year will be in the range -1 and 9999.*/
106 era = (year >= 0 ? year : year - 399) / 400;
107 year_of_era = year - era * 400;
108 day_of_year = (153 * (month > 2 ? month - 3 : month + 9) + 2) /
109 5 + day - 1;
110 day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era /
111 100 + day_of_year;
112 posix_days = era * 146097 + day_of_era - 719468;
113 *out_time = posix_days * SECS_PER_DAY + hours * SECS_PER_HOUR +
114 minutes * 60 + seconds;
115
116 return 1;
117}
118
119/*
120 * Inspired by algorithms presented in
121 * https://howardhinnant.github.io/date_algorithms.html
122 * (Public Domain)
123 */
124static int
125utc_from_posix_time(int64_t time, int *out_year, int *out_month, int *out_day,
126 int *out_hours, int *out_minutes, int *out_seconds)
127{
128 int64_t days, leftover_seconds, era, day_of_era, year_of_era,
129 day_of_year, month_of_year;
130
131 if (!is_valid_epoch_time(time))
132 return 0;
133
134 days = time / SECS_PER_DAY;
135 leftover_seconds = time % SECS_PER_DAY;
136 if (leftover_seconds < 0) {
137 days--;
138 leftover_seconds += SECS_PER_DAY;
139 }
140 days += 719468; /* Shift to starting epoch of Mar 1 0000. */
141
142 /* At this point, days will be in the range -61 and 3652364. */
143 era = (days > 0 ? days : days - 146096) / 146097;
144 day_of_era = days - era * 146097;
145 year_of_era = (day_of_era - day_of_era / 1460 + day_of_era / 36524 -
146 day_of_era / 146096) /
147 365;
148 *out_year = year_of_era + era * 400; /* Year starts on Mar 1 */
149 day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 -
150 year_of_era / 100);
151 month_of_year = (5 * day_of_year + 2) / 153;
152 *out_month = (month_of_year < 10 ? month_of_year + 3 :
153 month_of_year - 9);
154 if (*out_month <= 2)
155 (*out_year)++; /* Adjust year back to Jan 1 start of year. */
156
157 *out_day = day_of_year - (153 * month_of_year + 2) / 5 + 1;
158 *out_hours = leftover_seconds / SECS_PER_HOUR;
159 leftover_seconds %= SECS_PER_HOUR;
160 *out_minutes = leftover_seconds / 60;
161 *out_seconds = leftover_seconds % 60;
162
163 return 1;
164}
165
166static int
167asn1_time_tm_to_posix(const struct tm *tm, int64_t *out)
168{
169 /* Ensure additions below do not overflow */
170 if (tm->tm_year > 9999)
171 return 0;
172 if (tm->tm_mon > 12)
173 return 0;
174
175 return posix_time_from_utc(tm->tm_year + 1900, tm->tm_mon + 1,
176 tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, out);
177}
178
179static int
180asn1_time_posix_to_tm(int64_t time, struct tm *out_tm)
181{
182 memset(out_tm, 0, sizeof(struct tm));
183 if (!utc_from_posix_time(time, &out_tm->tm_year, &out_tm->tm_mon,
184 &out_tm->tm_mday, &out_tm->tm_hour, &out_tm->tm_min,
185 &out_tm->tm_sec))
186 return 0;
187
188 out_tm->tm_year -= 1900;
189 out_tm->tm_mon -= 1;
190
191 return 1;
192}
193
194int
195asn1_time_tm_to_time_t(const struct tm *tm, time_t *out)
196{
197 int64_t posix_time;
198
199 if (!asn1_time_tm_to_posix(tm, &posix_time))
200 return 0;
201
202#ifdef SMALL_TIME_T
203 /* For portable. */
204 if (sizeof(time_t) == sizeof(int32_t) &&
205 (posix_time > INT32_MAX || posix_time < INT32_MIN))
206 return 0;
207#endif
208
209 *out = posix_time;
210 return 1;
211}
212
213int
214asn1_time_time_t_to_tm(const time_t *time, struct tm *out_tm)
215{
216 int64_t posix_time = *time;
217
218 return asn1_time_posix_to_tm(posix_time, out_tm);
219}
220
221int
222OPENSSL_gmtime_adj(struct tm *tm, int off_day, long offset_sec)
223{
224 int64_t posix_time;
225
226 /* Ensure additions below do not overflow */
227 if (tm->tm_year > 9999)
228 return 0;
229 if (tm->tm_mon > 12)
230 return 0;
231
232 if (!posix_time_from_utc(tm->tm_year + 1900, tm->tm_mon + 1,
233 tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, &posix_time))
234 return 0;
235
236 if (!utc_from_posix_time(posix_time + off_day * SECS_PER_DAY +
237 offset_sec, &tm->tm_year, &tm->tm_mon, &tm->tm_mday, &tm->tm_hour,
238 &tm->tm_min, &tm->tm_sec))
239 return 0;
240
241 tm->tm_year -= 1900;
242 tm->tm_mon -= 1;
243
244 return 1;
245}
246
247int
248OPENSSL_gmtime_diff(int *out_days, int *out_secs, const struct tm *from,
249 const struct tm *to)
250{
251 int64_t time_to, time_from, timediff, daydiff;
252
253 if (!posix_time_from_utc(to->tm_year + 1900, to->tm_mon + 1,
254 to->tm_mday, to->tm_hour, to->tm_min, to->tm_sec, &time_to))
255 return 0;
256
257 if (!posix_time_from_utc(from->tm_year + 1900, from->tm_mon + 1,
258 from->tm_mday, from->tm_hour, from->tm_min,
259 from->tm_sec, &time_from))
260 return 0;
261
262 timediff = time_to - time_from;
263 daydiff = timediff / SECS_PER_DAY;
264 timediff %= SECS_PER_DAY;
265 if (daydiff > INT_MAX || daydiff < INT_MIN)
266 return 0;
267
268 *out_secs = timediff;
269 *out_days = daydiff;
270
271 return 1;
272}