1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 #include "utils/s2n_asn1_time.h"
17 #include "utils/s2n_result.h"
18 #include "utils/s2n_safety.h"
19 
20 #include <time.h>
21 #include <ctype.h>
22 
23 typedef enum parser_state {
24     ON_YEAR_DIGIT_1 = 0,
25     ON_YEAR_DIGIT_2,
26     ON_YEAR_DIGIT_3,
27     ON_YEAR_DIGIT_4,
28     ON_MONTH_DIGIT_1,
29     ON_MONTH_DIGIT_2,
30     ON_DAY_DIGIT_1,
31     ON_DAY_DIGIT_2,
32     ON_HOUR_DIGIT_1,
33     ON_HOUR_DIGIT_2,
34     ON_MINUTE_DIGIT_1,
35     ON_MINUTE_DIGIT_2,
36     ON_SECOND_DIGIT_1,
37     ON_SECOND_DIGIT_2,
38     ON_SUBSECOND,
39     ON_TIMEZONE,
40     ON_OFFSET_HOURS_DIGIT_1,
41     ON_OFFSET_HOURS_DIGIT_2,
42     ON_OFFSET_MINUTES_DIGIT_1,
43     ON_OFFSET_MINUTES_DIGIT_2,
44     FINISHED,
45     PARSE_ERROR
46 } parser_state;
47 
get_gmt_offset(struct tm * t)48 static inline long get_gmt_offset(struct tm *t) {
49     /* See: https://sourceware.org/git/?p=glibc.git;a=blob;f=include/features.h;h=ba272078cf2263ec88e039fda7524c136a4a7953;hb=HEAD */
50 #if defined(__USE_MISC) || defined(__ANDROID__) || defined(ANDROID) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD__)
51     return t->tm_gmtoff;
52 #else
53     return t->__tm_gmtoff;
54 #endif
55 }
56 
get_current_timesettings(long * gmt_offset,int * is_dst)57 static inline void get_current_timesettings(long *gmt_offset, int *is_dst) {
58     struct tm time_ptr = {0};
59     time_t raw_time;
60     time(&raw_time);
61     localtime_r(&raw_time, &time_ptr);
62     *gmt_offset = get_gmt_offset(&time_ptr);
63     *is_dst = time_ptr.tm_isdst;
64 }
65 
66 #define PARSE_DIGIT(c, d)  do { RESULT_ENSURE(isdigit(c), S2N_ERR_SAFETY); d = c - '0'; } while(0)
67 
68 /* this is just a standard state machine for ASN1 date format... nothing special.
69  * just do a character at a time and change the state per character encountered.
70  * when finished the above time structure should be filled in along with some
71  * crazy timezone info we'll need shortly afterwards.*/
process_state(parser_state * state,char current_char,struct parser_args * args)72 static S2N_RESULT process_state(parser_state *state, char current_char, struct parser_args *args) {
73     switch (*state) {
74         case ON_YEAR_DIGIT_1:
75             PARSE_DIGIT(current_char, args->current_digit);
76             args->time.tm_year = args->current_digit;
77             *state = ON_YEAR_DIGIT_2;
78             return S2N_RESULT_OK;
79         case ON_YEAR_DIGIT_2:
80             PARSE_DIGIT(current_char, args->current_digit);
81             args->time.tm_year = args->time.tm_year * 10 + args->current_digit;
82             *state = ON_YEAR_DIGIT_3;
83             return S2N_RESULT_OK;
84         case ON_YEAR_DIGIT_3:
85             PARSE_DIGIT(current_char, args->current_digit);
86             args->time.tm_year = args->time.tm_year * 10 + args->current_digit;
87             *state = ON_YEAR_DIGIT_4;
88             return S2N_RESULT_OK;
89         case ON_YEAR_DIGIT_4:
90             PARSE_DIGIT(current_char, args->current_digit);
91             args->time.tm_year = args->time.tm_year * 10 + args->current_digit;
92             args->time.tm_year -= 1900;
93             if (args->time.tm_year < 0) {
94                 return S2N_RESULT_ERROR;
95             }
96 
97             *state = ON_MONTH_DIGIT_1;
98             return S2N_RESULT_OK;
99         case ON_MONTH_DIGIT_1:
100             PARSE_DIGIT(current_char, args->current_digit);
101             args->time.tm_mon = args->current_digit;
102             *state = ON_MONTH_DIGIT_2;
103             return S2N_RESULT_OK;
104         case ON_MONTH_DIGIT_2:
105             PARSE_DIGIT(current_char, args->current_digit);
106             args->time.tm_mon = args->time.tm_mon * 10 + args->current_digit;
107             args->time.tm_mon -= 1;
108 
109             if (args->time.tm_mon < 0 || args->time.tm_mon > 11) {
110                 return S2N_RESULT_ERROR;
111             }
112 
113             *state = ON_DAY_DIGIT_1;
114             return S2N_RESULT_OK;
115         case ON_DAY_DIGIT_1:
116             PARSE_DIGIT(current_char, args->current_digit);
117             args->time.tm_mday = args->current_digit;
118             *state = ON_DAY_DIGIT_2;
119             return S2N_RESULT_OK;
120         case ON_DAY_DIGIT_2:
121             PARSE_DIGIT(current_char, args->current_digit);
122             args->time.tm_mday = args->time.tm_mday * 10 + args->current_digit;
123 
124             if (args->time.tm_mday < 0 || args->time.tm_mday > 31) {
125                 return S2N_RESULT_ERROR;
126             }
127 
128             *state = ON_HOUR_DIGIT_1;
129             return S2N_RESULT_OK;
130         case ON_HOUR_DIGIT_1:
131             PARSE_DIGIT(current_char, args->current_digit);
132             args->time.tm_hour = args->current_digit;
133             *state = ON_HOUR_DIGIT_2;
134             return S2N_RESULT_OK;
135         case ON_HOUR_DIGIT_2:
136             PARSE_DIGIT(current_char, args->current_digit);
137             args->time.tm_hour = args->time.tm_hour * 10 + args->current_digit;
138 
139             if (args->time.tm_hour < 0 || args->time.tm_hour > 23) {
140                 return S2N_RESULT_ERROR;
141             }
142 
143             *state = ON_MINUTE_DIGIT_1;
144             return S2N_RESULT_OK;
145         case ON_MINUTE_DIGIT_1:
146             PARSE_DIGIT(current_char, args->current_digit);
147             args->time.tm_min = args->current_digit;
148             *state = ON_MINUTE_DIGIT_2;
149             return S2N_RESULT_OK;
150         case ON_MINUTE_DIGIT_2:
151             PARSE_DIGIT(current_char, args->current_digit);
152             args->time.tm_min = args->time.tm_min * 10 + args->current_digit;
153 
154             if (args->time.tm_min < 0 || args->time.tm_min > 59) {
155                 return S2N_RESULT_ERROR;
156             }
157 
158             *state = ON_SECOND_DIGIT_1;
159             return S2N_RESULT_OK;
160         case ON_SECOND_DIGIT_1:
161             PARSE_DIGIT(current_char, args->current_digit);
162             args->time.tm_sec = args->current_digit;
163             *state = ON_SECOND_DIGIT_2;
164             return S2N_RESULT_OK;
165         case ON_SECOND_DIGIT_2:
166             PARSE_DIGIT(current_char, args->current_digit);
167             args->time.tm_sec = args->time.tm_sec * 10 + args->current_digit;
168 
169             if (args->time.tm_sec < 0 || args->time.tm_sec > 59) {
170                 return S2N_RESULT_ERROR;
171             }
172 
173             *state = ON_SUBSECOND;
174             return S2N_RESULT_OK;
175         case ON_SUBSECOND:
176             if (current_char == '.' || isdigit(current_char)) {
177                 *state = ON_SUBSECOND;
178                 return S2N_RESULT_OK;
179             }
180             FALL_THROUGH;
181         case ON_TIMEZONE:
182             if (current_char == 'Z' || current_char == 'z') {
183                 args->local_time_assumed = 0;
184                 *state = FINISHED;
185                 return S2N_RESULT_OK;
186             } else if (current_char == '-') {
187                 args->local_time_assumed = 0;
188                 args->offset_negative = 1;
189                 *state = ON_OFFSET_HOURS_DIGIT_1;
190                 return S2N_RESULT_OK;
191             } else if (current_char == '+') {
192                 args->local_time_assumed = 0;
193                 args->offset_negative = 0;
194                 *state = ON_OFFSET_HOURS_DIGIT_1;
195                 return S2N_RESULT_OK;
196             }
197 
198             return S2N_RESULT_ERROR;
199         case ON_OFFSET_HOURS_DIGIT_1:
200             PARSE_DIGIT(current_char, args->current_digit);
201             args->offset_hours = args->current_digit;
202             *state = ON_OFFSET_HOURS_DIGIT_2;
203             return S2N_RESULT_OK;
204         case ON_OFFSET_HOURS_DIGIT_2:
205             PARSE_DIGIT(current_char, args->current_digit);
206             args->offset_hours = args->offset_hours * 10 + args->current_digit;
207 
208             if (args->offset_hours < 0 || args->offset_hours > 23) {
209                 return S2N_RESULT_ERROR;
210             }
211 
212             *state = ON_OFFSET_MINUTES_DIGIT_1;
213             return S2N_RESULT_OK;
214         case ON_OFFSET_MINUTES_DIGIT_1:
215             PARSE_DIGIT(current_char, args->current_digit);
216             args->offset_minutes = args->current_digit;
217             *state = ON_OFFSET_MINUTES_DIGIT_2;
218             return S2N_RESULT_OK;
219         case ON_OFFSET_MINUTES_DIGIT_2:
220             PARSE_DIGIT(current_char, args->current_digit);
221             args->offset_minutes = args->offset_minutes * 10 + args->current_digit;
222 
223             if (args->offset_minutes < 0 || args->offset_minutes > 23) {
224                 return S2N_RESULT_ERROR;
225             }
226 
227             *state = FINISHED;
228             return S2N_RESULT_OK;
229         default:
230             return S2N_RESULT_ERROR;
231     }
232 }
233 
s2n_asn1_time_to_nano_since_epoch_ticks(const char * asn1_time,uint32_t len,uint64_t * ticks)234 S2N_RESULT s2n_asn1_time_to_nano_since_epoch_ticks(const char *asn1_time, uint32_t len, uint64_t *ticks) {
235 
236     /* figure out if we are on something other than UTC since timegm is not supported everywhere. */
237     long gmt_offset_current = 0;
238     int is_dst = 0;
239     get_current_timesettings(&gmt_offset_current, &is_dst);
240 
241     uint32_t str_len = len;
242     parser_state state = ON_YEAR_DIGIT_1;
243 
244     struct parser_args args = {
245         .time = {.tm_hour = 0, .tm_isdst = -1, .tm_mday = 0, .tm_min = 0, .tm_mon = 0,
246                 .tm_sec = 0, .tm_wday = 0, .tm_yday = 0, .tm_year = 0,
247         },
248             .current_digit = 0,
249             .local_time_assumed = 1,
250             .offset_hours = 0,
251             .offset_minutes = 0,
252             .offset_negative = 0
253     };
254 
255     size_t current_pos = 0;
256 
257     while (state < FINISHED && current_pos < str_len) {
258         char current_char = asn1_time[current_pos];
259         RESULT_ENSURE_OK(process_state(&state, current_char, &args), S2N_ERR_INVALID_ARGUMENT);
260         current_pos++;
261     }
262 
263     /* state on subsecond means no timezone info was found and we assume local time */
264     RESULT_ENSURE(state == FINISHED || state == ON_SUBSECOND, S2N_ERR_INVALID_ARGUMENT);
265 
266     time_t clock_data = mktime(&args.time);
267     RESULT_ENSURE_GTE(clock_data, 0);
268 
269     /* ASN1 + and - is in format HHMM. We need to convert it to seconds for the adjustment */
270     long gmt_offset = (args.offset_hours * 3600) + (args.offset_minutes * 60);
271 
272     if (args.offset_negative) {
273         gmt_offset = 0 - gmt_offset;
274     }
275 
276     /* if we detected UTC is being used (please always use UTC), we need to add the detected timezone on the local
277      * machine back to the offset. Also, the offset includes an offset for daylight savings time. When the time being parsed
278      * and the local time are on different sides of the dst barrier, the offset has to be adjusted to account for it. */
279     if (!args.local_time_assumed) {
280         gmt_offset -= gmt_offset_current;
281         gmt_offset -= args.time.tm_isdst != is_dst ? (args.time.tm_isdst - is_dst) * 3600 : 0;
282     }
283 
284     RESULT_ENSURE_GTE(clock_data, gmt_offset);
285 
286     /* convert to nanoseconds and add the timezone offset. */
287     *ticks = ((uint64_t) clock_data - gmt_offset) * 1000000000;
288     return S2N_RESULT_OK;
289 }
290