kopia lustrzana https://github.com/nextcloud/social
VERIFY linked data signature
rodzic
8b7b41deeb
commit
b99c3d4b67
|
@ -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"
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\Social\Exceptions;
|
||||
|
||||
class LinkedDataSignatureMissingException extends \Exception {
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue