/src/openssl/crypto/asn1/a_time_posix.c
Line  | Count  | Source  | 
1  |  | /*  | 
2  |  |  * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.  | 
3  |  |  *  | 
4  |  |  * Licensed under the Apache License 2.0 (the "License").  You may not use  | 
5  |  |  * this file except in compliance with the License.  You can obtain a copy  | 
6  |  |  * in the file LICENSE in the source distribution or at  | 
7  |  |  * https://www.openssl.org/source/license.html  | 
8  |  |  */  | 
9  |  |  | 
10  |  | /*  | 
11  |  |  * Time conversion to/from POSIX time_t and struct tm, with no support  | 
12  |  |  * for time zones other than UTC  | 
13  |  |  */  | 
14  |  |  | 
15  |  | #include <inttypes.h>  | 
16  |  | #include <limits.h>  | 
17  |  | #include <stdint.h>  | 
18  |  | #include <string.h>  | 
19  |  | #include <time.h>  | 
20  |  |  | 
21  |  | #include <openssl/asn1.h>  | 
22  |  | #include <openssl/posix_time.h>  | 
23  |  |  | 
24  |  | #include "asn1_local.h"  | 
25  |  |  | 
26  | 0  | #define SECS_PER_HOUR (int64_t)(60 * 60)  | 
27  | 0  | #define SECS_PER_DAY (int64_t)(24 * SECS_PER_HOUR)  | 
28  |  |  | 
29  |  | /*  | 
30  |  |  * Is a year/month/day combination valid, in the range from year 0000  | 
31  |  |  * to 9999?  | 
32  |  |  */  | 
33  |  | static int is_valid_date(int64_t year, int64_t month, int64_t day)  | 
34  | 0  | { | 
35  | 0  |     int days_in_month;  | 
36  |  | 
  | 
37  | 0  |     if (day < 1 || year < 0 || year > 9999)  | 
38  | 0  |         return 0;  | 
39  | 0  |     switch (month) { | 
40  | 0  |     case 1:  | 
41  | 0  |     case 3:  | 
42  | 0  |     case 5:  | 
43  | 0  |     case 7:  | 
44  | 0  |     case 8:  | 
45  | 0  |     case 10:  | 
46  | 0  |     case 12:  | 
47  | 0  |         days_in_month = 31;  | 
48  | 0  |         break;  | 
49  | 0  |     case 4:  | 
50  | 0  |     case 6:  | 
51  | 0  |     case 9:  | 
52  | 0  |     case 11:  | 
53  | 0  |         days_in_month = 30;  | 
54  | 0  |         break;  | 
55  | 0  |     case 2:  | 
56  | 0  |         if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)  | 
57  | 0  |             days_in_month = 29;  | 
58  | 0  |         else  | 
59  | 0  |             days_in_month = 28;  | 
60  | 0  |         break;  | 
61  | 0  |     default:  | 
62  | 0  |         return 0;  | 
63  | 0  |     }  | 
64  | 0  |     return day <= days_in_month;  | 
65  | 0  | }  | 
66  |  |  | 
67  |  | /*  | 
68  |  |  * Is a time valid? Leap seconds of 60 are not considered valid, as  | 
69  |  |  * the POSIX time in seconds does not include them.  | 
70  |  |  */  | 
71  |  | static int is_valid_time(int64_t hours, int64_t minutes, int64_t seconds)  | 
72  | 0  | { | 
73  | 0  |     return hours >= 0 && minutes >= 0 && seconds >= 0 && hours <= 23 &&  | 
74  | 0  |         minutes <= 59 && seconds <= 59;  | 
75  | 0  | }  | 
76  |  |  | 
77  |  | /* 0000-01-01 00:00:00 UTC */  | 
78  | 0  | #define MIN_POSIX_TIME INT64_C(-62167219200)  | 
79  |  | /* 9999-12-31 23:59:59 UTC */  | 
80  | 0  | #define MAX_POSIX_TIME INT64_C(253402300799)  | 
81  |  |  | 
82  |  | /* Is a int64 time representing a time within our expected range? */  | 
83  |  | static int is_valid_posix_time(int64_t time)  | 
84  | 0  | { | 
85  | 0  |     return MIN_POSIX_TIME <= time && time <= MAX_POSIX_TIME;  | 
86  | 0  | }  | 
87  |  |  | 
88  |  | /*  | 
89  |  |  * Inspired by algorithms presented in  | 
90  |  |  * https://howardhinnant.github.io/date_algorithms.html  | 
91  |  |  * (Public Domain)  | 
92  |  |  */  | 
93  |  | static int posix_time_from_utc(int64_t year, int64_t month, int64_t day,  | 
94  |  |                                int64_t hours, int64_t minutes, int64_t seconds,  | 
95  |  |                                int64_t *out_time)  | 
96  | 0  | { | 
97  | 0  |     int64_t era, year_of_era, day_of_year, day_of_era, posix_days;  | 
98  |  | 
  | 
99  | 0  |     if (!is_valid_date(year, month, day) ||  | 
100  | 0  |         !is_valid_time(hours, minutes, seconds))  | 
101  | 0  |         return 0;  | 
102  | 0  |     if (month <= 2)  | 
103  | 0  |         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  | 0  |     era = (year >= 0 ? year : year - 399) / 400;  | 
107  | 0  |     year_of_era = year - era * 400;  | 
108  | 0  |     day_of_year = (153 * (month > 2 ? month - 3 : month + 9) + 2) /  | 
109  | 0  |         5 + day - 1;  | 
110  | 0  |     day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era /  | 
111  | 0  |         100 + day_of_year;  | 
112  | 0  |     posix_days = era * 146097 + day_of_era - 719468;  | 
113  | 0  |     *out_time = posix_days * SECS_PER_DAY + hours * SECS_PER_HOUR +  | 
114  | 0  |         minutes * 60 + seconds;  | 
115  |  | 
  | 
116  | 0  |     return 1;  | 
117  | 0  | }  | 
118  |  |  | 
119  |  | /*  | 
120  |  |  * Inspired by algorithms presented in  | 
121  |  |  * https://howardhinnant.github.io/date_algorithms.html  | 
122  |  |  * (Public Domain)  | 
123  |  |  */  | 
124  |  | static int utc_from_posix_time(int64_t time, int *out_year, int *out_month,  | 
125  |  |                                int *out_day, int *out_hours, int *out_minutes,  | 
126  |  |                                int *out_seconds)  | 
127  | 0  | { | 
128  | 0  |     int64_t days, leftover_seconds, era, day_of_era, year_of_era, day_of_year;  | 
129  | 0  |     int64_t month_of_year;  | 
130  |  | 
  | 
131  | 0  |     if (!is_valid_posix_time(time))  | 
132  | 0  |         return 0;  | 
133  |  |  | 
134  | 0  |     days = time / SECS_PER_DAY;  | 
135  | 0  |     leftover_seconds = time % SECS_PER_DAY;  | 
136  | 0  |     if (leftover_seconds < 0) { | 
137  | 0  |         days--;  | 
138  | 0  |         leftover_seconds += SECS_PER_DAY;  | 
139  | 0  |     }  | 
140  | 0  |     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  | 0  |     era = (days > 0 ? days : days - 146096) / 146097;  | 
144  | 0  |     day_of_era = days - era * 146097;  | 
145  | 0  |     year_of_era = (day_of_era - day_of_era / 1460 + day_of_era / 36524 -  | 
146  | 0  |                    day_of_era / 146096) / 365;  | 
147  | 0  |     *out_year = (int) (year_of_era + era * 400);  /* Year starts on Mar 1 */  | 
148  | 0  |     day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 -  | 
149  | 0  |                                 year_of_era / 100);  | 
150  | 0  |     month_of_year = (5 * day_of_year + 2) / 153;  | 
151  | 0  |     *out_month = (int) (month_of_year < 10 ? month_of_year + 3 :  | 
152  | 0  |                         month_of_year - 9);  | 
153  | 0  |     if (*out_month <= 2)  | 
154  | 0  |         (*out_year)++;  /* Adjust year back to Jan 1 start of year. */  | 
155  |  | 
  | 
156  | 0  |     *out_day = (int) (day_of_year - (153 * month_of_year + 2) / 5 + 1);  | 
157  | 0  |     *out_hours = (int) leftover_seconds / SECS_PER_HOUR;  | 
158  | 0  |     leftover_seconds %= SECS_PER_HOUR;  | 
159  | 0  |     *out_minutes = (int) leftover_seconds / 60;  | 
160  | 0  |     *out_seconds = (int) leftover_seconds % 60;  | 
161  |  | 
  | 
162  | 0  |     return 1;  | 
163  | 0  | }  | 
164  |  |  | 
165  |  | int OPENSSL_tm_to_posix(const struct tm *tm, int64_t *out)  | 
166  | 0  | { | 
167  | 0  |     return posix_time_from_utc(tm->tm_year + (int64_t)1900,  | 
168  | 0  |                                tm->tm_mon + (int64_t)1, tm->tm_mday,  | 
169  | 0  |                                tm->tm_hour, tm->tm_min, tm->tm_sec, out);  | 
170  | 0  | }  | 
171  |  |  | 
172  |  | int OPENSSL_posix_to_tm(int64_t time, struct tm *out_tm)  | 
173  | 0  | { | 
174  | 0  |     struct tm tmp_tm = {0}; | 
175  |  | 
  | 
176  | 0  |     memset(out_tm, 0, sizeof(*out_tm));  | 
177  |  | 
  | 
178  | 0  |     if (!utc_from_posix_time(time, &tmp_tm.tm_year, &tmp_tm.tm_mon,  | 
179  | 0  |                              &tmp_tm.tm_mday, &tmp_tm.tm_hour,  | 
180  | 0  |                              &tmp_tm.tm_min, &tmp_tm.tm_sec))  | 
181  | 0  |         return 0;  | 
182  |  |  | 
183  | 0  |     tmp_tm.tm_year -= 1900;  | 
184  | 0  |     tmp_tm.tm_mon -= 1;  | 
185  |  | 
  | 
186  | 0  |     *out_tm = tmp_tm;  | 
187  |  | 
  | 
188  | 0  |     return 1;  | 
189  | 0  | }  | 
190  |  |  | 
191  |  | int ossl_asn1_time_tm_to_time_t(const struct tm *tm, time_t *out)  | 
192  | 0  | { | 
193  | 0  |     int64_t posix_time;  | 
194  | 0  |     time_t test_t = -1;  | 
195  | 0  |     int bad_idea_bears = (test_t > 0); /* time_t is unsigned */  | 
196  |  | 
  | 
197  | 0  |     if (!OPENSSL_tm_to_posix(tm, &posix_time))  | 
198  | 0  |         return 0;  | 
199  |  |  | 
200  | 0  |     if (sizeof(time_t) == sizeof(int32_t)  | 
201  | 0  |         && ((!bad_idea_bears && (posix_time > INT32_MAX  | 
202  | 0  |                                      || posix_time < INT32_MIN))  | 
203  | 0  |             || (bad_idea_bears && (posix_time > UINT32_MAX  | 
204  | 0  |                                    || posix_time < 0))))  | 
205  | 0  |         return 0;  | 
206  |  |  | 
207  | 0  |     *out = posix_time;  | 
208  | 0  |     return 1;  | 
209  | 0  | }  | 
210  |  |  | 
211  |  | int ossl_asn1_time_time_t_to_tm(const time_t *time, struct tm *out_tm)  | 
212  | 0  | { | 
213  | 0  |     int64_t posix_time = *time;  | 
214  |  | 
  | 
215  | 0  |     return OPENSSL_posix_to_tm(posix_time, out_tm);  | 
216  | 0  | }  | 
217  |  |  | 
218  |  | int OPENSSL_timegm(const struct tm *tm, time_t *out)  | 
219  | 0  | { | 
220  | 0  |     return ossl_asn1_time_tm_to_time_t(tm, out);  | 
221  | 0  | }  | 
222  |  |  | 
223  |  | struct tm * OPENSSL_gmtime(const time_t *time, struct tm *out_tm)  | 
224  | 0  | { | 
225  | 0  |     if (!ossl_asn1_time_time_t_to_tm(time, out_tm))  | 
226  | 0  |         return NULL;  | 
227  | 0  |     return out_tm;  | 
228  | 0  | }  | 
229  |  |  | 
230  |  | /* LibreSSL and BoringSSL use int64_t instead of long. */  | 
231  |  | int OPENSSL_gmtime_adj(struct tm *tm, int offset_day, long offset_sec)  | 
232  | 0  | { | 
233  | 0  |     int64_t posix_time;  | 
234  |  | 
  | 
235  | 0  |     if (!OPENSSL_tm_to_posix(tm, &posix_time))  | 
236  | 0  |         return 0;  | 
237  |  |  | 
238  | 0  |     OPENSSL_assert(INT_MAX <= INT64_MAX / SECS_PER_DAY);  | 
239  | 0  |     OPENSSL_assert(MAX_POSIX_TIME <= INT64_MAX - INT_MAX * SECS_PER_DAY);  | 
240  | 0  |     OPENSSL_assert(MIN_POSIX_TIME >= INT64_MIN - INT_MIN * SECS_PER_DAY);  | 
241  |  | 
  | 
242  | 0  |     posix_time += offset_day * SECS_PER_DAY;  | 
243  |  | 
  | 
244  | 0  |     if (posix_time > 0 && offset_sec > INT64_MAX - posix_time)  | 
245  | 0  |         return 0;  | 
246  | 0  |     if (posix_time < 0 && offset_sec < INT64_MIN - posix_time)  | 
247  | 0  |         return 0;  | 
248  | 0  |     posix_time += offset_sec;  | 
249  |  | 
  | 
250  | 0  |     if (!OPENSSL_posix_to_tm(posix_time, tm))  | 
251  | 0  |         return 0;  | 
252  |  |  | 
253  | 0  |     return 1;  | 
254  | 0  | }  | 
255  |  |  | 
256  |  | int OPENSSL_gmtime_diff(int *out_days, int *out_secs, const struct tm *from,  | 
257  |  |                         const struct tm *to)  | 
258  | 0  | { | 
259  | 0  |     int64_t time_to, time_from, timediff, daydiff;  | 
260  |  | 
  | 
261  | 0  |     if (!OPENSSL_tm_to_posix(to, &time_to) ||  | 
262  | 0  |         !OPENSSL_tm_to_posix(from, &time_from))  | 
263  | 0  |         return 0;  | 
264  |  |  | 
265  |  |     /* Times are in range, so these calculations cannot overflow. */  | 
266  | 0  |     OPENSSL_assert(SECS_PER_DAY <= INT_MAX);  | 
267  | 0  |     OPENSSL_assert((MAX_POSIX_TIME - MIN_POSIX_TIME) / SECS_PER_DAY <= INT_MAX);  | 
268  |  | 
  | 
269  | 0  |     timediff = time_to - time_from;  | 
270  | 0  |     daydiff = timediff / SECS_PER_DAY;  | 
271  | 0  |     timediff %= SECS_PER_DAY;  | 
272  |  | 
  | 
273  | 0  |     *out_secs = (int) timediff;  | 
274  | 0  |     *out_days = (int) daydiff;  | 
275  |  | 
  | 
276  | 0  |     return 1;  | 
277  | 0  | }  |