1#!/bin/bash
2#
3# Build EVRoots.plist in ./BuiltKeychains/, given the embedded list of
4# OIDs in this script and their associated root certificate(s) which can
5# be found by the specified filename in ./roots.
6#
7
8CWD=`/bin/pwd`
9ROOT_CERT_DIR="$CWD"/roots
10ALT_ROOT_CERT_DIR="$CWD"/removed
11KC_DIR="$CWD"/BuiltKeychains
12
13# Set USE_PUBKEY to a non-zero value to generate public key hashes;
14# default behavior is to generate a hash of the certificate itself
15USE_PUBKEY=0
16
17if [ "$USE_PUBKEY" -ne 0 ]; then
18### NOTE: the certlist tool is used here to generate the MD5 hash of a
19### certificate's public key. This functionality needs to be added to the
20### security command at some point; there doesn't seem to be a way to get
21### it from openssl.
22    CERTLIST="$CWD"/../../tests/certlist
23    if [ ! -e "$CERTLIST" ]; then
24    printf "### BUILD FAILED: $CERTLIST is missing\n"
25    exit 1
26    fi
27fi
28
29SECURITY=/usr/bin/security
30OPENSSL=/usr/bin/openssl
31PLB=/usr/libexec/PlistBuddy
32#PLB="$CWD"/../../tests/PlistBuddy
33
34SaveKeychainList() {
35    SAVED_KC_LIST=`"$SECURITY" list -d user`
36}
37
38RestoreKeychainList() {
39   /bin/echo -n "$SAVED_KC_LIST" | xargs "$SECURITY" list -d user -s
40}
41
42if [ ! -e "$ROOT_CERT_DIR" ] || [ ! -e "$KC_DIR" ]; then
43   printf "You do not seem to be in a current security_certificates directory. Aborting.\n"
44   exit 1
45fi
46
47EVROOTS_CONFIG="$CWD"/evroot.config
48
49EVROOTS_KC=EVRoots.keychain
50EVROOTS_KC_PATH="/tmp/$EVROOTS_KC"
51EVROOTS_PLIST=EVRoots.plist
52EVROOTS_PLIST_PATH="$KC_DIR/$EVROOTS_PLIST"
53
54# save keychain list so we don't add EVRoots.keychain to it
55SaveKeychainList
56
57printf "Creating empty %s...\n" "$EVROOTS_KC"
58/bin/rm -f "$EVROOTS_KC_PATH" || exit 1
59"$SECURITY" create-keychain -p "$EVROOTS_KC" "$EVROOTS_KC_PATH" || exit 1
60
61TMPIFS=$IFS
62IFS=$'\x0A'$'\x0D'
63
64# first pass: build the EVRoots keychain
65for OID in `cat "$EVROOTS_CONFIG"`; do
66	# ignore comments and blank lines
67	OID=`echo "$OID" | sed -e 's/^#.*//'`
68	if [ "$OID" = "" ]; then continue; fi
69	# grab OID key
70	OIDKEY=`echo "$OID" | awk '{print $1}'`
71	# convert rest of line into comma-delimited filename list
72	CERTFILES=`echo "$OID" | sed -e 's/^[0-9A-Z\.]* //' -e 's/\"\ */\:/g'`
73	IFS=$'\x3A'
74	for CERTFILE in $CERTFILES; do
75		if [ "$CERTFILE" = "" ]; then continue; fi
76		printf "Adding cert from file: %s\n" "$CERTFILE"
77		CERT_TO_ADD="$ROOT_CERT_DIR/$CERTFILE"
78		if [ ! -e "$CERT_TO_ADD" ]; then
79			CERT_TO_ADD="$ALT_ROOT_CERT_DIR/$CERTFILE"
80		fi
81		# should prune duplicates first; for now, just ignore errors
82		"$SECURITY" \
83			-q add-certificates \
84			-k "$EVROOTS_KC_PATH" \
85			"$CERT_TO_ADD" 2>/dev/null
86	done
87	IFS=$'\x0A'$'\x0D'
88done
89
90printf "Removing %s...\n" "$EVROOTS_PLIST"
91/bin/rm -f "$EVROOTS_PLIST_PATH"
92
93# second pass: get hashes and build the EVRoots plist
94for OID in `cat "$EVROOTS_CONFIG"`; do
95	# ignore comments and blank lines
96	OID=`echo "$OID" | sed -e 's/^#.*//'`
97	if [ "$OID" = "" ]; then continue; fi
98	# grab OID key
99	OIDKEY=`echo "$OID" | awk '{print $1}'`
100	# add an array for this OID key
101	"$PLB" -c "add :$OIDKEY array" "$EVROOTS_PLIST_PATH"
102	# convert rest of line into comma-delimited filename list
103	CERTFILES=`echo "$OID" | sed -e 's/^[0-9A-Z\.]* //' -e 's/\"\ */\:/g'`
104	IFS=$'\x3A'
105	# process each certificate file
106	IDX=0
107	for CERTFILE in $CERTFILES; do
108		if [ "$CERTFILE" = "" ]; then continue; fi
109		CERT_TO_HASH="$ROOT_CERT_DIR/$CERTFILE"
110		if [ ! -e "$CERT_TO_HASH" ]; then
111			printf "... Warning: \"%s\" has been removed, but its hash is still in EV map\n" "$CERTFILE"
112			CERT_TO_HASH="$ALT_ROOT_CERT_DIR/$CERTFILE"
113		fi
114		if [ ! -e "$CERT_TO_HASH" ]; then
115			printf "... Could not find file to hash: \"%s\"\n" "$CERT_TO_HASH"
116			continue
117		fi
118		
119		if [ "$USE_PUBKEY" -ne 0 ]; then
120		    # get hash values for the certificate's public key
121		    PK_SHA1=`"$OPENSSL" x509 -inform DER -in "$CERT_TO_HASH" -ocspid | grep "Public key" | awk '{print $5}'`
122		    PK_MD5=`"$CERTLIST" -k "$EVROOTS_KC_PATH" -p --md5 --sha1 | grep "$PK_SHA1" | sed -e 's/^.\{37\}//' | awk '{print $1}'`
123		    printf "Public key hashes for \"%s\":\n" "$CERTFILE"
124		    printf "  MD5: %s  SHA1: %s\n" "$PK_MD5" "$PK_SHA1"
125		    printf "%s" "$PK_MD5" | xxd -r -p > /tmp/md5hashtmp
126		    printf "%s" "$PK_SHA1" | xxd -r -p > /tmp/sha1hashtmp
127		    
128		    # add hash values to the array
129		    IDX_NEXT=`expr $IDX + 1`
130		    "$PLB"  -c "add :$OIDKEY:$IDX data" \
131			    -c "import :$OIDKEY:$IDX /tmp/md5hashtmp" \
132			    -c "add :$OIDKEY:$IDX_NEXT data" \
133			    -c "import :$OIDKEY:$IDX_NEXT /tmp/sha1hashtmp" \
134			    "$EVROOTS_PLIST_PATH"
135    
136		    # verify the hash values were added correctly
137		    VERIFY_ERROR=0
138		    DATA=`"$PLB" -c "print :$OIDKEY:$IDX data" \
139			    "$EVROOTS_PLIST_PATH" | \
140			    xxd -u -p | sed -e 's/0A$//'`
141		    if [ "$DATA" != "$PK_MD5" ]; then VERIFY_ERROR=1; fi
142		    DATA=`"$PLB" -c "print :$OIDKEY:$IDX_NEXT data" \
143			    "$EVROOTS_PLIST_PATH" | \
144			    xxd -u -p | sed -e 's/0A$//'`
145		    if [ "$DATA" != "$PK_SHA1" ]; then VERIFY_ERROR=1; fi
146		    if [ ! "$VERIFY_ERROR" -eq 0 ]; then
147			    printf "### BUILD FAILED: data verification error!\n"
148			    printf "You likely need to install a newer version of $PLB; see <rdar://6208924> for details\n"
149			    RestoreKeychainList
150			    /bin/rm -f "$EVROOTS_PLIST_PATH"
151			    exit 1
152		    fi
153		else
154		    # get SHA-1 hash value for the certificate
155		    CERT_SHA1=`"$OPENSSL" x509 -inform DER -in "$CERT_TO_HASH" -fingerprint -noout | sed -e 's/SHA1 Fingerprint=//' -e 's/://g'`
156		    printf "Certificate fingerprint for \"%s\":\n" "$CERTFILE"
157		    printf "  SHA1: %s\n" "$CERT_SHA1"
158		    printf "%s" "$CERT_SHA1" | xxd -r -p > /tmp/certsha1hashtmp
159		    # add hash value to the array
160		    IDX_NEXT=`expr $IDX + 1`
161		    "$PLB"  -c "add :$OIDKEY:$IDX data" \
162			    -c "import :$OIDKEY:$IDX /tmp/certsha1hashtmp" \
163			    "$EVROOTS_PLIST_PATH"
164		    # verify the hash value was added correctly
165		    VERIFY_ERROR=0
166		    DATA=`"$PLB" -c "print :$OIDKEY:$IDX data" \
167			    "$EVROOTS_PLIST_PATH" | \
168			    xxd -u -p | sed -e 's/0A$//'`
169		    if [ "$DATA" != "$CERT_SHA1" ]; then VERIFY_ERROR=1; fi
170		    if [ ! "$VERIFY_ERROR" -eq 0 ]; then
171			    printf "### BUILD FAILED: data verification error!\n"
172			    printf "You likely need to install a newer version of $PLB; see <rdar://6208924> for details\n"
173			    RestoreKeychainList
174			    /bin/rm -f "$EVROOTS_PLIST_PATH"
175			    exit 1
176		    fi
177		fi
178
179		IDX="$IDX_NEXT"
180	done
181	IFS=$'\x0A'$'\x0D'
182done
183IFS="$TMPIFS"
184
185RestoreKeychainList
186/bin/chmod 0644 "$EVROOTS_PLIST_PATH"
187
188printf "Built $EVROOTS_PLIST_PATH successfully\n"
189
190exit 0
191