From 51acff5bd3216c60a86a110b687210693fb5c0af Mon Sep 17 00:00:00 2001 From: Niklas Keller Date: Sun, 15 Apr 2018 19:14:36 +0200 Subject: [PATCH] Implement --rekey option Closes #65. Closes #19. --- .acme-client.yml.sample | 3 +++ doc/advanced-usage.md | 2 +- doc/usage.md | 3 +++ src/Commands/Auto.php | 18 +++++++++++++++--- src/Commands/Issue.php | 19 ++++++++++++++++--- 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/.acme-client.yml.sample b/.acme-client.yml.sample index c6d4e99..34bf1da 100644 --- a/.acme-client.yml.sample +++ b/.acme-client.yml.sample @@ -28,7 +28,10 @@ certificates: # user: User running the web server. Challenge files are world readable, # but some servers might require to be owner of files they serve. # + # rekey: Regenerate certificate key pairs even if a key pair already exists. + # - bits: 4096 + rekey: true paths: /var/www/example: - example.org diff --git a/doc/advanced-usage.md b/doc/advanced-usage.md index 9ffd311..2846fa5 100644 --- a/doc/advanced-usage.md +++ b/doc/advanced-usage.md @@ -24,7 +24,7 @@ You can separate multiple domains (`-d`) with `,`, `:` or `;`. You can separate If you specify less paths than domains, the last one will be used for the remaining domains. -Please note that Let's Encrypt has rate limits. Currently it's five certificates per domain per seven days. If you combine multiple subdomains in a single certificate, they count as just one certificate. If you just want to test things out, you can use their staging server, which has way higher rate limits by appending `--s letsencrypt:staging`. +Please note that Let's Encrypt has rate limits. Currently it's five certificates per domain per seven days. If you combine multiple subdomains in a single certificate, they count as just one certificate. If you just want to test things out, you can use their staging server, which has way higher rate limits by appending `--server letsencrypt:staging`. ## Revoke a Certificate diff --git a/doc/usage.md b/doc/usage.md index 7427c0c..fb1f4a4 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -53,7 +53,10 @@ certificates: # user: User running the web server. Challenge files are world readable, # but some servers might require to be owner of files they serve. # + # rekey: Regenerate certificate key pairs even if a key pair already exists. + # - bits: 4096 + rekey: true paths: /var/www/example: - example.org diff --git a/src/Commands/Auto.php b/src/Commands/Auto.php index d2dfe88..f78dce0 100644 --- a/src/Commands/Auto.php +++ b/src/Commands/Auto.php @@ -86,6 +86,13 @@ class Auto implements Command { return self::EXIT_CONFIG_ERROR; } + foreach ($config['certificates'] as $certificateConfig) { + if (isset($certificateConfig['rekey']) && !\is_bool($certificateConfig['rekey'])) { + $this->climate->error("Config file ({$configPath}) defines an invalid 'rekey' value."); + return self::EXIT_CONFIG_ERROR; + } + } + $concurrency = isset($config['challenge-concurrency']) ? (int) $config['challenge-concurrency'] : null; $process = new Process([ @@ -179,8 +186,7 @@ class Auto implements Command { $domainPathMap = $this->toDomainPathMap($certificate['paths']); $domains = \array_keys($domainPathMap); $commonName = \reset($domains); - - $process = new Process([ + $processArgs = [ PHP_BINARY, $GLOBALS['argv'][0], 'check', @@ -192,7 +198,13 @@ class Auto implements Command { $commonName, '--names', \implode(',', $domains), - ]); + ]; + + if ($certificate['rekey'] ?? false) { + $processArgs[] = '--rekey'; + } + + $process = new Process($processArgs); $process->start(); $exit = yield $process->join(); diff --git a/src/Commands/Issue.php b/src/Commands/Issue.php index 7647b42..e6d8594 100644 --- a/src/Commands/Issue.php +++ b/src/Commands/Issue.php @@ -97,14 +97,20 @@ class Issue implements Command { throw new AcmeException('Issuance failed, not all challenges could be solved.'); } - $path = 'certs/' . $keyFile . '/' . \reset($domains) . '/key.pem'; + $keyPath = 'certs/' . $keyFile . '/' . \reset($domains) . '/key.pem'; $bits = $args->get('bits'); + $regenerateKey = $args->get('rekey'); + try { - $key = yield $keyStore->get($path); + $key = yield $keyStore->get($keyPath); } catch (KeyStoreException $e) { + $regenerateKey = true; + } + + if ($regenerateKey) { + $this->climate->whisper(' Generating new key pair ...'); $key = (new RsaKeyGenerator($bits))->generateKey(); - $key = yield $keyStore->put($path, $key); } $this->climate->br(); @@ -117,6 +123,8 @@ class Issue implements Command { $path = AcmeClient\normalizePath($args->get('storage')) . '/certs/' . $keyFile; $certificateStore = new CertificateStore($path); + + yield $keyStore->put($keyPath, $key); yield $certificateStore->put($certificates); $this->climate->info(' Successfully issued certificate.'); @@ -232,6 +240,11 @@ class Issue implements Command { 'defaultValue' => 10, 'castTo' => 'int', ], + 'rekey' => [ + 'longPrefix' => 'rekey', + 'description' => 'Regenerate the key pair even if a key pair already exists.', + 'noValue' => true, + ], ]; } }