Created
November 21, 2025 18:21
-
-
Save gabrielfalcao/36e327069048412c6cec0906decbaa84 to your computer and use it in GitHub Desktop.
automatically download, install and trust DoD Root certificate on MacOS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -e | |
| set -o pipefail | |
| set -u | |
| export IFS=$'\n' | |
| unset IFS | |
| declare -- cert_filename="DoD-Root-CA-3.cer" | |
| declare -A cert_checksums=( | |
| ["crc32"]="8789d796" | |
| ["sha1sum"]="d73ca91102a2204a36459ed32213b467d7ce97fb" | |
| ["sha256sum"]="b107b33f453e5510f68e513110c6f6944bacc263df0137f821c1b3c2f8f863d2" | |
| ["sha384sum"]="3bf0696644c55d33fc3f9470c575b3186dcf50e0d9f4111f8a6ea9e0c2cbb61efd0d4768afb5db2ff88581366087d546" | |
| ["sha512sum"]="8f6b9d8c4d77fa32bc95b86a41215b7449058aa23d50bf823fdc969c492f6e765e6ef9cfd990b153acbe7681137658cf29480d8b085582fb26c97bc9f96c7ea9" | |
| ) | |
| declare -- base_url="https://mobileconnect.cce.af.mil" | |
| declare -- static_index_url="https://mobileconnect.cce.af.mil/cert" | |
| declare -- js_app_url="" | |
| declare -- default_cert_download_url="https://nps.edu/documents/111151326/111164251/DoD-Root-CA-3.cer/2b94718d-5f88-8eaf-3bda-92e0fc548412?t=1619469888977" | |
| declare -- stderr=$(mktemp) | |
| script_name="$(basename "${BASH_SOURCE[0]}")" | |
| script_path="$(2>/dev/random 1>/dev/random cd $(dirname "${BASH_SOURCE[0]}") && pwd)" | |
| this_script_path="${script_path}/${script_name}" | |
| declare -a argv=($@) | |
| declare argc=${#argv[@]} | |
| declare -A cli_args_option_list=() | |
| declare -A cli_args_flag_list=() | |
| declare -a cli_args_value_list=() | |
| declare -a valid_argument_types=('flag' 'option' 'value') | |
| declare -- error_prefix_color_rgb="255;0;66" | |
| declare -- error_color_rgb="255;62;92" | |
| declare -- warn_prefix_color_rgb="255;106;50" | |
| declare -- warn_color_rgb="255;161;50" | |
| declare -- info_prefix_color_rgb="0;66;255" | |
| declare -- info_color_rgb="62;92;255" | |
| declare -- debug_prefix_color_rgb="50;255;106" | |
| declare -- debug_color_rgb="50;255;161" | |
| on_exit() { | |
| rm -f "static.html" "app.js" | |
| repl sane | |
| } | |
| on_ctrlc() { | |
| repl no echo | |
| 1>&2 echo -e "\x1b[1;38;2;${error_color_rgb}m\rAborted with Ctrl-C\x1b[0m" | |
| repl sane | |
| exit 1 | |
| } | |
| trap on_exit exit | |
| trap on_ctrlc hup | |
| trap on_ctrlc int | |
| trap on_ctrlc bus | |
| trap on_ctrlc segv | |
| trap on_ctrlc sys | |
| repl() { | |
| local -a stty_args=() | |
| case "$1" in -*no*stdin | no*stdin | -*no*echo | no*echo | capture) args+=('-echo') ;; *) args+=('sane') ;; esac | |
| 2>/dev/random 1>/dev/random stty ${stty_args[@]} | |
| } | |
| usage() { | |
| repl no echo | |
| 1>&2 echo -e "$(basename $0) <ARGUMENT>" | |
| repl sane | |
| } | |
| exit_error() { | |
| error "${@}" | |
| exit 1 | |
| } | |
| warn_prefixed() { | |
| local -- prefix="$1" | |
| shift | |
| local -- message="$@" | |
| 1>&2 echo -e "\x1b[1;38;2;${warn_prefix_color_rgb}m${prefix}\x1b[1;38;2;${warn_color_rgb}m ${message}\x1b[0m" | |
| } | |
| warn() { | |
| local -- linenum="${BASH_LINENO[0]}" | |
| warn_prefixed "[warn] [${script_name}:${linenum}]" "${@}" | |
| } | |
| error() { | |
| local -- linenum="${BASH_LINENO[0]}" | |
| error_prefixed "[error] [${script_name}:${linenum}]" "${@}" | |
| } | |
| error_prefixed() { | |
| local -- prefix="$1" | |
| shift | |
| local -- message="$@" | |
| 1>&2 echo -e "\x1b[1;38;2;${error_prefix_color_rgb}m${prefix}\x1b[1;38;2;${error_color_rgb}m ${message}\x1b[0m" | |
| } | |
| info() { | |
| local -- linenum="${BASH_LINENO[0]}" | |
| info_prefixed "[info] [${script_name}:${linenum}]" "${@}" | |
| } | |
| info_prefixed() { | |
| local -- prefix="$1" | |
| shift | |
| local -- message="$@" | |
| 1>&2 echo -e "\x1b[1;38;2;${info_prefix_color_rgb}m${prefix}\x1b[1;38;2;${info_color_rgb}m ${message}\x1b[0m" | |
| } | |
| debug_prefixed() { | |
| local -- prefix="$1" | |
| shift | |
| local -- message="$@" | |
| 1>&2 echo -e "\x1b[1;38;2;${debug_prefix_color_rgb}m${prefix}\x1b[1;38;2;${debug_color_rgb}m ${message}\x1b[0m" | |
| } | |
| debug() { | |
| local -- linenum="${BASH_LINENO[0]}" | |
| debug_prefixed "[debug] [${script_name}:${linenum}]" "${@}" | |
| } | |
| trace() { | |
| if [ -z "${BASH_TRACE}" ] && [ "${BASH_LOGLEVEL}" != "trace" ]; then | |
| return 0 | |
| fi | |
| local -- linenum="${BASH_LINENO[0]}" | |
| local -- funcname="${FUNCNAME[1]}" | |
| debug_prefixed "[${FUNCNAME[0]} ${script_name}::${funcname}:${linenum}]" "${@}" | |
| } | |
| process_argv() { | |
| repl no echo | |
| repl sane | |
| } | |
| get_certificate_url() { | |
| if [ ! -e "app.js" ]; then | |
| if [ ! -e "static.html" ]; then | |
| info_prefixed "[certificate url]" "querying \x1b[1;38;5;231mmobileconnect.cce.af.mil" | |
| 2>${stderr} curl "${static_index_url}" > static.html | |
| fi | |
| js_app_pathname=$(sed -n -E 's/^.*script.*src="([^"]*main[.][^"]*[.]js)".*$/\1/gp' static.html) | |
| if [ -z "${js_app_pathname}" ]; then | |
| 1>&2 echo -e "[error] could not determine url of js app" | |
| exit 1 | |
| fi | |
| if grep -E '^https?://' <<< "${js_app_pathname}"; then | |
| js_app_url="${js_app_pathname}" | |
| elif grep -E '^/' <<< "${js_app_pathname}"; then | |
| js_app_url="${base_url}${js_app_pathname}" | |
| else | |
| js_app_url="${static_index_url}${js_app_pathname}" | |
| fi | |
| info_prefixed "[certificate url]" "checking javascript app data \x1b[1;38;5;231mmobileconnect.cce.af.mil" | |
| 2>${stderr} curl "${js_app_url}" > app.js | |
| fi | |
| regex='^.*href:"(https:\/\/[^"]+DoD-Root-CA-3[.]cer[^"]+)".*$' | |
| if latest_download_url=$(sed -n -E "s/${regex}/\n\1\n/gp" app.js | sort -u | sed -E '/^\s*$/d'); then | |
| echo "${latest_download_url}" | |
| else | |
| 1>&2 echo -e "[warning] could parse latest certificate url from ${static_index_url}, using default url" | |
| echo "${default_cert_download_url}" | |
| fi | |
| } | |
| download_certificate() { | |
| download_url=$(get_certificate_url) | |
| info_prefixed "[downloading certificate]" "${cert_filename}" | |
| 2>${stderr} curl "${download_url}" > ${cert_filename} | |
| info_prefixed "[checking integrity]" | |
| local -A passed=() | |
| local -A failed=() | |
| for algo in ${!cert_checksums[@]}; do | |
| local -- expected_value=${cert_checksums["$algo"]} | |
| if check_integrity=$(which ${algo}); then | |
| rgb_color="\x1b[1;38;2;$(( 0x${expected_value:0:2} ));$(( 0x${expected_value:2:2} ));$(( 0x${expected_value:4:2} ))m" | |
| info_prefixed "[checking integrity]" "${rgb_color}${algo}" | |
| if calc=$($check_integrity "${cert_filename}" | awk '{print $1}'); then | |
| if [ "${calc}" == "${cert_checksums[${algo}]}" ]; then | |
| passed+=(["${algo}"]="${calc}") | |
| else | |
| failed+=(["${algo}"]="${calc}") | |
| fi | |
| fi | |
| else | |
| warn_prefixed "[checking integrity]" "could not find ${algo} binary in \$PATH" | |
| continue | |
| fi | |
| done | |
| if [ ${#failed[@]} -gt 0 ]; then | |
| for algo in ${!failed[@]}; do | |
| error_prefixed "[checking integrity]" "${algo} checksum mismatch: expected ${cert_checksums[${algo}]@Q} got ${failed[${algo}]@Q}" | |
| done | |
| else | |
| info_prefixed "[checking integrity]" "succeeded: \x1b[1;38;5;231m${!passed[@]}" | |
| fi | |
| } | |
| main() { | |
| download_certificate | |
| info "installing ${cert_filename} in keychain" | |
| sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${cert_filename}" | |
| info "trusting ${cert_filename} in keychain" | |
| sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${cert_filename}" | |
| } | |
| if [ "${0}" == "${BASH_SOURCE[0]}" ]; then | |
| if process_argv ${argv[@]}; then | |
| main | |
| fi | |
| else | |
| 1>&2 echo -e "${BASH_SOURCE[0]} appears to being used as a library by ${0@Q}" | |
| fi | |
| repl sane |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment