-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Optionally trust CA certificates in GUI for SSL client actions (e.g. updates) #3558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
At each boot, /etc/ssl/certs is created and populated with all CA certificates configured in the web GUI.
Is checked when regenerating trusted CAs.
Instead of removing specific untrusted certs, delete all files in /etc/ssl/certs BUT leave symlinks in place for power users or for testing.
Moved out of system.inc so it can be accessed by system_camanager.php to refresh /etc/ssl/certs on changes to the CA list.
Call ca_trustedcerts_generate() after write_config() in system_camanager.php
Previously was cleaning out directory only if CAs were configured, so deleting last CA left an orphan, trusted certificate in /etc/ssl/certs
Prevents any possible hash name collision between symlinked certs and pfSense-created cert files
src/etc/inc/certs.inc
Outdated
| echo "ca_trustedcerts_generate() being called $mt\n"; | ||
| } | ||
|
|
||
| mwexec("/bin/mkdir -p {$g['etc_path']}/ssl/certs"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use a shell command for this, we have functions to make directories.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
src/etc/inc/certs.inc
Outdated
|
|
||
| mwexec("/bin/mkdir -p {$g['etc_path']}/ssl/certs"); | ||
| /* Remove all certs in the directory (but leave symlinks in place to allow power users to add trusted certs outside the Certificate Manager) */ | ||
| mwexec("/usr/bin/find {$g['etc_path']}/ssl/certs -type f -exec /bin/rm '{}' ';'"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use a shell command for this, use a function similar to this: https://github.com/pfsense/FreeBSD-ports/blob/devel/net-mgmt/pfSense-pkg-net-snmp/files/usr/local/pkg/net-snmp.inc#L223
If you want to exclude something, then write your files with a unique prefix and then glob using that prefix,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with this and the previous comment that I should be using pfSense library functions over shell commands. The mkdir code is easy to change; I had aped other code in certs.inc that did the same thing, so my next pull request may be a few patches to clean up that file.
I will refactor to eliminate the call to /usr/bin/find and use is_link and unlink instead. There's no way to use prefixes in /etc/ssl/certs, unfortunately, because OpenSSL requires that the files be named {cert_hash}.0, and OpenSSL doesn't support certificates in subdirectories of cert_path, so I can't do that either. Symlinks seemed a reasonable compromise to not completely obliterate the ability of power users and developers to manually place certificates in the directory.
src/etc/inc/certs.inc
Outdated
| } | ||
|
|
||
| function cert_get_hash($crt) { | ||
| $str_crt = base64_decode($crt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to make this variable for a single use, combine the two lines openssl_x509_parse(base64_decode($crt));
|
|
||
| /* add configured trusted CA certificates to /etc/ssl/certs */ | ||
| ca_trustedcerts_generate(); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do trusted certs need to be re/generated during boot up? /etc/ssl/certs is a static dir is it not? The certs should still be there across reboots. Only reason I think of is restoring/swapping a config. But the trusted certs re/generation for that scenario should and could be done by other means. Such as part of the config restore process. Or a trusted certs regeneration button in ca manager for those times when manually swapping a config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's safer to do it at boot time to ensure it's consistent. It's not like it's a huge amount of work or writes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And completely unnecessary. It's not like people are restoring or swapping configs in prod every other time they reboot. Also not friendly to write cycle sensitive media.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless you can check to be sure the state is consistent and update as needed, it is necessary. You don't know what someone might have done, or what might have happened on upgrade, etc. It's not like it's happening every few minutes, the number of writes overall is minimal. Better safe than sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless you can check to be sure the state is consistent and update as needed, it is necessary.
Which is how it should be done. Verify what is there against what config says should be there and add/remove accordingly. Also include a "synchronize" button on the CA manager page for user to be able to manually initiated the same thing.
| $name = $a_ca[$id]['descr']; | ||
| unset($a_ca[$id]); | ||
| write_config(); | ||
| ca_trustedcerts_generate(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would rather trusted certs be generated/removed on a per cert add/edit basis instead of regenerating all of them every time a change is made. Even changes that do not make changes to trusted certs settings.
|
Suggest squash some of these commits to create a cleaner history? Especially those that are just changes to the changes. |
|
|
||
| if (!$input_errors) { | ||
| write_config(); | ||
| ca_trustedcerts_generate(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would rather trusted certs be generated/removed on a per cert add/edit basis instead of regenerating all of them every time a change is made. Even changes that do not make changes to trusted certs settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it is a good idea to trust that files named *hashvalue*.0 have the correct certificate as their contents. I could certainly read in the existing file and check if it contains the correct certificate, but that seems like a lot of overhead compared to rewriting the certfiles during change/reboot. Thoughts on security implications of blindly trusting the file contents?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some more comments on this up higher. I'd rather see them rewritten each boot to be absolutely sure. I don't see saving 2-3 writes per reboot as being worth all the extra complexity. You could read the files in and compare, or compute and compare hashes without reading directly, but it's a lot of overhead for minimal benefit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deleting the entire set of cert files and rewriting them provides no additional safety.
-
If the contained cert does not match the hash name then it will fail for the site. The certs here are found for the site by their hash name. They are not all read until a match is found. That's why they are required to be hash named.
-
Symlinks are not being destroyed. So anyone who wishes to tamper can do so simply by creating a link to a cert located anywhere on the system they want.
-
Anyone wishing to tamper can simply put a cert of their choosing in the CA bundle (/usr/local/share/certs/ca-root-nss.crt). It is not purged and reloaded at boot either.
This argument of safety and trust is nonsensical.
No need to read the files in and compare. Just create an array of file names config says should be there and add/remove accordingly. Not much work at all really, and far less work than writing files that don't need to be written. Deleting and rewriting the files is much more work for no benefit.
// Get list of trust files for CA certs from config.
foreach($config['ca'] as $ca) {
$ca_hash = cert_get_hash($ca['crt']);
$ca_certfiles[] = "{$g['etc_path']}/ssl/certs/{$ca_hash}.0";
}
// Remove cert trust files that have no corresponding CA cert in config.
foreach (glob("{$g['etc_path']}/ssl/certs/*") as $cert_file) {
if (!in_array($cert_file, $ca_certfiles)) {
unlink_if_exists($cert_file);
}
}
// Add missing cert trust files for CA certs in config. Remove cert trust files of CA certs in config that are not trusted or are revoked.
foreach ($a_ca as $id => $value) {
ca_trustedcert_add($id);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wanting to be sure the firewall has correct contents in the files is hardly "nonsensical". When I say "safe" I mean "Ensure the contents of the files are correct and that all are present", not necessarily "safe" from a security standpoint (which is also important).
If a file is named properly but contains the wrong contents (0-byte file, truncated, overwritten, etc) it would be completely missed unless the contents of the files are checked/verified. There would be no way to correct that unless the file is overwritten or removed and rewritten. The use of the CA would fail but there's no way to correct that, and it wouldn't be obvious to the user that they might have to trigger a forced rewrite by edit/save of the CA if it was fixed to force an update then.
You're still jumping through a lot of hoops to avoid something that isn't even a sizable problem. A few writes per reboot isn't going to kill a disk. We need to be sure the files are in a consistent state with correct contents, making no assumptions and taking no shortcuts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just because it's the way you what pfSense to behave does not make it good or even beneficial. Microsoft does things because that's they way they want it to be too. Doesn't necessarily make it better. Doing that way just because that's the way we want it to be is not sound design. Any good engineer will tell you that the more you "touch" something increases the probability of introducing an error/failure/etc. The file is there, it's good. Leave it alone and let it be. Just the like CA bundle file.
If customers are willing to accept your inconsistently applied bloatware method of file integrity assurance. That is between them and you. But yes I prefer it be left alone once there. And that's how it will be done on my systems.
If it really is that critically important to you/pfSense then the rewriting on boot up is still not the "right"/"best" way of ensuring the content is correct, and has much more overhead than doing it in a well engineered fashion that could also be applied to any file deemed to be critical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You disagree, I get it. Let it go. No need for insults or name-calling. Your way is not better, it's riskier. You don't know the file is good until you check it to know it's good. You want to allow for the possibility that it's not right -- that's on you, but that isn't how we operate. Overwrite or read/verify checksum, either way is fine since you end up with a file in a known-good state.
Leaving unverified data in place because the filename happens to be correct isn't "well engineered".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. No need for insults or name-calling.
Where did I insult you? Where did I call you a name?
You certainly hurled some insults at me though.
My way is no more riskier than how the CA bundle is currently being handled. And like I stated. Rewriting files at boot up is still not the "right" way to ensure file integrity. There are better, more reliable methods with far less overhead.
Leaving unverified data in place because the filename happens to be correct isn't "well engineered".
But you and pfSense are okay with that as long is it's other just as critical files. Yes. That logic is well reasoned out. Not.
I have never said they shouldn't be verified. But if they are going to be then do it "right". With a well engineer method and consistently apply it to critical files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"inconsistently applied bloatware", for example. Uncalled for. You keep arguing in circles ignoring my explanations, so I'm done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talking down to someone with statements like; let me boil it down to basic, so you can get it and such. Yes those kinds of statements that you made are insults. I wasn't born yesterday. I know what those statements are about. They are intended to belittle.
I'm not the one arguing in circles and ignoring your explanations. I'm debunking them and the reasoning that's being given for them.
| if (!empty($a_ca[$id]['prv'])) { | ||
| $pconfig['key'] = base64_decode($a_ca[$id]['prv']); | ||
| } | ||
| $pconfig['trusted'] = $a_ca[$id]['trusted']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think this is what you really want to do here: $pconfig['trusted'] = isset($a_ca[$id]['trusted']);
| $pconfig['digest_alg'] = "sha256"; | ||
| $pconfig['lifetime'] = "3650"; | ||
| $pconfig['dn_commonname'] = "internal-ca"; | ||
| $pconfig['trusted'] = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't need this as it will not be set by default.
| } else { | ||
| $ca['trusted'] = $pconfig['trusted']; | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be all that it needed here: $ca['trusted'] = isset($pconfig['trusted']);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about this one and subsequent comment that suggests deleting && $ca['trusted']. isset() will return TRUE even if the variable is set FALSE, right? Is it completely safe to assume that a boolean FALSE never appears in global $config?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option set/not set rather that true/false is the way it is done throughout pfSense. Yes using isset instead of true/false works, is cleaner/clearer and is the consistent boolean option technique in pfSense config.
| <tr> | ||
| <td><?=$name?></td> | ||
| <td><i class="fa fa-<?= (!empty($ca['prv'])) ? "check" : "times" ; ?>"></i></td> | ||
| <td><i class="fa fa-<?= (isset($ca['trusted']) && $ca['trusted']) ? "check" : "times" ; ?>"></i></td> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need the && $ca['trusted'] here.
|
Hey Overhacked. Thanks for doing this. Really appreciate it. This has been a thorn in my side for a long time. I've given quite a few suggestions here. Most of them stem from what I've been doing for my own config to trust self signed certs. Please take them as constructive input. Again thanks. |
|
Let me toss another thing your direction to consider. Removing revoked CA certs. |
src/etc/inc/certs.inc
Outdated
|
|
||
| if (is_array($config['ca'])) { | ||
| foreach ($config['ca'] as & $ca) { | ||
| if ($ca['trusted']) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And along with the other issets this would be an isset test also. if (isset($ca['trusted']))
|
Re @NOYB commenting above about removing revoked CA certs: Isn't that something that OpenSSL takes care of when it is properly configured with a CRL? If a user configures one in the GUI, won't or couldn't it already be loaded into the validation via: |
|
Haven't study out how pfSense clients will handle that. But if the cert is not there then it won't be trusted. If you, or someone, can verify that all pfSense clients, cURL, SMTP, etc. will utilize the CRL then yeah it wouldn't matter then. |
Handling isset() more consistently Removed calls to shell commands in place of PHP functions
|
Just pushed fixes to changes directly requested above, except:
|
|
So now that you've made those changes. Thank you. Here is another thing to look into. Have only played with this on 2.3 (not 2.4) but are you sure that PHP's curl_exec will look in /etc/ssl/certs for certs? Maybe the PHP config is different in 2.4 but in 2.3 it does not. Have to add the CURLOPT_CAPATH option. Among things that could be affected are URL table downloads and check ip for custom DynDNS and RFC 2136 clients. Again thanks for doing this. |
|
Sadly, you're right about |
|
Fortunately I think there are only a few places the CURLOPT_CAPATH is needed. I'll provide a list of the places I found below. Look into openssl.capath/cainfo it may be usable for this but I don't know. Another thing I was thinking is maybe create a PHP var with the same path as being set in the environment variable. Then CURLOPT_CAPATH where needed could be set from the var instead of a hard coded string everywhere like I did for testing. It would probably have to be a global var. Maybe someone knows an even cleaner way. If so that would be great. There are some other places than these below where verify host/peer is used but they are for services that should be in the CA bundle already. Liking what you are doing. Keep it up. |
src/etc/inc/certs.inc
Outdated
|
|
||
| safe_mkdir("{$g['etc_path']}/ssl/certs"); | ||
| /* Remove all certs in the directory (but leave symlinks in place to allow power users to add trusted certs outside the Certificate Manager) */ | ||
| array_map('unlink', array_filter('is_file',glob("{$g['etc_path']}/ssl/certs/*"))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Believe array_filter syntax is incorrect. The callback function should be the second parameter.
Even so in my testing is_file was true for symlinks too. So don't think this will do what is expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was tired and mimicked array_map() syntax. I'll roll this into my next push. Speaking of, regarding your previous comment about squashing, won't a project maintainer be merging all this activity as a squash commit, anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The merge should have them all squashed together. But I know that on mine when I look at a file history it shows all the individual commits.
Refactored calls to curl_setopt() in various modules to a common function in pfsense-utils.inc. Did not modify curl_* calls in simplepie.inc, as it is an upstream library.
|
Most recent commits pushed address this issue in I deliberately omitted |
|
FYI, I submitted a PR to the PHP project that would address this issue in |
|
Thanks all for the feedback. I'm going to close this PR and submit another. (I will mention commenters above in the new PR.) I've discovered after doing further testing that the way webConfigurator spawns If you have any suggestions about whether it is worth implementing what amounts to kludges for each different way that pfSense spawns TLS-using processes, please chime in. Otherwise, my plan is to use the GUI code I've written and submit a PR that appends to the existing |
Similar to closed PR #3558 from overhacked, but with a number of changes.
pfSense can be configured to use a proxy server, but if it is a man-in-the-middle SSL proxy, there has been no GUI way to add trusted root certificates for HTTPS client actions like updates, URL alias downloads, etc.
This patch adds a "Trusted" checkbox (default False) to CAs in the GUI that will add the certificate to /etc/ssl/certs. An environment variable is set at boot-time that causes libfetch (eventually libssl) to look for root certificates in that directory.