1/*
2 * Copyright (c) 2000-2001,2011-2012,2014 Apple Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19/*
20 * tpTime.c - cert related time functions
21 *
22 */
23
24#include "tpTime.h"
25#include <string.h>
26#include <stdlib.h>
27#include <stdio.h>
28#include <ctype.h>
29#include <stdbool.h>
30
31/*
32 * Given a string containing either a UTC-style or "generalized time"
33 * time string, convert to a CFDateRef. Returns nonzero on
34 * error.
35 */
36int timeStringToCfDate(
37	const char			*str,
38	unsigned			len,
39	CFDateRef			*cfDate)
40{
41	char 		szTemp[5];
42	bool 		isUtc = false;			// 2-digit year
43	bool		isLocal = false;		// trailing timezone offset
44	bool		isCssmTime = false;		// no trailing 'Z'
45	bool		noSeconds = false;
46	int             x;
47	unsigned 	i;
48	char 		*cp;
49	CFGregorianDate		gd;
50	CFTimeZoneRef		timeZone;
51	CFTimeInterval		gmtOff = 0;
52
53	if((str == NULL) || (len == 0) || (cfDate == NULL)) {
54    	return 1;
55  	}
56
57  	/* tolerate NULL terminated or not */
58  	if(str[len - 1] == '\0') {
59  		len--;
60  	}
61  	switch(len) {
62		case UTC_TIME_NOSEC_LEN:		// 2-digit year, no seconds, not y2K compliant
63			isUtc = true;
64			noSeconds = true;
65			break;
66  		case UTC_TIME_STRLEN:			// 2-digit year, not Y2K compliant
67  			isUtc = true;
68  			break;
69		case CSSM_TIME_STRLEN:
70			isCssmTime = true;
71			break;
72  		case GENERALIZED_TIME_STRLEN:	// 4-digit year
73  			break;
74 		case LOCALIZED_UTC_TIME_STRLEN:	// "YYMMDDhhmmssThhmm" (where T=[+,-])
75			isUtc = 1;
76			// deliberate fallthrough
77		case LOCALIZED_TIME_STRLEN:		// "YYYYMMDDhhmmssThhmm" (where T=[+,-])
78			isLocal = 1;
79			break;
80 		default:						// unknown format
81  			return 1;
82  	}
83
84  	cp = (char *)str;
85
86	/* check that all characters except last (or timezone indicator, if localized) are digits */
87	for(i=0; i<(len - 1); i++) {
88		if ( !(isdigit(cp[i])) )
89			if ( !isLocal || !(cp[i]=='+' || cp[i]=='-') )
90				return 1;
91	}
92
93  	/* check last character is a 'Z' or digit as appropriate */
94	if(isCssmTime || isLocal) {
95		if(!isdigit(cp[len - 1])) {
96			return 1;
97		}
98	}
99	else {
100		if(cp[len - 1] != 'Z' )	{
101			return 1;
102		}
103	}
104
105  	/* YEAR */
106	szTemp[0] = *cp++;
107	szTemp[1] = *cp++;
108	if(!isUtc) {
109		/* two more digits */
110		szTemp[2] = *cp++;
111		szTemp[3] = *cp++;
112		szTemp[4] = '\0';
113	}
114	else {
115		szTemp[2] = '\0';
116	}
117	x = atoi( szTemp );
118	if(isUtc) {
119		/*
120		 * 2-digit year.
121		 *   0  <= year <  50 : assume century 21
122		 *   50 <= year <  70 : illegal per PKIX
123		 *   ...though we allow this as of 10/10/02...dmitch
124		 *   70 <  year <= 99 : assume century 20
125		 */
126		if(x < 50) {
127			x += 2000;
128		}
129		/*
130		else if(x < 70) {
131			return 1;
132		}
133		*/
134		else {
135			/* century 20 */
136			x += 1900;
137		}
138	}
139	gd.year = x;
140
141  	/* MONTH */
142	szTemp[0] = *cp++;
143	szTemp[1] = *cp++;
144	szTemp[2] = '\0';
145	x = atoi( szTemp );
146	/* in the string, months are from 1 to 12 */
147	if((x > 12) || (x <= 0)) {
148    	return 1;
149	}
150	gd.month = x;
151
152 	/* DAY */
153	szTemp[0] = *cp++;
154	szTemp[1] = *cp++;
155	szTemp[2] = '\0';
156	x = atoi( szTemp );
157	/* 1..31 in both formats */
158	if((x > 31) || (x <= 0)) {
159		return 1;
160	}
161	gd.day = x;
162
163	/* HOUR */
164	szTemp[0] = *cp++;
165	szTemp[1] = *cp++;
166	szTemp[2] = '\0';
167	x = atoi( szTemp );
168	if((x > 23) || (x < 0)) {
169		return 1;
170	}
171	gd.hour = x;
172
173  	/* MINUTE */
174	szTemp[0] = *cp++;
175	szTemp[1] = *cp++;
176	szTemp[2] = '\0';
177	x = atoi( szTemp );
178	if((x > 59) || (x < 0)) {
179		return 1;
180	}
181	gd.minute = x;
182
183  	/* SECOND */
184	if(noSeconds) {
185		gd.second = 0;
186	}
187	else {
188		szTemp[0] = *cp++;
189		szTemp[1] = *cp++;
190		szTemp[2] = '\0';
191		x = atoi( szTemp );
192		if((x > 59) || (x < 0)) {
193			return 1;
194		}
195		gd.second = x;
196	}
197
198	if (isLocal) {
199		/* ZONE INDICATOR */
200		switch(*cp++) {
201			case '+':
202				gmtOff = 1;
203				break;
204			case '-':
205				gmtOff = -1;
206				break;
207			default:
208				return 1;
209		}
210	  	/* ZONE HH OFFSET */
211		szTemp[0] = *cp++;
212		szTemp[1] = *cp++;
213		szTemp[2] = '\0';
214		x = atoi( szTemp ) * 60 * 60;
215		gmtOff *= x;
216	  	/* ZONE MM OFFSET */
217		szTemp[0] = *cp++;
218		szTemp[1] = *cp++;
219		szTemp[2] = '\0';
220		x = atoi( szTemp ) * 60;
221		if(gmtOff < 0) {
222			gmtOff -= x;
223		}
224		else {
225			gmtOff += x;
226		}
227	}
228	timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, gmtOff);
229	if (!timeZone) {
230		return 1;
231	}
232	*cfDate = CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(gd, timeZone));
233	CFRelease(timeZone);
234	return 0;
235}
236
237/*
238 * Compare two times. Assumes they're both in GMT. Returns:
239 * -1 if t1 <  t2
240 *  0 if t1 == t2
241 *  1 if t1 >  t2
242 */
243int compareTimes(
244	CFDateRef 	t1,
245	CFDateRef 	t2)
246{
247	switch(CFDateCompare(t1, t2, NULL)) {
248		case kCFCompareLessThan:
249			return -1;
250		case kCFCompareEqualTo:
251			return 0;
252		case kCFCompareGreaterThan:
253			return 1;
254	}
255	/* NOT REACHED */
256	assert(0);
257	return 0;
258}
259
260/*
261 * Create a time string, in either UTC (2-digit) or or Generalized (4-digit)
262 * year format. Caller mallocs the output string whose length is at least
263 * (UTC_TIME_STRLEN+1), (GENERALIZED_TIME_STRLEN+1), or (CSSM_TIME_STRLEN+1)
264 * respectively. Caller must hold tpTimeLock.
265 */
266void timeAtNowPlus(unsigned secFromNow,
267	TpTimeSpec timeSpec,
268	char *outStr)
269{
270	struct tm utc;
271	time_t baseTime;
272
273	baseTime = time(NULL);
274	baseTime += (time_t)secFromNow;
275	utc = *gmtime(&baseTime);
276
277	switch(timeSpec) {
278		case TIME_UTC:
279			/* UTC - 2 year digits - code which parses this assumes that
280			 * (2-digit) years between 0 and 49 are in century 21 */
281			if(utc.tm_year >= 100) {
282				utc.tm_year -= 100;
283			}
284			sprintf(outStr, "%02d%02d%02d%02d%02d%02dZ",
285				utc.tm_year /* + 1900 */, utc.tm_mon + 1,
286				utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec);
287			break;
288		case TIME_GEN:
289			sprintf(outStr, "%04d%02d%02d%02d%02d%02dZ",
290				/* note year is relative to 1900, hopefully it'll have
291				* four valid digits! */
292				utc.tm_year + 1900, utc.tm_mon + 1,
293				utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec);
294			break;
295		case TIME_CSSM:
296			sprintf(outStr, "%04d%02d%02d%02d%02d%02d",
297				/* note year is relative to 1900, hopefully it'll have
298				* four valid digits! */
299				utc.tm_year + 1900, utc.tm_mon + 1,
300				utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec);
301			break;
302	}
303}
304
305/*
306 * Convert a time string, which can be in any of three forms (UTC,
307 * generalized, or CSSM_TIMESTRING) into a CSSM_TIMESTRING. Caller
308 * mallocs the result, which must be at least (CSSM_TIME_STRLEN+1) bytes.
309 * Returns nonzero if incoming time string is badly formed.
310 */
311int tpTimeToCssmTimestring(
312	const char 	*inStr,			// not necessarily NULL terminated
313	unsigned	inStrLen,		// not including possible NULL
314	char 		*outTime)
315{
316	if((inStrLen == 0) || (inStr == NULL)) {
317		return 1;
318	}
319	outTime[0] = '\0';
320	switch(inStrLen) {
321		case UTC_TIME_STRLEN:
322		{
323			/* infer century and prepend to output */
324			char tmp[3];
325			int year;
326			tmp[0] = inStr[0];
327			tmp[1] = inStr[1];
328			tmp[2] = '\0';
329			year = atoi(tmp);
330
331			/*
332			 *   0  <= year <  50 : assume century 21
333			 *   50 <= year <  70 : illegal per PKIX
334			 *   70 <  year <= 99 : assume century 20
335			 */
336			if(year < 50) {
337				/* century 21 */
338				strcpy(outTime, "20");
339			}
340			else if(year < 70) {
341				return 1;
342			}
343			else {
344				/* century 20 */
345				strcpy(outTime, "19");
346			}
347			memmove(outTime + 2, inStr, inStrLen - 1);		// don't copy the Z
348			break;
349		}
350		case CSSM_TIME_STRLEN:
351			memmove(outTime, inStr, inStrLen);				// trivial case
352			break;
353		case GENERALIZED_TIME_STRLEN:
354			memmove(outTime, inStr, inStrLen - 1);			// don't copy the Z
355			break;
356
357		default:
358			return 1;
359	}
360	outTime[CSSM_TIME_STRLEN] = '\0';
361	return 0;
362}
363
364
365