You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
4.3 KiB
PHP

<?php
namespace Eater\Glim\Service;
use Eater\Glim\Model\Server as ServerModel;
use GuzzleHttp\Client as HttpClient;
use Eater\Glim\Core;
use Psr\Http\Message\ResponseInterface;
class Server extends Main
{
const MANAGEMENT_PORT = 7864;
private $httpClient = null;
private function getHttpClient() {
if ($this->httpClient === null) {
$this->httpClient = new HttpClient();
}
return $this->httpClient;
}
public function getCsrFromServer(ServerModel $server) {
$response = $this->doSignedRequest($server, '/create-csr', [
'hostname' => $server->getFqdn()
]);
if ($response->getStatusCode() !== 200) {
throw new \Exception('Requesting CSR failed (' . $response->getStatusCode() . '): ' . $response->getBody()->getContents());
}
$data = $this->decryptResponse($server, $response);
return $data['csr'];
}
/**
* @param ServerModel $server
* @throws \Exception
*/
public function deliverSignedCertificate(ServerModel $server) {
$response = $this->doSignedRequest($server, '/deliver-crt', [
'certificate' => $server->getCertificate()
]);
if ($response->getStatusCode() !== 200) {
throw new \Exception('Delivering signed certificate failed (' . $response->getStatusCode() . '): ' . $response->getBody()->getContents());
}
$server->setStatus('signed');
$server->save();
}
public function deliverOpenVPNConfig(ServerModel $server) {
/** @var \Twig_Environment $twig */
$twig = $this->get('twig');
$config = $twig->render('etc/openvpn-server.conf.twig', [
'server' => $server
]);
$response = $this->doSignedRequest($server, '/update-openvpn-config', [
'config' => $config
]);
if ($response->getStatusCode() !== 200) {
throw new \Exception('Updating openvpn server config failed (' . $response->getStatusCode() . '): ' . $response->getBody()->getContents());
}
}
/**
* @param ServerModel $server
* @param string $path
* @param string $data
* @return \Psr\Http\Message\ResponseInterface
* @throws \Exception
*/
public function doSignedRequest(ServerModel $server, $path, $data) {
$client = $this->getHttpClient();
/** @var CA $ca */
$ca = $this->get('ca');
$data['signature'] = bin2hex($ca->signWithCA($server->getFingerprint()));
$json = json_encode($data);
$password = bin2hex(openssl_random_pseudo_bytes(48));
$pubKey = openssl_get_publickey($server->getPublicKey());
$success = openssl_public_encrypt($password, $crypted, $pubKey, OPENSSL_PKCS1_PADDING);
if (!$success) {
throw new \Exception('Encrypting data failed: ' . openssl_error_string() . openssl_error_string());
}
$body = bin2hex($crypted) . bin2hex(openssl_encrypt($json, 'blowfish', substr($password, 0, 32), true, substr($password, 32, 8)));
return $client->post('http://' . $server->getExternalIp() . ':' . static::MANAGEMENT_PORT . $path, [
'body' => $body
]);
}
public function decryptResponse(ServerModel $server, ResponseInterface $response) {
$body = $response->getBody()->getContents();
$this->get('logger')->addDebug($body);
$encryptedPasswordAndIV = hex2bin(substr($body, 0, 512));
$encryptedBody = hex2bin(substr($body, 512));
/** @var CA $ca */
$ca = $this->get('ca');
$success = openssl_private_decrypt($encryptedPasswordAndIV, $passwordAndIV, $ca->getPrivateKey(), OPENSSL_PKCS1_PADDING);
if (!$success) {
throw new \Exception('Decrypting data failed: ' . openssl_error_string() . openssl_error_string());
}
$json = openssl_decrypt($encryptedBody, 'blowfish', substr($passwordAndIV, 0, 32), true, substr($passwordAndIV, 32, 8));
$data = \json_decode($json, true);
$ver = openssl_verify($server->getFingerprint(), hex2bin($data['signature']), $server->getPublicKey(), OPENSSL_ALGO_SHA256);
if ($ver === 1) {
return $data;
} else {
throw new \Exception("Checking signature failed ($ver): " . openssl_error_string());
}
}
}