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 * Class containing static helper methods for timestamp verification. 26 * 27 * @package tsp 28 */ 29class GTVerifier { 30 31 /** 32 * Verifies a short term timestamp using the given public key. 33 * 34 * @static 35 * @throws GTException 36 * @param CMSContentInfo $content timestamp token 37 * @param GTDataHash $dataHash datahash to verify the timestamp against 38 * @param resource $publicKey openssl public key to use for verification 39 * @return GTVerificationResult timestamp verification result 40 */ 41 public static function verifyWithSignature(CMSContentInfo $content, GTDataHash $dataHash, $publicKey) { 42 43 $result = new GTVerificationResult(); 44 $result->update(GTVerifier::verifyCommon($content, $dataHash)); 45 46 if (!$result->isValid()) { 47 return $result; 48 } 49 50 $signedData = $content->getContent(); 51 $signerInfo = $signedData->getSignerInfo(); 52 53 $timeSignature = new GTTimeSignature(); 54 $timeSignature->decode($signerInfo->getSignature()); 55 56 $certificates = $signedData->getCertificates(); 57 58 if (count($certificates) < 1) { 59 throw new GTException("Invalid timestamp: no certificates"); 60 } 61 62 $certificate = $certificates[0]; 63 64 $publicationId = new GTBigInteger( 65 $timeSignature->getPublishedData()->getPublicationIdentifier() 66 ); 67 68 $historyChain = GTHashChain::getHistoryInstance($timeSignature->getHistory()); 69 $historyId = $historyChain->computeHistoryId($publicationId); 70 $historyTime = $historyId->getValue(); 71 72 $result->update(GTVerifier::verifyCertificate($certificate, $historyTime, $publicKey)); 73 74 if (!$result->isValid()) { 75 return $result; 76 } 77 78 $result->update(GTVerifier::verifyPkSignature($timeSignature, $publicKey)); 79 80 return $result; 81 } 82 83 /** 84 * Verifies an extended timestamp against the given publication. 85 * 86 * @static 87 * @throws GTException 88 * @param CMSContentInfo $content timestamp token 89 * @param GTDataHash $dataHash datahash to verify this timestamp against 90 * @param string $publication base32 encoded publication to use for timestamp verification 91 * @return GTVerificationResult timestamp verification result 92 */ 93 public static function verifyWithPublication(CMSContentInfo $content, GTDataHash $dataHash, $publication) { 94 95 $result = new GTVerificationResult(); 96 $result->update(GTVerifier::verifyCommon($content, $dataHash)); 97 98 if (!$result->isValid()) { 99 return $result; 100 } 101 102 $signedData = $content->getContent(); 103 $signerInfo = $signedData->getSignerInfo(); 104 105 $timeSignature = new GTTimeSignature(); 106 $timeSignature->decode($signerInfo->getSignature()); 107 108 if (!$timeSignature->isExtended()) { 109 throw new GTException("Unable to verify timeSignature that is not extended with publication"); 110 } 111 112 $result->update(GTVerifier::verifyPublication($timeSignature, $publication)); 113 114 return $result; 115 } 116 117 /** 118 * Common verification, used by both publication and signature verification. 119 * 120 * @static 121 * @throws GTException 122 * @param CMSContentInfo $content timestamp token 123 * @param GTDataHash $dataHash data hash to verify this timestamp against 124 * @return GTVerificationResult timestamp verification result 125 */ 126 private static function verifyCommon(CMSContentInfo $content, GTDataHash $dataHash) { 127 128 $result = new GTVerificationResult(); 129 130 $signedData = $content->getContent(); 131 $signerInfo = $signedData->getSignerInfo(); 132 133 $timeSignature = new GTTimeSignature(); 134 $timeSignature->decode($signerInfo->getSignature()); 135 136 if ($timeSignature->getPubReference() != null) { 137 $result->updateStatus(GTVerificationResult::PUBLICATION_REFERENCE_PRESENT); 138 } 139 140 if ($timeSignature->getPkSignature() != null) { 141 $result->updateStatus(GTVerificationResult::PUBLIC_KEY_SIGNATURE_PRESENT); 142 } 143 144 $messageImprint = $signedData->getEncapsulatedContent()->getContent()->getMessageImprint(); 145 146 $result->update(GTVerifier::verifyDataHash($messageImprint, $dataHash)); 147 148 if (!$result->isValid()) { 149 return $result; 150 } 151 152 $digestAlgorithm = GTHashAlgorithm::getByOid($signerInfo->getDigestAlgorithm()->getAlgorithm()); 153 154 $signedAttrs = $signerInfo->getSignedAttrs(); 155 156 if ($signedAttrs == null) { 157 throw new GTException("Invalid signed attributes: null"); 158 } 159 160 // GTTimestamp already checks that the messageDigest attribute is 161 // the second signed attribute and has a single OCTECT STRING value 162 $messageDigest = $signedAttrs[1]->getValues(); 163 $messageDigest = $messageDigest[0]->getValue(); 164 165 // verify that signedAttrs.messageDigest == signerInfo.digestAlgorithm(TSTInfo.encodeDER()) 166 167 $dataHash = new GTDataHash($digestAlgorithm); 168 $dataHash->update($signedData->getEncapsulatedContent()->getContentRaw()); 169 $dataHash->close(); 170 171 if ($messageDigest !== $dataHash->getHashedMessage()) { 172 $result->updateErrors(GTVerificationResult::SYNTACTIC_CHECK_FAILURE); 173 } 174 175 if (!$result->isValid()) { 176 return $result; 177 } 178 179 // now feed the hash of signed attributes to the hash chains 180 181 $result->update(GTVerifier::verifyHashChains($timeSignature, $digestAlgorithm, $signedAttrs)); 182 183 return $result; 184 } 185 186 /** 187 * Data hash verification against the message imprint. 188 * 189 * @static 190 * @throws GTException 191 * @param TSPMessageImprint $messageImprint message imprint 192 * @param GTDataHash $dataHash data hash 193 * @return GTVerificationResult timestamp verification result 194 */ 195 private static function verifyDataHash(TSPMessageImprint $messageImprint, GTDataHash $dataHash) { 196 197 $result = new GTVerificationResult(); 198 199 if ($messageImprint == null) { 200 throw new GTException("Parameter messageImprint is required"); 201 } 202 203 if ($dataHash == null) { 204 return $result; 205 } 206 207 if ($dataHash->getHashAlgorithm()->getOid() != $messageImprint->getHashAlgorithm()->getAlgorithm()) { 208 $result->updateErrors(GTVerificationResult::WRONG_DOCUMENT_FAILURE); 209 210 } else if ($dataHash->getHashedMessage() != $messageImprint->getHashedMessage()) { 211 $result->updateErrors(GTVerificationResult::WRONG_DOCUMENT_FAILURE); 212 213 } 214 215 $result->updateStatus(GTVerificationResult::DATA_HASH_CHECKED); 216 217 return $result; 218 } 219 220 221 /** 222 * Hash chains verification method. 223 * 224 * @static 225 * @param GTTimeSignature $timeSignature 226 * @param GTHashAlgorithm $digestAlgorithm 227 * @param array $signedAttrs 228 * @return GTVerificationResult timestamp verification result 229 */ 230 private static function verifyHashChains(GTTimeSignature $timeSignature, GTHashAlgorithm $digestAlgorithm, array $signedAttrs) { 231 232 $result = new GTVerificationResult(); 233 234 $locationChainBytes = $timeSignature->getLocation(); 235 $historyChainBytes = $timeSignature->getHistory(); 236 237 $locationChain = null; 238 $historyChain = null; 239 240 try { 241 242 $locationChain = GTHashChain::getLocationInstance($locationChainBytes); 243 $historyChain = GTHashChain::getHistoryInstance($historyChainBytes); 244 245 } catch (GTException $e) { 246 $result->updateErrors(GTVerificationResult::SYNTACTIC_CHECK_FAILURE); 247 return $result; 248 } 249 250 $publicationImprint = $timeSignature->getPublishedData()->getPublicationImprint(); 251 $publicationImprintAlg = GTHashAlgorithm::getByGtid($publicationImprint[0]); 252 253 if ($publicationImprintAlg->getLength() + 1 != count($publicationImprint)) { 254 $result->updateErrors(GTVerificationResult::SYNTACTIC_CHECK_FAILURE); 255 return $result; 256 } 257 258 $set = new ASN1Set(); 259 260 foreach ($signedAttrs as $attr) { 261 $set->add($attr); 262 } 263 264 $input = new GTDataHash($digestAlgorithm); 265 $input->update($set->encodeDER()); 266 $input->close(); 267 268 $input = $input->getDataImprint(); 269 270 $locationOutput = $locationChain->computeOutput($input); 271 $historyOutput = $historyChain->computeOutput($locationOutput); 272 273 $output = new GTDataHash($publicationImprintAlg); 274 $output->update($historyOutput); 275 $output->close(); 276 277 $output = $output->getDataImprint(); 278 279 if ($output != $publicationImprint) { 280 $result->updateErrors(GTVerificationResult::HASHCHAIN_VERIFICATION_FAILURE); 281 } 282 283 return $result; 284 } 285 286 /** 287 * Publication verification. 288 * 289 * @static 290 * @throws GTException 291 * @param GTTimeSignature $timeSignature 292 * @param string $publication 293 * @return GTVerificationResult timestamp verification result 294 */ 295 private static function verifyPublication(GTTimeSignature $timeSignature, $publication) { 296 297 if ($publication == null) { 298 throw new GTException("parameter publication is required"); 299 } 300 301 $result = new GTVerificationResult(); 302 303 if ($publication != $timeSignature->getPublishedData()->getEncodedPublication()) { 304 $result->updateErrors(GTVerificationResult::PUBLICATION_FAILURE); 305 } 306 307 $result->updateStatus(GTVerificationResult::PUBLICATION_CHECKED); 308 309 return $result; 310 } 311 312 /** 313 * Certificate verification. 314 * 315 * @static 316 * @throws GTException 317 * @param ASN1Sequence|array $certificate 318 * @param string $historyTime 319 * @param resource $publicKey 320 * @return GTVerificationResult timestamp verification result 321 */ 322 private static function verifyCertificate($certificate, $historyTime, $publicKey) { 323 324 $result = new GTVerificationResult(); 325 326 if (empty($certificate)) { 327 throw new GTException("Parameter certificate is required"); 328 } 329 330 if (empty($historyTime)) { 331 throw new GTException("Parameter historyTime is required"); 332 } 333 334 if (empty($publicKey)) { 335 throw new GTException("Parameter publicKey is required"); 336 } 337 338 $cert = new X509Certificate($certificate); 339 340 // verify cert was valid when timestamp was created 341 342 $params = $cert->getParameters(); 343 344 if ($params["validFrom_time_t"] > $historyTime) { 345 $result->updateErrors(GTVerificationResult::CERTIFICATE_FAILURE); 346 } 347 348 if ($params["validTo_time_t"] < $historyTime) { 349 $result->updateErrors(GTVerificationResult::CERTIFICATE_FAILURE); 350 } 351 352 // verify public key belongs to cert 353 354 $hash1 = X509Certificate::getPublicKeyHash($publicKey); 355 $hash2 = X509Certificate::getPublicKeyHash($cert->getPublicKey()); 356 357 if ($hash1 != $hash2) { 358 $result->updateErrors(GTVerificationResult::CERTIFICATE_FAILURE); 359 } 360 361 return $result; 362 } 363 364 /** 365 * Public key signature verification. 366 * 367 * @static 368 * @throws GTException 369 * @param GTTimeSignature $timeSignature 370 * @param resource $publicKey 371 * @return GTVerificationResult timestamp verification result 372 */ 373 private static function verifyPkSignature(GTTimeSignature $timeSignature, $publicKey) { 374 375 $result = new GTVerificationResult(); 376 377 $signatureAlgorithm = $timeSignature->getPkSignature()->getSignatureAlgorithm()->getAlgorithm(); 378 379 switch ($signatureAlgorithm) { 380 381 case '1.2.840.113549.1.1.11': 382 $signatureAlgorithm = 'sha256'; 383 break; 384 385 default: 386 throw new GTException("Unsupported signature algorithm: {$signatureAlgorithm}"); 387 } 388 389 $sign = $timeSignature->getPkSignature()->getSignatureValue(); 390 $data = $timeSignature->getPublishedData()->encodeDER(); 391 392 if (!X509Certificate::verifyPublicKeySignature($publicKey, $data, $sign, $signatureAlgorithm)) { 393 $result->updateErrors(GTVerificationResult::PUBLIC_KEY_SIGNATURE_FAILURE); 394 } 395 396 return $result; 397 } 398 399} 400 401?> 402