Implement revoction
This commit is contained in:
15
README.md
15
README.md
@@ -6,7 +6,7 @@
|
|||||||
`kelunik/acme-client` is a standalone ACME client written in PHP.
|
`kelunik/acme-client` is a standalone ACME client written in PHP.
|
||||||
It's an alternative for the [official client](https://github.com/letsencrypt/letsencrypt) which is written in python.
|
It's an alternative for the [official client](https://github.com/letsencrypt/letsencrypt) which is written in python.
|
||||||
|
|
||||||
> **Warning**: This software is under heavy development. Use at your own risk. Revocation is not yet supported by this client.
|
> **Warning**: This software is under heavy development. Use at your own risk.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -18,6 +18,9 @@ composer install
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
> **Note**: This client stores all data in `./data`, be sure to backup this folder regularly.
|
||||||
|
> It contains your account keys, domain keys and certificates.
|
||||||
|
|
||||||
Before you can issue certificates, you have to register an account first and read and understand the terms of service of the ACME CA you're using.
|
Before you can issue certificates, you have to register an account first and read and understand the terms of service of the ACME CA you're using.
|
||||||
For Let's Encrypt there's a [subscriber agreement](https://letsencrypt.org/repository/) you have to accept.
|
For Let's Encrypt there's a [subscriber agreement](https://letsencrypt.org/repository/) you have to accept.
|
||||||
|
|
||||||
@@ -41,4 +44,12 @@ sudo bin/acme issue \
|
|||||||
--path /var/www/example.com
|
--path /var/www/example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
For renewal, just run this command again.
|
For renewal, just run this command again.
|
||||||
|
|
||||||
|
To revoke a certificate, you need a valid account key currently, just like for issuance.
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo bin/acme revoke \
|
||||||
|
--server acme-v01.api.letsencrypt.org/directory \
|
||||||
|
--cert data/live/example.com/cert.pem
|
||||||
|
```
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
"php": ">=7.0.0",
|
"php": ">=7.0.0",
|
||||||
"ext-posix": "*",
|
"ext-posix": "*",
|
||||||
"ext-openssl": "*",
|
"ext-openssl": "*",
|
||||||
"amphp/aerys": "dev-master",
|
|
||||||
"bramus/monolog-colored-line-formatter": "^2",
|
"bramus/monolog-colored-line-formatter": "^2",
|
||||||
"kelunik/acme": "dev-master",
|
"kelunik/acme": "dev-master",
|
||||||
|
"kelunik/certificate": "dev-master",
|
||||||
"league/climate": "^3",
|
"league/climate": "^3",
|
||||||
"monolog/monolog": "^1.17",
|
"monolog/monolog": "^1.17",
|
||||||
"psr/log": "^1",
|
"psr/log": "^1",
|
||||||
|
|||||||
@@ -3,20 +3,97 @@
|
|||||||
namespace Kelunik\AcmeClient\Commands;
|
namespace Kelunik\AcmeClient\Commands;
|
||||||
|
|
||||||
use Amp\Promise;
|
use Amp\Promise;
|
||||||
|
use Generator;
|
||||||
|
use Kelunik\Acme\AcmeClient;
|
||||||
use Kelunik\Acme\AcmeException;
|
use Kelunik\Acme\AcmeException;
|
||||||
|
use Kelunik\Acme\AcmeService;
|
||||||
|
use Kelunik\Acme\KeyPair;
|
||||||
|
use Kelunik\Certificate\Certificate;
|
||||||
use League\CLImate\Argument\Manager;
|
use League\CLImate\Argument\Manager;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use function Amp\File\exists;
|
||||||
|
use function Amp\File\get;
|
||||||
|
use function Amp\resolve;
|
||||||
|
|
||||||
class Revoke implements Command {
|
class Revoke implements Command {
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
public function __construct(LoggerInterface $logger) {
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
public function execute(Manager $args): Promise {
|
public function execute(Manager $args): Promise {
|
||||||
throw new AcmeException("Command not yet implemented!");
|
return resolve($this->doExecute($args));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function doExecute(Manager $args): Generator {
|
||||||
|
if (posix_geteuid() !== 0) {
|
||||||
|
throw new AcmeException("Please run this script as root!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = $args->get("server");
|
||||||
|
$protocol = substr($server, 0, strpos("://", $server));
|
||||||
|
|
||||||
|
if (!$protocol || $protocol === $server) {
|
||||||
|
$server = "https://" . $server;
|
||||||
|
} elseif ($protocol !== "https") {
|
||||||
|
throw new \InvalidArgumentException("Invalid server protocol, only HTTPS supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyPair = $this->checkRegistration($args);
|
||||||
|
$acme = new AcmeService(new AcmeClient($server, $keyPair), $keyPair);
|
||||||
|
|
||||||
|
$this->logger->info("Revoking certificate ...");
|
||||||
|
|
||||||
|
$pem = yield get($args->get("cert"));
|
||||||
|
$cert = new Certificate($pem);
|
||||||
|
|
||||||
|
if ($cert->getValidTo() < time()) {
|
||||||
|
$this->logger->warning("Certificate did already expire, no need to revoke it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->info("Certificate was valid for: " . implode(", ", $cert->getNames()));
|
||||||
|
|
||||||
|
yield $acme->revokeCertificate($pem);
|
||||||
|
|
||||||
|
$this->logger->info("Certificate has been revoked.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkRegistration(Manager $args) {
|
||||||
|
$server = $args->get("server");
|
||||||
|
$protocol = substr($server, 0, strpos("://", $server));
|
||||||
|
|
||||||
|
if (!$protocol || $protocol === $server) {
|
||||||
|
$server = "https://" . $server;
|
||||||
|
} elseif ($protocol !== "https") {
|
||||||
|
throw new \InvalidArgumentException("Invalid server protocol, only HTTPS supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
$identity = str_replace(["/", "%"], "-", substr($server, 8));
|
||||||
|
|
||||||
|
$path = __DIR__ . "/../../data/accounts";
|
||||||
|
$pathPrivate = "{$path}/{$identity}.private.key";
|
||||||
|
$pathPublic = "{$path}/{$identity}.public.key";
|
||||||
|
|
||||||
|
if (file_exists($pathPrivate) && file_exists($pathPublic)) {
|
||||||
|
$private = file_get_contents($pathPrivate);
|
||||||
|
$public = file_get_contents($pathPublic);
|
||||||
|
|
||||||
|
$this->logger->info("Found account keys.");
|
||||||
|
|
||||||
|
return new KeyPair($private, $public);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AcmeException("No registration found for server, please register first");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getDefinition(): array {
|
public static function getDefinition(): array {
|
||||||
return [
|
return [
|
||||||
"domains" => [
|
"cert" => [
|
||||||
"prefix" => "d",
|
"prefix" => "c",
|
||||||
"longPrefix" => "domains",
|
"longPrefix" => "cert",
|
||||||
"description" => "Domains to request a certificate for.",
|
"description" => "Certificate to be revoked.",
|
||||||
"required" => true,
|
"required" => true,
|
||||||
],
|
],
|
||||||
"server" => [
|
"server" => [
|
||||||
|
|||||||
Reference in New Issue
Block a user