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

* add raw dump button
* generate CRL and store text
* remove add/del footer buttons
This commit is contained in:
Ad Schellevis 2024-03-13 21:01:33 +01:00
parent ad5fed3c4d
commit bed2e66cf0
3 changed files with 124 additions and 10 deletions

View File

@ -231,6 +231,27 @@ class CrlController extends ApiControllerBase
}
}
}
$x509_crl = new \phpseclib3\File\X509();
if (empty($validations['crl.caref'])) {
/*
* create empty CRL. A quirk with phpseclib is that in order to correctly sign
* a new CRL, a CA must be loaded using a separate X509 container, which is passed
* to signCRL(). However, to validate the resulting signature, the original X509
* CRL container must load the same CA using loadCA() with a direct reference
* to the CA's public cert.
*/
$x509_crl->loadCA($ca_crt_str);
$x509_crl->loadCRL($x509_crl->saveCRL($x509_crl->signCRL($ca_cert, $x509_crl)));
/* Now validate the CRL to see if everything went well */
try {
if (!$x509_crl->validateSignature(false)) {
$validations['crl.caref'] = gettext('Cert revocation error: CRL signature invalid');
}
} catch (Exception $e) {
$validations['crl.caref'] = gettext('Cert revocation error: CRL signature invalid') . " " . $e;
}
}
if (!empty($validations)) {
Config::getInstance()->unlock();
@ -279,12 +300,16 @@ class CrlController extends ApiControllerBase
}
$crl->caref = (string)$caref;
$crl->descr = (string)$payload['descr'];
$crl->serial = !empty($payload['serial']) ? $payload['serial'] : $crl->serial;
$crl->serial = ((int)((string)$crl->serial)) + 1;
$to_delete = [];
$crl_certs = [];
foreach ($crl->cert as $cert) {
if (!isset($revoked_refs[(string)$cert->refid])) {
$to_delete[] = $cert;
} else {
$cert->reason = $revoked_refs[(string)$cert->refid];
$crl_certs[] = $cert;
unset($revoked_refs[(string)$cert->refid]);
}
}
@ -302,8 +327,31 @@ class CrlController extends ApiControllerBase
$tmp->prv = (string)$cert->prv;
$tmp->revoke_time = (string)time();
$tmp->reason = $revoked_refs[(string)$cert->refid];
$crl_certs[] = $tmp;
}
}
if ($payload['crlmethod'] == 'internal') {
/* add all cert serial numbers to crl */
foreach ($crl_certs as $cert) {
$tmp = @openssl_x509_parse(base64_decode((string)$cert->crt));
if ($tmp !== false && isset($tmp['serialNumber'])) {
$x509_crl->setRevokedCertificateExtension(
(string)$tmp['serialNumber'],
'id-ce-cRLReasons',
self::$status_codes[(string)$cert->reason]
);
}
}
$x509_crl->setSerialNumber((string)$crl->serial, 10);
/* consider dates after 2050 lifetime in GeneralizedTime format (rfc5280#section-4.1.2.5) */
$date = new \DateTimeImmutable(
'+' . (string)$crl->lifetime . ' days',
new \DateTimeZone(@date_default_timezone_get())
);
$x509_crl->setEndDate((int)$date->format("Y") < 2050 ? $date : 'lifetime');
$new_crl = $x509_crl->signCRL($ca_cert, $x509_crl);
$crl->text = base64_encode($x509_crl->saveCRL($new_crl) . PHP_EOL);
}
if ($last_crl) {
/* insert new item after last crl */
@ -321,4 +369,16 @@ class CrlController extends ApiControllerBase
}
return ['status' => 'failed'];
}
public function rawDumpAction($uuid)
{
$payload = $this->getAction($uuid);
if (!empty($payload['crl'])) {
if (!empty($payload['crl']['text'])) {
return CertStore::dumpCRL($payload['crl']['text']);
}
}
return [];
}
}

View File

@ -555,6 +555,30 @@ class Store
return $result;
}
/**
* @param string $cert certificate
* @return array [stdout|stderr]
*/
public static function dumpCRL($cert)
{
$result = [];
$process = proc_open(
'/usr/local/bin/openssl crl -fingerprint -sha256 -text',
[["pipe", "r"], ["pipe", "w"], ["pipe", "w"]],
$pipes
);
if (is_resource($process)) {
fwrite($pipes[0], $cert);
fclose($pipes[0]);
$result['stdout'] = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$result['stderr'] = stream_get_contents($pipes[2]);
fclose($pipes[2]);
proc_close($process);
}
return $result;
}
/**

View File

@ -32,7 +32,30 @@
get:'/api/trust/crl/get/',
set:'/api/trust/crl/set/',
del:'/api/trust/crl/del/',
datakey: 'refid'
datakey: 'refid',
commands: {
raw_dump: {
method: function(event){
let refid = $(this).data("row-id") !== undefined ? $(this).data("row-id") : '';
ajaxGet('/api/trust/crl/raw_dump/' + refid, {}, function(data, status){
if (data.stdout) {
BootstrapDialog.show({
title: "{{ lang._('CRL 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
},
copy: {
classname: undefined
}
}
});
$("#DialogCrl").click(function(){
@ -82,6 +105,22 @@
</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" id="tab_crls" href="#cert">{{ lang._('Index') }}</a></li>
<li><a data-toggle="tab" href="#edit_crl" id="DialogCrl" style="display: none;"> </a></li>
@ -99,15 +138,6 @@
</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>
<div id="edit_crl" class="tab-pane fade in">