mirror of
https://github.com/pfsense/pfsense.git
synced 2025-10-26 11:38:35 +00:00
GUI improvements for ECDSA certificate handling
* Make central functions to check and test ECDSA compatibility. Issue #9843 * Filter incompatible certificates from being offered for the GUI or Captive Portal. Implements #9897 * Do the same for IPsec, which implements #4991 * Add a check for key type when generating ipsec.secrets to allow ECDSA certs to work in IPsec for issue #4991 Note that as of this moment, the following curves are known to be compatible: HTTPS (GUI, Captive Portal): prime256v1, secp384r1 IPsec: prime256v1, secp384r1, secp521r1 Results may vary in other areas which are not yet well-tested, and in packages.
This commit is contained in:
parent
c3cda38e0a
commit
cffcf9bfaa
@ -2275,4 +2275,144 @@ function ca_setup_trust_store() {
|
||||
}
|
||||
}
|
||||
|
||||
/****f* certs/cert_get_pkey_curve
|
||||
* NAME
|
||||
* cert_get_pkey_curve - Get the ECDSA curve of a private key
|
||||
* INPUTS
|
||||
* $pkey : The private key to check
|
||||
* $decode: true: base64 decode the string before use, false to use as-is.
|
||||
* RESULT
|
||||
* false if the private key is not ECDSA or the private key is not present.
|
||||
* Otherwise, the name of the ECDSA curve used for the private key.
|
||||
******/
|
||||
|
||||
function cert_get_pkey_curve($pkey, $decode = true) {
|
||||
if ($decode) {
|
||||
$pkey = base64_decode($pkey);
|
||||
}
|
||||
|
||||
/* Attempt to read the private key, and if successful, its details. */
|
||||
$res_key = openssl_pkey_get_private($pkey);
|
||||
if ($res_key) {
|
||||
$key_details = openssl_pkey_get_details($res_key);
|
||||
/* If this is an EC key, and the curve name is not empty, return
|
||||
* that curve name. */
|
||||
if (($key_details['type'] == OPENSSL_KEYTYPE_EC) &&
|
||||
(!empty($key_details['ec']['curve_name']))) {
|
||||
return $key_details['ec']['curve_name'];
|
||||
}
|
||||
}
|
||||
|
||||
/* Either the private key could not be read, or this is not an EC certificate. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Array containing ECDSA curve names allowed in certain contexts. For instance,
|
||||
* HTTPS servers and web browsers only support specific curves in TLSv1.3. */
|
||||
global $cert_curve_compatible;
|
||||
$cert_curve_compatible = array(
|
||||
'HTTPS' => array('prime256v1', 'secp384r1'), /* Per TLSv1.3 spec and Mozilla compatibility list */
|
||||
'IPsec' => array('prime256v1', 'secp384r1', 'secp521r1') /* Per strongSwan docs/issues */
|
||||
);
|
||||
|
||||
/****f* certs/cert_build_curve_list
|
||||
* NAME
|
||||
* cert_build_curve_list - Build an option list of ECDSA curves with notations
|
||||
* about known compatible uses.
|
||||
* INPUTS
|
||||
* None
|
||||
* RESULT
|
||||
* Returns an option list of OpenSSL EC names with added notes. This can be
|
||||
* used directly in form option selection lists.
|
||||
******/
|
||||
|
||||
function cert_build_curve_list() {
|
||||
global $cert_curve_compatible;
|
||||
/* Get the default list of curve names */
|
||||
$openssl_ecnames = openssl_get_curve_names();
|
||||
/* Turn this into a hashed array where key==value */
|
||||
$curvelist = array_combine($openssl_ecnames, $openssl_ecnames);
|
||||
/* Check all known compatible curves and note matches */
|
||||
foreach ($cert_curve_compatible as $consumer => $validcurves) {
|
||||
/* $consumer will be a name like HTTPS or IPsec
|
||||
* $validcurves will be an array of curves compatible with the consumer */
|
||||
foreach ($validcurves as $vc) {
|
||||
/* If the valid curve is present in the curve list, add
|
||||
* a note with the consumer name to the value visible to
|
||||
* the user. */
|
||||
if (array_key_exists($vc, $curvelist)) {
|
||||
$curvelist[$vc] .= " [{$consumer}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
return $curvelist;
|
||||
}
|
||||
|
||||
/****f* certs/cert_check_pkey_compatibility
|
||||
* NAME
|
||||
* cert_check_pkey_compatibility - Check a private key to see if it can be
|
||||
* used in a specific compatible context.
|
||||
* INPUTS
|
||||
* $pkey : The private key to check
|
||||
* $consumer: The consumer name used to validate the curve. See the contents
|
||||
* of $cert_curve_compatible for details.
|
||||
* RESULT
|
||||
* true if the private key may be used in requested area, or if there are no
|
||||
* restrictions.
|
||||
* false if the private key cannot be used.
|
||||
******/
|
||||
|
||||
function cert_check_pkey_compatibility($pkey, $consumer) {
|
||||
global $cert_curve_compatible;
|
||||
|
||||
/* Read the curve name from the key */
|
||||
$curve = cert_get_pkey_curve($pkey);
|
||||
/* Return true if any of the following conditions are met:
|
||||
* * This is not an EC key
|
||||
* * The private key cannot be read
|
||||
* * There are no restrictions
|
||||
* * The requested curve is compatible */
|
||||
return (($curve === false) ||
|
||||
!array_key_exists($consumer, $cert_curve_compatible) ||
|
||||
in_array($curve, $cert_curve_compatible[$consumer]));
|
||||
}
|
||||
|
||||
/****f* certs/cert_build_list
|
||||
* NAME
|
||||
* cert_build_list - Build an option list of cert or CA entries, checked
|
||||
* against a specific consumer name.
|
||||
* INPUTS
|
||||
* $type : 'ca' for certificate authority entries, 'cert' for certificates.
|
||||
* $consumer: The consumer name used to filter certificates out of the result.
|
||||
* See the contents of $cert_curve_compatible for details.
|
||||
* RESULT
|
||||
* Returns an option list of entries with incompatible entries removed. This
|
||||
* can be used directly in form option selection lists.
|
||||
* NOTES
|
||||
* This can be expanded in the future to allow for other types of restrictions.
|
||||
******/
|
||||
|
||||
function cert_build_list($type = 'cert', $consumer = '') {
|
||||
global $config;
|
||||
|
||||
/* Ensure that $type is valid */
|
||||
if (!in_array($type, array('ca', 'cert'))) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/* Initialize arrays */
|
||||
init_config_arr(array($type));
|
||||
$list = array();
|
||||
|
||||
/* Create a hashed array with the certificate refid as the key and
|
||||
* descriptive name as the value. Exclude incompatible certificates. */
|
||||
foreach ($config[$type] as $cert) {
|
||||
if (cert_check_pkey_compatibility($cert['prv'], $consumer)) {
|
||||
$list[$cert['refid']] = $cert['descr'];
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
require_once("ipsec.inc");
|
||||
require_once("filter.inc");
|
||||
require_once("auth.inc");
|
||||
require_once("certs.inc");
|
||||
|
||||
function vpn_update_daemon_loglevel($category, $level) {
|
||||
global $ipsec_log_cats, $ipsec_log_sevs;
|
||||
@ -691,7 +692,9 @@ EOD;
|
||||
@chmod($ph1certfile, 0600);
|
||||
|
||||
/* XXX" Traffic selectors? */
|
||||
$pskconf .= " : RSA {$ph1keyfile}\n";
|
||||
$pskconf .= " : ";
|
||||
$pskconf .= (cert_get_pkey_curve($cert['prv']) === false) ? "RSA" : "ECDSA";
|
||||
$pskconf .= " {$ph1keyfile}\n";
|
||||
} else {
|
||||
list ($myid_type, $myid_data) = ipsec_find_id($ph1ent, 'local');
|
||||
list ($peerid_type, $peerid_data) = ipsec_find_id($ph1ent, 'peer', $rgmap);
|
||||
|
||||
@ -469,18 +469,6 @@ if ($_POST['save']) {
|
||||
}
|
||||
}
|
||||
|
||||
function build_cert_list() {
|
||||
global $a_cert;
|
||||
|
||||
$list = array();
|
||||
|
||||
foreach ($a_cert as $cert) {
|
||||
$list[$cert['refid']] = $cert['descr'];
|
||||
}
|
||||
|
||||
return($list);
|
||||
}
|
||||
|
||||
function build_authserver_list() {
|
||||
|
||||
$authlist = auth_get_authserver_list();
|
||||
@ -1106,8 +1094,8 @@ $section->addInput(new Form_Select(
|
||||
'certref',
|
||||
'*SSL Certificate',
|
||||
$pconfig['certref'],
|
||||
build_cert_list()
|
||||
))->setHelp('If no certificates are defined, one may be defined here: %1$sSystem > Cert. Manager%2$s', '<a href="system_certmanager.php">', '</a>');
|
||||
cert_build_list('cert', 'HTTPS')
|
||||
))->setHelp('Certificates known to be incompatible with use for HTTPS are not included in this list. If no certificates are defined, one may be defined here: %1$sSystem > Cert. Manager%2$s', '<a href="system_certmanager.php">', '</a>');
|
||||
|
||||
$section->addInput(new Form_Checkbox(
|
||||
'nohttpsforwards',
|
||||
|
||||
@ -428,17 +428,12 @@ if (!$certs_available) {
|
||||
|
||||
$section->add($group);
|
||||
|
||||
$values = array();
|
||||
foreach ($a_cert as $cert) {
|
||||
$values[ $cert['refid'] ] = $cert['descr'];
|
||||
}
|
||||
|
||||
$section->addInput($input = new Form_Select(
|
||||
'ssl-certref',
|
||||
'SSL Certificate',
|
||||
$pconfig['ssl-certref'],
|
||||
$values
|
||||
));
|
||||
cert_build_list('cert', 'HTTPS')
|
||||
))->setHelp('Certificates known to be incompatible with use for HTTPS are not included in this list.');
|
||||
|
||||
$section->addInput(new Form_Input(
|
||||
'webguiport',
|
||||
|
||||
@ -44,7 +44,7 @@ global $openssl_digest_algs;
|
||||
global $cert_strict_values;
|
||||
$max_lifetime = cert_get_max_lifetime();
|
||||
$default_lifetime = min(3650, $max_lifetime);
|
||||
$openssl_ecnames = openssl_get_curve_names();
|
||||
$openssl_ecnames = cert_build_curve_list();
|
||||
$class = "success";
|
||||
|
||||
init_config_arr(array('ca'));
|
||||
@ -211,7 +211,7 @@ if ($_POST['save']) {
|
||||
if (!in_array($_POST["keylen"], $ca_keylens)) {
|
||||
array_push($input_errors, gettext("Please select a valid Key Length."));
|
||||
}
|
||||
if (!in_array($_POST["ecname"], $openssl_ecnames)) {
|
||||
if (!in_array($_POST["ecname"], array_keys($openssl_ecnames))) {
|
||||
array_push($input_errors, gettext("Please select a valid Elliptic Curve Name."));
|
||||
}
|
||||
if (!in_array($_POST["digest_alg"], $openssl_digest_algs)) {
|
||||
@ -682,8 +682,8 @@ $group->add(new Form_Select(
|
||||
'ecname',
|
||||
null,
|
||||
$pconfig['ecname'],
|
||||
array_combine($openssl_ecnames, $openssl_ecnames)
|
||||
));
|
||||
$openssl_ecnames
|
||||
))->setHelp('Curves may not be compatible with all uses. Known compatible curve uses are denoted in brackets.');
|
||||
$section->add($group);
|
||||
|
||||
$section->addInput(new Form_Select(
|
||||
|
||||
@ -51,7 +51,7 @@ global $openssl_digest_algs;
|
||||
global $cert_strict_values;
|
||||
$max_lifetime = cert_get_max_lifetime();
|
||||
$default_lifetime = min(3650, $max_lifetime);
|
||||
$openssl_ecnames = openssl_get_curve_names();
|
||||
$openssl_ecnames = cert_build_curve_list();
|
||||
$class = "success";
|
||||
|
||||
if (isset($_REQUEST['userid']) && is_numericint($_REQUEST['userid'])) {
|
||||
@ -350,7 +350,7 @@ if ($_POST['save'] == gettext("Save")) {
|
||||
if (isset($_POST["keylen"]) && !in_array($_POST["keylen"], $cert_keylens)) {
|
||||
$input_errors[] = gettext("Please select a valid Key Length.");
|
||||
}
|
||||
if (isset($_POST["ecname"]) && !in_array($_POST["ecname"], $openssl_ecnames)) {
|
||||
if (isset($_POST["ecname"]) && !in_array($_POST["ecname"], array_keys($openssl_ecnames))) {
|
||||
$input_errors[] = gettext("Please select a valid Elliptic Curve Name.");
|
||||
}
|
||||
if (!in_array($_POST["digest_alg"], $openssl_digest_algs)) {
|
||||
@ -364,7 +364,7 @@ if ($_POST['save'] == gettext("Save")) {
|
||||
if (isset($_POST["csr_keylen"]) && !in_array($_POST["csr_keylen"], $cert_keylens)) {
|
||||
$input_errors[] = gettext("Please select a valid Key Length.");
|
||||
}
|
||||
if (isset($_POST["csr_ecname"]) && !in_array($_POST["csr_ecname"], $openssl_ecnames)) {
|
||||
if (isset($_POST["csr_ecname"]) && !in_array($_POST["csr_ecname"], array_keys($openssl_ecnames))) {
|
||||
$input_errors[] = gettext("Please select a valid Elliptic Curve Name.");
|
||||
}
|
||||
if (!in_array($_POST["csr_digest_alg"], $openssl_digest_algs)) {
|
||||
@ -842,8 +842,8 @@ if (in_array($act, array('new', 'edit')) || (($_POST['save'] == gettext("Save"))
|
||||
'ecname',
|
||||
null,
|
||||
$pconfig['ecname'],
|
||||
array_combine($openssl_ecnames, $openssl_ecnames)
|
||||
));
|
||||
$openssl_ecnames
|
||||
))->setHelp('Curves may not be compatible with all uses. Known compatible curve uses are denoted in brackets.');
|
||||
$section->add($group);
|
||||
|
||||
$section->addInput(new Form_Select(
|
||||
@ -946,7 +946,7 @@ if (in_array($act, array('new', 'edit')) || (($_POST['save'] == gettext("Save"))
|
||||
'csr_ecname',
|
||||
null,
|
||||
$pconfig['csr_ecname'],
|
||||
array_combine($openssl_ecnames, $openssl_ecnames)
|
||||
$openssl_ecnames
|
||||
));
|
||||
$section->add($group);
|
||||
|
||||
|
||||
@ -451,6 +451,13 @@ if ($_POST['save']) {
|
||||
$input_errors[] = gettext("Cannot disable a Phase 1 with a child Phase 2 while the interface is assigned. Remove the interface assignment before disabling this P2.");
|
||||
}
|
||||
|
||||
if (!empty($pconfig['certref'])) {
|
||||
$errchkcert =& lookup_cert($pconfig['certref']);
|
||||
if (is_array($errchkcert) && !cert_check_pkey_compatibility($errchkcert['prv'], 'IPsec')) {
|
||||
$input_errors[] = gettext("The selected ECDSA certificate does not use a curve compatible with IKEv2");
|
||||
}
|
||||
}
|
||||
|
||||
if (!$input_errors) {
|
||||
$ph1ent['ikeid'] = $pconfig['ikeid'];
|
||||
$ph1ent['iketype'] = $pconfig['iketype'];
|
||||
@ -623,34 +630,6 @@ function build_peerid_list() {
|
||||
return($list);
|
||||
}
|
||||
|
||||
function build_cert_list() {
|
||||
global $config;
|
||||
|
||||
$list = array();
|
||||
|
||||
if (is_array($config['cert'])) {
|
||||
foreach ($config['cert'] as $cert) {
|
||||
$list[$cert['refid']] = $cert['descr'];
|
||||
}
|
||||
}
|
||||
|
||||
return($list);
|
||||
}
|
||||
|
||||
function build_ca_list() {
|
||||
global $config;
|
||||
|
||||
$list = array();
|
||||
|
||||
if (is_array($config['ca'])) {
|
||||
foreach ($config['ca'] as $ca) {
|
||||
$list[$ca['refid']] = $ca['descr'];
|
||||
}
|
||||
}
|
||||
|
||||
return($list);
|
||||
}
|
||||
|
||||
function build_eal_list() {
|
||||
global $p1_ealgos;
|
||||
|
||||
@ -806,14 +785,14 @@ $section->addInput(new Form_Select(
|
||||
'certref',
|
||||
'*My Certificate',
|
||||
$pconfig['certref'],
|
||||
build_cert_list()
|
||||
cert_build_list('cert', 'IPsec')
|
||||
))->setHelp('Select a certificate previously configured in the Certificate Manager.');
|
||||
|
||||
$section->addInput(new Form_Select(
|
||||
'caref',
|
||||
'*Peer Certificate Authority',
|
||||
$pconfig['caref'],
|
||||
build_ca_list()
|
||||
cert_build_list('ca', 'IPsec')
|
||||
))->setHelp('Select a certificate authority previously configured in the Certificate Manager.');
|
||||
|
||||
$form->add($section);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user