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