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