From 282b6c666a2f95a51a4b46d89fa80357d2ebccb2 Mon Sep 17 00:00:00 2001 From: jim-p Date: Wed, 5 Jul 2017 16:41:38 -0400 Subject: [PATCH] Add the ability to set certificate type and SAN attributes in a CSR. Ticket #7527 TODO: They are not carried over after signing in the GUI --- src/etc/inc/certs.inc | 26 ++++- src/usr/local/share/pfSense/ssl/openssl.cnf | 101 ++++++++++---------- src/usr/local/www/system_certmanager.php | 70 +++++++++++++- 3 files changed, 139 insertions(+), 58 deletions(-) diff --git a/src/etc/inc/certs.inc b/src/etc/inc/certs.inc index 9e85177a44..d568fa919a 100644 --- a/src/etc/inc/certs.inc +++ b/src/etc/inc/certs.inc @@ -401,10 +401,32 @@ function cert_create(& $cert, $caref, $keylen, $lifetime, $dn, $type = "user", $ return true; } -function csr_generate(& $cert, $keylen, $dn, $digest_alg = "sha256") { +function csr_generate(& $cert, $keylen, $dn, $type = "user", $digest_alg = "sha256") { + + switch ($type) { + case "ca": + $cert_type = "v3_ca"; + break; + case "server": + case "self-signed": + $cert_type = "server"; + break; + default: + $cert_type = "usr_cert"; + break; + } + + // in case of using Subject Alternative Names use other sections (with postfix '_san') + // pass subjectAltName over environment variable 'SAN' + if ($dn['subjectAltName']) { + putenv("SAN={$dn['subjectAltName']}"); // subjectAltName can be set _only_ via configuration file + $cert_type .= '_san'; + unset($dn['subjectAltName']); + } $args = array( - "x509_extensions" => "v3_req", + "x509_extensions" => $cert_type, + "req_extensions" => "req_{$cert_type}", "digest_alg" => $digest_alg, "private_key_bits" => (int)$keylen, "private_key_type" => OPENSSL_KEYTYPE_RSA, diff --git a/src/usr/local/share/pfSense/ssl/openssl.cnf b/src/usr/local/share/pfSense/ssl/openssl.cnf index 8fd311a422..a353e2f845 100644 --- a/src/usr/local/share/pfSense/ssl/openssl.cnf +++ b/src/usr/local/share/pfSense/ssl/openssl.cnf @@ -168,68 +168,22 @@ unstructuredName = An optional company name [ usr_cert ] -# These extensions are added when 'ca' signs a request. - -# This goes against PKIX guidelines but some CAs do it and some software -# requires this to avoid interpreting an end user certificate as a CA. - -basicConstraints=CA:FALSE - -# Here are some examples of the usage of nsCertType. If it is omitted -# the certificate can be used for anything *except* object signing. - -# This is OK for an SSL server. -# nsCertType = server - -# For an object signing certificate this would be used. -# nsCertType = objsign - -# For normal client use this is typical -# nsCertType = client, email - -# and for everything including object signing: -# nsCertType = client, email, objsign - -# This is typical in keyUsage for a client certificate. -keyUsage = nonRepudiation, digitalSignature, keyEncipherment - -# This will be displayed in Netscape's comment listbox. +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment nsComment = "OpenSSL Generated User Certificate" - -# PKIX recommendations harmless if included in all certificates. -subjectKeyIdentifier=hash -authorityKeyIdentifier=keyid,issuer:always -extendedKeyUsage=clientAuth - -# This stuff is for subjectAltName and issuerAltname. -# Import the email address. -# subjectAltName=email:copy -# An alternative to produce certificates that aren't -# deprecated according to PKIX. -# subjectAltName=email:move - -# Copy subject details -# issuerAltName=issuer:copy - -#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem -#nsBaseUrl -#nsRevocationUrl -#nsRenewalUrl -#nsCaPolicyUrl -#nsSslServerName - -# This is required for TSA certificates. -# extendedKeyUsage = critical,timeStamping +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +extendedKeyUsage = clientAuth [ usr_cert_san ] # copy of [ usr_cert ] plus nonempty Subject Alternative Names basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment nsComment = "OpenSSL Generated User Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always extendedKeyUsage = clientAuth -keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = $ENV::SAN [ server ] @@ -237,22 +191,62 @@ subjectAltName = $ENV::SAN # Make a cert with nsCertType=server basicConstraints = CA:FALSE nsCertType = server +keyUsage = digitalSignature, keyEncipherment nsComment = "OpenSSL Generated Server Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always extendedKeyUsage = serverAuth,1.3.6.1.5.5.8.2.2 -keyUsage = digitalSignature, keyEncipherment [ server_san ] # copy of [ server ] plus nonempty Subject Alternative Names basicConstraints = CA:FALSE nsCertType = server +keyUsage = digitalSignature, keyEncipherment nsComment = "OpenSSL Generated Server Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always extendedKeyUsage = serverAuth,1.3.6.1.5.5.8.2.2 +subjectAltName = $ENV::SAN + +[ req_usr_cert ] + +# Copy of [ usr_cert ] for CSR +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +nsComment = "OpenSSL Generated User Certificate" +subjectKeyIdentifier = hash +extendedKeyUsage = clientAuth + +[ req_usr_cert_san ] + +# Copy of [ usr_cert_san ] for CSR +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +nsComment = "OpenSSL Generated User Certificate" +subjectKeyIdentifier = hash +extendedKeyUsage = clientAuth +subjectAltName = $ENV::SAN + +[ req_server ] + +# Copy of [ server ] for CSR +basicConstraints = CA:FALSE +nsCertType = server keyUsage = digitalSignature, keyEncipherment +nsComment = "OpenSSL Generated Server Certificate" +subjectKeyIdentifier = hash +extendedKeyUsage = serverAuth,1.3.6.1.5.5.8.2.2 + +[ req_server_san ] + +# Copy of [ server_san ] for CSR +basicConstraints = CA:FALSE +nsCertType = server +keyUsage = digitalSignature, keyEncipherment +nsComment = "OpenSSL Generated Server Certificate" +subjectKeyIdentifier = hash +extendedKeyUsage = serverAuth,1.3.6.1.5.5.8.2.2 subjectAltName = $ENV::SAN [ v3_req ] @@ -262,6 +256,7 @@ subjectAltName = $ENV::SAN basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment + [ v3_ca ] # Extensions for a typical CA diff --git a/src/usr/local/www/system_certmanager.php b/src/usr/local/www/system_certmanager.php index 97fdc14cc9..d19e24f667 100644 --- a/src/usr/local/www/system_certmanager.php +++ b/src/usr/local/www/system_certmanager.php @@ -479,15 +479,21 @@ if ($_POST['save']) { if (!empty($pconfig['csr_dn_organizationalunit'])) { $dn['organizationalUnitName'] = cert_escape_x509_chars($pconfig['csr_dn_organizationalunit']); } + + $altnames_tmp = array(cert_add_altname_type($pconfig['csr_dn_commonname'])); if (count($altnames)) { - $altnames_tmp = ""; foreach ($altnames as $altname) { - $altnames_tmp[] = "{$altname['type']}:{$altname['value']}"; + // The CN is added as a SAN automatically, do not add it again. + if ($altname['value'] != $pconfig['csr_dn_commonname']) { + $altnames_tmp[] = "{$altname['type']}:" . cert_escape_x509_chars($altname['value']); + } } + } + if (!empty($altnames_tmp)) { $dn['subjectAltName'] = implode(",", $altnames_tmp); } - if (!csr_generate($cert, $pconfig['csr_keylen'], $dn, $pconfig['csr_digest_alg'])) { + if (!csr_generate($cert, $pconfig['csr_keylen'], $dn, $pconfig['type'], $pconfig['csr_digest_alg'])) { $input_errors = array(); while ($ssl_err = openssl_error_string()) { if (strpos($ssl_err, 'NCONF_get_string:no value') === false) { @@ -916,6 +922,14 @@ if ($act == "new" || (($_POST['save'] == gettext("Save")) && $input_errors)) { ))->setHelp('NOTE: It is recommended to use an algorithm stronger than '. 'SHA1 when possible'); + $section->addInput(new Form_Select( + 'type', + '*Certificate Type', + $pconfig['type'], + $cert_types + ))->setHelp('Type of certificate to generate. Used for placing '. + 'restrictions on the usage of the generated certificate.'); + $section->addInput(new Form_Select( 'csr_dn_country', '*Country Code', @@ -971,6 +985,56 @@ if ($act == "new" || (($_POST['save'] == gettext("Save")) && $input_errors)) { ['placeholder' => 'e.g. internal-ca'] )); + if (empty($pconfig['altnames']['item'])) { + $pconfig['altnames']['item'] = array( + array('type' => null, 'value' => null) + ); + } + + $counter = 0; + $numrows = count($pconfig['altnames']['item']) - 1; + + foreach ($pconfig['altnames']['item'] as $item) { + + $group = new Form_Group($counter == 0 ? 'Alternative Names':''); + + $group->add(new Form_Select( + 'altname_type' . $counter, + 'Type', + $item['type'], + $cert_altname_types + ))->setHelp(($counter == $numrows) ? 'Type':null); + + $group->add(new Form_Input( + 'altname_value' . $counter, + null, + 'text', + $item['value'] + ))->setHelp(($counter == $numrows) ? 'Value':null); + + $group->add(new Form_Button( + 'deleterow' . $counter, + 'Delete', + null, + 'fa-trash' + ))->addClass('btn-warning'); + + $group->addClass('repeatable'); + + $group->setHelp('Enter additional identifiers for the certificate in this list. The Common Name field is automatically added to the certificate as an Alternative Name.'); + + $section->add($group); + + $counter++; + } + + $section->addInput(new Form_Button( + 'addrow', + 'Add', + null, + 'fa-plus' + ))->addClass('btn-success'); + $form->add($section); $section = new Form_Section('Choose an Existing Certificate'); $section->addClass('toggle-existing collapse');