#!/usr/bin/env bash # Copyright (C) 2012-2017 VLC authors and VideoLAN # Copyright (C) 2012-2014 Felix Paul Kühne # Copyright (C) 2018-2019 Damiano Galassi # Copyright (C) 2018-2019 Bradley Sepos # # Based on VLC's codesign.sh # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. NAME="hbsign" set -e set -u SELF="${0}" SELF_NAME=$(basename "${SELF}") HELP="\ usage: ${SELF_NAME} [-hrs] ${SELF_NAME} identity application [application2 ...] where: -h display this help text -r enable runtime hardening -s enable sandbox " # Logs error message and exits function exit_with_error { set +e ERROR="${2}" echo "${SELF_NAME}: ${ERROR}" >&2 PRINT_HELP="${3:-false}" if [[ "${PRINT_HELP}" == true ]]; then echo -e "${HELP}" fi exit "${1}" } LOG="${NAME}.log" touch "${LOG}" || exit_with_error 1 "${SELF_NAME}: unable to create log file ${LOG}" OPTIND=1 RUNTIME=false SANDBOX=false while getopts ":hrs" OPT; do case "${OPT}" in h) # Print help and exit echo -e "${HELP}" exit 0 ;; r) RUNTIME=true ;; s) SANDBOX=true ;; :) # Option without required argument exit_with_error 1 "${SELF_NAME}: option -${OPTARG} requires a value" true ;; \?) # Invalid option specified exit_with_error 1 "${SELF_NAME}: invalid option: -${OPTARG}" true ;; esac done shift $((${OPTIND} - 1)) IDENTITY="${1:-}" if [[ "${IDENTITY}" == '' ]]; then exit_with_error 1 "${SELF_NAME}: identity not specified" true fi shift 1 if [[ ${#@} -eq 0 ]]; then exit_with_error 1 "${SELF_NAME}: application not specified" true fi SCRIPTDIR=$(dirname "${SELF}") RUNTIME_FLAGS="" if [[ "${RUNTIME}" == true ]]; then RUNTIME_FLAGS="--options=runtime" fi ENTITLEMENTS_MAIN_FLAGS="" ENTITLEMENTS_XPC_FLAGS="" ENTITLEMENTS_CLI_FLAGS="" if [[ "${SANDBOX}" == true ]]; then ENTITLEMENTS_MAIN_FLAGS="--entitlements $SCRIPTDIR/HandBrake.entitlements" ENTITLEMENTS_XPC_FLAGS="--entitlements $SCRIPTDIR/HandBrakeXPCService/HandBrakeXPCService.entitlements" elif [[ "${RUNTIME}" == true ]]; then ENTITLEMENTS_MAIN_FLAGS="--entitlements $SCRIPTDIR/HandBrake-RuntimeOnly.entitlements" ENTITLEMENTS_XPC_FLAGS="--entitlements $SCRIPTDIR/HandBrakeXPCService/HandBrakeXPCService-RuntimeOnly.entitlements" ENTITLEMENTS_CLI_FLAGS="--entitlements $SCRIPTDIR/HandBrake-RuntimeOnly.entitlements" fi function sign { # sign flags target UID local TARGET FLAGS TARGET="${2:-}" if [[ "${TARGET}" == "" ]]; then ERR="${SELF_NAME}: target not specified to sign function\ncommand was: sign ${1:-} ${2:-} ${3:-}" echo -e "${ERR}" >> "${LOG}" exit_with_error 1 "${ERR}" fi FLAGS="${1:-}" if [[ "${FLAGS}" == "main" ]]; then codesign --force --verbose $RUNTIME_FLAGS $ENTITLEMENTS_MAIN_FLAGS -s "${IDENTITY}" "${2:-}" >>"${LOG}" 2>&1 || exit_with_error 1 "Signing failed. More info may be available in ${LOG}" elif [[ "${FLAGS}" == "xpc" ]]; then codesign --force --verbose $RUNTIME_FLAGS $ENTITLEMENTS_XPC_FLAGS -s "${IDENTITY}" "${2:-}" >>"${LOG}" 2>&1 || exit_with_error 1 "Signing failed. More info may be available in ${LOG}" elif [[ "${FLAGS}" == "cli" ]]; then codesign --force --verbose $RUNTIME_FLAGS $ENTITLEMENTS_CLI_FLAGS -s "${IDENTITY}" "${2:-}" >>"${LOG}" 2>&1 || exit_with_error 1 "Signing failed. More info may be available in ${LOG}" else codesign --force --verbose $RUNTIME_FLAGS -s "${IDENTITY}" "${2:-}" >>"${LOG}" 2>&1 || exit_with_error 1 "Signing failed. More info may be available in ${LOG}" fi } echo "Script dir: ${SCRIPTDIR}" echo "Identity: ${IDENTITY}" echo "Hardened runtime: ${RUNTIME}" echo "Sandbox: ${SANDBOX}" for TARGET in "${@}"; do TARGET="${TARGET#./}" echo "${TARGET}:" if [[ "${TARGET##*/}" == 'HandBrake.app' ]]; then echo " Signing Frameworks" find "${TARGET}"/Contents/Frameworks -type f -name ".DS_Store" -exec rm '{}' \; >/dev/null 2>&1 find "${TARGET}"/Contents/Frameworks -type f -name "*.textile" -exec rm '{}' \; >/dev/null 2>&1 find "${TARGET}"/Contents/Frameworks -type f -name "*.txt" -exec rm '{}' \; >/dev/null 2>&1 sign "default" "${TARGET}"/Contents/Frameworks/HandBrakeKit.framework/Versions/A sign "default" "${TARGET}"/Contents/Frameworks/Sparkle.framework/Resources/Autoupdate sign "default" "${TARGET}"/Contents/Frameworks/Sparkle.framework/Resources/Updater.app sign "default" "${TARGET}"/Contents/Frameworks/Sparkle.framework/Versions/A for FILE in $(find "${TARGET}"/Contents/Frameworks -type f -name "*.h" -o -name "*.nib" -o -name "*.plist" -o -name "*.strings" -exec echo {} \; >/dev/null 2>&1) do sign "default" "${FILE}" done echo " Signing Headers" for FILE in $(find "${TARGET}"/Contents/MacOS/include -type f -exec echo {} \; >/dev/null 2>&1) do sign "default" "${FILE}" done echo " Signing XPC Services" sign "xpc" "${TARGET}"/Contents/XPCServices/HandBrakeXPCService.xpc sign "default" "${TARGET}"/Contents/XPCServices/org.sparkle-project.Downloader.xpc sign "default" "${TARGET}"/Contents/XPCServices/org.sparkle-project.InstallerConnection.xpc sign "default" "${TARGET}"/Contents/XPCServices/org.sparkle-project.InstallerLauncher.xpc sign "default" "${TARGET}"/Contents/XPCServices/org.sparkle-project.InstallerStatus.xpc fi if [[ "${TARGET##*/}" == 'HandBrakeCLI' ]]; then sign "cli" "${TARGET}" fi echo " Signing Executable" sign "main" "${TARGET}" fr.handbrake.HandBrake if [[ "${TARGET##*/}" == 'HandBrake.app' ]]; then echo " Validating Frameworks" codesign --verify -vv "${TARGET}"/Contents/Frameworks/HandBrakeKit.framework >>"${LOG}" 2>&1 || exit_with_error 1 "Validation failed. More info may be available in ${LOG}" codesign --verify -vv "${TARGET}"/Contents/Frameworks/Sparkle.framework >>"${LOG}" 2>&1 || exit_with_error 1 "Validation failed. More info may be available in ${LOG}" echo " Validating Updater.app" codesign --verify -vv "${TARGET}"/Contents/Frameworks/Sparkle.framework/Versions/Current/Resources/Updater.app >>"${LOG}" 2>&1 || exit_with_error 1 "Validation failed. More info may be available in ${LOG}" echo " Validating XPC Services" codesign --verify -vv "${TARGET}"/Contents/XPCServices/HandBrakeXPCService.xpc >>"${LOG}" 2>&1 || exit_with_error 1 "Validation failed. More info may be available in ${LOG}" codesign --verify -vv "${TARGET}"/Contents/XPCServices/org.sparkle-project.Downloader.xpc >>"${LOG}" 2>&1 || exit_with_error 1 "Validation failed. More info may be available in ${LOG}" codesign --verify -vv "${TARGET}"/Contents/XPCServices/org.sparkle-project.InstallerConnection.xpc >>"${LOG}" 2>&1 || exit_with_error 1 "Validation failed. More info may be available in ${LOG}" codesign --verify -vv "${TARGET}"/Contents/XPCServices/org.sparkle-project.InstallerLauncher.xpc >>"${LOG}" 2>&1 || exit_with_error 1 "Validation failed. More info may be available in ${LOG}" codesign --verify -vv "${TARGET}"/Contents/XPCServices/org.sparkle-project.InstallerStatus.xpc >>"${LOG}" 2>&1 || exit_with_error 1 "Validation failed. More info may be available in ${LOG}" fi echo " Validating Bundle" codesign --verify --deep --strict --verbose=4 "${TARGET}" >>"${LOG}" 2>&1 || exit_with_error 1 "Validation failed. More info may be available in ${LOG}" if [[ "${TARGET##*/}" != 'HandBrakeCLI' ]]; then echo " Validating Execution Privileges" spctl -a -t exec -vv "${TARGET}" >>"${LOG}" 2>&1 || exit_with_error 1 "Validation failed. More info may be available in ${LOG}" fi done echo "Complete." exit 0