mirror of https://github.com/MISP/MISP
Merge pull request #8229 from JakubOnderka/sign-fix
chg: [sign] Simplified key handlingpull/8244/head
commit
4ff7cccc7a
|
@ -30,10 +30,6 @@ jobs:
|
|||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
# Stop mysql
|
||||
- name: Shutdown Ubuntu MySQL (SUDO)
|
||||
run: sudo service mysql stop
|
||||
|
||||
# Run mariadb
|
||||
- uses: getong/mariadb-action@v1.1
|
||||
with:
|
||||
|
@ -71,7 +67,7 @@ jobs:
|
|||
LC_ALL=C.UTF-8 sudo apt-add-repository ppa:ondrej/php -y
|
||||
if [[ $php_version == "7.2" ]]; then
|
||||
# hotfix due to: https://bugs.php.net/bug.php?id=81640 TODO: remove after libpcre2-8-0:10.36 gets to stable channel
|
||||
sudo apt --fix-broken install
|
||||
sudo apt-get --fix-broken install
|
||||
fi
|
||||
sudo apt-get -y install curl python3 python3-zmq python3-requests python3-pip python3-nose python3-redis python3-lxml apache2 libapache2-mod-php$php_version
|
||||
sudo pip3 install virtualenv # virtualenv must be instaled from pip and not from ubuntu packages
|
||||
|
@ -149,32 +145,33 @@ jobs:
|
|||
|
||||
- name: Configure MISP
|
||||
run: |
|
||||
sudo -E su $USER -c 'app/Console/cake userInit -q | sudo tee ./key.txt'
|
||||
sudo -u $USER app/Console/cake userInit -q | sudo tee ./key.txt
|
||||
echo "AUTH=`cat key.txt`" >> $GITHUB_ENV
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "Session.autoRegenerate" 0'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "Session.timeout" 600'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "Session.cookieTimeout" 3600'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.host_org_id" 1'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.email" "info@admin.test"'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.disable_emailing" false'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting --force "debug" true'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.CustomAuth_disable_logout" false'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.redis_host" "127.0.0.1"'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.redis_port" 6379'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.redis_database" 13'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.redis_password" ""'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "GnuPG.email" "info@admin.test"'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "GnuPG.homedir" "`pwd`/.gnupg"'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "GnuPG.password" "travistest"'
|
||||
sudo -u $USER app/Console/cake Admin setSetting "Session.autoRegenerate" 0
|
||||
sudo -u $USER app/Console/cake Admin setSetting "Session.timeout" 600
|
||||
sudo -u $USER app/Console/cake Admin setSetting "Session.cookieTimeout" 3600
|
||||
sudo -u $USER app/Console/cake Admin setSetting "MISP.host_org_id" 1
|
||||
sudo -u $USER app/Console/cake Admin setSetting "MISP.email" "info@admin.test"
|
||||
sudo -u $USER app/Console/cake Admin setSetting "MISP.disable_emailing" false
|
||||
sudo -u $USER app/Console/cake Admin setSetting --force "debug" true
|
||||
sudo -u $USER app/Console/cake Admin setSetting "Plugin.CustomAuth_disable_logout" false
|
||||
sudo -u $USER app/Console/cake Admin setSetting "MISP.redis_host" "127.0.0.1"
|
||||
sudo -u $USER app/Console/cake Admin setSetting "MISP.redis_port" 6379
|
||||
sudo -u $USER app/Console/cake Admin setSetting "MISP.redis_database" 13
|
||||
sudo -u $USER app/Console/cake Admin setSetting "MISP.redis_password" ""
|
||||
sudo -u $USER app/Console/cake Admin setSetting "GnuPG.email" "info@admin.test"
|
||||
sudo -u $USER app/Console/cake Admin setSetting "GnuPG.homedir" "`pwd`/.gnupg"
|
||||
sudo -u $USER app/Console/cake Admin setSetting "GnuPG.password" "travistest"
|
||||
sudo -u $USER app/Console/cake Admin setSetting "MISP.download_gpg_from_homedir" 1
|
||||
|
||||
- name: Configure ZMQ
|
||||
run: |
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_host" "127.0.0.1"'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_port" 6379'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_database" 1'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_password" ""'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_enable" 1'
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_audit_notifications_enable" 1'
|
||||
sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_host" "127.0.0.1"
|
||||
sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_port" 6379
|
||||
sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_database" 1
|
||||
sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_password" ""
|
||||
sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_enable" 1
|
||||
sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_audit_notifications_enable" 1
|
||||
|
||||
- name: Update Galaxies
|
||||
run: sudo -E su $USER -c 'app/Console/cake Admin updateGalaxies'
|
||||
|
@ -209,16 +206,7 @@ jobs:
|
|||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.python_bin" "$GITHUB_WORKSPACE/venv/bin/python"'
|
||||
. ./venv/bin/activate
|
||||
export PYTHONPATH=$PYTHONPATH:./app/files/scripts
|
||||
pushd ./app/files/scripts/cti-python-stix2
|
||||
pip install .
|
||||
popd
|
||||
pushd ./app/files/scripts/python-stix
|
||||
pip install .
|
||||
popd
|
||||
pushd PyMISP
|
||||
pip install .[fileobjects,email]
|
||||
popd
|
||||
pip install zmq redis plyara
|
||||
pip install ./PyMISP[fileobjects,email] ./app/files/scripts/python-stix ./app/files/scripts/cti-python-stix2 zmq redis plyara
|
||||
deactivate
|
||||
|
||||
- name: Test if apache is working
|
||||
|
@ -242,7 +230,7 @@ jobs:
|
|||
- name: Run PHP tests
|
||||
run: |
|
||||
./app/Vendor/bin/parallel-lint --exclude app/Lib/cakephp/ --exclude app/Vendor/ --exclude app/Lib/random_compat/ -e php,ctp app/
|
||||
./app/Vendor/bin/phpunit app/Test/
|
||||
sudo -u www-data ./app/Vendor/bin/phpunit app/Test/
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
|
|
|
@ -592,14 +592,14 @@ class RestResponseComponent extends Component
|
|||
}
|
||||
|
||||
if ($response instanceof TmpFileTool) {
|
||||
App::uses('CakeResponseFile', 'Tools');
|
||||
$cakeResponse = new CakeResponseFile(['status' => $code, 'type' => $type]);
|
||||
if ($this->signContents) {
|
||||
$this->CryptographicKey = ClassRegistry::init('CryptographicKey');
|
||||
$data = $response->intoString();
|
||||
$headers['x-pgp-signature'] = base64_encode($this->CryptographicKey->signWithInstanceKey($data));
|
||||
$cakeResponse = new CakeResponse(array('body' => $data, 'status' => $code, 'type' => $type));
|
||||
$cakeResponse = new CakeResponse(['body' => $data, 'status' => $code, 'type' => $type]);
|
||||
} else {
|
||||
App::uses('CakeResponseFile', 'Tools');
|
||||
$cakeResponse = new CakeResponseFile(['status' => $code, 'type' => $type]);
|
||||
$cakeResponse->file($response);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -6139,7 +6139,7 @@ class EventsController extends AppController
|
|||
|
||||
/**
|
||||
* @param array $event
|
||||
* @return CakeResponseTmp
|
||||
* @return CakeResponseFile
|
||||
* @throws Exception
|
||||
*/
|
||||
private function __restResponse(array $event)
|
||||
|
@ -6171,23 +6171,24 @@ class EventsController extends AppController
|
|||
|
||||
public function protect($id)
|
||||
{
|
||||
$this->__toggleProtect($id, true);
|
||||
return $this->__toggleProtect($id, true);
|
||||
}
|
||||
|
||||
public function unprotect($id)
|
||||
{
|
||||
$this->__toggleProtect($id, false);
|
||||
return $this->__toggleProtect($id, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $id Event ID or UUID
|
||||
* @param bool $protect
|
||||
* @return CakeResponse|void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function __toggleProtect($id, $protect)
|
||||
{
|
||||
$id = $this->Toolbox->findIdByUuid($this->Event, $id);
|
||||
$event = $this->Event->fetchSimpleEvent($this->Auth->user(), $id, ['contain' => ['Orgc']]);
|
||||
if (
|
||||
(!$this->_isSiteAdmin && $event['Event']['orgc_id'] !== $this->Auth->user('org_id')) ||
|
||||
!$event ||
|
||||
!$this->__canModifyEvent($event)
|
||||
) {
|
||||
$event = $this->Event->fetchSimpleEvent($this->Auth->user(), $id);
|
||||
if (empty($event) || !$this->__canModifyEvent($event)) {
|
||||
throw new NotFoundException(__('Invalid event'));
|
||||
}
|
||||
if ($this->request->is('post')) {
|
||||
|
@ -6197,7 +6198,7 @@ class EventsController extends AppController
|
|||
if ($this->Event->save($event)) {
|
||||
$message = __('Event switched to %s mode.', $protect ? __('protected') : __('unprotected'));
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->saveSuccessResponse('events', $protect ? 'protect' : 'unprotect', $id, false, $message);
|
||||
return $this->RestResponse->saveSuccessResponse('events', $protect ? 'protect' : 'unprotect', $event['Event']['id'], false, $message);
|
||||
} else {
|
||||
$this->Flash->success($message);
|
||||
$this->redirect(['controller' => 'events', 'action' => 'view', $id]);
|
||||
|
@ -6205,14 +6206,14 @@ class EventsController extends AppController
|
|||
} else {
|
||||
$message = __('Something went wrong - could not switch event to %s mode.', $protect ? __('protected') : __('unprotected'));
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->saveFailResponse('Events', $protect ? 'protect' : 'unprotect', false, $message, $this->response->type());
|
||||
return $this->RestResponse->saveFailResponse('Events', $protect ? 'protect' : 'unprotect', $event['Event']['id'], $message);
|
||||
} else {
|
||||
$this->Flash->error($message);
|
||||
$this->redirect(['controller' => 'events', 'action' => 'view', $id]);
|
||||
$this->redirect(['controller' => 'events', 'action' => 'view', $event['Event']['id']]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->set('id', $id);
|
||||
$this->set('id', $event['Event']['id']);
|
||||
$this->set('title', $protect ? __('Protect event') : __('Remove event protection'));
|
||||
$this->set(
|
||||
'question',
|
||||
|
|
|
@ -173,4 +173,21 @@ class CryptGpgExtended extends Crypt_GPG
|
|||
|
||||
return $armored;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @param bool $isFile
|
||||
* @param bool $allowEmpty
|
||||
* @return resource|string|null
|
||||
* @throws Crypt_GPG_FileException
|
||||
* @throws Crypt_GPG_NoDataException
|
||||
*/
|
||||
protected function _prepareInput($data, $isFile = false, $allowEmpty = true)
|
||||
{
|
||||
if ($isFile && $data instanceof TmpFileTool) {
|
||||
return $data->resource();
|
||||
}
|
||||
|
||||
return parent::_prepareInput($data, $isFile, $allowEmpty);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
class GpgTool
|
||||
{
|
||||
/** @var CryptGpgExtended */
|
||||
private $gpg;
|
||||
|
||||
/**
|
||||
* @return CryptGpgExtended
|
||||
* @throws Exception
|
||||
|
@ -22,19 +25,16 @@ class GpgTool
|
|||
throw new Exception("Configuration option 'GnuPG.homedir' is not set, Crypt_GPG cannot be initialized.");
|
||||
}
|
||||
|
||||
$options = array(
|
||||
$options = [
|
||||
'homedir' => $homedir,
|
||||
'gpgconf' => Configure::read('GnuPG.gpgconf'),
|
||||
'binary' => Configure::read('GnuPG.binary') ?: '/usr/bin/gpg',
|
||||
);
|
||||
];
|
||||
|
||||
return new CryptGpgExtended($options);
|
||||
}
|
||||
|
||||
/** @var CryptGpgExtended */
|
||||
private $gpg;
|
||||
|
||||
public function __construct($gpg)
|
||||
public function __construct(CryptGpgExtended $gpg = null)
|
||||
{
|
||||
$this->gpg = $gpg;
|
||||
}
|
||||
|
@ -47,11 +47,13 @@ class GpgTool
|
|||
public function searchGpgKey($search)
|
||||
{
|
||||
$uri = 'https://openpgp.circl.lu/pks/lookup?search=' . urlencode($search) . '&op=index&fingerprint=on&options=mr';
|
||||
$response = $this->keyServerLookup($uri);
|
||||
if ($response->code == 404) {
|
||||
return array(); // no keys found
|
||||
} else if ($response->code != 200) {
|
||||
throw new Exception("Fetching the '$uri' failed with HTTP error {$response->code}: {$response->reasonPhrase}");
|
||||
try {
|
||||
$response = $this->keyServerLookup($uri);
|
||||
} catch (HttpSocketHttpException $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
return [];
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
return $this->extractKeySearch($response->body);
|
||||
}
|
||||
|
@ -64,11 +66,13 @@ class GpgTool
|
|||
public function fetchGpgKey($fingerprint)
|
||||
{
|
||||
$uri = 'https://openpgp.circl.lu/pks/lookup?search=0x' . urlencode($fingerprint) . '&op=get&options=mr';
|
||||
$response = $this->keyServerLookup($uri);
|
||||
if ($response->code == 404) {
|
||||
return null; // key with given fingerprint not found
|
||||
} else if ($response->code != 200) {
|
||||
throw new Exception("Fetching the '$uri' failed with HTTP error {$response->code}: {$response->reasonPhrase}");
|
||||
try {
|
||||
$response = $this->keyServerLookup($uri);
|
||||
} catch (HttpSocketHttpException $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
return null;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$key = $response->body;
|
||||
|
@ -169,30 +173,20 @@ class GpgTool
|
|||
$advancedUrl = "https://openpgpkey.$domain/.well-known/openpgpkey/" . strtolower($domain) . "/hu/$localPartHash";
|
||||
try {
|
||||
$response = $this->keyServerLookup($advancedUrl);
|
||||
return $this->processWkdResponse($response);
|
||||
return $this->gpg->enarmor($response->body());
|
||||
} catch (Exception $e) {
|
||||
// pass, continue to direct method
|
||||
}
|
||||
|
||||
$directUrl = "https://$domain/.well-known/openpgpkey/hu/$localPartHash";
|
||||
$response = $this->keyServerLookup($directUrl);
|
||||
return $this->processWkdResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HttpSocketResponse $response
|
||||
* @return string
|
||||
* @throws Crypt_GPG_Exception
|
||||
* @throws Crypt_GPG_InvalidOperationException
|
||||
*/
|
||||
private function processWkdResponse(HttpSocketResponse $response)
|
||||
{
|
||||
if ($response->code == 404) {
|
||||
throw new NotFoundException("Key not found");
|
||||
} else if (!$response->isOk()) {
|
||||
throw new Exception("Fetching the WKD failed with HTTP error {$response->code}: {$response->reasonPhrase}");
|
||||
try {
|
||||
$response = $this->keyServerLookup($directUrl);
|
||||
} catch (HttpSocketHttpException $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
throw new NotFoundException("Key not found");
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $this->gpg->enarmor($response->body());
|
||||
}
|
||||
|
||||
|
@ -232,17 +226,18 @@ class GpgTool
|
|||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @return HttpSocketResponse
|
||||
* @return HttpSocketResponseExtended
|
||||
* @throws HttpSocketHttpException
|
||||
* @throws Exception
|
||||
*/
|
||||
private function keyServerLookup($uri)
|
||||
{
|
||||
App::uses('SyncTool', 'Tools');
|
||||
$syncTool = new SyncTool();
|
||||
$HttpSocket = $syncTool->setupHttpSocket();
|
||||
$HttpSocket = $syncTool->createHttpSocket(['compress' => true]);
|
||||
$response = $HttpSocket->get($uri);
|
||||
if ($response === false) {
|
||||
throw new Exception("Could not fetch '$uri'.");
|
||||
if (!$response->isOk()) {
|
||||
throw new HttpSocketHttpException($response, $uri);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ class JSONConverterTool
|
|||
if ($raw) {
|
||||
return $result;
|
||||
}
|
||||
return json_encode($result, JSON_PRETTY_PRINT);
|
||||
return json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,7 +127,7 @@ class JSONConverterTool
|
|||
}
|
||||
}
|
||||
if (isset($event['errors'])) {
|
||||
yield '},"errors":' . json_encode($event['errors']) . '}';
|
||||
yield '},"errors":' . json_encode($event['errors'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . '}';
|
||||
} else {
|
||||
yield "}}";
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ class ServerSyncTool
|
|||
/** @var HttpSocketExtended */
|
||||
private $socket;
|
||||
|
||||
/** @var CryptographicKey */
|
||||
private $cryptographicKey;
|
||||
|
||||
/** @var array|null */
|
||||
private $info;
|
||||
|
||||
|
@ -418,8 +421,10 @@ class ServerSyncTool
|
|||
throw new Exception(__('Remote instance is not protected event aware yet (< 2.4.156), aborting.'));
|
||||
}
|
||||
|
||||
$this->CryptographicKey = ClassRegistry::init('CryptographicKey');
|
||||
$signature = $this->CryptographicKey->signWithInstanceKey($data);
|
||||
if (!$this->cryptographicKey) {
|
||||
$this->cryptographicKey = ClassRegistry::init('CryptographicKey');
|
||||
}
|
||||
$signature = $this->cryptographicKey->signWithInstanceKey($data);
|
||||
if (empty($signature)) {
|
||||
throw new Exception(__("Invalid signing key. This should never happen."));
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<?php
|
||||
class TmpFileTool
|
||||
{
|
||||
/** @var resource */
|
||||
/** @var resource|null */
|
||||
private $tmpfile;
|
||||
|
||||
/** @var string */
|
||||
private $separator;
|
||||
|
||||
/**
|
||||
* @param int $maxInMemory How many bytes should keep in memory before creating file on disk. By default is is 2 MB.
|
||||
* @param int $maxInMemory How many bytes should keep in memory before creating file on disk. By default is is 5 MB.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($maxInMemory = null)
|
||||
|
@ -144,20 +144,17 @@ class TmpFileTool
|
|||
}
|
||||
|
||||
/**
|
||||
* @param boolean $close
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function intoString($close = true)
|
||||
public function intoString()
|
||||
{
|
||||
$this->rewind();
|
||||
$string = stream_get_contents($this->tmpfile);
|
||||
if ($string === false) {
|
||||
throw new Exception('Could not read from temporary file.');
|
||||
}
|
||||
if ($close) {
|
||||
$this->close();
|
||||
}
|
||||
$this->close();
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
@ -175,6 +172,16 @@ class TmpFileTool
|
|||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
* @throws Exception
|
||||
*/
|
||||
public function resource()
|
||||
{
|
||||
$this->rewind();
|
||||
return $this->tmpfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws Exception
|
||||
|
|
|
@ -35,6 +35,9 @@ class CryptographicKey extends AppModel
|
|||
|
||||
public $validate = [];
|
||||
|
||||
/** @var CryptGpgExtended|null */
|
||||
private $gpg;
|
||||
|
||||
public function __construct($id = false, $table = null, $ds = null)
|
||||
{
|
||||
parent::__construct($id, $table, $ds);
|
||||
|
@ -44,6 +47,12 @@ class CryptographicKey extends AppModel
|
|||
$this->gpg = null;
|
||||
}
|
||||
$this->validate = [
|
||||
'uuid' => [
|
||||
'uuid' => [
|
||||
'rule' => 'uuid',
|
||||
'message' => 'Please provide a valid RFC 4122 UUID',
|
||||
],
|
||||
],
|
||||
'type' => [
|
||||
'rule' => ['inList', $this->validTypes],
|
||||
'message' => __('Invalid key type'),
|
||||
|
@ -76,16 +85,32 @@ class CryptographicKey extends AppModel
|
|||
$this->data['CryptographicKey']['uuid'] = CakeText::uuid();
|
||||
$this->data['CryptographicKey']['fingerprint'] = $this->extractKeyData($this->data['CryptographicKey']['type'], $this->data['CryptographicKey']['key_data']);
|
||||
}
|
||||
$existingKeyForObject = $this->find('first', [
|
||||
'recursive'
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Instance key fingerprint
|
||||
* @throws Crypt_GPG_BadPassphraseException
|
||||
* @throws Crypt_GPG_Exception
|
||||
*/
|
||||
public function ingestInstanceKey()
|
||||
{
|
||||
// If instance just key stored just in GPG homedir, use that key.
|
||||
if (Configure::read('MISP.download_gpg_from_homedir')) {
|
||||
if (!$this->gpg) {
|
||||
throw new Exception("Could not initiate GPG");
|
||||
}
|
||||
/** @var Crypt_GPG_Key[] $keys */
|
||||
$keys = $this->gpg->getKeys(Configure::read('GnuPG.email'));
|
||||
if (empty($keys)) {
|
||||
return false;
|
||||
}
|
||||
$this->gpg->addSignKey($keys[0], Configure::read('GnuPG.password'));
|
||||
return $keys[0]->getPrimaryKey()->getFingerprint();
|
||||
}
|
||||
|
||||
try {
|
||||
$redis = $this->setupRedis();
|
||||
$redis = $this->setupRedisWithException();
|
||||
} catch (Exception $e) {
|
||||
$redis = false;
|
||||
}
|
||||
|
@ -123,25 +148,29 @@ class CryptographicKey extends AppModel
|
|||
return $fingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @return false|string
|
||||
* @throws Crypt_GPG_BadPassphraseException
|
||||
* @throws Crypt_GPG_Exception
|
||||
* @throws Crypt_GPG_KeyNotFoundException
|
||||
*/
|
||||
public function signWithInstanceKey($data)
|
||||
{
|
||||
if (!$this->ingestInstanceKey()) {
|
||||
return false;
|
||||
}
|
||||
$data = preg_replace("/\s+/", "", $data);
|
||||
$signature = $this->gpg->sign($data, Crypt_GPG::SIGN_MODE_DETACHED);
|
||||
return $signature;
|
||||
}
|
||||
|
||||
public function signFileWithInstanceKey($path)
|
||||
{
|
||||
if (!$this->ingestInstanceKey()) {
|
||||
return false;
|
||||
}
|
||||
$signature = $this->gpg->signFile($path, Crypt_GPG::SIGN_MODE_DETACHED);
|
||||
$signature = $this->gpg->sign($data, Crypt_GPG::SIGN_MODE_DETACHED, Crypt_GPG::ARMOR_BINARY);
|
||||
return $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param string $signature
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function verifySignature($data, $signature, $key)
|
||||
{
|
||||
$this->error = false;
|
||||
|
@ -217,20 +246,15 @@ class CryptographicKey extends AppModel
|
|||
|
||||
public function uniqueKeyForElement($data)
|
||||
{
|
||||
$existingKey = $this->find('first', [
|
||||
'recursive' => -1,
|
||||
'conditions' => [
|
||||
'parent_type' => $this->data['CryptographicKey']['parent_type'],
|
||||
'parent_id' => $this->data['CryptographicKey']['parent_id'],
|
||||
'key_data' => $this->data['CryptographicKey']['key_data'],
|
||||
'type' => $this->data['CryptographicKey']['type']
|
||||
],
|
||||
'fields' => ['id']
|
||||
return !$this->hasAny([
|
||||
'parent_type' => $this->data['CryptographicKey']['parent_type'],
|
||||
'parent_id' => $this->data['CryptographicKey']['parent_id'],
|
||||
'key_data' => $this->data['CryptographicKey']['key_data'],
|
||||
'type' => $this->data['CryptographicKey']['type'],
|
||||
]);
|
||||
return empty($existingKey);
|
||||
}
|
||||
|
||||
public function validateProtectedEvent($raw_data, $user, $pgp_signature, $event)
|
||||
public function validateProtectedEvent($raw_data, array $user, $pgp_signature, array $event)
|
||||
{
|
||||
$eventCryptoGraphicKey = [];
|
||||
if (!empty($event['Event']['CryptographicKey'])) { // Depending if $event comes from fetchEvent or from pushed data
|
||||
|
@ -240,8 +264,7 @@ class CryptographicKey extends AppModel
|
|||
}
|
||||
if (empty($eventCryptoGraphicKey)) {
|
||||
$message = __('No valid signatures found for validating the signature.');
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
$this->Log->createLogEntry($user, 'validateSig', 'Event', $event['Event']['id'], $message);
|
||||
$this->loadLog()->createLogEntry($user, 'validateSig', 'Event', $event['Event']['id'], $message);
|
||||
return false;
|
||||
}
|
||||
foreach ($eventCryptoGraphicKey as $supplied_key) {
|
||||
|
@ -249,19 +272,26 @@ class CryptographicKey extends AppModel
|
|||
return true;
|
||||
}
|
||||
}
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
$message = __('Could not validate the signature.');
|
||||
$this->Log->createLogEntry($user, 'validateSig', 'Event', $event['Event']['id'], $message);
|
||||
$this->loadLog()->createLogEntry($user, 'validateSig', 'Event', $event['Event']['id'], $message);
|
||||
return false;
|
||||
}
|
||||
|
||||
public function captureCryptographicKeyUpdate($user, $cryptographicKeys, $parent_id, $type)
|
||||
/**
|
||||
* @param array $user
|
||||
* @param array $cryptographicKeys
|
||||
* @param int $parent_id
|
||||
* @param string $type
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function captureCryptographicKeyUpdate(array $user, array $cryptographicKeys, $parent_id, $type)
|
||||
{
|
||||
$existingKeys = $this->find('first', [
|
||||
'recursive' => -1,
|
||||
'conditions' => [
|
||||
'parent_type' => $type,
|
||||
'parent_id' => $parent_id
|
||||
'parent_id' => $parent_id,
|
||||
],
|
||||
'fields' => [
|
||||
'id',
|
||||
|
@ -269,15 +299,14 @@ class CryptographicKey extends AppModel
|
|||
'parent_type',
|
||||
'parent_id',
|
||||
'revoked',
|
||||
'fingerprint'
|
||||
'fingerprint',
|
||||
]
|
||||
]);
|
||||
$toRemove = [];
|
||||
$results = ['add' => [], 'remove' => []];
|
||||
foreach ($existingKeys as $k => $existingKey) {
|
||||
foreach ($existingKeys as $existingKey) {
|
||||
foreach ($cryptographicKeys as $k2 => $cryptographicKey) {
|
||||
if ($existingKey['fingerprint'] === $cryptographicKey['fingerprint']) {
|
||||
$found = true;
|
||||
if ($cryptographicKey['revoked'] && !$existingKey['CryptographicKey']['revoked']) {
|
||||
$existingKey['CryptographicKey']['revoked'] = 1;
|
||||
$this->save($existingKey['CryptographicKey']);
|
||||
|
@ -314,7 +343,6 @@ class CryptographicKey extends AppModel
|
|||
$parent_id
|
||||
);
|
||||
$this->deleteAll(['CryptographicKey.id' => $toRemove]);
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
$this->Log->createLogEntry($user, 'updateCryptoKeys', $cryptographicKey['parent_type'], $cryptographicKey['parent_id'], $message);
|
||||
$this->loadLog()->createLogEntry($user, 'updateCryptoKeys', $cryptographicKey['parent_type'], $cryptographicKey['parent_id'], $message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5666,6 +5666,15 @@ class Server extends AppModel
|
|||
'type' => 'boolean',
|
||||
'null' => true,
|
||||
],
|
||||
'download_gpg_from_homedir' => [
|
||||
'level' => self::SETTING_OPTIONAL,
|
||||
'description' => __('Fetch GPG instance key from GPG homedir.'),
|
||||
'value' => false,
|
||||
'test' => 'testBool',
|
||||
'type' => 'boolean',
|
||||
'null' => true,
|
||||
'cli_only' => true,
|
||||
],
|
||||
),
|
||||
'GnuPG' => array(
|
||||
'branch' => 1,
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../Lib/Tools/GpgTool.php';
|
||||
require_once __DIR__ . '/../Lib/Tools/TmpFileTool.php';
|
||||
require_once __DIR__ . '/../Lib/Tools/CryptGpgExtended.php';
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class GpgToolTest extends TestCase
|
||||
{
|
||||
public function testInit(): void
|
||||
{
|
||||
$gpg = $this->init();
|
||||
$this->assertInstanceOf('CryptGpgExtended', $gpg);
|
||||
$this->assertIsString($gpg->getVersion());
|
||||
}
|
||||
|
||||
public function testSignAndVerify()
|
||||
{
|
||||
$gpg = $this->init();
|
||||
include __DIR__ . '/../Config/config.php';
|
||||
$gpg->addSignKey($config['GnuPG']['email'], $config['GnuPG']['password']);
|
||||
|
||||
$testString = 'ahojSvete';
|
||||
|
||||
$signature = $gpg->sign($testString, Crypt_GPG::SIGN_MODE_DETACHED, Crypt_GPG::ARMOR_BINARY);
|
||||
$this->assertIsString($signature);
|
||||
|
||||
$verified = $gpg->verify($testString, $signature);
|
||||
$this->assertIsArray($verified);
|
||||
$this->assertCount(1, $verified);
|
||||
$this->assertTrue($verified[0]->isValid());
|
||||
|
||||
$signature = $gpg->sign($testString, Crypt_GPG::SIGN_MODE_DETACHED, Crypt_GPG::ARMOR_ASCII);
|
||||
$this->assertIsString($signature);
|
||||
|
||||
$verified = $gpg->verify($testString, $signature);
|
||||
$this->assertIsArray($verified);
|
||||
$this->assertCount(1, $verified);
|
||||
$this->assertTrue($verified[0]->isValid());
|
||||
|
||||
// Tmp file
|
||||
$tmpFile = new TmpFileTool();
|
||||
$tmpFile->write($testString);
|
||||
$signature = $gpg->signFile($tmpFile, null, Crypt_GPG::SIGN_MODE_DETACHED, Crypt_GPG::ARMOR_BINARY);
|
||||
$this->assertIsString($signature);
|
||||
|
||||
$verified = $gpg->verify($testString, $signature);
|
||||
$this->assertIsArray($verified);
|
||||
$this->assertCount(1, $verified);
|
||||
$this->assertTrue($verified[0]->isValid());
|
||||
}
|
||||
|
||||
private function init(): CryptGpgExtended
|
||||
{
|
||||
require_once 'Crypt/GPG.php';
|
||||
include __DIR__ . '/../Config/config.php';
|
||||
|
||||
$options = [
|
||||
'homedir' => $config['GnuPG']['homedir'],
|
||||
'gpgconf' => $config['GnuPG']['gpgconf'] ?? null,
|
||||
'binary' => $config['GnuPG']['binary'] ?? '/usr/bin/gpg',
|
||||
];
|
||||
return new CryptGpgExtended($options);
|
||||
}
|
||||
}
|
|
@ -35,12 +35,28 @@ class JSONConverterToolTest extends TestCase
|
|||
$this->check($event);
|
||||
}
|
||||
|
||||
public function testCheckJsonIsValidUnicodeSlashes(): void
|
||||
{
|
||||
$attribute = ['id' => 1, 'event_id' => 2, 'type' => 'ip-src', 'value' => '1.1.1.1'];
|
||||
$event = ['Event' => ['id' => 2, 'info' => 'Test event ěšřžýáí \/'], 'errors' => 'chyba ě+š'];
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$event['Attribute'][] = $attribute;
|
||||
}
|
||||
$this->check($event);
|
||||
}
|
||||
|
||||
private function check(array $event): void
|
||||
{
|
||||
$json = '';
|
||||
foreach (JSONConverterTool::streamConvert($event) as $part) {
|
||||
$json .= $part;
|
||||
}
|
||||
|
||||
// Check if result is the same without spaces
|
||||
$jsonStreamWithoutSpaces = preg_replace("/\s+/", "", $json);
|
||||
$jsonNormalWithoutSpaces = preg_replace("/\s+/", "", JSONConverterTool::convert($event));
|
||||
$this->assertEquals($jsonNormalWithoutSpaces, $jsonStreamWithoutSpaces);
|
||||
|
||||
if (defined('JSON_THROW_ON_ERROR')) {
|
||||
json_decode($json, true, 512, JSON_THROW_ON_ERROR);
|
||||
$this->assertTrue(true);
|
||||
|
|
|
@ -719,6 +719,23 @@ class TestComprehensive(unittest.TestCase):
|
|||
response = request(self.admin_misp_connector, 'POST', f'warninglists/delete/{wl["Warninglist"]["id"]}')
|
||||
check_response(response)
|
||||
|
||||
def test_protected_event(self):
|
||||
event = create_simple_event()
|
||||
event = check_response(self.admin_misp_connector.add_event(event))
|
||||
|
||||
response = request(self.admin_misp_connector, 'POST', f'events/protect/{event.id}')
|
||||
check_response(response)
|
||||
|
||||
response = request(self.admin_misp_connector, 'POST', f'events/unprotect/{event.uuid}')
|
||||
check_response(response)
|
||||
|
||||
response = request(self.admin_misp_connector, 'POST', f'events/protect/{event.uuid}')
|
||||
check_response(response)
|
||||
|
||||
response = self.admin_misp_connector._prepare_request('GET', f'events/view/{event.id}')
|
||||
self.assertIn('x-pgp-signature', response.headers)
|
||||
self.assertTrue(len(response.headers['x-pgp-signature']) > 0, response.headers['x-pgp-signature'])
|
||||
|
||||
def _search(self, query: dict):
|
||||
response = self.admin_misp_connector._prepare_request('POST', 'events/restSearch', data=query)
|
||||
response = self.admin_misp_connector._check_response(response)
|
||||
|
|
Loading…
Reference in New Issue