token = $token; $this->verificationResult = null; $this->updateProperties(); } /** * Parses timestamp contents and checks vital parameters. * * This method performs basic timestamp syntactic checks. * * @throws GTException * @return void */ protected function updateProperties() { $this->properties = array(); $signedData = $this->token->getContent(); $tstInfo = $signedData->getEncapsulatedContent()->getContent(); $hashAlgorithm = $tstInfo->getMessageImprint()->getHashAlgorithm()->getAlgorithm(); $hashedMessage = $tstInfo->getMessageImprint()->getHashedMessage(); $this->dataHash = new GTDataHash(GTHashAlgorithm::getByOid($hashAlgorithm), $hashedMessage); // timestamps should only have 1 signerinfo if (count($signedData->getSignerInfos()) != 1) { throw new GTException("Invalid signerInfos size: " . count($signedData->getSignerInfos())); } $signerInfo = $signedData->getSignerInfo(); $signerInfoSid = $signerInfo->getSid(); if (empty($signerInfoSid)) { throw new GTException("Missing signerInfo.sid"); } // verify that sid is of type issuerAndSerialNumber for timestamps, not subjectKeyIdentifier $issuerAndSerialNumber = $signerInfoSid->getIssuerAndSerialNumber(); if (empty($issuerAndSerialNumber)) { throw new GTException("Missing signerInfo.sid.issuerAndSerialNumber"); } // check digest alg is SHA-256 if ($signerInfo->getDigestAlgorithm()->getAlgorithm() != GTHashAlgorithm::getByName('SHA256')->getOid()) { throw new GTException("Unsupported digestAlgorithm: {$signerInfo->getDigestAlgorithm()->getAlgorithm()}"); } // check signature alg is GTTimeSignature::OID if ($signerInfo->getSignatureAlgorithm()->getAlgorithm() != GTTimeSignature::OID) { throw new GTException("Unsupported signatureAlgorithm: {$signerInfo->getSignatureAlgorithm()->getAlgorithm()}"); } $signedAttributes = $signerInfo->getSignedAttrs(); if (empty($signedAttributes)) { throw new GTException("Signed attributes missing"); } if (count($signedAttributes) != 2) { throw new GTException("Signed attributes invalid size: " . count($signedAttributes)); } $contentType = $signedAttributes[0]; $messageDigest = $signedAttributes[1]; if ($contentType->getType() != self::CONTENT_TYPE_ID) { throw new GTException("Expecting content-type signedAttr, but found: " . $contentType->getType()); } if ($messageDigest->getType() != self::MESSAGE_DIGEST_ID) { throw new GTException("Expecting message-digest signedAttr, but found: " . $messageDigest->getType()); } $contentTypeValues = $contentType->getValues(); $messageDigestValues = $messageDigest->getValues(); if (count($contentTypeValues) != 1) { throw new GTException("Multiple values not allowed for signedAttr content-type"); } if (count($messageDigestValues) != 1) { throw new GTException("Multiple values not allowed for signedAttr message-digest"); } $contentTypeValue = $contentTypeValues[0]->getValue(); $messageDigestValue = $messageDigestValues[0]; if ($contentTypeValue != self::CONTENT_TYPE) { throw new GTException("Invalid signedAttr content-type: " . $contentTypeValue); } if (!$messageDigestValue instanceof ASN1OctetString) { throw new GTException("Invalid signedAttr message-digest: " . $messageDigestValue); } $this->properties[self::HASH_ALGORITHM] = $this->dataHash->getHashAlgorithm()->getOid(); $this->properties[self::HASHED_MESSAGE] = GTBase16::encode($this->dataHash->getHashedMessage()); $this->properties[self::POLICY_ID] = $tstInfo->getPolicy(); $this->properties[self::SERIAL_NUMBER] = $tstInfo->getSerialNumber(); $this->properties[self::REQUEST_TIME] = GTUtil::formatTime(GTUtil::decodeTime($tstInfo->getGenTime())); $accuracy = $tstInfo->getAccuracy(); if ($accuracy != null) { $this->properties[self::ACCURACY] = $accuracy->getFormatted(); } $tsa = $tstInfo->getFormattedTsa(); if ($tsa != null) { $this->properties[self::ISSUER_NAME] = $tsa; } $timeSignature = new GTTimeSignature(); $timeSignature->decode($signedData->getSignerInfo()->getSignature()); $publicationId = new GTBigInteger($timeSignature->getPublishedData()->getPublicationIdentifier()); $historyChain = GTHashChain::getHistoryInstance($timeSignature->getHistory()); $locationChain = GTHashChain::getLocationInstance($timeSignature->getLocation()); $historyId = $historyChain->computeHistoryId($publicationId); $locationId = $locationChain->computeLocationId(); $this->properties[self::HISTORY_ID] = $historyId->getValue(); $this->properties[self::LOCATION_ID] = $locationId->getValue(); $this->registeredTime = $historyId; $this->properties[self::REGISTERED_TIME] = GTUtil::formatTime($this->registeredTime->getValue(), 'UTC'); if ($timeSignature->isExtended()) { $publishedData = $timeSignature->getPublishedData(); $this->properties[self::PUBLICATION_ID] = $publicationId->getValue(); $this->properties[self::PUBLICATION_TIME] = GTUtil::formatTime($publicationId->getValue(), 'UTC'); $this->properties[self::PUBLICATION] = $publishedData->getEncodedPublication(); $this->properties[self::PUBLICATION_REFERENCE] = $timeSignature->getEncodedPublicationReferences(); } else { $pkSignature = $timeSignature->getPkSignature(); $pkSignatureAlgorithm = $pkSignature->getSignatureAlgorithm(); if (empty($pkSignatureAlgorithm)) { throw new GTException("Missing pkSignatureAlgorithm"); } if ($pkSignatureAlgorithm->getAlgorithm() != self::PK_SIG_ALGO_ID) { throw new GTException("Invalid pkSignatureAlgorithm: {$pkSignatureAlgorithm->getAlgorithm()}"); } } } /** * Extends this timestamp using the given extension response. * * @throws GTException * @param GTCertTokenResponse $certTokenResponse extension response * @return void * * @see GTHttpClient * @see GTCertTokenResponse */ public function extend(GTCertTokenResponse $certTokenResponse) { if (empty($certTokenResponse)) { throw new GTException("Parameter certTokenResponse is required"); } if (!$certTokenResponse instanceof GTCertTokenResponse) { throw new GTException("Expecting an GTCertTokenResponse"); } $statusCode = $certTokenResponse->getStatusCode(); if ($statusCode < 0 || $statusCode > 1) { throw new GTException("Extending not possible: " . $certTokenResponse->getStatus()->getStatusMessage()); } $certToken = $certTokenResponse->getToken(); $signedData = $this->token->getContent(); $signerInfo = $signedData->getSignerInfo(); $timeSignature = new GTTimeSignature(); $timeSignature->decode($signerInfo->getSignature()); $oldChain = GTHashChain::getHistoryInstance($timeSignature->getHistory()); $newChain = GTHashChain::getHistoryInstance($certToken->getHistory()); if (!$oldChain->checkPastEntries($newChain)) { throw new GTException("Extending failed: past history chains do not match in timestamp and response"); } $timeSignature->setPkSignature(null); $timeSignature->setHistory($certToken->getHistory()); $timeSignature->setPublishedData($certToken->getPublishedData()); $timeSignature->setPubReference($certToken->getPubReference()); $signerInfo->setSignature($timeSignature->encodeDER()); $signedData->setCertificates(null); $this->updateProperties(); } /** * Checks if this timestamp is extendable. * * @param GTPublicationsFile $publicationsFile the publications file to check agains * @return bool true if this timestamp is extendable */ public function isExtendable(GTPublicationsFile $publicationsFile) { if ($publicationsFile == null) { return false; } if ($this->isExtended()) { return false; } $regTime = $this->getRegisteredTime(); $pubTime = $publicationsFile->getLastPublicationTime(); return $regTime <= $pubTime; } /** * Verifies this timestamp using the given data hash and publications file. * * @throws GTException * @param GTDataHash $dataHash data hash to use for verification * @param GTPublicationsFile $publicationsFile publications file to use for verification * @return GTVerificationResult timestamp verification result */ public function verify(GTDataHash $dataHash, GTPublicationsFile $publicationsFile) { if ($publicationsFile == null) { throw new GTException("Parameter publicationsFile is required"); } if (!$publicationsFile instanceof GTPublicationsFile) { throw new GTException("Parameter publicationsFile must be an instance of GTPublicationsFile"); } $this->verificationResult = new GTVerificationResult(); $this->verificationResult->update($publicationsFile->verify()); if (!$this->verificationResult->isValid()) { return $this->verificationResult; } if ($this->isExtended()) { $publication = $this->getProperty(self::PUBLICATION); if (!$publicationsFile->contains($publication)) { $this->verificationResult->updateErrors(GTVerificationResult::PUBLICATION_FAILURE); } if (!$this->verificationResult->isValid()) { return $this->verificationResult; } $this->verificationResult->update(GTVerifier::verifyWithPublication($this->token, $dataHash, $publication)); } else { $certificates = $this->token->getContent()->getCertificates(); if (count($certificates) < 1) { throw new GTException("Invalid timestamp: no certificates"); } $certificate = $certificates[0]; $certificate = new X509Certificate($certificate); $hash = X509Certificate::getPublicKeyHash($certificate->getPublicKey()); if ($publicationsFile->containsPublicKey($hash)) { $this->verificationResult->updateStatus(GTVerificationResult::PUBLICATION_CHECKED); } else { $this->verificationResult->updateErrors(GTVerificationResult::PUBLIC_KEY_FAILURE); if (!$this->verificationResult->isValid()) { return $this->verificationResult; } } $this->verificationResult->update(GTVerifier::verifyWithSignature($this->token, $dataHash, $certificate->getPublicKey())); } return $this->verificationResult; } /** * Checks if this timestamp is extended (hash-linked). * * @return bool true if this timestamp is extended, false otherwise */ public function isExtended() { $timeSignature = new GTTimeSignature(); $timeSignature->decode($this->token->getContent()->getSignerInfo()->getSignature()); return $timeSignature->isExtended(); } /** * Gets the timestamp property by the given name. * * @param string $name property name * @return null|object property value or null if not set */ public function getProperty($name) { if (!isset($this->properties[$name])) { return null; } else { return $this->properties[$name]; } } /** * Gets the hash algorithm used to hash data which this timestamp was created for. * * @return GTHashAlgorithm hash algorithm */ public function getHashAlgorithm() { return $this->dataHash->getHashAlgorithm(); } /** * Gets the time this timestamp was registered at. * * @return GTBigInteger timestamp registration time */ public function getRegisteredTime() { return $this->registeredTime; } /** * Encodes the contents of this timestamp using DER. * * @return array byte array containing the contents of this timestamp using DER encoding */ public function encodeDER() { return $this->token->encodeDER(); } /** * Saves this timestamp to the specified file. * * @param string $file file name * @return void */ public function save($file) { GTUtil::write($file, $this->encodeDER()); } /** * Loads a GuardTime timestamp from the given file. * * @static * @param string $file file name * @return GTTimestamp loaded timestamp */ public static function load($file) { $bytes = GTUtil::read($file); $content = new CMSContentInfo(); $content->decode(ASN1DER::decode($bytes)); return new GTTimestamp($content); } } ?>