Skip to content
27 changes: 17 additions & 10 deletions app/Commands/Authority/Certificate/ImportCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,31 +40,35 @@ public function handle(): int
try {
$config = $this->getCaConfig();
} catch (\RuntimeException $e) {
error($e->getMessage());
stdErr(fn () => error($e->getMessage()));

return self::FAILURE;
}

$ca = $config->database()->ca();

if ($ca->metadata()?->certificate !== null && !$this->option('force')) {
error('A certificate already exists. Use --force to overwrite.');
if ($ca->metadata()?->certificate !== null && ! $this->option('force')) {
stdErr(fn () => error('A certificate already exists. Use --force to overwrite.'));

return self::FAILURE;
}

$path = $this->argument('pem');

if ($path) {
if (!file_exists($path)) {
error("File not found: {$path}");
if (! file_exists($path)) {
stdErr(fn () => error("File not found: {$path}"));

return self::FAILURE;
}
$pem = file_get_contents($path);
} else {
$pem = file_get_contents('php://stdin');
}

if (!$pem) {
error('No PEM data provided');
if (! $pem) {
stdErr(fn () => error('No PEM data provided'));

return self::FAILURE;
}

Expand All @@ -73,12 +77,14 @@ public function handle(): int
try {
$cert = $x509->loadX509($pem);
} catch (\Exception $e) {
error('Failed to load certificate: ' . $e->getMessage());
stdErr(fn () => error('Failed to load certificate: '.$e->getMessage()));

return self::FAILURE;
}

if ($cert === false) {
error('Failed to parse certificate.');
stdErr(fn () => error('Failed to parse certificate.'));

return self::FAILURE;
}

Expand All @@ -99,7 +105,8 @@ public function handle(): int
$key = $config->database()->keys()->forFingerprint($fingerprint);

if ($key === null) {
error("No matching key found in database for fingerprint [{$fingerprint}].");
stdErr(fn () => error("No matching key found in database for fingerprint [{$fingerprint}]."));

return self::FAILURE;
}

Expand Down
26 changes: 19 additions & 7 deletions app/Commands/Authority/Certificate/SelfSignedCertificate.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
use App\Storage\Entities\CaCertificateDetails;
use App\Storage\Entities\CaMetadata;
use App\Storage\Enums\CaFile;
use App\Support\CertificateFingerprint;
use App\Storage\Enums\KeyFile;
use App\Support\CertificateFingerprint;
use Carbon\CarbonImmutable;
use Illuminate\Console\Scheduling\Schedule;
use LaravelZero\Framework\Commands\Command;
Expand Down Expand Up @@ -46,30 +46,34 @@ public function handle(): int
$config = $this->getCaConfig();
} catch (\RuntimeException $e) {
error($e->getMessage());

return self::FAILURE;
}

$ca = $config->database()->ca();

if ($ca->metadata()?->certificate !== null && !$this->option('force')) {
if ($ca->metadata()?->certificate !== null && ! $this->option('force')) {
error('A certificate already exists. Use --force to overwrite.');

return self::FAILURE;
}

$distinguished_name = $this->argument('distinguished_name');
if (! (new X509)->setDN($distinguished_name)) {
error('Invalid distinguished name format.');

return self::FAILURE;
}

$serial_number = $this->option('serial-number');
if ($serial_number !== null) {
if (! ctype_xdigit($serial_number)) {
error('Serial number must be a valid hexadecimal string.');

return self::FAILURE;
}
} else if($config->certificationAuthority->randomSerialNumbers) {
$serial_number = new BigInteger(Random::string(20) & ("\x7F" . str_repeat("\xFF", 19)), 256)->toHex();
} elseif ($config->certificationAuthority->randomSerialNumbers) {
$serial_number = new BigInteger(Random::string(20) & ("\x7F".str_repeat("\xFF", 19)), 256)->toHex();
} else {
$existingSerial = $ca->metadata()?->certificate?->serial_number;
$serial_number = $existingSerial !== null
Expand All @@ -84,13 +88,20 @@ public function handle(): int
return self::FAILURE;
}

$pem = $config->database()->keys()->getFile($key_id, KeyFile::PrivateKey);
$keyEntity = $config->database()->keys()->find($key_id);
if ($keyEntity !== null && ! $keyEntity->private) {
error("Key [{$key_id}] is a public-only key. Self-signed certificate creation requires a private key.");

return self::FAILURE;
}

$pem = $config->database()->keys()->getFile($key_id, KeyFile::PrivateKey);

try {
$key = $this->loadPrivateKey($pem);
} catch (\Exception $e) {
error('Failed to load private key: ' . $e->getMessage());
error('Failed to load private key: '.$e->getMessage());

return self::FAILURE;
}

Expand All @@ -99,6 +110,7 @@ public function handle(): int
$path_length_constraint = (int) $path_length_constraint;
if ($path_length_constraint < 0) {
error('Path length constraint must be greater than or equal to zero.');

return self::FAILURE;
}
}
Expand All @@ -111,7 +123,7 @@ public function handle(): int
$rootIssuer->setPrivateKey($key);
$rootIssuer->setDN($rootSubject->getDN());

$validFrom = new CarbonImmutable();
$validFrom = new CarbonImmutable;
$validTo = new CarbonImmutable($this->option('validity'));

$x509 = new X509;
Expand Down
18 changes: 15 additions & 3 deletions app/Commands/Authority/Csr/CreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,29 @@ public function handle(): int
$config = $this->getCaConfig();
} catch (\RuntimeException $e) {
error($e->getMessage());

return self::FAILURE;
}

$ca = $config->database()->ca();

$hasCertificate = $ca->metadata()?->certificate !== null && $ca->hasFile(CaFile::Certificate);
if ($hasCertificate && !$this->option('ignore-existing-certificate')) {
if ($hasCertificate && ! $this->option('ignore-existing-certificate')) {
error('A certificate already exists. Use --ignore-existing-certificate to create a CSR anyway.');

return self::FAILURE;
}

if ($ca->hasFile(CaFile::Csr) && !$this->option('force')) {
if ($ca->hasFile(CaFile::Csr) && ! $this->option('force')) {
error('A CSR already exists. Use --force to overwrite.');

return self::FAILURE;
}

$distinguished_name = $this->argument('distinguished_name');
if (! (new X509)->setDN($distinguished_name)) {
error('Invalid distinguished name format.');

return self::FAILURE;
}

Expand All @@ -69,12 +73,20 @@ public function handle(): int
return self::FAILURE;
}

$keyEntity = $config->database()->keys()->find($key_id);
if ($keyEntity !== null && ! $keyEntity->private) {
error("Key [{$key_id}] is a public-only key. CSR creation requires a private key.");

return self::FAILURE;
}

$pem = $config->database()->keys()->getFile($key_id, KeyFile::PrivateKey);

try {
$key = $this->loadPrivateKey($pem);
} catch (\Exception $e) {
error('Failed to load private key: ' . $e->getMessage());
error('Failed to load private key: '.$e->getMessage());

return self::FAILURE;
}

Expand Down
45 changes: 45 additions & 0 deletions app/Commands/Certificate/ExistsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace App\Commands\Certificate;

use App\Commands\Concerns\LoadsCaConfiguration;
use Illuminate\Console\Scheduling\Schedule;
use LaravelZero\Framework\Commands\Command;

use function Laravel\Prompts\error;
use function Laravel\Prompts\info;

class ExistsCommand extends Command
{
use LoadsCaConfiguration;

protected $signature = 'certificate:exists {id} {--ca= : Configuration file}';

protected $description = 'Check if a certificate exists';

public function handle(): int
{
try {
$config = $this->getCaConfig();
} catch (\RuntimeException $e) {
error($e->getMessage());

return self::FAILURE;
}

$has = $config->database()->certificates()->exists($this->argument('id'));

if ($has) {
info('Certificate exists');
} else {
error('Certificate does not exist');
}

return $has ? self::SUCCESS : self::FAILURE;
}

public function schedule(Schedule $schedule): void
{
// $schedule->command(static::class)->everyMinute();
}
}
54 changes: 54 additions & 0 deletions app/Commands/Certificate/GetCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Commands\Certificate;

use App\Commands\Concerns\LoadsCaConfiguration;
use App\Storage\Enums\CertificateFile;
use Illuminate\Console\Scheduling\Schedule;
use LaravelZero\Framework\Commands\Command;

use function Laravel\Prompts\error;

class GetCommand extends Command
{
use LoadsCaConfiguration;

protected $signature = 'certificate:get {id?} {--ca= : Configuration file}';

protected $description = 'Get issued certificate in PEM format';

public function handle(): int
{
try {
$config = $this->getCaConfig();
} catch (\RuntimeException $e) {
stdErr(fn () => error($e->getMessage()));

return self::FAILURE;
}

$id = $this->argument('id') ?? trim(file_get_contents('php://stdin'));

if (! $id) {
stdErr(fn () => error('No certificate ID provided.'));

return self::FAILURE;
}
$content = $config->database()->certificates()->getFile($id, CertificateFile::Certificate);

if ($content === null) {
stdErr(fn () => error("Certificate [{$id}] not found."));

return self::FAILURE;
}

$this->output->write($content);

return self::SUCCESS;
}

public function schedule(Schedule $schedule): void
{
// $schedule->command(static::class)->everyMinute();
}
}
Loading
Loading