Skip to content

Commit

Permalink
feat: allow pkcs12 cert exports #470
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredhendrickson13 committed Sep 9, 2024
1 parent 09fd1c9 commit 5205c7f
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace RESTAPI\Endpoints;

require_once 'RESTAPI/autoloader.inc';

use RESTAPI\Core\Endpoint;

/**
* Defines an Endpoint for interacting with a singular CertificatePKCS12Export object at
* /api/v2/system/certificate/pkcs12/export.
*/
class SystemCertificatePKCS12ExportEndpoint extends Endpoint {
public function __construct() {
# Set Endpoint attributes
$this->url = '/api/v2/system/certificate/pkcs12/export';
$this->model_name = 'CertificatePKCS12Export';
$this->request_method_options = ['POST'];
$this->encode_content_handlers = ['BinaryContentHandler']; # Only allow binary DLs (application/octet-stream)

# Set help text
$this->post_help_text = 'Exports an existing certificate as a PKCS#12 archive.';

# Construct the parent Endpoint object
parent::__construct();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

namespace RESTAPI\Models;

use RESTAPI\Core\Model;
use RESTAPI\Fields\ForeignModelField;
use RESTAPI\Fields\StringField;
use RESTAPI\Responses\ServerError;

/**
* Defines a Model for handling the export of an existing Certificate as a PKCS12 file.
*/
class CertificatePKCS12Export extends Model
{
public ForeignModelField $certref;
public StringField $encryption;
public StringField $passphrase;
public StringField $filename;
public StringField $binary_data;

public function __construct(mixed $id = null, mixed $parent_id = null, mixed $data = [], ...$options)
{
# Set Model attributes
$this->internal_callable = 'get_internal';
$this->verbose_name = 'Certificate PKCS#12 Export';

# Define Model Fields
$this->certref = new ForeignModelField(
model_name: 'Certificate',
model_field: 'refid',
required: true,
help_text: 'The Certificate to export as a PKCS12 file.',
);
$this->encryption = new StringField(
default: 'high',
choices: ['high', 'low', 'legacy'],
help_text: 'The level of encryption to use when exporting the PKCS#12 archive.',
);
$this->passphrase = new StringField(
default: '',
allow_empty: true,
help_text: 'The passphrase to use when exporting the PKCS#12 archive. Leave empty for no passphrase.',
);
$this->filename = new StringField(
default: null,
allow_null: true,
read_only: true,
help_text: 'The filename used when exporting the PKCS#12 archive. This value cannot be changed and will '.
'always be certificate refid with the .p12 extension.',
);
$this->binary_data = new StringField(
default: null,
allow_null: true,
read_only: true,
help_text: 'The PKCS#12 archive binary data. This value cannot be changed.',
);

parent::__construct($id, $parent_id, $data, ...$options);
}

/**
* Psuedo-method to return the internal data of the Model. This Model has no internal data but must use an internal
* callable.
* @return array An empty array.
*/
public function get_internal(): array
{
return [];
}

/**
* Creates the PKCS#12 archive file based on the Model data.
*/
public function _create(): void {
# Set the filename. This will be used by the BinaryContentHandler to set the filename of the download.
$this->filename->value = $this->certref->value . '.p12';

# Create the PKCS#12 archive
$this->binary_data->value = cert_pkcs12_export(
cert: $this->certref->get_related_model()->to_internal(),
encryption: $this->encryption->value,
passphrase: $this->passphrase->value,
delivery: 'data'
);

# Throw an error if the PKCS#12 archive could not be created
if (!$this->binary_data->value) {
throw new ServerError(
message: 'The PKCS#12 archive could not be created for unknown reasons.',
response_id: 'CERTIFICATE_PKCS12_EXPORT_CREATION_FAILED',
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace RESTAPI\Tests;

use RESTAPI\Core\TestCase;
use RESTAPI\Models\Certificate;
use RESTAPI\Models\CertificatePKCS12Export;

class APIModelsCertificatePKCS12ExportTestCase extends TestCase
{
/**
* Ensure the get_internal() method returns an empty array. This model has no internal representation.
*/
public function test_get_internal(): void {
$model = new CertificatePKCS12Export();
$this->assert_equals([], $model->get_internal());
}

/**
* Ensure the _create() method correctly converts the certificate to a PKCS12 archive.
*/
public function test_create_with_pass(): void {
# Obtain the default certificate
$cert = new Certificate(id: 0);

# Create a PKCS12 archive from the certificate
$model = new CertificatePKCS12Export(
certref: $cert->refid->value,
encryption: 'high',
passphrase: 'testpass',
);
$model->create();

# Ensure we can load the PKCS12 archive
$pkcs12 = openssl_pkcs12_read($model->binary_data->value, $certs, 'testpass');
$this->assert_not_equals(false, $pkcs12);
}

}

0 comments on commit 5205c7f

Please sign in to comment.