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 <SecBase.h> 40namespace Security 41{ 42 43namespace 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 54void 55GetCurrentMacLongDateTime(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 64void 65TimeStringToMacSeconds (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 */ 77void 78TimeStringToMacLongDateTime (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 231void MacSecondsToTimeString(uint32 inMacDate, uint32 inLength, void *outData) 232{ 233 sint64 ldt = sint64(uint64(inMacDate)); 234 MacLongDateTimeToTimeString(ldt, inLength, outData); 235} 236 237void 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 270void 271CFDateToCssmDate(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 279void 280CssmDateToCFDate(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 296int 297CssmDateStringToCFDate(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 466