diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c03552 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +osx-ca-certs diff --git a/LICENSE b/LICENSE index 602a6e2..7866466 100644 --- a/LICENSE +++ b/LICENSE @@ -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 diff --git a/Makefile b/Makefile index e523f5c..17c934f 100644 --- a/Makefile +++ b/Makefile @@ -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:%=%) @@ -13,15 +12,19 @@ XMLARGS=$(ARGS:%=%) .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) @@ -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 diff --git a/README.md b/README.md index df46efc..68e317c 100644 --- a/README.md +++ b/README.md @@ -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`. @@ -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 diff --git a/bin/openssl-osx-ca b/bin/openssl-osx-ca index 03f30d8..f7b8366 100755 --- a/bin/openssl-osx-ca +++ b/bin/openssl-osx-ca @@ -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 ;; @@ -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}') diff --git a/osx-ca-certs.m b/osx-ca-certs.m new file mode 100644 index 0000000..3f8b33b --- /dev/null +++ b/osx-ca-certs.m @@ -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 +#include +#include +#include +#include + +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; +}