mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-16 17:44:41 +00:00
System: Trust: Certificates - work in progress for https://github.com/opnsense/core/issues/7248
Implement certificate actions, further optimize certificate store to limit code duplication.
This commit is contained in:
parent
b8bd667d64
commit
94952145b3
@ -48,7 +48,91 @@ class CertController extends ApiMutableModelControllerBase
|
||||
if (empty((string)$node->refid)) {
|
||||
$node->refid = uniqid();
|
||||
}
|
||||
throw new UserException((string)$node->refid, (string)$node->action);
|
||||
$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':
|
||||
$data = CertStore::createCert(
|
||||
(string)$node->key_type,
|
||||
(string)$node->lifetime,
|
||||
$node->dn(),
|
||||
(string)$node->digest,
|
||||
(string)$node->caref,
|
||||
(string)$node->cert_type,
|
||||
$node->extns()
|
||||
);
|
||||
if (!empty($data['crt']) && !empty($data['prv'])) {
|
||||
$node->crt = base64_encode($data['crt']);
|
||||
if ((string)$node->private_key_location == 'local') {
|
||||
/* return only in volatile storage */
|
||||
$node->prv_payload = $data['prv'];
|
||||
} else {
|
||||
$node->prv= base64_encode($data['prv']);
|
||||
}
|
||||
} else {
|
||||
$error = $data['error'] ?? '';
|
||||
}
|
||||
break;
|
||||
case 'external':
|
||||
$data = CertStore::createCert(
|
||||
(string)$node->key_type,
|
||||
(string)$node->lifetime,
|
||||
$node->dn(),
|
||||
(string)$node->digest,
|
||||
false,
|
||||
(string)$node->cert_type,
|
||||
$node->extns()
|
||||
);
|
||||
if (!empty($data['csr'])) {
|
||||
$node->csr = base64_encode($data['csr']);
|
||||
} else {
|
||||
$error = $data['error'] ?? '';
|
||||
}
|
||||
break;
|
||||
case 'import':
|
||||
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_csr':
|
||||
if (CertStore::parseX509((string)$node->crt_payload) === false) {
|
||||
$error = gettext('Invalid X509 certificate provided');
|
||||
} else {
|
||||
$node->crt = base64_encode((string)$node->crt_payload);
|
||||
}
|
||||
break;
|
||||
case 'reissue':
|
||||
$data = CertStore::reIssueCert(
|
||||
(string)$node->key_type,
|
||||
(string)$node->lifetime,
|
||||
$node->dn(),
|
||||
(string)$node->prv_payload,
|
||||
(string)$node->digest,
|
||||
(string)$node->caref,
|
||||
(string)$node->cert_type,
|
||||
$node->extns()
|
||||
);
|
||||
if (!empty($data['crt'])) {
|
||||
$node->crt = base64_encode($data['crt']);
|
||||
} else {
|
||||
$error = $data['error'] ?? '';
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ($error !== false) {
|
||||
throw new UserException($error, "Certificate error");
|
||||
}
|
||||
}
|
||||
|
||||
public function searchAction()
|
||||
|
||||
@ -7,20 +7,22 @@
|
||||
<field>
|
||||
<type>header</type>
|
||||
<label>Key</label>
|
||||
<style>action action_internal action_reissue action_internal</style>
|
||||
<style>action action_internal action_reissue action_internal action_external</style>
|
||||
</field>
|
||||
<field>
|
||||
<id>cert.cert_type</id>
|
||||
<label>Type</label>
|
||||
<type>dropdown</type>
|
||||
<style>selectpicker action action_internal action_reissue</style>
|
||||
</field>
|
||||
<field>
|
||||
<id>cert.private_key_location</id>
|
||||
<label>Private key location</label>
|
||||
<style>selectpicker action action_internal</style>
|
||||
<type>dropdown</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>cert.key</id>
|
||||
<id>cert.key_type</id>
|
||||
<label>Key type</label>
|
||||
<type>dropdown</type>
|
||||
</field>
|
||||
@ -35,10 +37,15 @@
|
||||
<style>selectpicker action action_internal action_reissue</style>
|
||||
<type>dropdown</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>cert.lifetime</id>
|
||||
<label>Lifetime (days)</label>
|
||||
<type>text</type>
|
||||
</field>
|
||||
<field>
|
||||
<type>header</type>
|
||||
<label>General</label>
|
||||
<style>action action_internal action_reissue action_internal</style>
|
||||
<style>action action_internal action_reissue action_internal action_external</style>
|
||||
</field>
|
||||
<field>
|
||||
<id>cert.descr</id>
|
||||
@ -84,12 +91,13 @@
|
||||
<id>cert.ocsp_uri</id>
|
||||
<label>OCSP uri</label>
|
||||
<type>text</type>
|
||||
<style>action action_internal action_reissue</style>
|
||||
</field>
|
||||
<field>
|
||||
<type>header</type>
|
||||
<label>Alternative Names</label>
|
||||
<collapse>true</collapse>
|
||||
<style>action action_internal action_reissue action_internal</style>
|
||||
<style>action action_internal action_reissue action_internal action_external</style>
|
||||
</field>
|
||||
<field>
|
||||
<id>cert.altnames_dns</id>
|
||||
@ -131,6 +139,6 @@
|
||||
<id>cert.csr_payload</id>
|
||||
<label>Certificate signing request</label>
|
||||
<type>textbox</type>
|
||||
<style>selectpicker action action_internal action_reissue action_internal</style>
|
||||
<style>selectpicker action action_import_csr</style>
|
||||
</field>
|
||||
</form>
|
||||
|
||||
@ -184,6 +184,62 @@ class Store
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* create openssl options config including configuration file to use
|
||||
* @param string $keylen_curve rsa key length or elliptic curve name to use
|
||||
* @param string $digest_alg digest algorithm
|
||||
* @param string $x509_extensions openssl section to use
|
||||
* @param array $extns template fragments to replace in openssl.cnf
|
||||
* @return array needed for certificate generation, caller is responsible for removing ['filename'] after use
|
||||
*/
|
||||
private static function _createSSLOptions($keylen_curve, $digest_alg, $x509_extensions = 'usr_cert', $extns = [])
|
||||
{
|
||||
// define temp filename to use for openssl.cnf and add extensions values to it
|
||||
$configFilename = tempnam(sys_get_temp_dir(), 'ssl');
|
||||
|
||||
$template = file_get_contents('/usr/local/etc/ssl/opnsense.cnf');
|
||||
foreach (array_keys($extns) as $extnTag) {
|
||||
$template_extn = $extnTag . ' = ' . str_replace(array("\r", "\n"), '', $extns[$extnTag]);
|
||||
// Overwrite the placeholders for this property
|
||||
$template = str_replace('###OPNsense:' . $extnTag . '###', $template_extn, $template);
|
||||
}
|
||||
file_put_contents($configFilename, $template);
|
||||
|
||||
$args = [
|
||||
'config' => $configFilename,
|
||||
'x509_extensions' => $x509_extensions,
|
||||
'digest_alg' => $digest_alg,
|
||||
'encrypt_key' => false
|
||||
];
|
||||
if (is_numeric($keylen_curve)) {
|
||||
$args['private_key_type'] = OPENSSL_KEYTYPE_RSA;
|
||||
$args['private_key_bits'] = (int)$keylen_curve;
|
||||
} else {
|
||||
$args['private_key_type'] = OPENSSL_KEYTYPE_EC;
|
||||
$args['curve_name'] = $keylen_curve;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array to add 'error' result to when openssl_error_string returns data
|
||||
*/
|
||||
private static function _addSSLErrors(&$arr)
|
||||
{
|
||||
$data = '';
|
||||
while ($ssl_err = openssl_error_string()) {
|
||||
$data .= " " . $ssl_err;
|
||||
}
|
||||
if (!empty(trim($data))) {
|
||||
if (!isset($arr['error'])) {
|
||||
$arr['error'] = $data;
|
||||
} else {
|
||||
$arr['error'] .= ("\n" . $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a certificate, when signed by a CA, make sure to serialize the config after doing so,
|
||||
* it's the callers responsibility to update the serial number administration of the supplied CA.
|
||||
@ -208,39 +264,56 @@ class Store
|
||||
$extns = []
|
||||
) {
|
||||
$old_err_level = error_reporting(0); /* prevent openssl error from going to stderr/stdout */
|
||||
// handle parameters which can only be set via the configuration file
|
||||
$config_filename = create_temp_openssl_config($extns);
|
||||
$args = [
|
||||
'config' => $config_filename,
|
||||
'x509_extensions' => $x509_extensions,
|
||||
'digest_alg' => $digest_alg,
|
||||
'encrypt_key' => false
|
||||
];
|
||||
if (is_numeric($keylen_curve)) {
|
||||
$args['private_key_type'] = OPENSSL_KEYTYPE_RSA;
|
||||
$args['private_key_bits'] = (int)$keylen_curve;
|
||||
} else {
|
||||
$args['private_key_type'] = OPENSSL_KEYTYPE_EC;
|
||||
$args['curve_name'] = $keylen_curve;
|
||||
}
|
||||
|
||||
$tmp = self::_signCert($csr, $caref, $lifetime, $args);
|
||||
if (!empty($tmp['crt'])) {
|
||||
$result = ['crt' => $tmp['crt']];
|
||||
} else {
|
||||
$result = $tmp;
|
||||
}
|
||||
$args = self::_createSSLOptions($keylen_curve, $digest_alg, $x509_extensions, $extns);
|
||||
$result = self::_signCert($csr, $caref, $lifetime, $args);
|
||||
|
||||
/* something went wrong, return openssl error */
|
||||
if (empty($result) || !empty($result['error'])) {
|
||||
$result['error'] = $result['error'] ?? '';
|
||||
while ($ssl_err = openssl_error_string()) {
|
||||
$result['error'] .= " " . $ssl_err;
|
||||
}
|
||||
}
|
||||
self::_addSSLErrors($result);
|
||||
|
||||
// remove tempfile (template)
|
||||
@unlink($config_filename);
|
||||
@unlink($args['filename']);
|
||||
error_reporting($old_err_level);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* re-issue a certificate, when signed by a CA, make sure to serialize the config after doing so,
|
||||
* it's the callers responsibility to update the serial number administration of the supplied CA.
|
||||
* A call to \OPNsense\Core\Config::getInstance()->save(); would persist the new serial.
|
||||
*
|
||||
* @param string $keylen_curve rsa key length or elliptic curve name to use
|
||||
* @param int $lifetime in number of days
|
||||
* @param array $dn subject to use
|
||||
* @param string $prv private key
|
||||
* @param string $digest_alg digest algorithm
|
||||
* @param string $caref key to certificate authority
|
||||
* @param string $x509_extensions openssl section to use
|
||||
* @param array $extns template fragments to replace in openssl.cnf
|
||||
* @return array containing generated certificate or returned errors
|
||||
*/
|
||||
public static function reIssueCert(
|
||||
$keylen_curve,
|
||||
$lifetime,
|
||||
$dn,
|
||||
$prv,
|
||||
$digest_alg,
|
||||
$caref,
|
||||
$x509_extensions = 'usr_cert',
|
||||
$extns = []
|
||||
) {
|
||||
$old_err_level = error_reporting(0); /* prevent openssl error from going to stderr/stdout */
|
||||
|
||||
$args = self::_createSSLOptions($keylen_curve, $digest_alg, $x509_extensions, $extns);
|
||||
$csr = openssl_csr_new($dn, $prv, $args);
|
||||
if ($csr !== false) {
|
||||
$result = self::_signCert($csr, $caref, $lifetime, $args);
|
||||
} else {
|
||||
$result = [];
|
||||
}
|
||||
self::_addSSLErrors($result);
|
||||
|
||||
// remove tempfile (template)
|
||||
@unlink($args['filename']);
|
||||
error_reporting($old_err_level);
|
||||
return $result;
|
||||
}
|
||||
@ -270,22 +343,7 @@ class Store
|
||||
) {
|
||||
$result = [];
|
||||
$old_err_level = error_reporting(0); /* prevent openssl error from going to stderr/stdout */
|
||||
|
||||
// handle parameters which can only be set via the configuration file
|
||||
$config_filename = create_temp_openssl_config($extns);
|
||||
$args = [
|
||||
'config' => $config_filename,
|
||||
'x509_extensions' => $x509_extensions,
|
||||
'digest_alg' => $digest_alg,
|
||||
'encrypt_key' => false
|
||||
];
|
||||
if (is_numeric($keylen_curve)) {
|
||||
$args['private_key_type'] = OPENSSL_KEYTYPE_RSA;
|
||||
$args['private_key_bits'] = (int)$keylen_curve;
|
||||
} else {
|
||||
$args['private_key_type'] = OPENSSL_KEYTYPE_EC;
|
||||
$args['curve_name'] = $keylen_curve;
|
||||
}
|
||||
$args = self::_createSSLOptions($keylen_curve, $digest_alg, $x509_extensions, $extns);
|
||||
|
||||
// generate a new key pair
|
||||
$res_key = openssl_pkey_new($args);
|
||||
@ -293,7 +351,7 @@ class Store
|
||||
$res_csr = openssl_csr_new($dn, $res_key, $args);
|
||||
if ($res_csr !== false) {
|
||||
if ($caref !== false) {
|
||||
$tmp = self::_signCert($res_csr, $caref !== null ? $caref : $res_key, $lifetime, $args);
|
||||
$tmp = self::_signCert($res_csr, !empty($caref) ? $caref : $res_key, $lifetime, $args);
|
||||
if (!empty($tmp['crt'])) {
|
||||
$result = ['caref' => $caref, 'crt' => $tmp['crt'], 'prv' => $str_key];
|
||||
} else {
|
||||
@ -307,16 +365,10 @@ class Store
|
||||
}
|
||||
}
|
||||
}
|
||||
/* something went wrong, return openssl error */
|
||||
if (empty($result) || !empty($result['error'])) {
|
||||
$result['error'] = $result['error'] ?? '';
|
||||
while ($ssl_err = openssl_error_string()) {
|
||||
$result['error'] .= " " . $ssl_err;
|
||||
}
|
||||
}
|
||||
self::_addSSLErrors($result);
|
||||
|
||||
// remove tempfile (template)
|
||||
@unlink($config_filename);
|
||||
@unlink($args['filename']);
|
||||
error_reporting($old_err_level);
|
||||
return $result;
|
||||
}
|
||||
@ -412,26 +464,4 @@ class Store
|
||||
}
|
||||
return implode("\n", $chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a temporary config file, to help with calls that require properties that can only be set via the config file.
|
||||
*
|
||||
* @param $dn
|
||||
* @return string The name of the temporary config file.
|
||||
*/
|
||||
public static function createTempOpenSSLconfig($extns = [])
|
||||
{
|
||||
// define temp filename to use for openssl.cnf and add extensions values to it
|
||||
$configFilename = tempnam(sys_get_temp_dir(), 'ssl');
|
||||
|
||||
$template = file_get_contents('/usr/local/etc/ssl/opnsense.cnf');
|
||||
|
||||
foreach (array_keys($extns) as $extnTag) {
|
||||
$template_extn = $extnTag . ' = ' . str_replace(array("\r", "\n"), '', $extns[$extnTag]);
|
||||
// Overwrite the placeholders for this property
|
||||
$template = str_replace('###OPNsense:' . $extnTag . '###', $template_extn, $template);
|
||||
}
|
||||
file_put_contents($configFilename, $template);
|
||||
return $configFilename;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
<reissue>Reissue and replace certificate (does not restart services)</reissue>
|
||||
</OptionValues>
|
||||
</action>
|
||||
<key type="OptionField" volatile="true">
|
||||
<key_type type="OptionField" volatile="true">
|
||||
<Required>Y</Required>
|
||||
<Default>2048</Default>
|
||||
<OptionValues>
|
||||
@ -38,7 +38,7 @@
|
||||
<secp384r1>Elliptic Curve secp384r1</secp384r1>
|
||||
<secp521r1>Elliptic Curve secp521r1</secp521r1>
|
||||
</OptionValues>
|
||||
</key>
|
||||
</key_type>
|
||||
<digest type="OptionField" volatile="true">
|
||||
<Required>Y</Required>
|
||||
<Default>sha256</Default>
|
||||
@ -60,9 +60,13 @@
|
||||
<v3_ca>Certificate Authority</v3_ca>
|
||||
</OptionValues>
|
||||
</cert_type>
|
||||
<lifetime type="IntegerField" volatile="true">
|
||||
<Required>Y</Required>
|
||||
<Default>397</Default>
|
||||
</lifetime>
|
||||
<private_key_location type="OptionField" volatile="true">
|
||||
<Required>Y</Required>
|
||||
<Default>usr_cert</Default>
|
||||
<Default>firewall</Default>
|
||||
<OptionValues>
|
||||
<firewall>Save on this firewall</firewall>
|
||||
<local>Download and do not save</local>
|
||||
@ -85,7 +89,9 @@
|
||||
<Default>NL</Default>
|
||||
<Required>Y</Required>
|
||||
</country>
|
||||
<email type="EmailField"/>
|
||||
<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>
|
||||
|
||||
@ -115,11 +115,15 @@
|
||||
target.show();
|
||||
}
|
||||
});
|
||||
/* expand PEM section */
|
||||
/* 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user