diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/dialogCert.xml b/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/dialogCert.xml
index 419d20a98..d4decdfd9 100644
--- a/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/dialogCert.xml
+++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/dialogCert.xml
@@ -6,12 +6,17 @@
header
-
+
- cert.descr
-
- text
+ cert.cert_type
+
+ dropdown
+
+
+ cert.private_key_location
+
+ dropdown
cert.key
@@ -28,6 +33,15 @@
dropdown
+
+ header
+
+
+
+ cert.descr
+
+ text
+
cert.country
diff --git a/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php b/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php
index 67028549a..73a52a6fa 100644
--- a/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php
+++ b/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php
@@ -111,6 +111,161 @@ class Store
return false;
}
+ /**
+ * Create a new 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 $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 createCert(
+ $keylen_curve,
+ $lifetime,
+ $dn,
+ $digest_alg,
+ $caref = null,
+ $x509_extensions = 'usr_cert',
+ $extns = []
+ ) {
+ $result = [];
+ $ca = null;
+ $ca_res_crt = null;
+ $old_err_level = error_reporting(0); /* prevent openssl error from going to stderr/stdout */
+ if ($caref !== null) {
+ $ca = self::getCA($caref);
+ if ($ca == null || empty((string)$ca->prv)) {
+ $result = ['error' => 'missing CA key'];
+ }
+ $ca_res_crt = openssl_x509_read(base64_decode($ca->crt));
+ $ca_res_key = openssl_pkey_get_private(array(0 => base64_decode($ca->prv), 1 => ""));
+ if (!$ca_res_key) {
+ $result = ['error' => 'invalid CA'];
+ }
+ }
+ if (!empty($result)) {
+ error_reporting($old_err_level);
+ return $result;
+ }
+
+ // 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;
+ }
+
+ // generate a new key pair
+ $res_key = openssl_pkey_new($args);
+ if ($res_key !== false) {
+ $res_csr = openssl_csr_new($dn, $res_key, $args);
+ if ($res_csr !== false) {
+ // self sign the certificate
+ $res_crt = openssl_csr_sign(
+ $res_csr,
+ $ca_res_crt,
+ $ca_res_key ?? $res_key,
+ $lifetime,
+ $args,
+ $ca_serial
+ );
+ if (openssl_pkey_export($res_key, $str_key) && openssl_x509_export($res_crt, $str_crt)) {
+ $result = ['caref' => $caref, 'crt' => $str_crt, 'prv' => $str_key];
+ if ($ca !== null) {
+ $ca->serial = (int)$ca->serial + 1;
+ }
+ }
+ }
+ }
+ /* something went wrong, return openssl error */
+ if (empty($result)){
+ $result['error'] = '';
+ while ($ssl_err = openssl_error_string()) {
+ $result['error'] .= " " . $ssl_err;
+ }
+ }
+
+ // remove tempfile (template)
+ @unlink($config_filename);
+ error_reporting($old_err_level);
+ return $result;
+ }
+
+ /**
+ * Extract certificate info into easy to use flattened chunks
+ * @return array|bool
+ */
+ public static function parseX509($cert)
+ {
+ $issue_map = [
+ 'L' => 'city',
+ 'ST' => 'state',
+ 'O' => 'organization',
+ 'C' => 'country',
+ 'emailAddress' => 'email',
+ 'CN' => 'commonname'
+ ];
+ $altname_map = [
+ 'IP Address' => 'altnames_ip',
+ 'DNS' => 'altnames_dns',
+ 'email' => 'altnames_email',
+ 'URI' => 'altnames_uri',
+ ];
+
+ $crt = @openssl_x509_parse($cert);
+ if ($crt !== null) {
+ $result = [];
+ // valid from/to and name of this cert
+ $result['valid_from'] = $crt['validFrom_time_t'];
+ $result['valid_to'] = $crt['validTo_time_t'];
+ $result['name'] = $crt['name'];
+ foreach ($issue_map as $key => $target) {
+ if (!empty($crt['issuer'][$key])) {
+ $result[$target] = $crt['issuer'][$key];
+ }
+ }
+ // OCSP URI
+ if (!empty($crt['extensions']) && !empty($crt['extensions']['authorityInfoAccess'])) {
+ foreach (explode("\n", $crt['extensions']['authorityInfoAccess']) as $line) {
+ if (str_starts_with($line, 'OCSP - URI')) {
+ $result['ocsp_uri'] = explode(":", $line, 2)[1];
+ }
+ }
+ }
+ // Altnames
+ if (!empty($crt['extensions']) && !empty($crt['extensions']['subjectAltName'])) {
+ $altnames = [];
+ foreach (explode(',', trim($crt['extensions']['subjectAltName'])) as $altname) {
+ $parts = explode(':', trim($altname), 2);
+ $target = $altname_map[$parts[0]];
+ if (isset($altnames[$target])) {
+ $altnames[$target] = [];
+ }
+ $altnames[$target][] = $parts[1];
+ }
+ foreach ($altnames as $key => $values) {
+ $result[$target] = implode('\n', $values);
+ }
+ }
+ return $result;
+ }
+ return false;
+ }
/**
* Extract certificate chain
diff --git a/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.xml b/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.xml
index 2992a1742..41a008052 100644
--- a/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.xml
+++ b/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.xml
@@ -25,17 +25,17 @@
Y
- RSA-2048
+ 2048
- RSA-512
- RSA-1024
- RSA-2048
- RSA-3072
- RSA-4096
- RSA-8192
- Elliptic Curve prime256v1
- Elliptic Curve secp384r1
- Elliptic Curve secp521r1
+ RSA-512
+ RSA-1024
+ RSA-2048
+ RSA-3072
+ RSA-4096
+ RSA-8192
+ Elliptic Curve prime256v1
+ Elliptic Curve secp384r1
+ Elliptic Curve secp521r1
@@ -49,6 +49,24 @@
SHA512
+
+ Y
+ usr_cert
+
+ Client Certificate
+ Server Certificate
+ Combined Client/Server Certificate
+ Certificate Authority
+
+
+
+ Y
+ usr_cert
+
+ Save on this firewall
+ Download and do not save
+
+
/^[^\x00-\x08\x0b\x0c\x0e-\x1f\n]*$/
diff --git a/src/opnsense/mvc/app/models/OPNsense/Trust/FieldTypes/CertificatesField.php b/src/opnsense/mvc/app/models/OPNsense/Trust/FieldTypes/CertificatesField.php
index d9f4f0f61..80bd8f463 100644
--- a/src/opnsense/mvc/app/models/OPNsense/Trust/FieldTypes/CertificatesField.php
+++ b/src/opnsense/mvc/app/models/OPNsense/Trust/FieldTypes/CertificatesField.php
@@ -31,59 +31,19 @@ namespace OPNsense\Trust\FieldTypes;
use OPNsense\Base\FieldTypes\ArrayField;
use OPNsense\Base\FieldTypes\TextField;
+
class CertificatesField extends ArrayField
{
protected function actionPostLoadingEvent()
{
- $issue_map = [
- 'L' => 'city',
- 'ST' => 'state',
- 'O' => 'organization',
- 'C' => 'country',
- 'emailAddress' => 'email',
- 'CN' => 'commonname'
- ];
- $altname_map = [
- 'IP Address' => 'altnames_ip',
- 'DNS' => 'altnames_dns',
- 'email' => 'altnames_email',
- 'URI' => 'altnames_uri',
- ];
foreach ($this->internalChildnodes as $node) {
$cert_data = base64_decode($node->crt);
if (!empty($cert_data)) {
- $crt = @openssl_x509_parse($cert_data);
- if ($crt !== null) {
- // valid from/to and name of this cert
- $node->valid_from = $crt['validFrom_time_t'];
- $node->valid_to = $crt['validTo_time_t'];
- $node->name = $crt['name'];
- foreach ($issue_map as $key => $target) {
- if (!empty($crt['issuer'][$key])) {
- $node->$target = $crt['issuer'][$key];
- }
- }
- // OCSP URI
- if (!empty($crt['extensions']) && !empty($crt['extensions']['authorityInfoAccess'])) {
- foreach (explode("\n", $crt['extensions']['authorityInfoAccess']) as $line) {
- if (str_starts_with($line, 'OCSP - URI')) {
- $node->ocsp_uri = explode(":", $line, 2)[1];
- }
- }
- }
- // Altnames
- if (!empty($crt['extensions']) && !empty($crt['extensions']['subjectAltName'])) {
- $altnames = [];
- foreach (explode(',', trim($crt['extensions']['subjectAltName'])) as $altname) {
- $parts = explode(':', trim($altname), 2);
- $target = $altname_map[$parts[0]];
- if (isset($altnames[$target])) {
- $altnames[$target] = [];
- }
- $altnames[$target][] = $parts[1];
- }
- foreach ($altnames as $key => $values) {
- $node->$target = implode('\n', $values);
+ $payload = \OPNsense\Trust\Store::parseX509($cert_data);
+ if ($payload !== false) {
+ foreach ($payload as $key => $value) {
+ if (isset($node->$key)) {
+ $node->$key = $value;
}
}
}