piler/webui/system/helper/TrustedTimestamps.php

246 lines
9.0 KiB
PHP
Raw Normal View History

2015-04-22 12:26:04 +02:00
<?php
/**
* TrustedTimestamps.php - Creates Timestamp Requestfiles, processes the request at a Timestamp Authority (TSA) after RFC 3161
*
* bases on OpenSSL and RFC 3161: http://www.ietf.org/rfc/rfc3161.txt
*
* WARNING:
* needs openssl ts, which is availible in OpenSSL versions >= 0.99
* This is currently (2011-03-02) not the case in Debian
* (see http://stackoverflow.com/questions/5043393/openssl-ts-command-not-working-trusted-timestamps)
* -> Possibility: Debian Experimentals -> http://wiki.debian.org/DebianExperimental
*
* For OpenSSL on Windows, see
* http://www.slproweb.com/products/Win32OpenSSL.html
* http://www.switch.ch/aai/support/howto/openssl-windows.html
*
* 2021-10-26 Frank Schmirler:
* - extract certificate chain from TSResponse and feed into ts -verify as -untrusted
* - unlink temporary files
* - support for sha256 and sha512 hashes
*
2015-04-22 12:26:04 +02:00
* @version 0.3
* @author David M<EFBFBD>ller
* @package trustedtimestamps
*/
class TrustedTimestamps
{
/**
* Creates a Timestamp Requestfile from a hash
*
* @param string $hash: The hashed data (sha1, sha256 or sha512)
2015-04-22 12:26:04 +02:00
* @return string: path of the created timestamp-requestfile
*/
public static function createRequestfile ($hash)
{
if (strlen($hash) === 40)
$digest="-sha1";
elseif (strlen($hash) === 64)
$digest="-sha256";
elseif (strlen($hash) === 128)
$digest="-sha512";
else
2015-04-22 12:26:04 +02:00
throw new Exception("Invalid Hash.");
2015-04-22 12:26:04 +02:00
$outfilepath = self::createTempFile();
$cmd = OPENSSL_BINARY . " ts -query $digest -digest ".escapeshellarg($hash)." -cert -out ".escapeshellarg($outfilepath);
2015-04-22 12:26:04 +02:00
$retarray = array();
exec($cmd." 2>&1", $retarray, $retcode);
if ($retcode !== 0)
throw new Exception("OpenSSL does not seem to be installed: ".implode(", ", $retarray));
2015-04-22 12:26:04 +02:00
if (count($retarray) > 0 && stripos($retarray[0], "openssl:Error") !== false)
throw new Exception("There was an error with OpenSSL. Is version >= 0.99 installed?: ".implode(", ", $retarray));
return $outfilepath;
}
/**
* Signs a timestamp requestfile at a TSA using CURL
*
* @param string $requestfile_path: The path to the Timestamp Requestfile as created by createRequestfile
* @param string $tsa_url: URL of a TSA such as http://zeitstempel.dfn.de
* @return array of response_string with the unix-timetamp of the timestamp response and the base64-encoded response_string
*/
public static function signRequestfile ($requestfile_path, $tsa_url)
{
if (!file_exists($requestfile_path))
throw new Exception("The Requestfile was not found");
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $tsa_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents($requestfile_path));
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/timestamp-query'));
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TSA_VERIFY_CERTIFICATE);
2015-04-22 12:26:04 +02:00
$binary_response_string = curl_exec($ch);
$error = curl_error($ch);
2015-04-22 12:26:04 +02:00
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
2015-04-22 12:26:04 +02:00
curl_close($ch);
2015-04-22 12:26:04 +02:00
if ($status != 200 || !strlen($binary_response_string))
throw new Exception("The request failed. Status: $status, error: $error");
2015-04-22 12:26:04 +02:00
$base64_response_string = base64_encode($binary_response_string);
2015-04-22 12:26:04 +02:00
$response_time = self::getTimestampFromAnswer ($base64_response_string);
2015-04-22 12:26:04 +02:00
return array("response_string" => $base64_response_string,
"response_time" => $response_time);
}
/**
* Extracts the unix timestamp from the base64-encoded response string as returned by signRequestfile
*
* @param string $base64_response_string: Response string as returned by signRequestfile
* @return int: unix timestamp
*/
public static function getTimestampFromAnswer ($base64_response_string)
{
$binary_response_string = base64_decode($base64_response_string);
$responsefile = self::createTempFile($binary_response_string);
$cmd = OPENSSL_BINARY . " ts -reply -in ".escapeshellarg($responsefile)." -text";
2015-04-22 12:26:04 +02:00
$retarray = array();
exec($cmd." 2>&1", $retarray, $retcode);
unlink($responsefile);
2015-04-22 12:26:04 +02:00
if ($retcode !== 0)
throw new Exception("The reply failed: ".implode(", ", $retarray));
2015-04-22 12:26:04 +02:00
$matches = array();
$response_time = 0;
/*
* Format of answer:
*
* Foobar: some stuff
* Time stamp: 21.08.2010 blabla GMT
* Somestuff: Yayayayaya
*/
foreach ($retarray as $retline)
{
if (preg_match("~^Time\sstamp\:\s(.*)~", $retline, $matches))
{
$response_time = strtotime($matches[1]);
break;
2015-04-22 12:26:04 +02:00
}
}
if (!$response_time)
throw new Exception("The Timestamp was not found");
2015-04-22 12:26:04 +02:00
return $response_time;
}
/**
*
* @param string $hash: sha1 hash of the data which should be checked
* @param string $base64_response_string: The response string as returned by signRequestfile
* @param int $response_time: The response time, which should be checked
* @param string $tsa_cert_file: The path to the TSAs certificate chain (e.g. https://pki.pca.dfn.de/global-services-ca/pub/cacert/chain.txt)
* @return <type>
*/
public static function validate ($hash, $base64_response_string, $response_time, $tsa_cert_file)
{
if (strlen($hash) !== 40 && strlen($hash) !== 64 && strlen($hash) !== 128)
2015-04-22 12:26:04 +02:00
throw new Exception("Invalid Hash");
2015-04-22 12:26:04 +02:00
$binary_response_string = base64_decode($base64_response_string);
2015-04-22 12:26:04 +02:00
if (!strlen($binary_response_string))
throw new Exception("There was no response-string");
2015-04-22 12:26:04 +02:00
if (!intval($response_time))
throw new Exception("There is no valid response-time given");
2015-04-22 12:26:04 +02:00
if (!file_exists($tsa_cert_file))
throw new Exception("The TSA-Certificate could not be found");
2015-04-22 12:26:04 +02:00
$responsefile = self::createTempFile($binary_response_string);
/*
* extract chain from response
* openssl ts -verify does not include them for verification despite of the man page stating otherwise
*/
$untrustedfile = self::createTempFile();
$cmd = OPENSSL_BINARY . " ts -reply -in ".escapeshellarg($responsefile)." -token_out | " . OPENSSL_BINARY . " pkcs7 -inform DER -print_certs -out ".escapeshellarg($untrustedfile);
shell_exec($cmd);
$cmd = OPENSSL_BINARY . " ts -verify -digest ".escapeshellarg($hash)." -in ".escapeshellarg($responsefile)." -CAfile ".escapeshellarg($tsa_cert_file)." -untrusted ".escapeshellarg($untrustedfile);
2015-04-22 12:26:04 +02:00
$retarray = array();
exec($cmd." 2>&1", $retarray, $retcode);
unlink($untrustedfile);
unlink($responsefile);
2015-04-22 12:26:04 +02:00
/*
* just 2 "normal" cases:
* 1) Everything okay -> retcode 0 + retarray[0] == "Verification: OK"
* 2) Hash is wrong -> retcode 1 + strpos(retarray[somewhere], "message imprint mismatch") !== false
*
* every other case (Certificate not found / invalid / openssl is not installed / ts command not known)
* are being handled the same way -> retcode 1 + any retarray NOT containing "message imprint mismatch"
*
* For openssl 1.1.x it's 2 lines actually:
*
* Using configuration from /usr/lib/ssl/openssl.cnf
* Verification: OK
*
2015-04-22 12:26:04 +02:00
*/
if ($retcode === 0) {
foreach ($retarray as $line) {
if(strtolower(trim($line)) == "verification: ok") {
if (self::getTimestampFromAnswer ($base64_response_string) != $response_time)
throw new Exception("The responsetime of the request was changed");
return true;
}
}
2015-04-22 12:26:04 +02:00
}
foreach ($retarray as $retline)
{
if (stripos($retline, "message imprint mismatch") !== false)
return false;
}
throw new Exception("Systemcommand failed: ".implode(", ", $retarray));
}
/**
* Create a tempfile in the systems temp path
*
* @param string $str: Content which should be written to the newly created tempfile
* @return string: filepath of the created tempfile
*/
public static function createTempFile ($str = "")
{
$tempfilename = tempnam(sys_get_temp_dir(), rand());
if (!file_exists($tempfilename))
throw new Exception("Tempfile could not be created");
2015-04-22 12:26:04 +02:00
if (!empty($str) && !file_put_contents($tempfilename, $str))
throw new Exception("Could not write to tempfile");
return $tempfilename;
}
}