[vtable_cacerts] New CA certificates table.

This commit is contained in:
Teddy Reed 2014-08-17 01:44:22 -07:00
parent c71241d294
commit 444cea0649
7 changed files with 581 additions and 1 deletions

View File

@ -15,6 +15,7 @@ build:
python tools/gentable.py osquery/tables/specs/alf_services.table
python tools/gentable.py osquery/tables/specs/apps.table
python tools/gentable.py osquery/tables/specs/launchd.table
python tools/gentable.py osquery/tables/specs/cacerts.table
mkdir -p build
cd build && cmake .. && make -j5

View File

@ -220,6 +220,35 @@ getSerializedScheduledQueryLogItemJSON() {
return std::make_pair(ss.str(), results.second);
}
std::string getCACertificateContent() {
std::string content = R"(
MIIESzCCAzOgAwIBAgIJAI1bGeY2YPlhMA0GCSqGSIb3DQEBBQUAMIG7MQswCQYD
VQQGEwItLTESMBAGA1UECAwJU29tZVN0YXRlMREwDwYDVQQHDAhTb21lQ2l0eTEZ
MBcGA1UECgwQU29tZU9yZ2FuaXphdGlvbjEfMB0GA1UECwwWU29tZU9yZ2FuaXph
dGlvbmFsVW5pdDEeMBwGA1UEAwwVbG9jYWxob3N0LmxvY2FsZG9tYWluMSkwJwYJ
KoZIhvcNAQkBFhpyb290QGxvY2FsaG9zdC5sb2NhbGRvbWFpbjAeFw0xNDA4MTkx
OTEyMTZaFw0xNTA4MTkxOTEyMTZaMIG7MQswCQYDVQQGEwItLTESMBAGA1UECAwJ
U29tZVN0YXRlMREwDwYDVQQHDAhTb21lQ2l0eTEZMBcGA1UECgwQU29tZU9yZ2Fu
aXphdGlvbjEfMB0GA1UECwwWU29tZU9yZ2FuaXphdGlvbmFsVW5pdDEeMBwGA1UE
AwwVbG9jYWxob3N0LmxvY2FsZG9tYWluMSkwJwYJKoZIhvcNAQkBFhpyb290QGxv
Y2FsaG9zdC5sb2NhbGRvbWFpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAM6EsaVoMaHrYqH/s4YlhF6ke1XmUhzksB2eqpNqdgZw1JcZi9droRpuYmIf
bNyvWqUffHW9mKRv+udF5Woueshn+7Kj9YnnL9jfMzFaVEC8WRwWk54RIdNkxgFq
dqlaiwBWLvZkNUS9k/nugxVTbNu/GTqQlUG1XsIWBDJ2qRqniRfMKrfBKOxPYCZA
l7KeFguRA+xOsA7/71OMXJZKneMSWN8duTQCFt7uYCQXWc/IV6BfKTaR/ZQQ4w7/
iEMYPMZPSNprjun7rx0r2zPZGyrkGSCiS+4e+dfy0NbmYXodGHDxb/vBlm4q8CqF
OoH9aq0F/3581uZcuvU2ydX/LWcCAwEAAaNQME4wHQYDVR0OBBYEFPK5mwDg7mDV
fEJs4+ZOP9xvZBHAMB8GA1UdIwQYMBaAFPK5mwDg7mDVfEJs4+ZOP9xvZBHAMAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKNNP6f0JKxBtfq8hakrhHyl
cSN83SmVPcrsTLeaW8w0hi+JOtNOjD9sM8KNSbmLXfhRH4yPqYV+0dpJi5+SeelW
DjxZwbcFoI4EEu+zqufTUpu0T51eqnGvIedlIu1i2CiaoAJEmAN2OKQuN7uIQW27
2gL/RS+DVkevaidLRh7q2QI23B0n1XZuyEUiUKB1YfTPrupMZkostuyGybAJaxrc
ONmxUsB38pWJRCef9N/5APS74uIesfxSvEZXcXfPA+wrQY0yXn+bsEhz9pJOxZvD
WxULUHBC6qH9gAlKEqZYS3CwpCEl/Blznwi30r4CwwQ6dLfeXoPQDxAt7LyPpV4=
)";
return content;
}
std::string getEtcHostsContent() {
std::string content = R"(
##

View File

@ -78,6 +78,9 @@ getSerializedScheduledQueryLogItem();
std::pair<std::string, osquery::db::ScheduledQueryLogItem>
getSerializedScheduledQueryLogItemJSON();
// generate content for a PEM-encoded certificate
std::string getCACertificateContent();
// generate the content that would be found in an /etc/hosts file
std::string getEtcHostsContent();

View File

@ -16,13 +16,15 @@ ADD_LIBRARY(osquery_tables
system/firewall.cpp
system/apps.cpp
system/launchd.cpp
system/cacerts.cpp
)
TARGET_LINK_LIBRARIES(osquery_tables boost_filesystem)
TARGET_LINK_LIBRARIES(osquery_tables glog)
TARGET_LINK_LIBRARIES(osquery_tables osquery_filesystem)
TARGET_LINK_LIBRARIES(osquery_tables osquery_sqlite)
TARGET_LINK_LIBRARIES(osquery_tables "-Wl,-all_load")
TARGET_LINK_LIBRARIES(osquery_tables "-fobjc-arc -fobjc-link-runtime -framework Foundation -framework IOKit -framework CoreFoundation")
TARGET_LINK_LIBRARIES(osquery_tables "-fobjc-arc -fobjc-link-runtime -framework Foundation -framework IOKit -framework CoreFoundation -framework Security")
ADD_EXECUTABLE(etc_hosts_tests networking/etc_hosts_tests.cpp)
TARGET_LINK_LIBRARIES(etc_hosts_tests gtest)
@ -55,3 +57,12 @@ TARGET_LINK_LIBRARIES(launchd_tests osquery_core)
TARGET_LINK_LIBRARIES(launchd_tests osquery_database)
TARGET_LINK_LIBRARIES(launchd_tests osquery_filesystem)
TARGET_LINK_LIBRARIES(launchd_tests osquery_tables)
ADD_EXECUTABLE(cacerts_tests system/cacerts_tests.cpp)
TARGET_LINK_LIBRARIES(cacerts_tests gtest)
TARGET_LINK_LIBRARIES(cacerts_tests glog)
TARGET_LINK_LIBRARIES(cacerts_tests osquery_core)
TARGET_LINK_LIBRARIES(cacerts_tests osquery_database)
TARGET_LINK_LIBRARIES(cacerts_tests osquery_filesystem)
TARGET_LINK_LIBRARIES(cacerts_tests osquery_tables)
TARGET_LINK_LIBRARIES(osquery_tables "-framework CoreFoundation -framework Security")

View File

@ -0,0 +1,13 @@
table_name("cacerts")
schema([
Column(name="common_name", type="std::string"),
Column(name="not_valid_before", type="int"),
Column(name="not_valid_after", type="int"),
Column(name="key_algorithm", type="std::string"),
Column(name="key_usage", type="std::string"),
Column(name="subject_key_id", type="std::string"),
Column(name="authority_key_id", type="std::string"),
Column(name="sha1", type="std::string"),
])
implementation("osquery/tables/system/cacerts@genCerts")

View File

@ -0,0 +1,394 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include <string>
#include <iomanip>
#include <stdio.h>
#include <stdlib.h>
#include <boost/lexical_cast.hpp>
#include <boost/uuid/sha1.hpp>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <glog/logging.h>
#include "osquery/core.h"
#include "osquery/database.h"
using namespace osquery::core;
using namespace osquery::db;
namespace osquery {
namespace tables {
std::string genNumberProperty(const CFDataRef);
std::string genKIDProperty(const CFDataRef);
std::string genCommonNameProperty(const CFDataRef);
std::string genAlgorithmProperty(const CFDataRef);
typedef std::string (*PropGenerator)(const CFDataRef);
typedef std::pair<CFTypeRef, PropGenerator> Property;
const std::vector<std::string> kSystemKeychainPaths = {
"/System/Library/Keychains", "/Library/Keychains", };
const std::vector<std::string> kUserKeychainPaths = {"/Library/Keychains", };
const std::map<std::string, Property> kCertificateProperties = {
{"common_name", std::make_pair(kSecOIDCommonName, genCommonNameProperty)},
{"not_valid_before",
std::make_pair(kSecOIDX509V1ValidityNotBefore, genNumberProperty)},
{"not_valid_after",
std::make_pair(kSecOIDX509V1ValidityNotAfter, genNumberProperty)},
{"key_algorithm", std::make_pair(kSecOIDX509V1SubjectPublicKeyAlgorithm,
genAlgorithmProperty)},
{"key_usage", std::make_pair(kSecOIDKeyUsage, genNumberProperty)},
{"subject_key_id",
std::make_pair(kSecOIDSubjectKeyIdentifier, genKIDProperty)},
{"authority_key_id",
std::make_pair(kSecOIDAuthorityKeyIdentifier, genKIDProperty)}, };
// From SecCertificatePriv.h
typedef uint32_t SecKeyUsage;
enum {
kSecKeyUsageUnspecified = 0,
kSecKeyUsageDigitalSignature = 1 << 0,
kSecKeyUsageNonRepudiation = 1 << 1,
kSecKeyUsageContentCommitment = 1 << 1,
kSecKeyUsageKeyEncipherment = 1 << 2,
kSecKeyUsageDataEncipherment = 1 << 3,
kSecKeyUsageKeyAgreement = 1 << 4,
kSecKeyUsageKeyCertSign = 1 << 5,
kSecKeyUsageCRLSign = 1 << 6,
kSecKeyUsageEncipherOnly = 1 << 7,
kSecKeyUsageDecipherOnly = 1 << 8,
kSecKeyUsageCritical = 1 << 31,
kSecKeyUsageAll = 0x7FFFFFFF
};
std::string safeSecString(const CFStringRef cf_string) {
CFIndex length;
char *buffer;
// Access, then convert the CFString. CFStringGetCStringPtr is less-safe.
length = CFStringGetLength(cf_string);
buffer = (char *)malloc(length + 1);
if (!CFStringGetCString(
cf_string, buffer, length + 1, kCFStringEncodingASCII)) {
free(buffer);
return "";
}
// Cleanup allocations.
std::string result(buffer);
free(buffer);
return result;
}
std::string genNumberProperty(const CFDataRef number) {
CFNumberType type;
unsigned int value;
if (CFGetTypeID(number) != CFNumberGetTypeID() ||
!CFNumberGetValue((CFNumberRef)number, kCFNumberIntType, &value)) {
return "0";
}
// Cast as a string.
return boost::lexical_cast<std::string>(value);
}
std::string genKIDProperty(const CFDataRef kid) {
CFDataRef kid_data = NULL;
CFDictionaryRef kid_dict = NULL;
const char *kid_value = 0;
// Find the key identifier data within the property mess.
for (CFIndex i = 0; i < CFArrayGetCount((CFArrayRef)kid); i++) {
kid_dict = (CFDictionaryRef)CFArrayGetValueAtIndex((CFArrayRef)kid, i);
kid_value =
(const char *)CFDictionaryGetValue(kid_dict, kSecPropertyKeyValue);
if (CFGetTypeID(kid_value) == CFDataGetTypeID()) {
kid_data = (CFDataRef)kid_value;
break;
}
}
if (kid_data == NULL) {
// No key identifier found.
return "";
}
// Provide an ASCII-representation of the KID, similar to keychain.
std::stringstream ascii_kid;
int kid_byte;
for (CFIndex i = 0; i < CFDataGetLength(kid_data) /* 20 */; i++) {
kid_byte = (uint8_t)CFDataGetBytePtr(kid_data)[i];
ascii_kid << std::setfill('0') << std::setw(2) << std::hex << kid_byte;
// Then make it easy to read.
if (i < CFDataGetLength(kid_data) - 1) {
ascii_kid << "";
}
}
return ascii_kid.str();
}
std::string genCommonNameProperty(const CFDataRef ca) {
CFDataRef ca_data = NULL;
CFStringRef ca_string = NULL;
char *ca_buffer;
CFIndex ca_length;
// Find the key identifier data within the property mess.
for (CFIndex i = 0; i < CFArrayGetCount((CFArrayRef)ca); i++) {
ca_data = (CFDataRef)CFArrayGetValueAtIndex((CFArrayRef)ca, i);
if (CFGetTypeID(ca_data) == CFStringGetTypeID()) {
ca_string = (CFStringRef)ca_data;
break;
}
}
if (ca_string == NULL) {
// Could not find a CFString reference within the common name array.
return "";
}
// Access, then convert the CFString. CFStringGetCStringPtr is less-safe.
return safeSecString(ca_string);
}
std::string genAlgorithmProperty(const CFDataRef alg) {
std::string expected_label = "Algorithm";
CFStringRef label, value;
CFDictionaryRef alg_item;
// Find the key identifier data within the property mess.
for (CFIndex i = 0; i < CFArrayGetCount((CFArrayRef)alg); i++) {
alg_item = (CFDictionaryRef)CFArrayGetValueAtIndex((CFArrayRef)alg, i);
label = (CFStringRef)CFDictionaryGetValue(alg_item, kSecPropertyKeyLabel);
value = (CFStringRef)CFDictionaryGetValue(alg_item, kSecPropertyKeyValue);
if (expected_label.compare(safeSecString(label)) == 0) {
return safeSecString(value);
}
}
// Unknown algorithm OID.
return "";
}
std::string genSHA1ForCertificate(const SecCertificateRef ca) {
boost::uuids::detail::sha1 sha1;
CFDataRef ca_data;
// Access raw data, hash and release.
ca_data = SecCertificateCopyData(ca);
sha1.process_bytes(CFDataGetBytePtr(ca_data), CFDataGetLength(ca_data));
CFRelease(ca_data);
std::stringstream hash_output;
unsigned int hash[5];
// Return a hex-encoded friendly version of the 20byte-sha1.
sha1.get_digest(hash);
hash_output << std::hex << std::setfill('0') << std::setw(2);
for (std::size_t i = 0; i < sizeof(hash) / sizeof(hash[0]); i++) {
hash_output << hash[i];
}
return hash_output.str();
}
CFNumberRef CFNumberCreateCopy(const CFNumberRef number) {
// Easy way to get allow releasing numbers existing in arrays/dicts.
// This follows Apple's guidance for "Create" APIs, caller controls memory.
CFNumberRef copy;
unsigned int value;
if (!CFNumberGetValue(number, kCFNumberIntType, &value)) {
return NULL;
}
copy = CFNumberCreate(NULL, kCFNumberIntType, &value);
return copy;
}
CFDataRef CreatePropertyFromCertificate(const SecCertificateRef &cert,
const CFTypeRef &oid) {
CFDictionaryRef certificate_values;
CFDictionaryRef property_values;
CFDataRef property;
CFMutableArrayRef keys;
// Set the list of attributes.
keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(keys, oid); // SecCertificateOIDs.h
// Request dictionary of dictionaries (one for each attribute).
certificate_values = SecCertificateCopyValues(cert, keys, NULL);
CFRelease(keys);
if (!CFDictionaryContainsKey(certificate_values, oid)) {
// Certificate does not have the requested property.
CFRelease(certificate_values);
return NULL;
}
property_values =
(CFDictionaryRef)CFDictionaryGetValue(certificate_values, oid);
if (!CFDictionaryContainsKey(property_values, kSecPropertyKeyValue)) {
// Odd, there was not value in the property result.
CFRelease(certificate_values);
return NULL;
}
// Create copy of the property value, which is an index to owned dict.
property =
(CFDataRef)CFDictionaryGetValue(property_values, kSecPropertyKeyValue);
if (CFGetTypeID(property) == CFArrayGetTypeID()) {
property = (CFDataRef)CFArrayCreateCopy(NULL, (CFArrayRef)property);
} else if (CFGetTypeID(property) == CFNumberGetTypeID()) {
property = (CFDataRef)CFNumberCreateCopy((CFNumberRef)property);
} else {
LOG(ERROR) << "This property type is unknown...";
}
// Release and give the caller control of the property.
CFRelease(certificate_values);
return property;
}
bool CertificateIsCA(const SecCertificateRef cert) {
std::string expected_label = "Certificate Authority";
std::string expected_value = "Yes";
CFDataRef constraints;
// Create copy of the basic constrains OID.
constraints = CreatePropertyFromCertificate(cert, kSecOIDBasicConstraints);
if (constraints == NULL) {
return false;
}
// Must return an array of constrains.
if (CFGetTypeID(constraints) != CFArrayGetTypeID()) {
CFRelease(constraints);
return false;
}
CFStringRef label, value;
CFDictionaryRef constraint;
bool isCA = false;
// Find the expected value/label combination constraint.
for (CFIndex i = 0; i < CFArrayGetCount((CFArrayRef)constraints); i++) {
constraint =
(CFDictionaryRef)CFArrayGetValueAtIndex((CFArrayRef)constraints, i);
label = (CFStringRef)CFDictionaryGetValue(constraint, kSecPropertyKeyLabel);
value = (CFStringRef)CFDictionaryGetValue(constraint, kSecPropertyKeyValue);
if (expected_label.compare(safeSecString(label)) == 0 &&
expected_value.compare(safeSecString(value)) == 0) {
isCA = true;
break;
}
}
CFRelease(constraints);
return isCA;
}
bool genOSXAuthorities(CFArrayRef &reference) {
CFArrayRef keychain_certs;
CFMutableDictionaryRef query;
OSStatus status = errSecSuccess;
query = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
// This can be added to restrict results to x509v3
// CFDictionaryAddValue(query, kSecAttrCertificateType, 0x03);
CFDictionaryAddValue(query, kSecAttrCanVerify, kCFBooleanTrue);
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
status = SecItemCopyMatching(query, (CFTypeRef *)&keychain_certs);
CFRelease(query);
if (status != errSecSuccess) {
reference = NULL;
}
// Limit certificates to authorities (kSecOIDBasicConstraints).
CFMutableArrayRef authorities;
SecCertificateRef cert;
// Store just the authority certificates.
authorities = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
// For each certificate returned from the search, get the constraints prop.
for (CFIndex i = 0; i < CFArrayGetCount(keychain_certs); i++) {
cert = (SecCertificateRef)CFArrayGetValueAtIndex(keychain_certs, i);
if (CertificateIsCA(cert)) {
CFArrayAppendValue(authorities, cert);
}
}
reference = (CFArrayRef)authorities;
CFRelease(keychain_certs);
return (status == errSecSuccess);
}
QueryData genCerts() {
QueryData results;
CFArrayRef authorities = NULL;
// Keychains/certificate stores belonging to the OS.
if (!genOSXAuthorities(authorities)) {
LOG(ERROR) << "Could not find OSX Keychain Certificate Authorities.";
return results;
}
// Must have returned an array of matching certificates.
if (CFGetTypeID(authorities) != CFArrayGetTypeID()) {
LOG(ERROR) << "Unknown certificate authorities type.";
return results;
}
// Evaluate the certificate data, check for CA in Basic constraints.
unsigned int certificate_count = 0;
SecCertificateRef ca = NULL;
CFDataRef property = NULL;
certificate_count = CFArrayGetCount((CFArrayRef)authorities);
for (CFIndex i = 0; i < certificate_count; i++) {
Row r;
ca = (SecCertificateRef)CFArrayGetValueAtIndex(authorities, i);
// Iterate through each selected certificate property.
for (const auto &property_iterator : kCertificateProperties) {
property =
CreatePropertyFromCertificate(ca, property_iterator.second.first);
if (property == NULL) {
continue;
}
// Each property may be stored differently, apply a generator function.
r[property_iterator.first] = property_iterator.second.second(property);
CFRelease(property);
}
r["sha1"] = genSHA1ForCertificate(ca);
results.push_back(r);
}
CFRelease(authorities);
return results;
}
}
}

View File

@ -0,0 +1,129 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/algorithm/string.hpp>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <gtest/gtest.h>
#include <glog/logging.h>
#include "osquery/core/test_util.h"
#include "osquery/database.h"
using namespace osquery::core;
using namespace osquery::db;
namespace bai = boost::archive::iterators;
namespace osquery {
namespace tables {
typedef bai::binary_from_base64<const char*> base64_str;
typedef bai::transform_width<base64_str, 8, 6> base64_dec;
bool CertificateIsCA(const SecCertificateRef);
CFDataRef CreatePropertyFromCertificate(const SecCertificateRef&,
const CFTypeRef&);
std::string genSHA1ForCertificate(const SecCertificateRef);
std::string genCommonNameProperty(const CFDataRef);
std::string genNumberProperty(const CFDataRef);
std::string genKIDProperty(const CFDataRef);
std::string base64_decode(const std::string& encoded) {
std::string is;
std::stringstream os;
is = encoded;
boost::replace_all(is, "\r\n", "");
boost::replace_all(is, "\n", "");
uint32_t size = is.size();
// Remove the padding characters
if (size && is[size - 1] == '=') {
--size;
if (size && is[size - 1] == '=') {
--size;
}
}
if (size == 0) {
return std::string();
}
std::copy(base64_dec(is.data()),
base64_dec(is.data() + size),
std::ostream_iterator<char>(os));
return os.str();
}
class CACertsTests : public ::testing::Test {
protected:
virtual void SetUp() {
std::string raw;
CFDataRef data;
raw = base64_decode(getCACertificateContent());
data = CFDataCreate(NULL, (const UInt8*)raw.c_str(), (CFIndex)raw.size());
cert = SecCertificateCreateWithData(NULL, data);
CFRelease(data);
}
virtual void TearDown() {
if (cert != NULL) {
CFRelease(cert);
}
}
SecCertificateRef cert;
};
TEST_F(CACertsTests, test_certificate_is_ca) {
EXPECT_EQ(true, CertificateIsCA(cert));
}
TEST_F(CACertsTests, test_certificate_sha1) {
std::string sha1;
sha1 = genSHA1ForCertificate(cert);
EXPECT_EQ("f149bae28e3c754ff4bb062b2c1b8bac81b8783e", sha1);
}
TEST_F(CACertsTests, test_certificate_properties) {
CFDataRef property;
CFTypeRef oid;
std::string prop_string;
oid = kSecOIDCommonName;
property = CreatePropertyFromCertificate(cert, oid);
prop_string = genCommonNameProperty(property);
EXPECT_EQ("localhost.localdomain", prop_string);
CFRelease(property);
oid = kSecOIDSubjectKeyIdentifier;
property = CreatePropertyFromCertificate(cert, oid);
prop_string = genKIDProperty(property);
EXPECT_EQ("f2b99b00e0ee60d57c426ce3e64e3fdc6f6411c0", prop_string);
CFRelease(property);
oid = kSecOIDX509V1ValidityNotBefore;
property = CreatePropertyFromCertificate(cert, oid);
prop_string = genNumberProperty(property);
EXPECT_EQ("430168336", prop_string);
CFRelease(property);
}
}
}
int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
google::InitGoogleLogging(argv[0]);
return RUN_ALL_TESTS();
}