/ OSX / libsecurity_keychain / lib / cssmdatetime.cpp
cssmdatetime.cpp
  1  /*
  2   * Copyright (c) 2000-2004,2011-2014 Apple Inc. All Rights Reserved.
  3   * 
  4   * @APPLE_LICENSE_HEADER_START@
  5   * 
  6   * This file contains Original Code and/or Modifications of Original Code
  7   * as defined in and that are subject to the Apple Public Source License
  8   * Version 2.0 (the 'License'). You may not use this file except in
  9   * compliance with the License. Please obtain a copy of the License at
 10   * http://www.opensource.apple.com/apsl/ and read it before using this
 11   * file.
 12   * 
 13   * The Original Code and all software distributed under the License are
 14   * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 15   * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 16   * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 17   * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 18   * Please see the License for the specific language governing rights and
 19   * limitations under the License.
 20   * 
 21   * @APPLE_LICENSE_HEADER_END@
 22   *
 23   * cssmdatetime.cpp -- CSSM date and time utilities for the Mac
 24   */
 25  
 26  #ifdef __MWERKS__
 27  #define _CPP_CSSM_DATE_TIME_UTILS
 28  #endif
 29  
 30  #include "cssmdatetime.h"
 31  
 32  #include <string.h>
 33  #include <stdio.h>
 34  #include <security_utilities/errors.h>
 35  #include <CoreFoundation/CFDate.h>
 36  #include <CoreFoundation/CFTimeZone.h>
 37  #include <ctype.h>
 38  #include <stdlib.h>
 39  #include <Security/SecBase.h>
 40  namespace Security
 41  {
 42  
 43  namespace CSSMDateTimeUtils
 44  {
 45  
 46  #define UTC_TIME_NOSEC_LEN			11
 47  #define UTC_TIME_STRLEN				13
 48  #define GENERALIZED_TIME_STRLEN		15
 49  #define LOCALIZED_UTC_TIME_STRLEN	17
 50  #define LOCALIZED_TIME_STRLEN		19
 51  #define MAX_TIME_STR_LEN			30
 52  
 53  
 54  void
 55  GetCurrentMacLongDateTime(sint64 &outMacDate)
 56  {
 57  	CFTimeZoneRef timeZone = CFTimeZoneCopyDefault();
 58  	CFAbsoluteTime absTime = CFAbsoluteTimeGetCurrent();
 59  	absTime += CFTimeZoneGetSecondsFromGMT(timeZone, absTime);
 60  	CFRelease(timeZone);
 61  	outMacDate = sint64(double(absTime + kCFAbsoluteTimeIntervalSince1904));
 62  }
 63  
 64  void
 65  TimeStringToMacSeconds (const CSSM_DATA &inUTCTime, uint32 &ioMacDate)
 66  {
 67      sint64 ldt;
 68      TimeStringToMacLongDateTime(inUTCTime, ldt);
 69      ioMacDate = uint32(ldt);
 70  }
 71  
 72  /*
 73   * Given a CSSM_DATA containing either a UTC-style or "generalized time"
 74   * time string, convert to 32-bit Mac time in seconds.
 75   * Returns nonzero on error. 
 76   */
 77  void
 78  TimeStringToMacLongDateTime (const CSSM_DATA &inUTCTime, sint64 &outMacDate)
 79  {
 80  	char 		szTemp[5];
 81  	size_t          len;
 82  	int 		isUtc;
 83  	sint32 		x;
 84  	sint32 		i;
 85  	char 		*cp;
 86  
 87  	CFGregorianDate date;
 88  	::memset( &date, 0, sizeof(date) );
 89  
 90  	if ((inUTCTime.Data == NULL) || (inUTCTime.Length == 0))
 91      {
 92      	MacOSError::throwMe(errSecParam);
 93    	}
 94  
 95    	/* tolerate NULL terminated or not */
 96    	len = inUTCTime.Length;
 97    	if (inUTCTime.Data[len - 1] == '\0')
 98    		len--;
 99  
100    	switch(len)
101      {
102    		case UTC_TIME_STRLEN:			// 2-digit year, not Y2K compliant
103    			isUtc = 1;
104    			break;
105    		case GENERALIZED_TIME_STRLEN:	// 4-digit year
106    			isUtc = 0;
107    			break;
108    		default:						// unknown format 
109              MacOSError::throwMe(errSecParam);
110    	}
111  
112    	cp = (char *)inUTCTime.Data;
113    	
114  	/* check that all characters except last are digits */
115      for(i=0; i<(sint32)(len - 1); i++) {
116  		if ( !(isdigit(cp[i])) ) {
117              MacOSError::throwMe(errSecParam);
118  		}
119  	}
120  
121    	/* check last character is a 'Z' */
122    	if(cp[len - 1] != 'Z' )	{
123          MacOSError::throwMe(errSecParam);
124    	}
125  
126    	/* YEAR */
127  	szTemp[0] = *cp++;
128  	szTemp[1] = *cp++;
129  	if(!isUtc) {
130  		/* two more digits */
131  		szTemp[2] = *cp++;
132  		szTemp[3] = *cp++;
133  		szTemp[4] = '\0';
134  	}
135  	else { 
136  		szTemp[2] = '\0';
137  	}
138  	x = atoi( szTemp );
139  	if(isUtc) {
140  		/* 
141  		 * 2-digit year. 
142  		 *   0  <= year <= 50 : assume century 21
143  		 *   50 <  year <  70 : illegal per PKIX
144  		 *   70 <  year <= 99 : assume century 20
145  		 */
146  		if(x <= 50) {
147  			x += 100;
148  		}
149  		else if(x < 70) {
150              MacOSError::throwMe(errSecParam);
151  		}
152  		/* else century 20, OK */
153  
154  		/* bug fix... we need to end up with a 4-digit year! */
155  		x += 1900;
156  	}
157    	/* by definition - tm_year is year - 1900 */
158    	//tmp->tm_year = x - 1900;
159    	date.year = x;
160  
161    	/* MONTH */
162  	szTemp[0] = *cp++;
163  	szTemp[1] = *cp++;
164  	szTemp[2] = '\0';
165  	x = atoi( szTemp );
166  	/* in the string, months are from 1 to 12 */
167  	if((x > 12) || (x <= 0)) {
168          MacOSError::throwMe(errSecParam);
169  	}
170  	/* in a tm, 0 to 11 */
171    	//tmp->tm_mon = x - 1;
172    	date.month = x;
173  
174   	/* DAY */
175  	szTemp[0] = *cp++;
176  	szTemp[1] = *cp++;
177  	szTemp[2] = '\0';
178  	x = atoi( szTemp );
179  	/* 1..31 in both formats */
180  	if((x > 31) || (x <= 0)) {
181          MacOSError::throwMe(errSecParam);
182  	}
183    	//tmp->tm_mday = x;
184    	date.day = x;
185  
186  	/* HOUR */
187  	szTemp[0] = *cp++;
188  	szTemp[1] = *cp++;
189  	szTemp[2] = '\0';
190  	x = atoi( szTemp );
191  	if((x > 23) || (x < 0)) {
192          MacOSError::throwMe(errSecParam);
193  	}
194  	//tmp->tm_hour = x;
195  	date.hour = x;
196  
197    	/* MINUTE */
198  	szTemp[0] = *cp++;
199  	szTemp[1] = *cp++;
200  	szTemp[2] = '\0';
201  	x = atoi( szTemp );
202  	if((x > 59) || (x < 0)) {
203          MacOSError::throwMe(errSecParam);
204  	}
205    	//tmp->tm_min = x;
206    	date.minute = x;
207  
208    	/* SECOND */
209  	szTemp[0] = *cp++;
210  	szTemp[1] = *cp++;
211  	szTemp[2] = '\0';
212    	x = atoi( szTemp );
213  	if((x > 59) || (x < 0)) {
214          MacOSError::throwMe(errSecParam);
215  	}
216    	//tmp->tm_sec = x;
217    	date.second = x;
218  
219  	CFTimeZoneRef timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
220  	CFAbsoluteTime absTime = CFGregorianDateGetAbsoluteTime(date, timeZone);
221  	CFRelease(timeZone);
222  
223  	// Adjust abstime to local timezone
224  	timeZone = CFTimeZoneCopyDefault();
225  	absTime += CFTimeZoneGetSecondsFromGMT(timeZone, absTime);
226  	CFRelease(timeZone);
227  
228  	outMacDate = sint64(double(absTime + kCFAbsoluteTimeIntervalSince1904));
229  }
230  
231  void MacSecondsToTimeString(uint32 inMacDate, uint32 inLength, void *outData)
232  {
233      sint64 ldt = sint64(uint64(inMacDate));
234      MacLongDateTimeToTimeString(ldt, inLength, outData);
235  }
236  
237  void MacLongDateTimeToTimeString(const sint64 &inMacDate,
238                                          uint32 inLength, void *outData)
239  {
240  	// @@@ this code is close, but on the fringe case of a daylight savings time it will be off for a little while
241  	CFAbsoluteTime absTime = inMacDate - kCFAbsoluteTimeIntervalSince1904;
242  
243  	// Remove local timezone component from absTime
244  	CFTimeZoneRef timeZone = CFTimeZoneCopyDefault();
245  	absTime -= CFTimeZoneGetSecondsFromGMT(timeZone, absTime);
246  	CFRelease(timeZone);
247  
248  	timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
249  	CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(absTime, timeZone);
250  	CFRelease(timeZone);
251  
252      if (inLength == 16)
253      {
254          sprintf((char *)(outData), "%04d%02d%02d%02d%02d%02dZ",
255                  int(date.year % 10000), date.month, date.day,
256                  date.hour, date.minute, int(date.second));
257      }
258      else if (inLength == 14)
259      {
260   		/* UTC - 2 year digits - code which parses this assumes that
261  		 * (2-digit) years between 0 and 49 are in century 21 */
262          sprintf((char *)(outData), "%02d%02d%02d%02d%02d%02dZ",
263                  int(date.year % 100), date.month, date.day,
264                  date.hour, date.minute, int(date.second));
265      }
266      else
267          MacOSError::throwMe(errSecParam);
268  }
269  
270  void
271  CFDateToCssmDate(CFDateRef date, char *outCssmDate)
272  {
273  	CFTimeZoneRef timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
274  	CFGregorianDate gd = CFAbsoluteTimeGetGregorianDate(CFDateGetAbsoluteTime(date), timeZone);
275  	sprintf(outCssmDate, "%04d%02d%02d%02d%02d%02dZ", (int)gd.year, gd.month, gd.day, gd.hour, gd.minute, (unsigned int)gd.second);
276  	CFRelease(timeZone);
277  }
278  
279  void
280  CssmDateToCFDate(const char *cssmDate, CFDateRef *outCFDate)
281  {
282  	CFTimeZoneRef timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
283  	CFGregorianDate gd;
284  	unsigned int year, month, day, hour, minute, second;
285  	sscanf(cssmDate, "%4d%2d%2d%2d%2d%2d", &year, &month, &day, &hour, &minute, &second);
286  	gd.year = year;
287  	gd.month = month;
288  	gd.day = day;
289  	gd.hour = hour;
290  	gd.minute = minute;
291  	gd.second = second;
292  	*outCFDate = CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(gd, timeZone));
293  	CFRelease(timeZone);
294  }
295  
296  int
297  CssmDateStringToCFDate(const char *cssmDate, unsigned int len, CFDateRef *outCFDate)
298  {
299  	CFTimeZoneRef timeZone;
300  	CFGregorianDate gd;
301  	CFTimeInterval ti=0;
302  	char szTemp[5];
303  	unsigned isUtc=0, isLocal=0, i;
304          int x;
305  	unsigned noSeconds=0;
306  	char *cp;
307  
308  	if((cssmDate == NULL) || (len == 0) || (outCFDate == NULL))
309      	return 1;
310    	
311    	/* tolerate NULL terminated or not */
312    	if(cssmDate[len - 1] == '\0')
313    		len--;
314  
315    	switch(len) {
316  		case UTC_TIME_NOSEC_LEN:		// 2-digit year, no seconds, not y2K compliant
317     			isUtc = 1;
318  			noSeconds = 1;
319    			break;
320   		case UTC_TIME_STRLEN:			// 2-digit year, not Y2K compliant
321    			isUtc = 1;
322    			break;
323    		case GENERALIZED_TIME_STRLEN:	// 4-digit year
324  			//isUtc = 0;
325    			break;
326  		case LOCALIZED_UTC_TIME_STRLEN:	// "YYMMDDhhmmssThhmm" (where T=[+,-])
327  			isUtc = 1;
328  			// deliberate fallthrough
329  		case LOCALIZED_TIME_STRLEN:		// "YYYYMMDDhhmmssThhmm" (where T=[+,-])
330  			isLocal = 1;
331  			break;
332    		default:						// unknown format 
333    			return 1;
334    	}
335    	
336    	cp = (char *)cssmDate;
337    	
338  	/* check that all characters except last (or timezone indicator, if localized) are digits */
339  	for(i=0; i<(len - 1); i++) {
340  		if ( !(isdigit(cp[i])) )
341  			if ( !isLocal || !(cp[i]=='+' || cp[i]=='-') )
342  				return 1;
343  	}
344  	/* check last character is a 'Z', unless localized */
345  	if(!isLocal && cp[len - 1] != 'Z' )	{
346  		return 1;
347  	}
348  
349    	/* YEAR */
350  	szTemp[0] = *cp++;
351  	szTemp[1] = *cp++;
352  	if(!isUtc) {
353  		/* two more digits */
354  		szTemp[2] = *cp++;
355  		szTemp[3] = *cp++;
356  		szTemp[4] = '\0';
357  	}
358  	else { 
359  		szTemp[2] = '\0';
360  	}
361  	x = atoi( szTemp );
362  	if(isUtc) {
363  		/* 
364  		 * 2-digit year. 
365  		 *   0  <= year <  50 : assume century 21
366  		 *   50 <= year <  70 : illegal per PKIX
367  		 *   70 <  year <= 99 : assume century 20
368  		 */
369  		if(x < 50) {
370  			x += 2000;
371  		}
372  		else if(x < 70) {
373  			return 1;
374  		}
375  		else {
376  			/* century 20 */
377  			x += 1900;			
378  		}
379  	}
380  	gd.year = x;
381  
382    	/* MONTH */
383  	szTemp[0] = *cp++;
384  	szTemp[1] = *cp++;
385  	szTemp[2] = '\0';
386  	x = atoi( szTemp );
387  	/* in the string, months are from 1 to 12 */
388  	if((x > 12) || (x <= 0)) {
389      	return 1;
390  	}
391  	gd.month = x;
392  
393   	/* DAY */
394  	szTemp[0] = *cp++;
395  	szTemp[1] = *cp++;
396  	szTemp[2] = '\0';
397  	x = atoi( szTemp );
398  	/* 1..31 in both formats */
399  	if((x > 31) || (x <= 0)) {
400  		return 1;
401  	}
402  	gd.day = x;
403  
404  	/* HOUR */
405  	szTemp[0] = *cp++;
406  	szTemp[1] = *cp++;
407  	szTemp[2] = '\0';
408  	x = atoi( szTemp );
409  	if((x > 23) || (x < 0)) {
410  		return 1;
411  	}
412  	gd.hour = x;
413  
414    	/* MINUTE */
415  	szTemp[0] = *cp++;
416  	szTemp[1] = *cp++;
417  	szTemp[2] = '\0';
418  	x = atoi( szTemp );
419  	if((x > 59) || (x < 0)) {
420  		return 1;
421  	}
422  	gd.minute = x;
423  
424    	/* SECOND */
425  	if(noSeconds) {
426  		gd.second = 0;
427  	}
428  	else {
429  		szTemp[0] = *cp++;
430  		szTemp[1] = *cp++;
431  		szTemp[2] = '\0';
432  		x = atoi( szTemp );
433  		if((x > 59) || (x < 0)) {
434  			return 1;
435  		}
436  		gd.second = x;
437  	}
438  	
439  	if (isLocal) {
440  		/* ZONE INDICATOR */
441  		ti = (*cp++ == '+') ? 1 : -1;
442  	  	/* ZONE HH OFFSET */
443  		szTemp[0] = *cp++;
444  		szTemp[1] = *cp++;
445  		szTemp[2] = '\0';
446  		x = atoi( szTemp ) * 60 * 60;
447  		ti *= x;
448  	  	/* ZONE MM OFFSET */
449  		szTemp[0] = *cp++;
450  		szTemp[1] = *cp++;
451  		szTemp[2] = '\0';
452  		x = atoi( szTemp ) * 60;
453  		ti += ((ti < 0) ? (x*-1) : x);
454  	}
455  	timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, ti);
456  	if (!timeZone) return 1;
457  	*outCFDate = CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(gd, timeZone));
458  	CFRelease(timeZone);
459  	
460  	return 0;
461  }
462  
463  }; // end namespace CSSMDateTimeUtils
464  
465  } // end namespace Security