1<?php 2/* 3 * Copyright 2008-2010 GuardTime AS 4 * 5 * This file is part of the GuardTime PHP SDK. 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 */ 19 20/** 21 * @package tsp 22 */ 23 24/** 25 * Timestamp object. 26 * 27 * GuardTime timestamps can be either PKI-signed or hash-linked. 28 * 29 * PKI-signed timestamps can be verified using GuardTime public key certificate and thus 30 * are directly usable only until the certificate expires. 31 * 32 * Hash-linked (extended) timestamps can be verified independently from GuardTime using control 33 * publications. There are no time limitations for hash-linked timestamps, so those are more 34 * suitable for long-term archival. 35 * 36 * GuardTime timestamps are internally represented in ASN.1 and serialized and stored in DER 37 * encoding. This class is a wrapper to decode, format and otherwise process a timestmap object. 38 * 39 * @package tsp 40 */ 41class GTTimestamp implements ASN1DEREncodable { 42 43 const CONTENT_TYPE_ID = "1.2.840.113549.1.9.3"; // id-contentType 44 const CONTENT_TYPE = "1.2.840.113549.1.9.16.1.4"; // id-ct-TSTInfo 45 const MESSAGE_DIGEST_ID = "1.2.840.113549.1.9.4"; // id-messageDigest 46 47 const PK_SIG_ALGO_ID = "1.2.840.113549.1.1.11"; // id-sha256WithRSAEncryption 48 49 /** 50 * Name of the property to retrieve the claimed accuracy of the request time field. 51 * 52 * @see getProperty 53 * @see REQUEST_TIME 54 */ 55 const ACCURACY = "issuer.accuracy"; 56 /** 57 * Name of the property to retrieve the algorithm used to hash the timestamped document. 58 * 59 * @see getProperty 60 * @see HASHED_MESSAGE 61 */ 62 const HASH_ALGORITHM = "hashAlgorithm"; 63 /** 64 * Name of the property to retrieve the hash value of the timestamped document. 65 * 66 * @see getProperty 67 * @see HASH_ALGORITHM 68 */ 69 const HASHED_MESSAGE = "hashedMessage"; 70 /** 71 * Name of the property to retrieve the history identifier of the timestamp. 72 * 73 * This is the number of seconds from 1970-01-01 00:00:00 UTC to the moment 74 * the timestamp was registered in the GuardTime Calendar. 75 * 76 * @see getProperty 77 * @see REGISTERED_TIME 78 */ 79 const HISTORY_ID = "history.id"; 80 /** 81 * Name of the property to retrieve the issue of the timestamp. 82 * 83 * For GuardTime timestamps, this is the DNS name of the issuing Gateway. 84 * 85 * @see getProperty 86 * @see LOCATION_ID 87 */ 88 const ISSUER_NAME = "issuer.name"; 89 /** 90 * Name of the property to retrieve the identifier of the issuing Gateway 91 * within the GuardTime aggregation and distribution network. 92 * 93 * At the moment, this is mostly for troubleshooting. 94 * 95 * @see getProperty 96 * @see ISSUER_NAME 97 */ 98 const LOCATION_ID = "location.id"; 99 /** 100 * Name of the property to retrieve the policy under which the timestamp was issued. 101 * 102 * @see getProperty 103 */ 104 const POLICY_ID = "policy.id"; 105 /** 106 * Name of the property to retrieve the control publication for the timestamp. 107 * 108 * This is available for extended timestamps only. 109 * 110 * @see getProperty 111 * @see PUBLICATION_ID, PUBLICATION_TIME, PUBLICATION_REFERENCE 112 */ 113 const PUBLICATION = "publication.value"; 114 /** 115 * Name of the property to retrieve the identifier of the control publication for the timestamp. 116 * 117 * This is the number of seconds from 1970-01-01 00:00:00 UTC to the moment 118 * the control publicatin was generated in the GuardTime Calendar. 119 * 120 * This is available for extended timestamps only. 121 * 122 * @see getProperty 123 * @see PUBLICATION, PUBLICATION_TIME, PUBLICATION_REFERENCE 124 */ 125 const PUBLICATION_ID = "publication.id"; 126 /** 127 * Name of the property to retrieve the time of the control publication for the timestamp. 128 * 129 * This is the moment the control publicatin was generated in the GuardTime Calendar, 130 * given as UTC time and date. 131 * 132 * This is available for extended timestamps only. 133 * 134 * @see getProperty 135 * @see PUBLICATION, PUBLICATION_ID, PUBLICATION_REFERENCE 136 */ 137 const PUBLICATION_TIME = "publication.time"; 138 /** 139 * Name of the property to retrieve the bibliographic references for the control publication for the timestamp. 140 * 141 * This is currently empty for all timestamps, and in the future will be available for extended timestamps only. 142 * 143 * @see getProperty 144 * @see PUBLICATION, PUBLICATION_ID, PUBLICATION_TIME 145 */ 146 const PUBLICATION_REFERENCE = "publication.references"; 147 /** 148 * Name of the property to retrieve the registration time of the timestamp. 149 * 150 * This is the moment the timestamp request was registered in the GuardTime Calendar, 151 * given as UTC time and date. 152 * 153 * @see getProperty 154 * @see HISTORY_ID 155 */ 156 const REGISTERED_TIME = "registeredTime"; 157 /** 158 * Name of the property to retrieve the time when the GuardTime Gateway received the 159 * timestamp request. 160 * 161 * This is given as UTC time and date according to the Gateway's local clock. 162 * 163 * @see getProperty 164 * @see ACCURACY 165 */ 166 const REQUEST_TIME = "issuer.genTime"; 167 /** 168 * Name of the property to retrieve the unique serial number of the timestamp. 169 * 170 * @see getProperty 171 */ 172 const SERIAL_NUMBER = "issuer.serialNumber"; 173 174 private $token; 175 private $verificationResult; 176 private $properties; 177 private $registeredTime; 178 private $dataHash; 179 180 /** 181 * Constructs a new GTTimestamp instance. 182 * 183 * @param CMSContentInfo $token timestamp token 184 */ 185 public function __construct(CMSContentInfo $token) { 186 $this->token = $token; 187 $this->verificationResult = null; 188 189 $this->updateProperties(); 190 } 191 192 /** 193 * Parses timestamp contents and checks vital parameters. 194 * 195 * This method performs basic timestamp syntactic checks. 196 * 197 * @throws GTException 198 * @return void 199 */ 200 protected function updateProperties() { 201 $this->properties = array(); 202 203 $signedData = $this->token->getContent(); 204 205 $tstInfo = $signedData->getEncapsulatedContent()->getContent(); 206 207 $hashAlgorithm = $tstInfo->getMessageImprint()->getHashAlgorithm()->getAlgorithm(); 208 $hashedMessage = $tstInfo->getMessageImprint()->getHashedMessage(); 209 $this->dataHash = new GTDataHash(GTHashAlgorithm::getByOid($hashAlgorithm), $hashedMessage); 210 211 // timestamps should only have 1 signerinfo 212 if (count($signedData->getSignerInfos()) != 1) { 213 throw new GTException("Invalid signerInfos size: " . count($signedData->getSignerInfos())); 214 } 215 216 $signerInfo = $signedData->getSignerInfo(); 217 $signerInfoSid = $signerInfo->getSid(); 218 219 if (empty($signerInfoSid)) { 220 throw new GTException("Missing signerInfo.sid"); 221 } 222 223 // verify that sid is of type issuerAndSerialNumber for timestamps, not subjectKeyIdentifier 224 $issuerAndSerialNumber = $signerInfoSid->getIssuerAndSerialNumber(); 225 226 if (empty($issuerAndSerialNumber)) { 227 throw new GTException("Missing signerInfo.sid.issuerAndSerialNumber"); 228 } 229 230 // check digest alg is SHA-256 231 if ($signerInfo->getDigestAlgorithm()->getAlgorithm() != GTHashAlgorithm::getByName('SHA256')->getOid()) { 232 throw new GTException("Unsupported digestAlgorithm: {$signerInfo->getDigestAlgorithm()->getAlgorithm()}"); 233 } 234 235 // check signature alg is GTTimeSignature::OID 236 if ($signerInfo->getSignatureAlgorithm()->getAlgorithm() != GTTimeSignature::OID) { 237 throw new GTException("Unsupported signatureAlgorithm: {$signerInfo->getSignatureAlgorithm()->getAlgorithm()}"); 238 } 239 240 $signedAttributes = $signerInfo->getSignedAttrs(); 241 242 if (empty($signedAttributes)) { 243 throw new GTException("Signed attributes missing"); 244 } 245 246 if (count($signedAttributes) != 2) { 247 throw new GTException("Signed attributes invalid size: " . count($signedAttributes)); 248 } 249 250 $contentType = $signedAttributes[0]; 251 $messageDigest = $signedAttributes[1]; 252 253 if ($contentType->getType() != self::CONTENT_TYPE_ID) { 254 throw new GTException("Expecting content-type signedAttr, but found: " . $contentType->getType()); 255 } 256 257 if ($messageDigest->getType() != self::MESSAGE_DIGEST_ID) { 258 throw new GTException("Expecting message-digest signedAttr, but found: " . $messageDigest->getType()); 259 } 260 261 $contentTypeValues = $contentType->getValues(); 262 $messageDigestValues = $messageDigest->getValues(); 263 264 if (count($contentTypeValues) != 1) { 265 throw new GTException("Multiple values not allowed for signedAttr content-type"); 266 } 267 268 if (count($messageDigestValues) != 1) { 269 throw new GTException("Multiple values not allowed for signedAttr message-digest"); 270 } 271 272 $contentTypeValue = $contentTypeValues[0]->getValue(); 273 $messageDigestValue = $messageDigestValues[0]; 274 275 if ($contentTypeValue != self::CONTENT_TYPE) { 276 throw new GTException("Invalid signedAttr content-type: " . $contentTypeValue); 277 } 278 279 if (!$messageDigestValue instanceof ASN1OctetString) { 280 throw new GTException("Invalid signedAttr message-digest: " . $messageDigestValue); 281 } 282 283 $this->properties[self::HASH_ALGORITHM] = $this->dataHash->getHashAlgorithm()->getOid(); 284 $this->properties[self::HASHED_MESSAGE] = GTBase16::encode($this->dataHash->getHashedMessage()); 285 286 $this->properties[self::POLICY_ID] = $tstInfo->getPolicy(); 287 $this->properties[self::SERIAL_NUMBER] = $tstInfo->getSerialNumber(); 288 289 $this->properties[self::REQUEST_TIME] = GTUtil::formatTime(GTUtil::decodeTime($tstInfo->getGenTime())); 290 291 $accuracy = $tstInfo->getAccuracy(); 292 293 if ($accuracy != null) { 294 $this->properties[self::ACCURACY] = $accuracy->getFormatted(); 295 } 296 297 $tsa = $tstInfo->getFormattedTsa(); 298 299 if ($tsa != null) { 300 $this->properties[self::ISSUER_NAME] = $tsa; 301 } 302 303 $timeSignature = new GTTimeSignature(); 304 $timeSignature->decode($signedData->getSignerInfo()->getSignature()); 305 306 $publicationId = new GTBigInteger($timeSignature->getPublishedData()->getPublicationIdentifier()); 307 308 $historyChain = GTHashChain::getHistoryInstance($timeSignature->getHistory()); 309 $locationChain = GTHashChain::getLocationInstance($timeSignature->getLocation()); 310 311 $historyId = $historyChain->computeHistoryId($publicationId); 312 $locationId = $locationChain->computeLocationId(); 313 314 $this->properties[self::HISTORY_ID] = $historyId->getValue(); 315 $this->properties[self::LOCATION_ID] = $locationId->getValue(); 316 317 $this->registeredTime = $historyId; 318 319 $this->properties[self::REGISTERED_TIME] = GTUtil::formatTime($this->registeredTime->getValue(), 'UTC'); 320 321 if ($timeSignature->isExtended()) { 322 323 $publishedData = $timeSignature->getPublishedData(); 324 325 $this->properties[self::PUBLICATION_ID] = $publicationId->getValue(); 326 $this->properties[self::PUBLICATION_TIME] = GTUtil::formatTime($publicationId->getValue(), 'UTC'); 327 $this->properties[self::PUBLICATION] = $publishedData->getEncodedPublication(); 328 $this->properties[self::PUBLICATION_REFERENCE] = $timeSignature->getEncodedPublicationReferences(); 329 330 } else { 331 332 $pkSignature = $timeSignature->getPkSignature(); 333 $pkSignatureAlgorithm = $pkSignature->getSignatureAlgorithm(); 334 335 if (empty($pkSignatureAlgorithm)) { 336 throw new GTException("Missing pkSignatureAlgorithm"); 337 } 338 339 if ($pkSignatureAlgorithm->getAlgorithm() != self::PK_SIG_ALGO_ID) { 340 throw new GTException("Invalid pkSignatureAlgorithm: {$pkSignatureAlgorithm->getAlgorithm()}"); 341 } 342 } 343 344 } 345 346 /** 347 * Extends this timestamp using the given extension response. 348 * 349 * @throws GTException 350 * @param GTCertTokenResponse $certTokenResponse extension response 351 * @return void 352 * 353 * @see GTHttpClient 354 * @see GTCertTokenResponse 355 */ 356 public function extend(GTCertTokenResponse $certTokenResponse) { 357 358 if (empty($certTokenResponse)) { 359 throw new GTException("Parameter certTokenResponse is required"); 360 } 361 362 if (!$certTokenResponse instanceof GTCertTokenResponse) { 363 throw new GTException("Expecting an GTCertTokenResponse"); 364 } 365 366 $statusCode = $certTokenResponse->getStatusCode(); 367 368 if ($statusCode < 0 || $statusCode > 1) { 369 throw new GTException("Extending not possible: " . $certTokenResponse->getStatus()->getStatusMessage()); 370 } 371 372 $certToken = $certTokenResponse->getToken(); 373 374 $signedData = $this->token->getContent(); 375 $signerInfo = $signedData->getSignerInfo(); 376 377 $timeSignature = new GTTimeSignature(); 378 $timeSignature->decode($signerInfo->getSignature()); 379 380 $oldChain = GTHashChain::getHistoryInstance($timeSignature->getHistory()); 381 $newChain = GTHashChain::getHistoryInstance($certToken->getHistory()); 382 383 if (!$oldChain->checkPastEntries($newChain)) { 384 throw new GTException("Extending failed: past history chains do not match in timestamp and response"); 385 } 386 387 $timeSignature->setPkSignature(null); 388 $timeSignature->setHistory($certToken->getHistory()); 389 $timeSignature->setPublishedData($certToken->getPublishedData()); 390 $timeSignature->setPubReference($certToken->getPubReference()); 391 392 $signerInfo->setSignature($timeSignature->encodeDER()); 393 $signedData->setCertificates(null); 394 395 $this->updateProperties(); 396 } 397 398 /** 399 * Checks if this timestamp is extendable. 400 * 401 * @param GTPublicationsFile $publicationsFile the publications file to check agains 402 * @return bool true if this timestamp is extendable 403 */ 404 public function isExtendable(GTPublicationsFile $publicationsFile) { 405 406 if ($publicationsFile == null) { 407 return false; 408 } 409 410 if ($this->isExtended()) { 411 return false; 412 } 413 414 $regTime = $this->getRegisteredTime(); 415 $pubTime = $publicationsFile->getLastPublicationTime(); 416 417 return $regTime <= $pubTime; 418 } 419 420 /** 421 * Verifies this timestamp using the given data hash and publications file. 422 * 423 * @throws GTException 424 * @param GTDataHash $dataHash data hash to use for verification 425 * @param GTPublicationsFile $publicationsFile publications file to use for verification 426 * @return GTVerificationResult timestamp verification result 427 */ 428 public function verify(GTDataHash $dataHash, GTPublicationsFile $publicationsFile) { 429 430 if ($publicationsFile == null) { 431 throw new GTException("Parameter publicationsFile is required"); 432 } 433 434 if (!$publicationsFile instanceof GTPublicationsFile) { 435 throw new GTException("Parameter publicationsFile must be an instance of GTPublicationsFile"); 436 } 437 438 $this->verificationResult = new GTVerificationResult(); 439 $this->verificationResult->update($publicationsFile->verify()); 440 441 if (!$this->verificationResult->isValid()) { 442 return $this->verificationResult; 443 } 444 445 if ($this->isExtended()) { 446 447 $publication = $this->getProperty(self::PUBLICATION); 448 449 if (!$publicationsFile->contains($publication)) { 450 $this->verificationResult->updateErrors(GTVerificationResult::PUBLICATION_FAILURE); 451 } 452 453 if (!$this->verificationResult->isValid()) { 454 return $this->verificationResult; 455 } 456 457 $this->verificationResult->update(GTVerifier::verifyWithPublication($this->token, $dataHash, $publication)); 458 459 } else { 460 461 $certificates = $this->token->getContent()->getCertificates(); 462 463 if (count($certificates) < 1) { 464 throw new GTException("Invalid timestamp: no certificates"); 465 } 466 467 $certificate = $certificates[0]; 468 $certificate = new X509Certificate($certificate); 469 470 $hash = X509Certificate::getPublicKeyHash($certificate->getPublicKey()); 471 472 if ($publicationsFile->containsPublicKey($hash)) { 473 $this->verificationResult->updateStatus(GTVerificationResult::PUBLICATION_CHECKED); 474 475 } else { 476 477 $this->verificationResult->updateErrors(GTVerificationResult::PUBLIC_KEY_FAILURE); 478 479 if (!$this->verificationResult->isValid()) { 480 return $this->verificationResult; 481 } 482 483 } 484 485 $this->verificationResult->update(GTVerifier::verifyWithSignature($this->token, $dataHash, $certificate->getPublicKey())); 486 } 487 488 return $this->verificationResult; 489 490 } 491 492 /** 493 * Checks if this timestamp is extended (hash-linked). 494 * 495 * @return bool true if this timestamp is extended, false otherwise 496 */ 497 public function isExtended() { 498 499 $timeSignature = new GTTimeSignature(); 500 $timeSignature->decode($this->token->getContent()->getSignerInfo()->getSignature()); 501 502 return $timeSignature->isExtended(); 503 } 504 505 /** 506 * Gets the timestamp property by the given name. 507 * 508 * @param string $name property name 509 * @return null|object property value or null if not set 510 */ 511 public function getProperty($name) { 512 513 if (!isset($this->properties[$name])) { 514 return null; 515 516 } else { 517 return $this->properties[$name]; 518 519 } 520 } 521 522 /** 523 * Gets the hash algorithm used to hash data which this timestamp was created for. 524 * 525 * @return GTHashAlgorithm hash algorithm 526 */ 527 public function getHashAlgorithm() { 528 return $this->dataHash->getHashAlgorithm(); 529 } 530 531 /** 532 * Gets the time this timestamp was registered at. 533 * 534 * @return GTBigInteger timestamp registration time 535 */ 536 public function getRegisteredTime() { 537 return $this->registeredTime; 538 } 539 540 /** 541 * Encodes the contents of this timestamp using DER. 542 * 543 * @return array byte array containing the contents of this timestamp using DER encoding 544 */ 545 public function encodeDER() { 546 return $this->token->encodeDER(); 547 } 548 549 /** 550 * Saves this timestamp to the specified file. 551 * 552 * @param string $file file name 553 * @return void 554 */ 555 public function save($file) { 556 GTUtil::write($file, $this->encodeDER()); 557 } 558 559 /** 560 * Loads a GuardTime timestamp from the given file. 561 * 562 * @static 563 * @param string $file file name 564 * @return GTTimestamp loaded timestamp 565 */ 566 public static function load($file) { 567 568 $bytes = GTUtil::read($file); 569 570 $content = new CMSContentInfo(); 571 $content->decode(ASN1DER::decode($bytes)); 572 573 return new GTTimestamp($content); 574 } 575 576} 577 578?> 579