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