VERIFY linked data signature

pull/206/head
Maxence Lange 2018-12-11 10:42:33 -01:00 zatwierdzone przez Julius Härtl
rodzic 8b7b41deeb
commit b99c3d4b67
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4C614C6ED2CDE6DF
7 zmienionych plików z 411 dodań i 16 usunięć

Wyświetl plik

@ -10,7 +10,8 @@
}
],
"require": {
"daita/my-small-php-tools": "dev-master"
"daita/my-small-php-tools": "dev-master",
"digitalbazaar/json-ld": "0.4.7"
},
"require-dev": {
"jakub-onderka/php-parallel-lint": "^1.0"

56
composer.lock wygenerowano
Wyświetl plik

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "17d7e7fc4c9cdd0ddc5d6166607fce8f",
"content-hash": "2c21b8c338f3d2f929f349df19edbc0e",
"packages": [
{
"name": "daita/my-small-php-tools",
@ -12,12 +12,12 @@
"source": {
"type": "git",
"url": "https://github.com/daita/my-small-php-tools.git",
"reference": "405a5e6afadfd7c0630cf33b9e48a39f6938f605"
"reference": "29754f18951856a22c0fd5fc388b6162ea98fe8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/daita/my-small-php-tools/zipball/405a5e6afadfd7c0630cf33b9e48a39f6938f605",
"reference": "405a5e6afadfd7c0630cf33b9e48a39f6938f605",
"url": "https://api.github.com/repos/daita/my-small-php-tools/zipball/29754f18951856a22c0fd5fc388b6162ea98fe8a",
"reference": "29754f18951856a22c0fd5fc388b6162ea98fe8a",
"shasum": ""
},
"require": {
@ -40,7 +40,53 @@
}
],
"description": "My small PHP Tools",
"time": "2018-12-08T15:17:26+00:00"
"time": "2018-12-18T00:38:01+00:00"
},
{
"name": "digitalbazaar/json-ld",
"version": "0.4.7",
"source": {
"type": "git",
"url": "https://github.com/digitalbazaar/php-json-ld.git",
"reference": "dc1bd23f0ee2efd27ccf636d32d2738dabcee182"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/digitalbazaar/php-json-ld/zipball/dc1bd23f0ee2efd27ccf636d32d2738dabcee182",
"reference": "dc1bd23f0ee2efd27ccf636d32d2738dabcee182",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": ">=5.3.0"
},
"type": "library",
"autoload": {
"files": [
"jsonld.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Digital Bazaar, Inc.",
"email": "support@digitalbazaar.com"
}
],
"description": "A JSON-LD Processor and API implementation in PHP.",
"homepage": "https://github.com/digitalbazaar/php-json-ld",
"keywords": [
"JSON-LD",
"Linked Data",
"RDF",
"Semantic Web",
"json",
"jsonld"
],
"time": "2016-04-25T04:17:52+00:00"
}
],
"packages-dev": [

Wyświetl plik

@ -35,8 +35,16 @@ use Exception;
use OC\AppFramework\Http;
use OCA\Social\AppInfo\Application;
use OCA\Social\Db\NotesRequest;
use OCA\Social\Exceptions\ActivityPubFormatException;
use OCA\Social\Exceptions\InvalidResourceEntryException;
use OCA\Social\Exceptions\InvalidResourceException;
use OCA\Social\Exceptions\Request410Exception;
use OCA\Social\Exceptions\RequestException;
use OCA\Social\Exceptions\SignatureIsGoneException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Exceptions\UnknownItemException;
use OCA\Social\Exceptions\UrlCloudException;
use OCA\Social\Model\ActivityPub\ACore;
use OCA\Social\Service\ActivityPub\FollowService;
use OCA\Social\Service\ActivityPub\PersonService;
use OCA\Social\Service\ActivityService;
@ -181,7 +189,10 @@ class ActivityPubController extends Controller {
$origin = $this->activityService->checkRequest($this->request);
$activity = $this->importService->importFromJson($body);
$activity->setOrigin($origin);
if (!$this->activityService->checkObject($activity)) {
$activity->setOrigin($origin);
}
try {
$this->importService->parseIncomingRequest($activity);
} catch (UnknownItemException $e) {
@ -219,7 +230,10 @@ class ActivityPubController extends Controller {
// $actor = $this->actorService->getActor($username);
$activity = $this->importService->importFromJson($body);
$activity->setOrigin($origin);
if (!$this->activityService->checkObject($activity)) {
$activity->setOrigin($origin);
}
try {
$this->importService->parseIncomingRequest($activity);
} catch (UnknownItemException $e) {

Wyświetl plik

@ -0,0 +1,8 @@
<?php
namespace OCA\Social\Exceptions;
class LinkedDataSignatureMissingException extends \Exception {
}

Wyświetl plik

@ -0,0 +1,293 @@
<?php
declare(strict_types=1);
/**
* Nextcloud - Social Support
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2018, Maxence Lange <maxence@artificial-owl.com>
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Social\Model;
use daita\MySmallPhpTools\Traits\TArrayTools;
use JsonSerializable;
use OCA\Social\Exceptions\LinkedDataSignatureMissingException;
use OCA\Social\Model\ActivityPub\ACore;
/**
* Class InstancePath
*
* @package OCA\Social\Model
*/
class LinkedDataSignature implements JsonSerializable {
use TArrayTools;
/** @var string */
private $type = '';
/** @var string */
private $creator = '';
/** @var string */
private $created = '';
/** @var string */
private $signatureValue = '';
/** @var string */
private $privateKey = '';
/** @var string */
private $publicKey = '';
/** @var array */
private $object = [];
/**
* LinkedDataSignature constructor.
*/
public function __construct() {
}
/**
* @return string
*/
public function getType(): string {
return $this->type;
}
/**
* @param string $type
*
* @return LinkedDataSignature
*/
public function setType(string $type): LinkedDataSignature {
$this->type = $type;
return $this;
}
/**
* @return string
*/
public function getCreator(): string {
return $this->creator;
}
/**
* @param string $creator
*
* @return LinkedDataSignature
*/
public function setCreator(string $creator): LinkedDataSignature {
$this->creator = $creator;
return $this;
}
/**
* @return string
*/
public function getCreated(): string {
return $this->created;
}
/**
* @param string $created
*
* @return LinkedDataSignature
*/
public function setCreated(string $created): LinkedDataSignature {
$this->created = $created;
return $this;
}
/**
* @return string
*/
public function getSignatureValue(): string {
return $this->signatureValue;
}
/**
* @param string $signatureValue
*
* @return LinkedDataSignature
*/
public function setSignatureValue(string $signatureValue): LinkedDataSignature {
$this->signatureValue = $signatureValue;
return $this;
}
/**
* @return array
*/
public function getObject(): array {
return $this->object;
}
/**
* @param array $object
*
* @return LinkedDataSignature
*/
public function setObject(array $object): LinkedDataSignature {
$this->object = $object;
return $this;
}
/**
* @return string
*/
public function getPrivateKey(): string {
return $this->privateKey;
}
/**
* @param string $privateKey
*
* @return LinkedDataSignature
*/
public function setPrivateKey(string $privateKey): LinkedDataSignature {
$this->privateKey = $privateKey;
return $this;
}
/**
* @return string
*/
public function getPublicKey(): string {
return $this->publicKey;
}
/**
* @param string $publicKey
*
* @return LinkedDataSignature
*/
public function setPublicKey(string $publicKey): LinkedDataSignature {
$this->publicKey = $publicKey;
return $this;
}
public function sign() {
}
/**
* @return bool
*/
public function verify(): bool {
$header = [
'@context' => 'https://w3id.org/identity/v1',
'creator' => $this->getCreator(),
'created' => $this->getCreated()
];
$hash = $this->hashedCanonicalize($header) . $this->hashedCanonicalize($this->getObject());
$signed = base64_decode($this->getSignatureValue());
$algo = OPENSSL_ALGO_SHA256;
if ($this->getType() === 'RsaSignature2017') {
$algo = OPENSSL_ALGO_SHA256;
}
if (openssl_verify($hash, $signed, $this->getPublicKey(), $algo) === 1) {
return true;
}
return false;
}
private function hashedCanonicalize(array $data): string {
$object = json_decode(json_encode($data), false);
$res = jsonld_normalize(
$object,
[
'algorithm' => 'URDNA2015',
'format' => 'application/nquads'
]
);
return hash('sha256', $res);
}
/**
* @param array $data
*
* @throws LinkedDataSignatureMissingException
*/
public function import(array $data) {
if (!in_array(ACore::CONTEXT_SECURITY, $this->getArray('@context', $data, []))) {
throw new LinkedDataSignatureMissingException();
}
$signature = $this->getArray('signature', $data, []);
if ($signature === []) {
throw new LinkedDataSignatureMissingException();
}
$this->setType($this->get('type', $signature, ''));
$this->setCreator($this->get('creator', $signature, ''));
$this->setCreated($this->get('created', $signature, ''));
$this->setSignatureValue($this->get('signatureValue', $signature, ''));
unset($data['signature']);
$this->setObject($data);
}
/**
* @return array
*/
public function jsonSerialize(): array {
return [
'type' => $this->getType(),
'creator' => $this->getCreator(),
'created' => $this->getCreated(),
'signatureValue' => $this->getSignatureValue()
];
}
}

Wyświetl plik

@ -42,6 +42,7 @@ use OCA\Social\Exceptions\ActorDoesNotExistException;
use OCA\Social\Exceptions\EmptyQueueException;
use OCA\Social\Exceptions\InvalidOriginException;
use OCA\Social\Exceptions\InvalidResourceException;
use OCA\Social\Exceptions\LinkedDataSignatureMissingException;
use OCA\Social\Exceptions\NoHighPriorityRequestException;
use OCA\Social\Exceptions\QueueStatusException;
use OCA\Social\Exceptions\Request410Exception;
@ -56,6 +57,7 @@ use OCA\Social\Model\ActivityPub\Activity\Delete;
use OCA\Social\Model\ActivityPub\Person;
use OCA\Social\Model\ActivityPub\Tombstone;
use OCA\Social\Model\InstancePath;
use OCA\Social\Model\LinkedDataSignature;
use OCA\Social\Model\RequestQueue;
use OCA\Social\Service\ActivityPub\PersonService;
use OCP\IRequest;
@ -72,9 +74,6 @@ class ActivityService {
const TIMEOUT_ASYNC = 5;
const TIMEOUT_SERVICE = 10;
const CONTEXT_ACTIVITYSTREAMS = 'https://www.w3.org/ns/activitystreams';
const CONTEXT_SECURITY = 'https://w3id.org/security/v1';
const TO_PUBLIC = 'https://www.w3.org/ns/activitystreams#Public';
const DATE_FORMAT = 'D, d M Y H:i:s T';
@ -452,6 +451,37 @@ class ActivityService {
}
/**
* @param ACore $object
*
* @return bool
* @throws InvalidResourceException
* @throws Request410Exception
* @throws RequestException
* @throws SocialAppConfigException
* @throws UrlCloudException
* @throws InvalidOriginException
*/
public function checkObject(ACore $object): bool {
try {
$actorId = $object->getActorId();
$signature = new LinkedDataSignature();
$signature->import(json_decode($object->getSource(), true));
$signature->setPublicKey($this->retrieveKey($actorId));
if ($signature->verify()) {
$object->setOrigin($this->getKeyOrigin($actorId));
return true;
}
} catch (LinkedDataSignatureMissingException $e) {
}
return false;
}
/**
* @param ACore $activity
*
@ -482,7 +512,7 @@ class ActivityService {
/**
* @param IRequest $request
*
* @return
* @return string
* @throws InvalidResourceException
* @throws MalformedArrayException
* @throws Request410Exception
@ -492,7 +522,7 @@ class ActivityService {
* @throws UrlCloudException
* @throws InvalidOriginException
*/
private function checkSignature(IRequest $request) {
private function checkSignature(IRequest $request): string {
$signatureHeader = $request->getHeader('Signature');
$sign = $this->parseSignatureHeader($signatureHeader);

Wyświetl plik

@ -36,6 +36,7 @@ use Exception;
use OCA\Social\Exceptions\ActivityPubFormatException;
use OCA\Social\Exceptions\InvalidResourceEntryException;
use OCA\Social\Exceptions\InvalidResourceException;
use OCA\Social\Exceptions\LinkedDataSignatureMissingException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Exceptions\UnknownItemException;
use OCA\Social\Exceptions\UrlCloudException;
@ -50,6 +51,7 @@ use OCA\Social\Model\ActivityPub\Follow;
use OCA\Social\Model\ActivityPub\Image;
use OCA\Social\Model\ActivityPub\Note;
use OCA\Social\Model\ActivityPub\Activity\Undo;
use OCA\Social\Model\LinkedDataSignature;
use OCA\Social\Service\ActivityPub\DeleteService;
use OCA\Social\Service\ActivityPub\FollowService;
use OCA\Social\Service\ActivityPub\NoteService;
@ -108,22 +110,23 @@ class ImportService {
* @param string $json
*
* @return ACore
* @throws ActivityPubFormatException
* @throws InvalidResourceEntryException
* @throws SocialAppConfigException
* @throws UnknownItemException
* @throws UrlCloudException
* @throws SocialAppConfigException
* @throws ActivityPubFormatException
*/
public function importFromJson(string $json) {
$data = json_decode($json, true);
if (!is_array($data)) {
throw new ActivityPubFormatException();
}
$activity = $this->importFromData($data, null);
return $activity;
}
/**
* @param array $data
* @param ACore $root