add: configure gnupg, add gnupg tests

pull/9323/head
Luciano Righetti 2023-10-10 10:54:23 +02:00
parent 948c99165e
commit b907a5b9ec
11 changed files with 427 additions and 28 deletions

3
.gitignore vendored
View File

@ -14,4 +14,5 @@ docker/run/
config.json
phpunit.xml
docker-compose.override.yml
.gnupg
.gnupg
*.asc

View File

@ -16,7 +16,7 @@ services:
ADMIN_ORG: ${ADMIN_ORG}
ADMIN_EMAIL: ${ADMIN_EMAIL}
ADMIN_INITIAL_PASSWORD: ${ADMIN_INITIAL_PASSWORD}
ADMIN_USER_API_KEY: ${ADMIN_USER_API_KEY}
ADMIN_API_KEY: ${ADMIN_API_KEY}
GPG_PASSPHRASE: ${GPG_PASSPHRASE}
DISABLE_BACKGROUND_WORKERS: ${DISABLE_BACKGROUND_WORKERS:-0}
NUM_WORKERS_DEFAULT: ${NUM_WORKERS_DEFAULT:-5}

View File

@ -6,18 +6,19 @@ MISP_COMMIT=
MODULES_TAG=
MODULES_COMMIT=
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=misp3
MYSQL_USER=misp
MYSQL_PASSWORD=misp
ADMIN_ORG=
ADMIN_ORG_UUID=
ADMIN_EMAIL=
ADMIN_INITIAL_PASSWORD=
ADMIN_USER_API_KEY=
ADMIN_API_KEY=
GPG_PASSPHRASE=
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=misp3
MYSQL_USER=misp
MYSQL_PASSWORD=misp
EMAIL_HOST=mailhog
EMAIL_PORT=1025
EMAIL_USERNAME=

View File

@ -1,23 +1,32 @@
# General settings
ENV=prod
DEBUG=0
DOCKER_HUB_PROXY=
# MISP version
MISP_TAG=
MISP_COMMIT=
MODULES_TAG=
MODULES_COMMIT=
# MISP settings
ADMIN_ORG=
ADMIN_ORG_UUID=
ADMIN_EMAIL=
ADMIN_INITIAL_PASSWORD=
ADMIN_API_KEY=
GPG_PASSPHRASE=
# MISP.email, used for notifications. Also used
# for GnuPG.email and GPG autogeneration.
# MISP_EMAIL=
# MySQL settings
MYSQL_ROOT_PASSWORD=
MYSQL_DATABASE=misp3
MYSQL_USER=misp
MYSQL_PASSWORD=
ADMIN_ORG=
ADMIN_ORG_UUID=
ADMIN_EMAIL=
ADMIN_INITIAL_PASSWORD=
ADMIN_USER_API_KEY=
GPG_PASSPHRASE=
# Email and SMTP settings
EMAIL_HOST=
EMAIL_PORT=
EMAIL_USERNAME=

View File

@ -6,18 +6,19 @@ MISP_COMMIT=
MODULES_TAG=
MODULES_COMMIT=
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=misp3_test
MYSQL_USER=misp
MYSQL_PASSWORD=misp
ADMIN_ORG=ORGNAME
ADMIN_ORG_UUID=a1f2be0f-73a4-4b4a-9a87-26a8ae0511fb
ADMIN_EMAIL=admin@admin.test
ADMIN_INITIAL_PASSWORD=admin
ADMIN_USER_API_KEY=5E8sFPZa9K6ge1q1INmgkaeVu1mhv6cEg2Hsmx2Y
ADMIN_API_KEY=5E8sFPZa9K6ge1q1INmgkaeVu1mhv6cEg2Hsmx2Y
GPG_PASSPHRASE=foobar
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=misp3_test
MYSQL_USER=misp
MYSQL_PASSWORD=misp
EMAIL_HOST=mailhog
EMAIL_PORT=1025
EMAIL_USERNAME=

View File

@ -79,7 +79,7 @@ RUN pecl install -f xdebug pcov \
# Install additional packages
RUN apt-get update \
&& apt-get install -y \
git sendmail \
git sendmail sudo \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View File

@ -61,5 +61,15 @@ return [
'supervisor_port' => '9001',
'supervisor_user' => 'supervisor',
'supervisor_password' => 'supervisor',
],
'GnuPG' => [
'onlyencrypted' => false,
'email' => env('MISP_EMAIL', env('ADMIN_EMAIL')),
'homedir' => env('GPG_DIR', '/var/www/.gnupg'),
'password' => env('GPG_PASSPHRASE', 'passphrase'),
'bodyonlyencrypted' => false,
'sign' => true,
'obscure_subject' => false,
'binary' => '/usr/bin/gpg'
]
];

View File

@ -13,6 +13,11 @@ rm -f "${MISP_READY_STATUS_FLAG}"
[ -z "$MISP_DB" ] && MISP_DB=misp3
[ -z "$MYSQL_PWD" ] && MYSQL_PWD=$MISP_DB_PASSWORD
[ -z "$MYSQLCMD" ] && MYSQLCMD="mysql --defaults-file=/etc/mysql/conf.d/misp.cnf -P $MYSQL_PORT -h $MYSQL_HOST -r -N $MISP_DB"
[ -z "$GPG_PASSPHRASE" ] && GPG_PASSPHRASE="passphrase"
[ -z "$GPG_DIR" ] && GPG_DIR="/var/www/.gnupg"
# Switches to selectively disable configuration logic
[ -z "$AUTOCONF_GPG" ] && AUTOCONF_GPG="true"
# create mysql default config
cat <<EOF >/etc/mysql/conf.d/misp.cnf
@ -70,12 +75,55 @@ init_user() {
ADMIN_USER_ID=$(echo "SELECT id FROM users WHERE EMAIL='${ADMIN_EMAIL}';" | ${MYSQLCMD} | tr -d '\n')
# Insert Admin user API key
if [ -z "$ADMIN_USER_API_KEY" ]; then
if [ -z "$ADMIN_API_KEY" ]; then
echo >&2 "Creating admin user API key..."
ADMIN_USER_API_KEY_START=$(echo ${ADMIN_USER_API_KEY} | head -c 4)
ADMIN_USER_API_KEY_END=$(echo ${ADMIN_USER_API_KEY} | tail -c 5)
export ADMIN_USER_API_KEY_HASH=$(php -r "echo password_hash('${ADMIN_USER_API_KEY}', PASSWORD_DEFAULT);" | tr -d \')
echo "INSERT INTO auth_keys (uuid, authkey, authkey_start, authkey_end, created, expiration, user_id) VALUES ((SELECT uuid()), '${ADMIN_USER_API_KEY_HASH}', '${ADMIN_USER_API_KEY_START}', '${ADMIN_USER_API_KEY_END}', 0, 0, ${ADMIN_USER_ID});" | ${MYSQLCMD}
ADMIN_API_KEY_START=$(echo ${ADMIN_API_KEY} | head -c 4)
ADMIN_API_KEY_END=$(echo ${ADMIN_API_KEY} | tail -c 5)
export ADMIN_API_KEY_HASH=$(php -r "echo password_hash('${ADMIN_API_KEY}', PASSWORD_DEFAULT);" | tr -d \')
echo "INSERT INTO auth_keys (uuid, authkey, authkey_start, authkey_end, created, expiration, user_id) VALUES ((SELECT uuid()), '${ADMIN_API_KEY_HASH}', '${ADMIN_API_KEY_START}', '${ADMIN_API_KEY_END}', 0, 0, ${ADMIN_USER_ID});" | ${MYSQLCMD}
fi
}
configure_gnupg() {
if [ "$AUTOCONF_GPG" != "true" ]; then
echo "... GPG auto configuration disabled"
return
fi
GPG_DIR=/var/www/.gnupg
GPG_ASC=/var/www/html/webroot/gpg.asc
GPG_TMP=/tmp/gpg.tmp
if [ ! -f "${GPG_DIR}/trustdb.gpg" ]; then
echo "... generating new GPG key in ${GPG_DIR}"
cat >${GPG_TMP} <<GPGEOF
%echo Generating a basic OpenPGP key
Key-Type: RSA
Key-Length: 3072
Name-Real: MISP Admin
Name-Email: ${MISP_EMAIL-$ADMIN_EMAIL}
Expire-Date: 0
Passphrase: $GPG_PASSPHRASE
%commit
%echo Done
GPGEOF
mkdir -p ${GPG_DIR}
gpg --homedir ${GPG_DIR} --gen-key --batch ${GPG_TMP}
rm -f ${GPG_TMP}
else
echo "... found pre-generated GPG key in ${GPG_DIR}"
fi
# Fix permissions
chown -R www-data:www-data ${GPG_DIR}
find ${GPG_DIR} -type f -exec chmod 600 {} \;
find ${GPG_DIR} -type d -exec chmod 700 {} \;
if [ ! -f ${GPG_ASC} ]; then
echo "... exporting GPG key"
sudo -u www-data gpg --homedir ${GPG_DIR} --export --armor ${MISP_EMAIL-$ADMIN_EMAIL} >${GPG_ASC}
else
echo "... found exported key ${GPG_ASC}"
fi
}
@ -91,6 +139,8 @@ done
init_user
configure_gnupg
# Test php-fpm config
php-fpm -t

View File

@ -2,8 +2,7 @@
namespace App\Lib\Tools;
use Cake\Core\Exception\Exception;
use Cake\Core\Configure;
use Exception;
class CryptGpgExtended extends \Crypt_GPG
{
@ -64,7 +63,7 @@ class CryptGpgExtended extends \Crypt_GPG
$operation = '--export';
$operation .= ' ' . escapeshellarg($fingerprint);
$arguments = array('--export-options', 'export-minimal');
$arguments = ['--export-options', 'export-minimal'];
if ($armor) {
$arguments[] = '--armor';
}

View File

@ -0,0 +1,258 @@
<?php
namespace App\Lib\Tools;
use Exception;
use Generator;
class TmpFileTool
{
/** @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 5 MB.
* @throws Exception
*/
public function __construct($maxInMemory = null)
{
if ($maxInMemory === null) {
$maxInMemory = 5 * 1024 * 1024;
}
$this->tmpfile = fopen("php://temp/maxmemory:$maxInMemory", "w+");
if ($this->tmpfile === false) {
throw new Exception('Could not create temporary file.');
}
}
/**
* Write data to stream with separator. Separator will be prepend to content for next call.
* @param string|Generator $content
* @param string $separator
* @throws Exception
*/
public function writeWithSeparator($content, $separator)
{
if (isset($this->separator)) {
if ($content instanceof Generator) {
$this->write($this->separator);
foreach ($content as $part) {
$this->write($part);
}
} else {
$this->write($this->separator . $content);
}
} else {
if ($content instanceof Generator) {
foreach ($content as $part) {
$this->write($part);
}
} else {
$this->write($content);
}
}
$this->separator = $separator;
}
/**
* @param string $content
* @throws Exception
*/
public function write($content)
{
if (fwrite($this->tmpfile, $content) === false) {
if ($this->tmpfile === null) {
throw new Exception('Could not write to finished temporary file.');
}
$tmpFolder = sys_get_temp_dir();
$freeSpace = disk_free_space($tmpFolder);
throw new Exception("Could not write to temporary file in $tmpFolder folder. Maybe not enough space? ($freeSpace bytes left)");
}
}
/**
* @param string $path
* @throws Exception
*/
public function writeFromFile($path)
{
$file = fopen($path, 'r');
if (!$file) {
throw new Exception("Could not open file $file.");
}
if (stream_copy_to_stream($file, $this->tmpfile) === false) {
throw new Exception("Could not copy content of file $file into TmpFile.");
}
}
/**
* Returns generator of parsed CSV line from file.
*
* @param string $delimiter
* @param string $enclosure
* @param string $escape
* @return Generator<array>
* @throws Exception
*/
public function intoParsedCsv($delimiter = ',', $enclosure = '"', $escape = "\\")
{
$this->rewind();
$line = 0;
while (!feof($this->tmpfile)) {
$result = fgetcsv($this->tmpfile, 0, $delimiter, $enclosure, $escape);
if ($result === false) {
throw new Exception("Could not read line $line from temporary CSV file.");
}
$line++;
yield $result;
}
$this->close();
}
/**
* Returns generator of line from file.
*
* @return Generator
* @throws Exception
*/
public function intoLines()
{
$this->rewind();
while (!feof($this->tmpfile)) {
$result = fgets($this->tmpfile);
if ($result === false) {
throw new Exception('Could not read line from temporary file.');
}
yield $result;
}
$this->close();
}
/**
* @param int $chunkSize In bytes
* @return Generator
* @throws Exception
*/
public function intoChunks($chunkSize = 8192)
{
$this->rewind();
while (!feof($this->tmpfile)) {
$result = fread($this->tmpfile, $chunkSize);
if ($result === false) {
throw new Exception('Could not read from temporary file.');
}
yield $result;
}
$this->close();
}
/**
* @return string
* @throws Exception
*/
public function intoString()
{
$this->rewind();
$string = stream_get_contents($this->tmpfile);
if ($string === false) {
throw new Exception('Could not read from temporary file.');
}
$this->close();
return $string;
}
/**
* Pass data to output.
*
* @throws Exception
*/
public function intoOutput()
{
$this->rewind();
if (fpassthru($this->tmpfile) === false) {
throw new Exception('Could not pass temporary file to output.');
}
$this->close();
}
/**
* @return resource
* @throws Exception
*/
public function resource()
{
$this->rewind();
return $this->tmpfile;
}
/**
* @return int
* @throws Exception
*/
public function size()
{
$this->isOpen();
return fstat($this->tmpfile)['size'];
}
/**
* @param string $algo
* @return string
* @throws Exception
*/
public function hash($algo)
{
$this->rewind();
$hash = hash_init($algo);
hash_update_stream($hash, $this->tmpfile);
return hash_final($hash);
}
/**
* @return string
* @throws Exception
*/
public function __toString()
{
return $this->intoString();
}
/**
* @return bool
*/
public function close()
{
if ($this->tmpfile) {
$result = fclose($this->tmpfile);
$this->tmpfile = null;
return $result;
}
return true;
}
/**
* @throws Exception
*/
private function isOpen()
{
if ($this->tmpfile === null) {
throw new Exception('Temporary file is already closed.');
}
}
/**
* Seek to start of file.
*
* @throws Exception
*/
private function rewind()
{
$this->isOpen();
if (fseek($this->tmpfile, 0) === -1) {
throw new Exception('Could not seek to start of temporary file.');
}
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Test\TestCase\Tool;
use App\Lib\Tools\CryptGpgExtended;
use App\Lib\Tools\TmpFileTool;
use Cake\TestSuite\TestCase;
use Cake\Core\Configure;
// use PHPUnit\Framework\TestCase;
class CryptGpgExtendedTest extends TestCase
{
public function testInit(): void
{
$gpg = $this->init();
$this->assertInstanceOf('App\Lib\Tools\CryptGpgExtended', $gpg);
$this->assertIsString($gpg->getVersion());
}
public function testSignAndVerify()
{
$gpg = $this->init();
$config = Configure::read('GnuPG');
$gpg->addSignKey($config['email'], $config['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
{
$config = Configure::read('GnuPG');
$options = [
'homedir' => $config['homedir'],
'gpgconf' => $config['gpgconf'] ?? null,
'binary' => $config['binary'] ?? '/usr/bin/gpg',
];
return new CryptGpgExtended($options);
}
}