Skip to content

Commit

Permalink
security: do not export untrusted certs
Browse files Browse the repository at this point in the history
The prior implementation relied on subcommands of `security(1)` that
all output certificates marked as "untrusted" in keychains.

As there appears to be no native command line application in OSX that
enables differentiation of trusted and untrusted certificates, some
objective c dependencies are now included. The code used requires El
Capitan or newer, and depends on XCode.

The security report was delivered by Eric Hodel. Thank you for your
dilligence, excellent report format, and perfect reporting practice.

The code used to implement the binary introduced by this change was
adapted from code in the Go programming language, and is copyright
"Copyright 2011 The Go Authors" and is covered by the Go LICENSE.

Apologies, but due to lack of time and effort, and also apparent usage,
I also chose not to carry forward the two --skip-* options that
previously allowed users to skip login and/or system certificates.
  • Loading branch information
raggi committed Oct 29, 2016
1 parent 35c3673 commit 1039bec
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 30 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
osx-ca-certs
6 changes: 6 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File: osx-ca-certs.m:
Copyright (c) 2016 James Tucker
Copyright 2011 The Go Authors.
Governed by a BSD-style license that can be found in the LICENSE file in the Go
language at 4a15508c663429652d32f5363c0964152b28dd74
14 changes: 11 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ PLISTDIR:=${PREFIX}/Library/LaunchAgents
BINDIR:=${PREFIX}/bin
BREW:=$(shell which brew)
FREQUENCY:=3600
ARGS:=
# ARGS:=--skip-login-keychain --skip-system-keychain
ARGS:=-path $(BINDIR)/osx-ca-certs

PLIST=org.ra66i.openssl-osx-ca.plist
XMLARGS=$(ARGS:%=<string>%</string>)
Expand All @@ -13,15 +12,19 @@ XMLARGS=$(ARGS:%=<string>%</string>)
.PHONY: uninstall
.PHONY: copy

osx-ca-certs: osx-ca-certs.m
clang -framework CoreFoundation -framework Security $< -o $@

install: copy
launchctl load $(PLISTDIR)/$(PLIST)

uninstall:
launchctl unload $(PLISTDIR)/$(PLIST)
rm $(BINDIR)/openssl-osx-ca
rm $(BINDIR)/osx-ca-certs
rm $(PLISTDIR)/org.ra66i.openssl-osx-ca.plist

copy: $(PLISTDIR)/$(PLIST) $(BINDIR)/openssl-osx-ca
copy: $(PLISTDIR)/$(PLIST) $(BINDIR)/osx-ca-certs $(BINDIR)/openssl-osx-ca

$(PLISTDIR):
install -d $(PLISTDIR)
Expand All @@ -39,3 +42,8 @@ $(PLISTDIR)/$(PLIST): Library/LaunchAgents/$(PLIST) $(PLISTDIR) Makefile
$(BINDIR)/openssl-osx-ca: bin/openssl-osx-ca $(BINDIR)
install -m 0755 $< $@

$(BINDIR)/osx-ca-certs: osx-ca-certs $(BINDIR)
install -m 0755 $< $@

clean:
rm -f osx-ca-certs
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# openssl-osx-ca (and libressl-osx-ca)
# osx-ca-certs (previously openssl-osx-ca (and libressl-osx-ca))

A simple script intended to be run from `cron(1)` to sync an openssl style CA
pem with the certificates found in the OSX Keychain(s).
A simple tool and script intended to be run periodically by `launchd(8)` to sync
an openssl style CA pem with the certificates found in the OSX Keychain(s).

The name is now a misnomer, as the software will manage certificate bundles for
both openssl and libressl installed under Homebrew.
The original name is now a misnomer, as the software will manage certificate
bundles for both openssl and libressl installed under Homebrew.

The Makefile contains a target called `osx-ca-certs` that acts a lot like
`security export -t certs -p`, except that it does not dump certificates that
are marked as untrusted as the latter does.

The keychains exported to the CA bundle by default are:
* System.keychain
* SystemRootCertificates.keychain
* login.keychain (if run as a user)

Note: You can disable login.keychain exports by supplying
`--skip-login-keychain`. If you install as root, remember that user logins
keychains will not be included.

The installed CA pem file will be made available through the default X.509 store
path, commonly `/usr/local/etc/openssl/cert.pem`.

Expand Down Expand Up @@ -44,11 +44,14 @@ path, commonly `/usr/local/etc/openssl/cert.pem`.
## Intended use cases

* Ruby 2.0.0+
* Other brew installed programs that rely on modern OpenSSL versions
* Programs that require a ca-certificates style bundle
* LibreSSL users
* OpenSSL users
* Other brew / manually installed things that link a non-Apple TLS
implementations

## Known limitations & Notes

* Only supports El Capitan and above.
* Syncs are by default perfomed once per hour.
* Syncs may not be sufficiently atomic. There is a small possiblity of race
conditions that could cause `openssl` programs to fail. The sync time is very
Expand Down
23 changes: 7 additions & 16 deletions bin/openssl-osx-ca
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
#!/bin/bash

usage() {
echo "$(basename $0) [--skip-login-keychain] [--skip-system-keychain] [-h|--help] [\`which brew\`]"
echo "$(basename $0) [-h|--help] [-path osx-ca-certs] [\`which brew\`]"
}

skip_system_keychain=false
skip_login_keychain=false
osx_ca_certs=osx-ca-certs

while [ ! $# -eq 0 ]; do
case "$1" in
--skip-login-keychain)
skip_login_keychain=true
;;
--skip-system-keychain)
skip_system_keychain=true
;;
-h|--help)
usage
exit 1
;;
-path)
shift
osx_ca_certs=$1
;;
*brew)
brew=$1
;;
Expand Down Expand Up @@ -68,13 +65,7 @@ genbundle() {
[[ "${tmpdir}" = "" ]] && echo "mktemp failed" && err "${tmpdir}" && return 1

local certs="${tmpdir}/cert.pem"
if ! $skip_system_keychain ; then
security find-certificate -a -p /Library/Keychains/System.keychain > $certs
fi
security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain >> $certs
if [[ -f ~/Library/Keychains/login.keychain ]] && ! $skip_login_keychain ; then
security find-certificate -a -p ~/Library/Keychains/login.keychain >> $certs
fi
$osx_ca_certs > $certs

d1=$($openssl md5 ${openssldir}/cert.pem | awk '{print $2}')
d2=$($openssl md5 ${tmpdir}/cert.pem | awk '{print $2}')
Expand Down
84 changes: 84 additions & 0 deletions osx-ca-certs.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* A large portion of the code in this file is copied from the Go programming
* language sources. It specifically comes from:
* src/crypto/x509/root_cgo_darwin.go at
* 4a15508c663429652d32f5363c0964152b28dd74 in the Go repo. It is copied here
* with great regard and thanks to the Go authors, who are consistently awesome.
*/

// Copyright 2011 The Go Authors. All rights reserved. Use of this source code
// is governed by a BSD-style license that can be found in the LICENSE file in
// the Go language project sources at version
// 4a15508c663429652d32f5363c0964152b28dd74, with modiciations under the
// openssl-osx-ca MIT license and copyright James Tucker.

#include <stdio.h>
#include <errno.h>
#include <sys/sysctl.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>

int main() {
// Get certificates from all domains, not just System, this lets
// the user add CAs to their "login" keychain, and Admins to add
// to the "System" keychain
SecTrustSettingsDomain domains[] = { kSecTrustSettingsDomainSystem,
kSecTrustSettingsDomainAdmin,
kSecTrustSettingsDomainUser };

int numDomains = sizeof(domains)/sizeof(SecTrustSettingsDomain);

CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0);

for (int i = 0; i < numDomains; i++) {
CFArrayRef certs = NULL;
// Only get certificates from domain that are trusted
OSStatus err = SecTrustSettingsCopyCertificates(domains[i], &certs);
if (err != noErr) {
continue;
}
int numCerts = CFArrayGetCount(certs);
for (int j = 0; j < numCerts; j++) {
CFDataRef data = NULL;
CFErrorRef errRef = NULL;
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, j);
if (cert == NULL) {
continue;
}
// We only want to add Root CAs, so make sure Subject and Issuer Name match
CFDataRef subjectName = SecCertificateCopyNormalizedSubjectContent(cert, &errRef);
if (errRef != NULL) {
CFRelease(errRef);
continue;
}
CFDataRef issuerName = SecCertificateCopyNormalizedIssuerContent(cert, &errRef);
if (errRef != NULL) {
CFRelease(subjectName);
CFRelease(errRef);
continue;
}
Boolean equal = CFEqual(subjectName, issuerName);
CFRelease(subjectName);
CFRelease(issuerName);
if (!equal) {
continue;
}
// Note: SecKeychainItemExport is deprecated as of 10.7 in favor of SecItemExport.
// Once we support weak imports via cgo we should prefer that, and fall back to this
// for older systems.
err = SecItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data);
if (err != noErr) {
continue;
}
if (data != NULL) {
CFDataAppendBytes(combinedData, CFDataGetBytePtr(data), CFDataGetLength(data));
CFRelease(data);
}
}

fwrite(CFDataGetBytePtr(combinedData), 1, CFDataGetLength(combinedData), stdout);

CFRelease(certs);
}
return 0;
}

0 comments on commit 1039bec

Please sign in to comment.