Browse Source

add certbot configuration tool

pull/39/head
Aaron Parecki 7 years ago
parent
commit
8c488b41ea
No known key found for this signature in database GPG Key ID: 276C2817346D6056
8 changed files with 331 additions and 0 deletions
  1. +1
    -0
      composer.json
  2. +3
    -0
      config.production.php
  3. +3
    -0
      config.template.php
  4. +156
    -0
      controllers/Certbot.php
  5. +26
    -0
      create-password.php
  6. +8
    -0
      public/index.php
  7. +24
    -0
      test-password.php
  8. +110
    -0
      views/certbot.php

+ 1
- 0
composer.json View File

@ -14,6 +14,7 @@
"controllers/Main.php", "controllers/Main.php",
"controllers/Parse.php", "controllers/Parse.php",
"controllers/Token.php", "controllers/Token.php",
"controllers/Certbot.php",
"lib/HTTPCurl.php", "lib/HTTPCurl.php",
"lib/HTTPStream.php", "lib/HTTPStream.php",
"lib/HTTP.php", "lib/HTTP.php",

+ 3
- 0
config.production.php View File

@ -1,4 +1,7 @@
<?php <?php
class Config { class Config {
public static $cache = true; public static $cache = true;
public static $admins = [
'https://aaronparecki.com/'
];
} }

+ 3
- 0
config.template.php View File

@ -1,4 +1,7 @@
<?php <?php
class Config { class Config {
public static $cache = false; public static $cache = false;
public static $admins = [
'https://you.example.com/'
];
} }

+ 156
- 0
controllers/Certbot.php View File

@ -0,0 +1,156 @@
<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class Certbot {
private $mc;
private $http;
public function index(Request $request, Response $response) {
session_start();
$state = mt_rand(10000,99999);
$_SESSION['state'] = $state;
$response->setContent(view('certbot', [
'title' => 'X-Ray',
'state' => $state
]));
return $response;
}
public function start_auth(Request $request, Response $response) {
session_start();
$_SESSION['client_id'] = $request->get('client_id');
$_SESSION['redirect_uri'] = $request->get('redirect_uri');
$query = http_build_query([
'me' => $request->get('me'),
'client_id' => $request->get('client_id'),
'redirect_uri' => $request->get('redirect_uri'),
'state' => $request->get('state'),
]);
$response->headers->set('Location', 'https://indieauth.com/auth?'.$query);
$response->setStatusCode(302);
return $response;
}
public function redirect(Request $request, Response $response) {
session_start();
$this->http = new p3k\HTTP();
if(!isset($_SESSION['state']) || $_SESSION['state'] != $request->get('state')) {
$response->headers->set('Location', '/cert?error=invalid_state');
$response->setStatusCode(302);
return $response;
}
if($code = $request->get('code')) {
$res = $this->http->post('https://indieauth.com/auth', http_build_query([
'code' => $code,
'client_id' => $_SESSION['client_id'],
'redirect_uri' => $_SESSION['redirect_uri'],
'state' => $_SESSION['state']
]), [
'Accept: application/json'
]);
$verify = json_decode($res['body'], true);
unset($_SESSION['state']);
if(isset($verify['me'])) {
if(in_array($verify['me'], Config::$admins)) {
$_SESSION['me'] = $verify['me'];
$response->headers->set('Location', '/cert');
} else {
$response->headers->set('Location', '/cert?error=invalid_user');
}
} else {
$response->headers->set('Location', '/cert?error=invalid');
}
} else {
$response->headers->set('Location', '/cert?error=missing_code');
}
$response->setStatusCode(302);
return $response;
}
public function save_challenge(Request $request, Response $response) {
session_start();
if(!isset($_SESSION['me']) || !in_array($_SESSION['me'], Config::$admins)) {
$response->headers->set('Location', '/cert?error=forbidden');
$response->setStatusCode(302);
return $response;
}
$token = $request->get('token');
$challenge = $request->get('challenge');
if(preg_match('/acme-challenge\/(.+)/', $token, $match)) {
$token = $match[1];
} elseif(!preg_match('/^[_a-zA-Z0-9]+$/', $token)) {
echo "Invalid token format\n";
die();
}
$this->_mc();
$this->mc->set('acme-challenge-'.$token, json_encode([
'token' => $token,
'challenge' => $challenge
]), 0, 600);
$response->setContent(view('certbot', [
'title' => 'X-Ray',
'challenge' => $challenge,
'token' => $token,
'verified' => true
]));
return $response;
}
public function logout(Request $request, Response $response) {
session_start();
unset($_SESSION['me']);
unset($_SESSION['client_id']);
unset($_SESSION['redirect_uri']);
unset($_SESSION['state']);
session_destroy();
$response->headers->set('Location', '/cert');
$response->setStatusCode(302);
return $response;
}
public function challenge(Request $request, Response $response, array $args) {
$this->_mc();
$token = $args['token'];
if($cache = $this->mc->get('acme-challenge-'.$token)) {
$acme = json_decode($cache, true);
$response->setContent($acme['challenge']);
} else {
$response->setStatusCode(404);
$response->setContent("Not Found\n");
}
$response->headers->set('Content-Type', 'text/plain');
return $response;
}
private function _mc() {
$this->mc = new Memcache();
$this->mc->addServer('127.0.0.1');
}
}

+ 26
- 0
create-password.php View File

@ -0,0 +1,26 @@
<?php
echo "Enter a password: ";
hide_term();
$password1 = trim(fgets(STDIN), PHP_EOL);
echo PHP_EOL;
echo "Confirm password: ";
$password2 = trim(fgets(STDIN), PHP_EOL);
echo PHP_EOL;
restore_term();
if($password1 == $password2) {
$hash = password_hash($password1, PASSWORD_DEFAULT);
echo "Password hash: $hash\n";
} else {
echo "Passwords did not match\n";
die(1);
}
function hide_term() {
system('stty -echo');
}
function restore_term() {
system('stty echo');
}

+ 8
- 0
public/index.php View File

@ -10,6 +10,7 @@ if(file_exists(dirname(__FILE__).'/../config.php')) {
} else { } else {
class Config { class Config {
public static $cache = false; public static $cache = false;
public static $admins = [];
} }
} }
@ -23,6 +24,13 @@ $router->addRoute('GET', '/parse', 'Parse::parse');
$router->addRoute('POST', '/parse', 'Parse::parse'); $router->addRoute('POST', '/parse', 'Parse::parse');
$router->addRoute('POST', '/token', 'Token::token'); $router->addRoute('POST', '/token', 'Token::token');
$router->addRoute('GET', '/cert', 'Certbot::index');
$router->addRoute('GET', '/cert/auth', 'Certbot::start_auth');
$router->addRoute('GET', '/cert/logout', 'Certbot::logout');
$router->addRoute('GET', '/cert/redirect', 'Certbot::redirect');
$router->addRoute('POST', '/cert/save-challenge', 'Certbot::save_challenge');
$router->addRoute('GET', '/.well-known/acme-challenge/{token}', 'Certbot::challenge');
$dispatcher = $router->getDispatcher(); $dispatcher = $router->getDispatcher();
$request = Request::createFromGlobals(); $request = Request::createFromGlobals();

+ 24
- 0
test-password.php View File

@ -0,0 +1,24 @@
<?php
echo "Enter password hash to verify against: ";
$hash = trim(fgets(STDIN), PHP_EOL);
echo "Enter password: ";
hide_term();
$password = trim(fgets(STDIN), PHP_EOL);
echo PHP_EOL;
restore_term();
$verified = password_verify($password, $hash);
if($verified)
echo "Password verified\n";
else
echo "Password did not match\n";
function hide_term() {
system('stty -echo');
}
function restore_term() {
system('stty echo');
}

+ 110
- 0
views/certbot.php View File

@ -0,0 +1,110 @@
<?php $this->layout('layout', ['title' => $title]); ?>
<div class="column">
<h1>X-Ray Certificate Setup</h1>
<?php if(isset($_SESSION['me'])): ?>
<?php if(isset($verified) && $verified): ?>
<div class="section">
<p>The challenge was saved and is now accessible via the <code>.well-known</code> path.</p>
<p><a href="/.well-known/acme-challenge/<?= $token ?>">view challenge</a></p>
</div>
<?php else: ?>
<div class="section">
<form class="" action="/cert/save-challenge" method="post">
<div class="field"><input type="text" name="token" placeholder="http://xray.p3k.io/.well-known/acme-challenge/_Tzyxwvut..." value="<?= isset($token) ? $token : '' ?>"></div>
<div class="field"><textarea name="challenge" rows="4" placeholder="challenge value"><?= isset($challenge) ? $challenge : '' ?></textarea></div>
<div class="field"><button type="submit" class="button">Save</button></div>
</form>
</div>
<?php endif ?>
<div style="margin-top: 1em; font-size: 12px;">
Signed in as <?= $_SESSION['me'] ?> <a href="/cert/logout">Sign Out</a>.
</div>
<?php else: ?>
<div class="section">
<form class="" action="/cert/auth" method="get">
<div class="field"><input type="url" name="me" placeholder="https://you.example.com"></div>
<div class="field"><button type="submit" class="button">Sign In</button></div>
<input type="hidden" name="client_id" value="https://<?= $_SERVER['SERVER_NAME'] ?>/">
<input type="hidden" name="redirect_uri" value="https://<?= $_SERVER['SERVER_NAME'] ?>/cert/redirect">
<input type="hidden" name="state" value="<?= isset($state) ? $state : '' ?>">
</form>
</div>
<?php endif ?>
</div>
<script>
var base = window.location.protocol + "//" + window.location.hostname + "/";
document.querySelector("input[name=client_id]").value = base;
document.querySelector("input[name=redirect_uri]").value = base+"cert/redirect";
</script>
<style type="text/css">
body {
color: #212121;
font-family: "Helvetica Neue", "Calibri Light", Roboto, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
background-color: #e9e9e9;
font-size: 16px;
}
h1 {
padding-top: 6rem;
padding-bottom: 1rem;
text-align: center;
}
a {
color: #4183c4;
text-decoration: none;
}
.column {
max-width: 450px;
margin: 0 auto;
}
.section {
border: 1px #ccc solid;
border-radius: 6px;
background: white;
padding: 12px;
margin-top: 2em;
}
.help {
text-align: center;
font-size: 0.9rem;
}
form .field {
margin-bottom: .5rem;
display: flex;
}
form input, form textarea, form button {
width: 100%;
border: 1px #ccc solid;
border-radius: 4px;
flex: 1 0;
font-size: 1rem;
}
form input, form textarea {
padding: .5rem;
}
form .button {
background-color: #009c95;
border: 0;
border-radius: 4px;
color: white;
font-weight: bold;
font-size: 1rem;
cursor: pointer;
padding: 0.5rem;
}
</style>

Loading…
Cancel
Save