update(GTVerifier::verifyCommon($content, $dataHash)); if (!$result->isValid()) { return $result; } $signedData = $content->getContent(); $signerInfo = $signedData->getSignerInfo(); $timeSignature = new GTTimeSignature(); $timeSignature->decode($signerInfo->getSignature()); $certificates = $signedData->getCertificates(); if (count($certificates) < 1) { throw new GTException("Invalid timestamp: no certificates"); } $certificate = $certificates[0]; $publicationId = new GTBigInteger( $timeSignature->getPublishedData()->getPublicationIdentifier() ); $historyChain = GTHashChain::getHistoryInstance($timeSignature->getHistory()); $historyId = $historyChain->computeHistoryId($publicationId); $historyTime = $historyId->getValue(); $result->update(GTVerifier::verifyCertificate($certificate, $historyTime, $publicKey)); if (!$result->isValid()) { return $result; } $result->update(GTVerifier::verifyPkSignature($timeSignature, $publicKey)); return $result; } /** * Verifies an extended timestamp against the given publication. * * @static * @throws GTException * @param CMSContentInfo $content timestamp token * @param GTDataHash $dataHash datahash to verify this timestamp against * @param string $publication base32 encoded publication to use for timestamp verification * @return GTVerificationResult timestamp verification result */ public static function verifyWithPublication(CMSContentInfo $content, GTDataHash $dataHash, $publication) { $result = new GTVerificationResult(); $result->update(GTVerifier::verifyCommon($content, $dataHash)); if (!$result->isValid()) { return $result; } $signedData = $content->getContent(); $signerInfo = $signedData->getSignerInfo(); $timeSignature = new GTTimeSignature(); $timeSignature->decode($signerInfo->getSignature()); if (!$timeSignature->isExtended()) { throw new GTException("Unable to verify timeSignature that is not extended with publication"); } $result->update(GTVerifier::verifyPublication($timeSignature, $publication)); return $result; } /** * Common verification, used by both publication and signature verification. * * @static * @throws GTException * @param CMSContentInfo $content timestamp token * @param GTDataHash $dataHash data hash to verify this timestamp against * @return GTVerificationResult timestamp verification result */ private static function verifyCommon(CMSContentInfo $content, GTDataHash $dataHash) { $result = new GTVerificationResult(); $signedData = $content->getContent(); $signerInfo = $signedData->getSignerInfo(); $timeSignature = new GTTimeSignature(); $timeSignature->decode($signerInfo->getSignature()); if ($timeSignature->getPubReference() != null) { $result->updateStatus(GTVerificationResult::PUBLICATION_REFERENCE_PRESENT); } if ($timeSignature->getPkSignature() != null) { $result->updateStatus(GTVerificationResult::PUBLIC_KEY_SIGNATURE_PRESENT); } $messageImprint = $signedData->getEncapsulatedContent()->getContent()->getMessageImprint(); $result->update(GTVerifier::verifyDataHash($messageImprint, $dataHash)); if (!$result->isValid()) { return $result; } $digestAlgorithm = GTHashAlgorithm::getByOid($signerInfo->getDigestAlgorithm()->getAlgorithm()); $signedAttrs = $signerInfo->getSignedAttrs(); if ($signedAttrs == null) { throw new GTException("Invalid signed attributes: null"); } // GTTimestamp already checks that the messageDigest attribute is // the second signed attribute and has a single OCTECT STRING value $messageDigest = $signedAttrs[1]->getValues(); $messageDigest = $messageDigest[0]->getValue(); // verify that signedAttrs.messageDigest == signerInfo.digestAlgorithm(TSTInfo.encodeDER()) $dataHash = new GTDataHash($digestAlgorithm); $dataHash->update($signedData->getEncapsulatedContent()->getContentRaw()); $dataHash->close(); if ($messageDigest !== $dataHash->getHashedMessage()) { $result->updateErrors(GTVerificationResult::SYNTACTIC_CHECK_FAILURE); } if (!$result->isValid()) { return $result; } // now feed the hash of signed attributes to the hash chains $result->update(GTVerifier::verifyHashChains($timeSignature, $digestAlgorithm, $signedAttrs)); return $result; } /** * Data hash verification against the message imprint. * * @static * @throws GTException * @param TSPMessageImprint $messageImprint message imprint * @param GTDataHash $dataHash data hash * @return GTVerificationResult timestamp verification result */ private static function verifyDataHash(TSPMessageImprint $messageImprint, GTDataHash $dataHash) { $result = new GTVerificationResult(); if ($messageImprint == null) { throw new GTException("Parameter messageImprint is required"); } if ($dataHash == null) { return $result; } if ($dataHash->getHashAlgorithm()->getOid() != $messageImprint->getHashAlgorithm()->getAlgorithm()) { $result->updateErrors(GTVerificationResult::WRONG_DOCUMENT_FAILURE); } else if ($dataHash->getHashedMessage() != $messageImprint->getHashedMessage()) { $result->updateErrors(GTVerificationResult::WRONG_DOCUMENT_FAILURE); } $result->updateStatus(GTVerificationResult::DATA_HASH_CHECKED); return $result; } /** * Hash chains verification method. * * @static * @param GTTimeSignature $timeSignature * @param GTHashAlgorithm $digestAlgorithm * @param array $signedAttrs * @return GTVerificationResult timestamp verification result */ private static function verifyHashChains(GTTimeSignature $timeSignature, GTHashAlgorithm $digestAlgorithm, array $signedAttrs) { $result = new GTVerificationResult(); $locationChainBytes = $timeSignature->getLocation(); $historyChainBytes = $timeSignature->getHistory(); $locationChain = null; $historyChain = null; try { $locationChain = GTHashChain::getLocationInstance($locationChainBytes); $historyChain = GTHashChain::getHistoryInstance($historyChainBytes); } catch (GTException $e) { $result->updateErrors(GTVerificationResult::SYNTACTIC_CHECK_FAILURE); return $result; } $publicationImprint = $timeSignature->getPublishedData()->getPublicationImprint(); $publicationImprintAlg = GTHashAlgorithm::getByGtid($publicationImprint[0]); if ($publicationImprintAlg->getLength() + 1 != count($publicationImprint)) { $result->updateErrors(GTVerificationResult::SYNTACTIC_CHECK_FAILURE); return $result; } $set = new ASN1Set(); foreach ($signedAttrs as $attr) { $set->add($attr); } $input = new GTDataHash($digestAlgorithm); $input->update($set->encodeDER()); $input->close(); $input = $input->getDataImprint(); $locationOutput = $locationChain->computeOutput($input); $historyOutput = $historyChain->computeOutput($locationOutput); $output = new GTDataHash($publicationImprintAlg); $output->update($historyOutput); $output->close(); $output = $output->getDataImprint(); if ($output != $publicationImprint) { $result->updateErrors(GTVerificationResult::HASHCHAIN_VERIFICATION_FAILURE); } return $result; } /** * Publication verification. * * @static * @throws GTException * @param GTTimeSignature $timeSignature * @param string $publication * @return GTVerificationResult timestamp verification result */ private static function verifyPublication(GTTimeSignature $timeSignature, $publication) { if ($publication == null) { throw new GTException("parameter publication is required"); } $result = new GTVerificationResult(); if ($publication != $timeSignature->getPublishedData()->getEncodedPublication()) { $result->updateErrors(GTVerificationResult::PUBLICATION_FAILURE); } $result->updateStatus(GTVerificationResult::PUBLICATION_CHECKED); return $result; } /** * Certificate verification. * * @static * @throws GTException * @param ASN1Sequence|array $certificate * @param string $historyTime * @param resource $publicKey * @return GTVerificationResult timestamp verification result */ private static function verifyCertificate($certificate, $historyTime, $publicKey) { $result = new GTVerificationResult(); if (empty($certificate)) { throw new GTException("Parameter certificate is required"); } if (empty($historyTime)) { throw new GTException("Parameter historyTime is required"); } if (empty($publicKey)) { throw new GTException("Parameter publicKey is required"); } $cert = new X509Certificate($certificate); // verify cert was valid when timestamp was created $params = $cert->getParameters(); if ($params["validFrom_time_t"] > $historyTime) { $result->updateErrors(GTVerificationResult::CERTIFICATE_FAILURE); } if ($params["validTo_time_t"] < $historyTime) { $result->updateErrors(GTVerificationResult::CERTIFICATE_FAILURE); } // verify public key belongs to cert $hash1 = X509Certificate::getPublicKeyHash($publicKey); $hash2 = X509Certificate::getPublicKeyHash($cert->getPublicKey()); if ($hash1 != $hash2) { $result->updateErrors(GTVerificationResult::CERTIFICATE_FAILURE); } return $result; } /** * Public key signature verification. * * @static * @throws GTException * @param GTTimeSignature $timeSignature * @param resource $publicKey * @return GTVerificationResult timestamp verification result */ private static function verifyPkSignature(GTTimeSignature $timeSignature, $publicKey) { $result = new GTVerificationResult(); $signatureAlgorithm = $timeSignature->getPkSignature()->getSignatureAlgorithm()->getAlgorithm(); switch ($signatureAlgorithm) { case '1.2.840.113549.1.1.11': $signatureAlgorithm = 'sha256'; break; default: throw new GTException("Unsupported signature algorithm: {$signatureAlgorithm}"); } $sign = $timeSignature->getPkSignature()->getSignatureValue(); $data = $timeSignature->getPublishedData()->encodeDER(); if (!X509Certificate::verifyPublicKeySignature($publicKey, $data, $sign, $signatureAlgorithm)) { $result->updateErrors(GTVerificationResult::PUBLIC_KEY_SIGNATURE_FAILURE); } return $result; } } ?>