3 # life_without_ca.sh -- a script that creates a very limited set of root CAs to your firefox's cert8.db
5 # before you use this script, i suggest you run certpatrol for something like half a year to get enough certificate "samples"
7 # here's roughly what this script does:
9 # - build index of CAs from operating systems global cert store (${CERT_PATH})
10 # - build index of (intermediate) CAs from firefox's cert8.db cert store
11 # - get issuers from all the certificates seen from certificate patrol's CertPatrol.sqlite DB
12 # - try to locate the CA from the global index
13 # - if that doesn't work out (when the issuer is an intermediate CA), try to locate the intermediate CA from the cert8.db index (as firefox caches the intermediate CAs)
14 # - try to find the CA of that
15 # - construct at list of required _root_ CAs
16 # - locate those from ${CERT_PATH}
17 # - import to (new) cert8.db
18 # - note that you must have removed the libnssckbi.so cert library, so firefox doesn't use it's bloated CA store (for more info, see: https://blog.torproject.org/blog/life-without-ca)
19 # - set trusted to verify websites
23 # - add openssl verify magic
24 # - we could construct the required CAs list from only the intermediate CAs in cert8.db, although it will not be complete
25 # - use /usr/share/ca-certificates instead of ${CERT_PATH}
26 # - search all sites from certpatrol, that use CA <x>
27 # - events.ccc.de - /usr/share/ca-certificates/cacert.org/cacert.org.crt -> should we include this? maybe as untrusted?
28 # - crawl Firefox history for HTTPS sites and get certs?
30 # domains that don't work (use some other CA):
39 if ! hash "${PROGRAM}" 2>/dev
/null
41 printf "[-] error: command not found in PATH: %s\n" "${PROGRAM}" >&2
47 # from http://wiki.bash-hackers.org/scripting/debuggingtips#making_xtrace_more_useful:
48 export PS4
='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
50 declare -A CERT8_CAs
=()
51 #declare -A CERT8_NICKS=()
52 # this uses all the available firefox profiles
54 declare -a BASIC_LIST
=(
55 "mozilla/AddTrust_External_Root.crt"
56 "mozilla/Baltimore_CyberTrust_Root.crt"
57 "mozilla/COMODO_Certification_Authority.crt"
58 "mozilla/Deutsche_Telekom_Root_CA_2.crt"
59 "mozilla/DigiCert_High_Assurance_EV_Root_CA.crt"
60 "mozilla/DigiCert_Global_Root_CA.crt"
61 "mozilla/Entrust.net_Secure_Server_CA.crt"
62 "mozilla/Entrust.net_Premium_2048_Secure_Server_CA.crt"
63 "mozilla/Entrust_Root_Certification_Authority.crt"
64 "mozilla/Equifax_Secure_CA.crt"
65 "mozilla/GTE_CyberTrust_Global_Root.crt"
66 "mozilla/GeoTrust_Global_CA.crt"
67 "mozilla/GeoTrust_Primary_Certification_Authority.crt"
68 "mozilla/GeoTrust_Primary_Certification_Authority_-_G3.crt"
69 "mozilla/GlobalSign_Root_CA.crt"
70 "mozilla/Go_Daddy_Class_2_CA.crt"
71 "mozilla/Go_Daddy_Root_Certificate_Authority_-_G2.crt"
72 "mozilla/Starfield_Class_2_CA.crt"
73 "mozilla/StartCom_Certification_Authority.crt"
74 "mozilla/UTN_USERFirst_Hardware_Root_CA.crt"
75 "mozilla/ValiCert_Class_2_VA.crt"
76 "mozilla/Verisign_Class_3_Public_Primary_Certification_Authority_-_G3.crt"
77 "mozilla/VeriSign_Class_3_Public_Primary_Certification_Authority_-_G5.crt"
78 "mozilla/thawte_Primary_Root_CA.crt"
79 "mozilla/thawte_Primary_Root_CA_-_G3.crt"
80 "mozilla/SecureTrust_CA.crt"
81 "mozilla/QuoVadis_Root_CA_2.crt"
82 "mozilla/DST_Root_CA_X3.crt"
84 CERT_PATH
="$( openssl version -a|grep "^OPENSSLDIR
:"|cut -d'"' -f2 )/certs"
89 function import_cas() {
93 if [ -z "${FF_HOME}" ]
95 echo "[-] error: FF_HOME not defined!" 1>&2
99 echo -e "\nimporting CAs:"
100 # create cert8.db for the new profile
101 for REQUIRED_CA in ${REQUIRED_CAs[*]}
103 #if [ "${REQUIRED_CA:0:1}" != "/" ]
105 # REQUIRED_CA="${CERT_PATH}/${REQUIRED_CA}"
107 if [ ! -f "${REQUIRED_CA}" ]
109 echo "[-] error: file \`${REQUIRED_CA}' not found
!" 1>&2
112 echo " ${REQUIRED_CA}"
113 # certutil requires a "nickname
", so we'll use the CN or OU
115 NICKNAME=$( openssl x509 -in "${REQUIRED_CA}" -noout -subject | sed 's/^.*\(CN\|OU\)=//' )
116 certutil -A -n "${NICKNAME}" -t CT,c,c -a -d "${FF_HOME}" 0<"${REQUIRED_CA}"
118 # TEST!!! allow code signing
119 #cat "${REQUIRED_CA}" | certutil -A -n "${NICKNAME}" -t CT,c,C -a -d "${FF_HOME}"
125 function expand_cert_path() {
126 # this function follows symlinked files, until the actual file is found.
128 if [ ! -f "${FILE}" ]
130 echo "[-] error
: file \
`${FILE}' not found!" 1>&2
133 while [ -h "${FILE}" ]
135 FILE=$( readlink "${FILE}" )
136 if [ "${FILE:0:1}" != "/" ]
138 FILE="${CERT_PATH}/${FILE}"
143 } # expand_cert_path()
145 function get_required_cas_list() {
147 local -a FINGERPRINTS
154 echo "[-] error: no CertPatrol path defined!" 1>&2
156 elif [ ! -f "${CP}" ]
158 echo "[-] error: certpatrol DB \`${CP}' not found!" 1>&2
162 # read all the issuer fingerprints from certificate patrol's DB
163 echo "reading issuer fingerprints from certpatrol's DB"
164 FINGERPRINTS
=( $
( sqlite3
"${CP}" 0<<<"select distinct issuerSha1Fingerprint from certificates where issuerSha1Fingerprint is not '';" ) )
165 echo -e "${#FINGERPRINTS[*]} issuer fingerprints found\n"
166 for FINGERPRINT in ${FINGERPRINTS[*]}
169 # check if the issuer cert is a root CA
170 if [ -n "${CAs[${FINGERPRINT}]}" ]
172 echo "${FINGERPRINT}: root CA found: ${CAs[${FINGERPRINT}]}"
173 REQUIRED_CA="${CAs[${FINGERPRINT}]}"
174 # TODO: openssl verify
175 # this is the most common case, as the certs are usually signed by intermediate CA
176 elif [ -n "${CERT8_CAs[${FINGERPRINT}]}" ]
178 echo "${FINGERPRINT}: found on cert8.db \"${CERT8_CAs[${FINGERPRINT}]}\""
180 # find the root CA that signed the intermediate CA
181 # TODO: it would be better to get the fingerprint of the issuer, instead of issuer_hash
182 ISSUER=$( certutil -L -n "${CERT8_CAs[${FINGERPRINT}]}" -a -d "${OLD_FF_HOME}" | openssl x509 -noout -issuer_hash )
183 # TODO: iterate through n
184 REQUIRED_CA=$( expand_cert_path "${CERT_PATH}/${ISSUER}.0" 2>/dev/null )
185 if [ -z "${REQUIRED_CA}" ]
187 # TODO: check if the cert actually is root CA
188 echo -e " [${WRN}-${RST}]WARNING: no root CA found for this cert -> continue"
191 echo " root CA found on file system: ${REQUIRED_CA}"
194 #certutil -L -n "${CERT8_CAs[${FINGERPRINT}]}" -a -d "${OLD_FF_HOME}" | openssl verify -CAfile "${REQUIRED_CA}"
196 # issuer cert not found
199 ISSUER_CN=$( sqlite3 "${CP}" 0<<<"select distinct issuerCommonName from certificates where issuerSha1Fingerprint is
\"${FINGERPRINT}\";" )
200 echo -e "${FINGERPRINT}: \033[1;31mnot
\033[0m found
\"${ISSUER_CN}\"!" 1>&2
201 # print hosts that use this issuer
202 echo " sites that use this CA
:"
203 sqlite3 "${CP}" 0<<<"select host from certificates where issuerSha1Fingerprint is \"${FINGERPRINT}\";" |
sed 's/^/ /'
206 if [ -n "${REQUIRED_CA}" ]
208 REQUIRED_CAs
+=( ${REQUIRED_CA} )
210 done # for FINGERPRINT
212 REQUIRED_CAs
=( $
( tr ' ' '\n' 0<<<"${REQUIRED_CAs[*]}" |
sort -u ) )
215 } # get_required_cas_list()
217 function print_required_cas_list
() {
220 echo -e "\n\nrequired CAs (${#REQUIRED_CAs[*]}):"
221 for REQUIRED_CA
in ${REQUIRED_CAs[*]}
223 echo " ${REQUIRED_CA}"
224 if [ ! -f "${REQUIRED_CA}" ]
226 echo " [-] WARNING: not found!" 1>&2
231 } # print_required_cas_list()
233 function print_countries
() {
240 # get the "nicknames", as this is the way certutil handles the certs
241 NICKNAMES
=( $
( certutil
-L -d "${FF_HOME}" |
grep -F -v ",," |
sed '1,4d' | gawk
'NF--' ) )
243 for NICKNAME
in "${NICKNAMES[@]}"
245 # print the PEM from the cert8.db and get the FP with openssl
246 COUNTRY
=$
( certutil
-L -n "${NICKNAME}" -a -d "${FF_HOME}" | openssl x509
-noout -subject |
grep -o "C=[A-Z]\+" )
247 if [ -n "${COUNTRY}" ]
251 echo "[-] warning: country not found for \`${NICKNAME}'!" 1>&2
253 done |
sort |
uniq -c
255 } # print_countries()
257 function reverse_index
() {
258 # this builds an index of the cert8.db nicknames
266 if [ -z "${FF_HOME}" ]
268 echo "[-] error: FF_HOME not defined!" 1>&2
274 NICKNAMES
=( $
( certutil
-L -d "${FF_HOME}" |
sed '1,4d' |
grep -F -v ',,' | gawk
'NF--' ) )
277 for NICKNAME
in "${NICKNAMES[@]}"
279 # print the PEM from the cert8.db and get the FP with openssl
280 FP
=$
( certutil
-L -n "${NICKNAME}" -a -d "${FF_HOME}" | openssl x509
-noout -fingerprint -sha1 |
sed 's/^.*Fingerprint=//' )
283 echo "[-] WARNING: could not get fingerprint for \`${NICKNAME}'!" 1>&2
285 #FPS+=( $( certutil -L -n "${NICKNAME}" -a -d "${FF_HOME}" | openssl x509 -noout -fingerprint -sha1 | sed 's/^.*Fingerprint=//' ) )
289 echo -e "reverse index:\n"
293 if [ -n "${CAs[${FP}]}" ]
297 echo "[-] WARNING: \`${NICKNAME}' not found (fp=${FP})!" 1>&2
304 function index_cas
() {
314 # index the certs to CAs[] associative array
315 echo "indexing ${CERT_PATH}"
317 # dereference symbolic links
318 for FILE
in ${CERT_PATH}/*
320 if [ ! -f "${FILE}" ]
324 FILE
=$
( expand_cert_path
"${FILE}" )
328 for FILE
in ${FILES[*]}
330 if [ ! -f "${FILE}" ]
334 FP
=$
( openssl x509
-in "${FILE}" -noout -fingerprint -sha1 |
sed 's/^.*Fingerprint=//' )
337 CAs
["${FP}"]="${FILE}"
341 echo -e "${#CAs[*]} CAs\n"
343 if [ -z "${OLD_FF_HOME}" ]
345 echo "[-] WARNING: OLD_FF_HOME not defined -> returning" 1>&2
350 # use all available firefox profiles
351 if (( INDEX_ALL_CERT8
))
355 # find all cert8.db files under ~/.mozilla/firefox
356 CERT8S
=( $
( find ~
/.mozilla
/firefox
-type f
-name cert8.db |
sed 's/\/cert8\.db$//' ) )
359 CERT8S
=( "${OLD_FF_HOME}" )
361 for CERT8
in "${CERT8S[@]}"
363 echo "indexing cert8.db from \`${CERT8}'"
366 # get the "nicknames", as this is the way certutil handles the certs
367 NICKNAMES
=( $
( certutil
-L -d "${CERT8}" |
sed '1,4d' | gawk
'NF--' ) )
369 for NICKNAME
in "${NICKNAMES[@]}"
371 # print the PEM from the cert8.db and get the FP with openssl
372 FP
=$
( certutil
-L -n "${NICKNAME}" -a -d "${CERT8}" | openssl x509
-noout -fingerprint -sha1 |
sed 's/^.*Fingerprint=//' )
375 #echo "${NICKNAME}: ${FP}"
376 CERT8_CAs
["${FP}"]="${NICKNAME}"
377 #CERT8_NICKS["${NICKNAME}"]="${FP}"
381 echo -e "${#CERT8_CAs[*]} CAs\n"
386 function rename_libnssckbi
() {
387 #find /usr/{lib,lib64} -name 'libnssckbi.so'
388 find /usr
/{lib
,lib64
} -name 'libnssckbi.so' -exec mv -v '{}' '{}.saved' \
;
390 # lrwxrwxrwx 1 root root 19 Sep 22 20:27 /usr/lib64/seamonkey-2.29/libnssckbi.so -> libnssckbi.so.saved
392 # mv: '/usr/lib64/seamonkey-2.29/libnssckbi.so' and '/usr/lib64/seamonkey-2.29/libnssckbi.so.saved' are the same file
393 find /usr
/{lib
,lib64
} -name 'libnssckbi.so' -exec rm -v '{}' \
;
396 } # rename_libnssckbi()
400 ${0##*/} OPTIONS ACTION
404 -p path path to CertPatrol's DB (reference)
405 this constructs the list of required CAs
406 -P path path to Firefox profile to update (new profile)
407 -c PEM file path to single root CA PEM file to import
408 -C use "basic list" (${#BASIC_LIST[*]} CAs)
411 both paths are paths to Firefox's profile directory, e.g. ~/.mozilla/firefox/XXXXXXXX.default
415 -a import required CAs list
416 -A print required CAs list (dry-run)
417 -l get rid of libnssckbi.so
419 (list the certs on the file system that are on the cert8.db)
430 while getopts "dhp:P:c:CaAlr" OPTION
439 OLD_FF_HOME
="${OPTARG}"
440 CP
="${OLD_FF_HOME}/CertPatrol.sqlite"
443 if [ -z "${OPTARG}" ] ||
[ ! -d "${OPTARG}" ]
445 echo "[-] error: -P requires an option!" 1>&2
451 REQUIRED_CAs
=( "${OPTARG}" )
454 if [ ! -d /usr
/share
/ca-certificates
]
456 echo "[-] error: directory \`/usr/share/ca-certificates' does not exist!" 1>&2
457 echo " you might be running RH/CentOS, which has different system for CAs. see https://github.com/pyllyukko/user.js/issues/140" 1>&2
460 REQUIRED_CAs
=( ${BASIC_LIST[*]/#/\/usr\/share\/ca-certificates\/} )
462 "a") ACTION
="import_cas" ;;
463 "A") ACTION
="print" ;;
464 "l") ACTION
="renamelib" ;;
472 if [ ${#REQUIRED_CAs[*]} -eq 0 ]
475 get_required_cas_list
479 "import_cas") import_cas
;;
480 "print") print_required_cas_list
;;
481 "rev") reverse_index
;;
482 "renamelib") rename_libnssckbi
;;
484 echo "[-] error: no action defined." 1>&2
489 if [ -n "${FF_HOME}" ]
491 # list the final result
492 CERT_COUNT
=$
(( $
( certutil
-L -d "${FF_HOME}" |
wc -l ) - 4 ))
493 echo -e "\ncurrent root CA list from ${FF_HOME}/cert8.db (${CERT_COUNT} certificates):\n"
494 certutil
-L -d "${FF_HOME}" |
sed 1,4d |
grep -v '\(,,\|u,u,u\)'
495 echo -e "\ncountries (check the codes from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements):\n"