Skip to content

Commit

Permalink
Merge pull request joomla#3 from fancyFranci/tuf-client
Browse files Browse the repository at this point in the history
Database stuff
  • Loading branch information
bembelimen authored Jun 10, 2022
2 parents ca1d7e7 + 9b62a03 commit 133eacd
Show file tree
Hide file tree
Showing 7 changed files with 353 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--
-- Table structure for table `#__tuf_metadata`
--

CREATE TABLE IF NOT EXISTS `#__tuf_metadata` (
`id` int NOT NULL AUTO_INCREMENT,
`extension_id` int DEFAULT 0,
`root_json` text DEFAULT NULL,
`target_json` text DEFAULT NULL,
`snapshot_json` text DEFAULT NULL,
`timestamp_json` text DEFAULT NULL,
`mirrors_json` text DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci COMMENT='Secure TUF Updates';

-- --------------------------------------------------------
INSERT INTO `#__tuf_metadata` (`extension_id`, `root_json`)
SELECT `extension_id`, '{"keytype": "ed25519", "scheme": "ed25519", "keyid": "02c3130c26fb3fe13fda279d578f3bc251f2ca3a42e5878de063e0ee345533c9", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "f813a2882b305389cac36a9b8ebee7576ba7a7de671d2617074b03c12fb003aa", "private": "b7cb4fab28bae035a6fc5d46736e6f2d10ea4ef943e6aace8c637c1fd141ac72"}}' FROM `#__extensions` WHERE `type`='file' AND `element`='joomla';
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"web-auth/webauthn-lib": "2.1.*",
"composer/ca-bundle": "^1.2",
"dragonmantank/cron-expression": "^3.1",
"symfony/validator": "^5.4",
"enshrined/svg-sanitize": "^0.15.4"
},
"require-dev": {
Expand Down
23 changes: 23 additions & 0 deletions installation/sql/mysql/base.sql
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,29 @@ CREATE TABLE IF NOT EXISTS `#__updates` (

-- --------------------------------------------------------

--
-- Table structure for table `#__tuf_updates`
--

CREATE TABLE IF NOT EXISTS `#__tuf_metadata` (
`id` int NOT NULL AUTO_INCREMENT,
`extension_id` int DEFAULT 0,
`root_json` text DEFAULT NULL,
`targets_json` text DEFAULT NULL,
`snapshot_json` text DEFAULT NULL,
`timestamp_json` text DEFAULT NULL,
`mirrors_json` text DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci COMMENT='Secure TUF Updates';

--
-- Dumping data for table `#__tuf_metadata`
--
INSERT INTO `#__tuf_metadata` (`extension_id`, `root_json`)
SELECT `extension_id`, '{"keytype": "ed25519", "scheme": "ed25519", "keyid": "02c3130c26fb3fe13fda279d578f3bc251f2ca3a42e5878de063e0ee345533c9", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "f813a2882b305389cac36a9b8ebee7576ba7a7de671d2617074b03c12fb003aa", "private": "b7cb4fab28bae035a6fc5d46736e6f2d10ea4ef943e6aace8c637c1fd141ac72"}}' FROM `#__extensions` WHERE `type`='file' AND `element`='joomla';

-- --------------------------------------------------------

--
-- Table structure for table `#__update_sites`
--
Expand Down
109 changes: 109 additions & 0 deletions libraries/src/TUF/DatabaseStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

namespace Joomla\CMS\TUF;

use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Tuf;
use Joomla\CMS\TUF\Exception\RoleNotFoundException;
use Joomla\Database\DatabaseDriver;

\defined('JPATH_PLATFORM') or die;

/**
* @since __DEPLOY_VERSION__
*/
class DatabaseStorage implements \ArrayAccess
{
/**
* The Tuf table object
*
* @var Table
*/
protected Table $table;

/**
* Initialize the DatabaseStorage class
*
* @param DatabaseDriver $db
* @param integer $extensionId
*/
public function __construct(DatabaseDriver $db, int $extensionId)
{
$this->table = new Tuf($db);

$this->table->load($extensionId);
}

/**
* {@inheritdoc}
*/
public function offsetExists(mixed $offset): bool
{
$column = $this->getCleanColumn($offset);

return substr($offset, -5) === '_json' && $this->table->hasField($column) && strlen($this->table->$column);
}

/**
* {@inheritdoc}
*/
public function offsetGet($offset): mixed
{
if (!$this->offsetExists($offset))
{
throw new RoleNotFoundException;
}

$column = $this->getCleanColumn($offset);

return $this->table->$column;
}

/**
* {@inheritdoc}
*/
public function offsetSet($offset, $value): void
{
if (!$this->offsetExists($offset))
{
throw new RoleNotFoundException;
}

$this->table->$offset = $value;

$this->table->store();
}

/**
* {@inheritdoc}
*/
public function offsetUnset($offset): void
{
if (!$this->offsetExists($offset))
{
throw new RoleNotFoundException;
}

$this->table->$offset = '';

$this->table->store();
}

/**
* Convert file names to table columns
*
* @param string $name
*
* @return string
*/
protected function getCleanColumn($name): string
{
return str_replace('.', '_', $name);
}
}
20 changes: 20 additions & 0 deletions libraries/src/TUF/Exception/RoleNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

namespace Joomla\CMS\TUF\Exception;

\defined('JPATH_PLATFORM') or die;

/**
* Exception class defining that the Role could not be found
*
* @since __DEPLOY_VERSION__
*/
class RoleNotFoundException extends \Exception
{
}
151 changes: 151 additions & 0 deletions libraries/src/TUF/TufValidation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

namespace Joomla\CMS\TUF;

use Joomla\CMS\Factory;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\ParameterType;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Tuf\Client\GuzzleFileFetcher;
use Tuf\Client\Updater;
use Tuf\Exception\Attack\FreezeAttackException;
use Tuf\Exception\Attack\RollbackAttackException;
use Tuf\Exception\Attack\SignatureThresholdException;
use Tuf\Exception\MetadataException;
use Tuf\JsonNormalizer;

\defined('JPATH_PLATFORM') or die;

/**
* @since __DEPLOY_VERSION__
*/
class TufValidation
{
/**
* The id of the extension to be updated
*
* @var integer
*/
private int $extensionId;

/**
* The params of the validator
*
* @var mixed
*/
private mixed $params;

/**
* Validating updates with TUF
*
* @param integer $extensionId The ID of the extension to be checked
* @param mixed $params The parameters containing the Base-URI, the Metadata- and Targets-Path and mirrors for
* the update
*/
public function __construct(int $extensionId, mixed $params)
{
$this->extensionId = $extensionId;

$resolver = new OptionsResolver;

try
{
$this->configureTufOptions($resolver);
}
catch (\Exception)
{
}

try
{
$params = $resolver->resolve($params);
}
catch (\Exception $e)
{
if ($e instanceof UndefinedOptionsException || $e instanceof InvalidOptionsException)
{
throw $e;
}
}

$this->params = $params;
}

/**
* Configures default values or pass arguments to params
*
* @param OptionsResolver $resolver The OptionsResolver for the params
* @return void
*/
protected function configureTufOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'url_prefix' => 'https://raw.githubusercontent.com',
'metadata_path' => '/joomla/updates/test/repository/',
'targets_path' => '/targets/',
'mirrors' => [],
]
)
->setAllowedTypes('url_prefix', 'string')
->setAllowedTypes('metadata_path', 'string')
->setAllowedTypes('targets_path', 'string')
->setAllowedTypes('mirrors', 'array');
}

/**
* Checks for updates and writes it into the database if they are valid. Then it gets the targets.json content and
* returns it
*
* @return mixed Returns the targets.json if the validation is successful, otherwise null
*/
public function getValidUpdate(): mixed
{
$db = Factory::getContainer()->get(DatabaseDriver::class);

// $db = Factory::getDbo();

$fileFetcher = GuzzleFileFetcher::createFromUri($this->params['url_prefix'], $this->params['metadata_path'], $this->params['targets_path']);
$updater = new Updater(
$fileFetcher,
$this->params['mirrors'],
new DatabaseStorage($db, $this->extensionId)
);

try
{
// Refresh the data if needed, it will be written inside the DB, then we fetch it afterwards and return it to
// the caller
$updater->refresh();
$query = $db->getQuery(true)
->select('targets_json')
->from($db->quoteName('#__tuf_metadata', 'map'))
->where($db->quoteName('map.id') . ' = :id')
->bind(':id', $this->extensionId, ParameterType::INTEGER);
$db->setQuery($query);

$resultArray = (array) $db->loadObject();

return JsonNormalizer::decode($resultArray['targets_json']);
}
catch (FreezeAttackException | MetadataException | SignatureThresholdException | RollbackAttackException $e)
{
// When the validation fails, for example when one file is written but the others don't, we roll back everything
// and cancel the update
$query = $db->getQuery(true)
->delete('#__tuf_metadata')
->columns(['snapshot_json', 'targets_json', 'timestamp_json']);
$db->setQuery($query);

return null;
}
}
}
31 changes: 31 additions & 0 deletions libraries/src/Table/Tuf.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

namespace Joomla\CMS\Table;

\defined('JPATH_PLATFORM') or die;

/**
* TUF map table
*
* @since __DEPLOY_VERSION__
*/
class Tuf extends Table
{
/**
* Constructor
*
* @param \Joomla\Database\DatabaseDriver $db A database connector object
*
* @since __DEPLOY_VERSION__
*/
public function __construct($db)
{
parent::__construct('#__tuf_metadata', 'id', $db);
}
}

0 comments on commit 133eacd

Please sign in to comment.