System: Trust: Certificates - work in progress for https://github.com/opnsense/core/issues/7248

* add download file dialog (key,cert or p12)
This commit is contained in:
Ad Schellevis 2024-03-04 16:08:36 +01:00
parent f25adff8fa
commit e2e365df4b
3 changed files with 128 additions and 1 deletions

View File

@ -231,4 +231,43 @@ class CertController extends ApiMutableModelControllerBase
}
return $result;
}
/**
* generate file download content
* @param string $uuid certificate reference
* @param string $type one of crt/prv/pkcs12,
* $_POST['password'] my contain an optional password for the pkcs12 format
* @return array
*/
public function generateFileAction($uuid=null, $type='crt')
{
$result = ['status' => 'failed'];
if ($this->request->isPost() && !empty($uuid)) {
$node = $this->getModel()->getNodeByReference('cert.'. $uuid);
if ($node === null || empty((string)$node->crt_payload)) {
$result['error'] = gettext('Misssing certificate');
} elseif ($type == 'crt') {
$result['status'] = 'ok';
$result['payload'] = (string)$node->crt_payload;
} elseif ($type == 'prv') {
$result['status'] = 'ok';
$result['payload'] = (string)$node->prv_payload;
} elseif ($type == 'pkcs12') {
$passphrase = $this->request->getPost('password', null, '');
$tmp = CertStore::getPKCS12(
(string)$node->crt_payload,
(string)$node->prv_payload,
(string)$node->descr,
$passphrase
);
if (!empty($tmp['payload'])) {
// binary data, we need to encode it to deliver it to the client
$result['payload_b64'] = base64_encode($tmp['payload']);
} else {
$result['error'] = $tmp['error'] ?? '';
}
}
}
return $result;
}
}

View File

@ -485,6 +485,28 @@ class Store
return false;
}
/**
* @param string $certificate
* @param string $private_key
* @param string $friendly_name
* @param string $passphrase
* @return string
*/
public static function getPKCS12($certificate, $private_key, $friendly_name=null, $passphrase=null)
{
$old_err_level = error_reporting(0); /* prevent openssl error from going to stderr/stdout */
$options = [];
if (!empty($friendly_name)) {
$options['friendly_name'] = $friendly_name;
}
$result = [];
if (!openssl_pkcs12_export($certificate, $result['payload'], $private_key, $passphrase, $options)) {
self::_addSSLErrors($result);
}
error_reporting($old_err_level);
return $result;
}
/**
* @param string $cert certificate
* @return array [stdout|stderr]

View File

@ -74,6 +74,72 @@
classname: 'fa fa-fw fa-info-circle',
title: "{{ lang._('show certificate info') }}",
sequence: 10
},
download: {
method: function(event){
let uuid = $(this).data("row-id") !== undefined ? $(this).data("row-id") : '';
let $container = $("<div style='height:150px;'/>");
let $type = $("<select id='download_type'/>");
let $password = $("<input id='download_password' type='password'/>");
$type.append($("<option value='crt'/>").text('Certificate'));
$type.append($("<option value='prv'/>").text('Private key'));
$type.append($("<option value='pkcs12' selected=selected/>").text('PKCS #12'));
$container.append(
$("<div class='form-group'/>").append(
$("<label for='download_type'>{{ lang._('File type') }}</label>"),
$type
)
);
$container.append(
$("<div class='form-group'/>").append(
$("<label for='download_password'>{{ lang._('Password') }}</label>"),
$password)
);
$type.change(function(){
if ($(this).val() != 'pkcs12') {
$password.closest('div').hide();
} else {
$password.closest('div').show();
}
});
BootstrapDialog.show({
title: "{{ lang._('Certificate download') }}",
type:BootstrapDialog.TYPE_INFO,
message: $container,
buttons: [{
label: "{{ lang._('Download') }}",
action: function(dialogItself){
let params = {};
if ($password.val()) {
params['password'] = $password.val();
}
ajaxCall(
'/api/trust/cert/generate_file/'+uuid+'/'+$type.val(),
params,
function(data, status) {
let payload = null;
let filename = null;
if (data.payload_b64) {
payload = atob(data.payload_b64);
filename = 'cert.p12';
} else if (data.payload) {
payload = data.payload;
filename = $type.val() + '.pem';
}
if (payload !== null) {
download_content(payload, filename, 'application/octet-stream');
}
}
)
dialogItself.close();
}
}]
});
},
classname: 'fa fa-fw fa-cloud-download',
title: "{{ lang._('Download') }}",
sequence: 10
}
}
});
@ -210,7 +276,7 @@
<th data-column-id="name" data-type="string">{{ lang._('Name') }}</th>
<th data-column-id="valid_from" data-width="10em" data-type="datetime">{{ lang._('Valid from') }}</th>
<th data-column-id="valid_to" data-width="10em" data-type="datetime">{{ lang._('Valid to') }}</th>
<th data-column-id="commands" data-width="9em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
<th data-column-id="commands" data-width="11em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>