<?php
namespace ParagonIE\Certainty;
use ParagonIE\Certainty\Exception\BundleException;
use ParagonIE\Certainty\Exception\EncodingException;
use ParagonIE\Certainty\Exception\FilesystemException;
/**
* Class Fetch
* @package ParagonIE\Certainty
*/
class Fetch
{
const CHECK_SIGNATURE_BY_DEFAULT = false;
const CHECK_CHRONICLE_BY_DEFAULT = false;
/**
* @var string $dataDirectory
*/
protected $dataDirectory = '';
/**
* Fetch constructor.
*
* You almost certainly want to use RemoteFetch instead.
*
* @param string $dataDir Where the certificates and configuration lives
*/
public function __construct($dataDir = '')
{
if (!empty($dataDir) && \is_readable($dataDir)) {
$this->dataDirectory = $dataDir;
} else {
$this->dataDirectory = \dirname(__DIR__) . '/data';
}
}
/**
* Get the latest bundle. Checks the SHA256 hash of the file versus what
* is expected. Optionally checks the Ed25519 signature.
*
* @param bool|null $checkEd25519Signature Enforce Ed25519 signatures?
* @param bool|null $checkChronicle Require cert bundles be stored
* inside a Chronicle instance?
* @return Bundle
* @throws BundleException
*/
public function getLatestBundle($checkEd25519Signature = null, $checkChronicle = null)
{
if (\is_null($checkEd25519Signature)) {
$checkEd25519Signature = (bool) static::CHECK_SIGNATURE_BY_DEFAULT;
}
if (\is_null($checkChronicle)) {
$checkChronicle = (bool) static::CHECK_CHRONICLE_BY_DEFAULT;
}
/** @var Bundle $bundle */
foreach ($this->listBundles() as $bundle) {
if ($bundle->hasCustom()) {
$validator = $bundle->getValidator();
} else {
$validator = new Validator();
}
// If the SHA256 doesn't match, fail fast.
if ($validator::checkSha256Sum($bundle)) {
/** @var bool $valid */
$valid = true;
if ($checkEd25519Signature) {
$valid = $valid && $validator::checkEd25519Signature($bundle);
}
if ($checkChronicle) {
$valid = $valid && $validator::checkChronicleHash($bundle);
}
if ($valid) {
return $bundle;
}
}
}
throw new BundleException('No valid bundles were found in the data directory.');
}
/**
* Get an array of all of the Bundles, ordered most-recent to oldest.
*
* No validation is performed automatically.
*
* @param string $customValidator Fully-qualified class name for Validator
* @return array<int, Bundle>
*/
public function getAllBundles($customValidator = '')
{
return \array_values($this->listBundles($customValidator));
}
/**
* List bundles
*
* @param string $customValidator Fully-qualified class name for Validator
* @return array<int, Bundle>
* @throws \Exception
*/
protected function listBundles($customValidator = '')
{
if (!\file_exists($this->dataDirectory . '/ca-certs.json')) {
throw new FilesystemException('ca-certs.json not found in data directory.');
}
if (!\is_readable($this->dataDirectory . '/ca-certs.json')) {
throw new FilesystemException('ca-certs.json is not readable.');
}
$contents = \file_get_contents($this->dataDirectory . '/ca-certs.json');
if (!\is_string($contents)) {
throw new FilesystemException('ca-certs.json could not be read.');
}
$data = \json_decode($contents, true);
if (!\is_array($data)) {
throw new EncodingException('ca-certs.json is not a valid JSON file.');
}
$bundles = [];
foreach ($data as $row) {
if (!isset($row['date'], $row['file'], $row['sha256'], $row['signature'])) {
// The necessary keys are not defined.
continue;
}
$key = (int) (\preg_replace('/[^0-9]/', '', $row['date']) . '0000');
while (isset($bundles[$key])) {
++$key;
}
$bundles[$key] = new Bundle(
$this->dataDirectory . '/' . $row['file'],
$row['sha256'],
$row['signature'],
!empty($row['custom']) ? $row['custom'] : $customValidator,
isset($row['chronicle']) ? $row['chronicle'] : ''
);
}
\krsort($bundles);
return $bundles;
}
}
|