Add new darwin certificate trust settings table by Micah-Kolide · Pull Request #8715 · osquery/osquery
Conversation
This PR adds a new table for darwin devices, the certificate_trust_settings table to acquire the configured cert trust settings of each certificate domain for increased visibility into the status of installed certificates. This data is similar to executing security dump-trust-settings -d (for the 'admin' domain), or security dump-trust-settings -s (for the 'system' domain).
Something that security fails to do, is correctly report the configured trust settings of MDM distributed certificates. At least in some cases, it will report that 0 settings are configured regardless of which settings are set up.
For this table I'm implementing Apple's Security framework to copy the certificate trust settings for a cert domain, and enumerate the returned data.
When I was building the table, I could see that the array returned from SecTrustSettingsCopyTrustSettings included the trust setting policy name: kSecTrustSettingsPolicyName, but when building the table it would fail for the use of an undeclared identifier. This is why I am manually defining this key identifier.
Here is some redacted output:
osquery> SELECT * FROM certificate_trust_settings WHERE trust_domain = 'admin';
+------------------------------------------------------------------+------------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+
| common_name | serial_number | trust_domain | trust_policy_name | trust_policy_data | trust_allowed_error | trust_key_usage | trust_result |
+------------------------------------------------------------------+------------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+
| Charles Proxy CA (13 Sep 2024, ) | 000000000000 | admin | sslServer | | -2147408896 | | trusted_root |
| Zscaler Root CA | 000000000000000000 | admin | sslServer | | -2147408896 | | trusted_root |
+------------------------------------------------------------------+------------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+
@Micah-Kolide can you please rebase/merge for macOS CI fixes?
Will do!
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looking good though I have requested some changes.
@Micah-Kolide on my system, trust_policy_name | trust_policy_data | trust_allowed_error | trust_key_usage | trust_result are all empty for all the rows. Is that expected? Do you know a way I can test these with nonempty values?
@Micah-Kolide on my system, trust_policy_name | trust_policy_data | trust_allowed_error | trust_key_usage | trust_result are all empty for all the rows. Is that expected? Do you know a way I can test these with nonempty values?
Hey @zwass, does running the below commands generate any output?
/usr/bin/security dump-trust-settings -s | awk '/trust settings : [1-9]/{print prev_line; print} {prev_line=$0}'
/usr/bin/security dump-trust-settings -d | awk '/trust settings : [1-9]/{print prev_line; print} {prev_line=$0}'
Mine looks like this (minus the *'s):
$ /usr/bin/security dump-trust-settings -s | awk '/trust settings : [1-9]/{print prev_line; print} {prev_line=$0}'
$ /usr/bin/security dump-trust-settings -d | awk '/trust settings : [1-9]/{print prev_line; print} {prev_line=$0}'
Cert 0: Charles Proxy CA (13 Sep 2024, ***********************)
Number of trust settings : 2
Cert 1: Zscaler Root CA
Number of trust settings : 2
If there are any trust settings configured, then removing the awk will show you the other variables.
Trust Setting 0:
Policy OID : SSL
Allowed Error : CSSMERR_TP_CERT_EXPIRED
Result Type : kSecTrustSettingsResultTrustRoot
Trust Setting 1:
Policy OID : SSL
Allowed Error : Host name mismatch
Result Type : kSecTrustSettingsResultTrustRoot
I edited the trust settings in my keychain on the Zscaler Root CA cert for these to show up, but I've also tested this by using mkcert for a local CA and using that for my trust setting changes.
zwass
left a comment
•
Loading
zwass
left a comment
•
Loading
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I installed Charles Proxy and was able see the trust settings.
When I got this to start returning data, it brought up a number of questions for me.
I'm pretty confident that we need to be emitting one row for each of the trust settings entries (see comment below).
Also,
Should we emit a row when there are no trust settings configured? I think maybe not? But I'm not sure exactly how the table would be used. Certificates with no configured trust settings do already show up in the certificates table.
On my machine I see several entries with no common name. This gives me some concern. Any idea what that's about?
+-------------+----------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+
| common_name | serial | trust_domain | trust_policy_name | trust_policy_data | trust_allowed_error | trust_key_usage | trust_result |
+-------------+----------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+
| | 15C8BD65475CAFB897005EE406D2BC9D | system | | | | | |
| | 5D938D306736C8061D1AC754846907 | system | | | | | |
| | 00 | system | | | | | |
| | 00 | system | | | | | |
| | 00 | system | | | | | |
| | 110034B64EC6362D36 | system | | | | | |
| | 200605167002 | system | | | | | |
+-------------+----------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+
I also never saw any values for trust_policy_data and trust_key_usage even though it does seem like you used them according to the documentation.
I installed Charles Proxy and was able see the trust settings.
When I got this to start returning data, it brought up a number of questions for me.
I'm pretty confident that we need to be emitting one row for each of the trust settings entries (see comment below).
Also,
Should we emit a row when there are no trust settings configured? I think maybe not? But I'm not sure exactly how the table would be used. Certificates with no configured trust settings do already show up in the
certificatestable.On my machine I see several entries with no common name. This gives me some concern. Any idea what that's about?
+-------------+----------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+ | common_name | serial | trust_domain | trust_policy_name | trust_policy_data | trust_allowed_error | trust_key_usage | trust_result | +-------------+----------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+ | | 15C8BD65475CAFB897005EE406D2BC9D | system | | | | | | | | 5D938D306736C8061D1AC754846907 | system | | | | | | | | 00 | system | | | | | | | | 00 | system | | | | | | | | 00 | system | | | | | | | | 110034B64EC6362D36 | system | | | | | | | | 200605167002 | system | | | | | | +-------------+----------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+I also never saw any values for
trust_policy_dataandtrust_key_usageeven though it does seem like you used them according to the documentation.
You are very right about returning one result per trust setting, and I feel bad for missing that. I have updated the table results to have one per trust setting, and omit certificates without trust settings.
osquery> SELECT * FROM certificate_trust_settings;
+------------------------------------------------------------------+------------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+
| common_name | serial | trust_domain | trust_policy_name | trust_policy_data | trust_allowed_error | trust_key_usage | trust_result |
+------------------------------------------------------------------+------------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+
| Charles Proxy CA (13 Sep 2024, ) | 000000000000 | admin | sslServer | | -2147409654 | | trusted_root |
| Charles Proxy CA (13 Sep 2024, ) | 000000000000 | admin | sslServer | | -2147408896 | | trusted_root |
| Zscaler Root CA | 000000000000000000 | admin | sslServer | | -2147409654 | | trusted_root |
| Zscaler Root CA | 000000000000000000 | admin | sslServer | | -2147408896 | | trusted_root |
+------------------------------------------------------------------+------------------------------------+--------------+-------------------+-------------------+---------------------+-----------------+--------------+
I unfortunately also saw the entires without a common name and I'm not sure what those are about, but they are also omitted with the latest changes.
I also never saw any values for trust_policy_data and trust_key_usage either, but I think this one isn't as scary because I also don't see those in the trust settings array returned by /usr/bin/security dump-trust-settings.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice improvements! My last request is to return the error as a string. This code seems to work and correspond with what the CLI command returns:
// Log the trust_allowed_error as a string (convert with
// SecCopyErrorMessageString)
uint32_t trust_allowed_error_value;
CFNumberGetValue((CFNumberRef)trust_allowed_error,
kCFNumberSInt32Type,
&trust_allowed_error_value);
CFStringRef error =
SecCopyErrorMessageString(trust_allowed_error_value, nil);
LOG(ERROR) << "Trust allowed error: " << stringFromCFString(error)
<< " name:: " << r["common_name"]
<< " policy:: " << r["trust_policy_name"];
CFRelease(error);
Nice improvements! My last request is to return the error as a string. This code seems to work and correspond with what the CLI command returns:
// Log the trust_allowed_error as a string (convert with // SecCopyErrorMessageString) uint32_t trust_allowed_error_value; CFNumberGetValue((CFNumberRef)trust_allowed_error, kCFNumberSInt32Type, &trust_allowed_error_value); CFStringRef error = SecCopyErrorMessageString(trust_allowed_error_value, nil); LOG(ERROR) << "Trust allowed error: " << stringFromCFString(error) << " name:: " << r["common_name"] << " policy:: " << r["trust_policy_name"]; CFRelease(error);
Thank you! Here are the results with the trust_allowed_error being a string:
+------------------------------------------------------------------+------------------------------------+--------------+-------------------+-------------------+-------------------------+-----------------+--------------+
| common_name | serial | trust_domain | trust_policy_name | trust_policy_data | trust_allowed_error | trust_key_usage | trust_result |
+------------------------------------------------------------------+------------------------------------+--------------+-------------------+-------------------+-------------------------+-----------------+--------------+
| Charles Proxy CA (13 Sep 2024, ) | 000000000000 | admin | sslServer | | CSSMERR_TP_CERT_EXPIRED | | trusted_root |
| Charles Proxy CA (13 Sep 2024, ) | 000000000000 | admin | sslServer | | Host name mismatch | | trusted_root |
| Zscaler Root CA | 000000000000000000 | admin | sslServer | | CSSMERR_TP_CERT_EXPIRED | | trusted_root |
| Zscaler Root CA | 000000000000000000 | admin | sslServer | | Host name mismatch | | trusted_root |
+------------------------------------------------------------------+------------------------------------+--------------+-------------------+-------------------+-------------------------+-----------------+--------------+
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hopefully last changes 🤞
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thank you for the persistence!
Awesome, thank you for the persistence!
Of course! Thank you for the very helpful and patient reviews.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters