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

* add boilerplate code (more or less the same as Certificates)
This commit is contained in:
Ad Schellevis 2024-03-07 19:12:38 +01:00
parent 46354f486c
commit 7cb95beef7
8 changed files with 851 additions and 1 deletions

View File

@ -0,0 +1,196 @@
<?php
/*
* Copyright (C) 2024 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Trust\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Base\UserException;
use OPNsense\Core\Config;
use OPNsense\Trust\Store as CertStore;
/**
* Class CaController
* @package OPNsense\Trust\Api
*/
class CaController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'ca';
protected static $internalModelClass = 'OPNsense\Trust\Ca';
protected function setBaseHook($node)
{
if (empty((string)$node->refid)) {
$node->refid = uniqid();
}
$error = false;
if (!empty((string)$node->prv_payload)) {
/** private key manually offered */
$node->prv = base64_encode((string)$node->prv_payload);
}
switch ((string)$node->action) {
case 'internal':
break;
case 'existing':
if (CertStore::parseX509((string)$node->crt_payload) === false) {
$error = gettext('Invalid X509 certificate provided');
} else {
$node->crt = base64_encode((string)$node->crt_payload);
if (
!empty(trim((string)$node->prv_payload)) &&
openssl_pkey_get_private((string)$node->prv_payload) === false
) {
$error = gettext('Invalid private key provided');
}
}
break;
case 'import':
break;
case 'ocsp':
break;
}
if ($error !== false) {
throw new UserException($error, "Certificate error");
}
}
public function searchAction()
{
$carefs = $this->request->get('carefs');
$filter_funct = function ($record) use ($carefs) {
return empty($carefs) || array_intersect(explode(',', $record->caref), $carefs);
};
return $this->searchBase(
'ca',
['refid', 'descr', 'caref', 'name', 'valid_from', 'valid_to'],
null,
$filter_funct
);
}
public function getAction($uuid = null)
{
return $this->getBase('ca', 'ca', $uuid);
}
public function addAction()
{
return $this->addBase('ca', 'ca');
}
public function setAction($uuid = null)
{
return $this->setBase('ca', 'ca', $uuid);
}
public function delAction($uuid)
{
if ($this->request->isPost() && !empty($uuid)) {
$node = $this->getModel()->getNodeByReference('ca.' . $uuid);
if ($node !== null) {
$this->checkAndThrowValueInUse((string)$node->refid, false, false, ['ca']);
}
return $this->delBase('ca', $uuid);
}
return ['status' => 'failed'];
}
public function toggleAction($uuid, $enabled = null)
{
return $this->toggleBase('ca', $uuid, $enabled);
}
public function caInfoAction($caref)
{
if ($this->request->isGet()) {
$ca = CertStore::getCACertificate($caref);
if ($ca) {
$payload = CertStore::parseX509($ca['cert']);
if ($payload) {
return $payload;
}
}
}
return [];
}
public function rawDumpAction($uuid)
{
$payload = $this->getBase('ca', 'ca', $uuid);
if (!empty($payload['ca'])) {
if (!empty($payload['ca']['crt_payload'])) {
return CertStore::dumpX509($payload['ca']['crt_payload']);
}
}
return [];
}
public function caListAction()
{
$result = [];
if ($this->request->isGet()) {
$result['rows'] = [];
if (isset(Config::getInstance()->object()->ca)) {
foreach (Config::getInstance()->object()->ca as $cert) {
if (isset($cert->refid)) {
$result['rows'][] = [
'caref' => (string)$cert->refid,
'descr' => (string)$cert->descr
];
}
}
}
$result['count'] = count($result['rows']);
}
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('ca.' . $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;
}
}
return $result;
}
}

View File

@ -0,0 +1,45 @@
<?php
/*
* Copyright (C) 2024 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Trust;
class CaController extends \OPNsense\Base\IndexController
{
protected function templateJSIncludes()
{
$result = parent::templateJSIncludes();
$result[] = '/ui/js/moment-with-locales.min.js';
return $result;
}
public function indexAction()
{
$this->view->formDialogEditCert = $this->getForm("dialogCa");
$this->view->pick('OPNsense/Trust/ca');
}
}

View File

@ -0,0 +1,123 @@
<form>
<field>
<id>ca.action</id>
<label>Method</label>
<type>dropdown</type>
</field>
<field>
<type>header</type>
<label>Key</label>
<style>action action_internal action_internal</style>
</field>
<field>
<id>ca.cert_type</id>
<label>Type</label>
<type>dropdown</type>
<style>selectpicker action action_internal </style>
</field>
<field>
<id>ca.private_key_location</id>
<label>Private key location</label>
<style>selectpicker action action_internal</style>
<type>dropdown</type>
</field>
<field>
<id>ca.key_type</id>
<label>Key type</label>
<type>dropdown</type>
</field>
<field>
<id>ca.digest</id>
<label>Digest Algorithm</label>
<type>dropdown</type>
</field>
<field>
<id>ca.caref</id>
<label>Issuer</label>
<style>selectpicker action action_internal </style>
<type>dropdown</type>
</field>
<field>
<id>ca.lifetime</id>
<label>Lifetime (days)</label>
<type>text</type>
</field>
<field>
<type>header</type>
<label>General</label>
<style>action action_internal</style>
</field>
<field>
<id>ca.descr</id>
<label>Description</label>
<type>text</type>
</field>
<field>
<id>ca.country</id>
<label>Country Code</label>
<type>dropdown</type>
</field>
<field>
<id>ca.state</id>
<label>State or Province</label>
<type>text</type>
</field>
<field>
<id>ca.city</id>
<label>City</label>
<type>text</type>
</field>
<field>
<id>ca.organization</id>
<label>Organization</label>
<type>text</type>
</field>
<field>
<id>ca.organizationalunit</id>
<label>Organizational Unit</label>
<type>text</type>
</field>
<field>
<id>ca.email</id>
<label>Email Address</label>
<type>text</type>
</field>
<field>
<id>ca.commonname</id>
<label>Common Name</label>
<type>text</type>
</field>
<field>
<id>ca.ocsp_uri</id>
<label>OCSP uri</label>
<type>text</type>
<style>action action_internal </style>
</field>
<field>
<id>ca.altnames_email</id>
<label>Email addresses</label>
<type>textbox</type>
</field>
<field>
<type>header</type>
<label>Output (PEM format)</label>
<collapse>true</collapse>
<style>pem_section</style>
</field>
<field>
<id>ca.crt_payload</id>
<label>Certificate data</label>
<type>textbox</type>
</field>
<field>
<id>ca.prv_payload</id>
<label>Private key data</label>
<type>textbox</type>
</field>
<field>
<id>ca.csr_payload</id>
<label>Certificate signing request</label>
<type>textbox</type>
<style>selectpicker action action_import_csr</style>
</field>
</form>

View File

@ -0,0 +1,42 @@
<?php
/**
* Copyright (C) 2024 Deciso B.V.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\Trust;
use OPNsense\Base\Messages\Message;
use OPNsense\Base\BaseModel;
/**
* Class Ca
* @package OPNsense\Trust
*/
class Ca extends BaseModel
{
}

View File

@ -0,0 +1,87 @@
<model>
<mount>/ca+</mount>
<version>1.0.0</version>
<items>
<ca type=".\CAsField">
<refid type="TextField"/>
<descr type="DescriptionField"/>
<crt type="TextField"/>
<prv type="TextField"/>
<serial type="IntegerField"/>
<caref type="CertificateField">
<type>ca</type>
<BlankDesc>self-signed</BlankDesc>
<ValidationMessage>Please select a valid certificate from the list</ValidationMessage>
</caref>
<action type="OptionField" volatile="true">
<Default>internal</Default>
<Required>Y</Required>
<OptionValues>
<existing>Import an existing Certificate Authority</existing>
<internal>Create an internal Certificate Authority</internal>
<ocsp>Create an OCSP signing certificate</ocsp>
</OptionValues>
</action>
<key_type type="OptionField" volatile="true">
<Required>Y</Required>
<Default>2048</Default>
<OptionValues>
<RSA-512 value='512'>RSA-512</RSA-512>
<RSA-1024 value='1024'>RSA-1024</RSA-1024>
<RSA-2048 value='2048'>RSA-2048</RSA-2048>
<RSA-3072 value='3072'>RSA-3072</RSA-3072>
<RSA-4096 value='4096'>RSA-4096</RSA-4096>
<RSA-8192 value='8192'>RSA-8192</RSA-8192>
<prime256v1>Elliptic Curve prime256v1</prime256v1>
<secp384r1>Elliptic Curve secp384r1</secp384r1>
<secp521r1>Elliptic Curve secp521r1</secp521r1>
</OptionValues>
</key_type>
<digest type="OptionField" volatile="true">
<Required>Y</Required>
<Default>sha256</Default>
<OptionValues>
<sha1>SHA1</sha1>
<sha224>SHA224</sha224>
<sha256>SHA256</sha256>
<sha384>SHA384</sha384>
<sha512>SHA512</sha512>
</OptionValues>
</digest>
<lifetime type="IntegerField" volatile="true">
<Required>Y</Required>
<Default>397</Default>
</lifetime>
<city type="TextField" volatile="true">
<Mask>/^[^\x00-\x08\x0b\x0c\x0e-\x1f\n]*$/</Mask>
</city>
<state type="TextField" volatile="true">
<Mask>/^[^\x00-\x08\x0b\x0c\x0e-\x1f\n]*$/</Mask>
</state>
<organization type="TextField" volatile="true">
<Mask>/^[^\x00-\x08\x0b\x0c\x0e-\x1f\n]*$/</Mask>
</organization>
<organizationalunit type="TextField" volatile="true">
<Mask>/^[^\x00-\x08\x0b\x0c\x0e-\x1f\n]*$/</Mask>
</organizationalunit>
<country type="CountryField" volatile="true">
<Mask>/^[^\x00-\x08\x0b\x0c\x0e-\x1f\n]*$/</Mask>
<Default>NL</Default>
<Required>Y</Required>
</country>
<email type="TextField" volatile="true">
<Mask>/^[^\x00-\x08\x0b\x0c\x0e-\x1f\n]*$/</Mask>
</email>
<commonname type="TextField" volatile="true">
<Mask>/^[^\x00-\x08\x0b\x0c\x0e-\x1f\n]*$/</Mask>
</commonname>
<ocsp_uri type="UrlField" volatile="true"/>
<crt_payload type="TextField" volatile="true"/>
<prv_payload type="TextField" volatile="true"/>
<name type="TextField" volatile="true"/>
<valid_from type="TextField" volatile="true"/>
<valid_to type="TextField" volatile="true"/>
</ca>
</items>
</model>

View File

@ -0,0 +1,119 @@
<?php
/*
* Copyright (C) 2024 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Trust\FieldTypes;
use OPNsense\Core\Config;
use OPNsense\Base\FieldTypes\ArrayField;
use OPNsense\Base\FieldTypes\ContainerField;
use OPNsense\Base\FieldTypes\TextField;
/**
* Class CaContainerField
* @package OPNsense\Trust\FieldTypes
*/
class CaContainerField extends ContainerField
{
/**
* @return array dn
*/
public function dn()
{
$dn = [];
foreach (
[
'country' => 'countryName',
'state' => 'stateOrProvinceName',
'city' => 'localityName',
'organization' => 'organizationName',
'organizationalunit' => 'organizationalUnitName',
'email' => 'emailAddress',
'commonname' => 'commonName',
] as $source => $target
) {
if (!empty((string)$this->$source)) {
$dn[$target] = (string)$this->$source;
}
}
return $dn;
}
}
/**
* Class CAsField
* @package OPNsense\Trust\FieldTypes
*/
class CAsField extends ArrayField
{
/**
* @inheritDoc
*/
public function newContainerField($ref, $tagname)
{
$container_node = new CaContainerField($ref, $tagname);
$pmodel = $this->getParentModel();
$container_node->setParentModel($pmodel);
return $container_node;
}
protected function actionPostLoadingEvent()
{
foreach ($this->internalChildnodes as $node) {
$node->crt_payload = !empty((string)$node->crt) ? (string)base64_decode($node->crt) : '';
$payload = false;
if (!empty((string)$node->crt_payload)) {
$payload = \OPNsense\Trust\Store::parseX509($node->crt_payload);
}
if ($payload !== false) {
$countries = [];
foreach ($payload as $key => $value) {
if (isset($node->$key)) {
/* prevent injection of invalid countries which trip migrations */
if ($key == 'country') {
if (empty($countries)) {
$countries = array_keys($node->$key->getNodeData());
}
if (in_array($value, $countries)) {
$node->$key = $value;
}
} else {
$node->$key = $value;
}
}
}
}
$node->prv_payload = !empty((string)$node->prv) ? (string)base64_decode($node->prv) : '';
if (!empty((string)$node->crt_payload)) {
$node->action = 'existing';
}
}
return parent::actionPostLoadingEvent();
}
}

View File

@ -119,7 +119,17 @@ class CertificatesField extends ArrayField
if ($payload !== false) {
foreach ($payload as $key => $value) {
if (isset($node->$key)) {
$node->$key = $value;
/* prevent injection of invalid countries which trip migrations */
if ($key == 'country') {
if (empty($countries)) {
$countries = array_keys($node->$key->getNodeData());
}
if (in_array($value, $countries)) {
$node->$key = $value;
}
} else {
$node->$key = $value;
}
}
}
}

View File

@ -0,0 +1,228 @@
{#
# Copyright (c) 2024 Deciso B.V.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#}
<script>
'use strict';
function download_content(payload, filename, file_type) {
let a_tag = $('<a></a>').attr('href','data:application/json;charset=utf8,' + encodeURIComponent(payload))
.attr('download', filename).appendTo('body');
a_tag.ready(function() {
if ( window.navigator.msSaveOrOpenBlob && window.Blob ) {
var blob = new Blob( [ payload ], { type: file_type } );
navigator.msSaveOrOpenBlob( blob, 'aliases.json' );
} else {
a_tag.get(0).click();
}
});
}
$( document ).ready(function () {
let grid_cert = $("#grid-cert").UIBootgrid({
search:'/api/trust/ca/search/',
get:'/api/trust/ca/get/',
add:'/api/trust/ca/add/',
set:'/api/trust/ca/set/',
del:'/api/trust/ca/del/',
commands: {
raw_dump: {
method: function(event){
let uuid = $(this).data("row-id") !== undefined ? $(this).data("row-id") : '';
ajaxGet('/api/trust/ca/raw_dump/' + uuid, {}, function(data, status){
if (data.stdout) {
BootstrapDialog.show({
title: "{{ lang._('Certificate info') }}",
type:BootstrapDialog.TYPE_INFO,
message: $("<div/>").text(data.stdout).html(),
cssClass: 'monospace-dialog',
});
}
});
},
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'));
$container.append(
$("<div class='form-group'/>").append(
$("<label for='download_type'>{{ lang._('File type') }}</label>"),
$type
)
);
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/ca/generate_file/'+uuid+'/'+$type.val(),
params,
function(data, status) {
download_content(data.payload, $type.val() + '.pem', 'application/octet-stream');
}
)
dialogItself.close();
}
}]
});
},
classname: 'fa fa-fw fa-cloud-download',
title: "{{ lang._('Download') }}",
sequence: 10
}
}
});
/**
* Autofill certificate fields when choosing a different CA
*/
$("#ca\\.caref").change(function(event){
if (event.originalEvent !== undefined) {
// not called on form open, only when the user chooses a new ca
ajaxGet('/api/trust/ca/ca_info/' + $(this).val(), {}, function(data, status){
if (data.name !== undefined) {
[
'city', 'state', 'country', 'name', 'email', 'organization', 'ocsp_uri'
].forEach(function(field){
if (data[field]) {
$("#ca\\." + field).val(data[field]);
}
});
}
$("#ca\\.country").selectpicker('refresh');
});
}
});
$("#ca\\.action").change(function(event){
if (event.originalEvent === undefined) {
// lock valid options based on server offered action
let visible_options = [$(this).val()];
if ($(this).val() == 'internal') {
visible_options.push('existing');
visible_options.push('ocsp');
}
$("#ca\\.action option").each(function(){
if (visible_options.includes($(this).val())) {
$(this).attr('disabled', null);
} else {
$(this).attr('disabled', 'disabled');
}
});
}
let this_action = $(this).val();
$(".action").each(function(){
let target = null;
if ($(this)[0].tagName == 'DIV') {
target = $(this)
} else {
target = $(this).closest("tr");
}
target.hide();
if ($(this).hasClass('action_' + this_action)) {
target.show();
}
});
/* expand/collapse PEM section */
if (['import', 'import_csr'].includes($(this).val())) {
if ($(".pem_section > table > tbody > tr:eq(0) > td:eq(0)").is(':hidden')) {
$(".pem_section > table > thead").click();
}
} else {
if (!$(".pem_section > table > tbody > tr:eq(0) > td:eq(0)").is(':hidden')) {
$(".pem_section > table > thead").click();
}
}
});
});
</script>
<style>
.monospace-dialog {
font-family: monospace;
white-space: pre;
}
.monospace-dialog > .modal-dialog {
width:70% !important;
}
.modal-body {
max-height: calc(100vh - 210px);
overflow-y: auto;
}
</style>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#cert">{{ lang._('Certificates') }}</a></li>
</ul>
<div class="tab-content content-box">
<div id="cert" class="tab-pane fade in active">
<table id="grid-cert" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogCert">
<thead>
<tr>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
<th data-column-id="descr" data-width="15em" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="caref" data-width="15em" data-type="string">{{ lang._('Issuer') }}</th>
<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="11em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-primary"><span class="fa fa-fw fa-plus"></span></button>
<button data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-fw fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
</div>
{{ partial("layout_partials/base_dialog",['fields':formDialogEditCert,'id':'DialogCert','label':lang._('Edit Certificate')])}}