#!/usr/bin/env bash set -u ENTITLEMENT_KEY="com.apple.developer.networking.HotspotConfiguration" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TIMESTAMP="$(date '+%Y%m%d-%H%M%S')" LOG_FILE="${SCRIPT_DIR}/ios-hotspot-signature-check-${TIMESTAMP}.log" WORK_DIR="" exec > >(tee -a "${LOG_FILE}") 2>&1 cleanup() { if [[ -n "${WORK_DIR}" && -d "${WORK_DIR}" ]]; then rm -rf "${WORK_DIR}" fi } trap cleanup EXIT print_usage() { cat </dev/null 2>&1 } extract_plist_from_binary() { local source_file="$1" local target_file="$2" python3 - "$source_file" "$target_file" <<'PY' import sys from pathlib import Path source = Path(sys.argv[1]) target = Path(sys.argv[2]) data = source.read_bytes() start = data.find(b"", start) if end < 0: sys.exit(3) target.write_bytes(data[start:end + len(b"")]) PY } check_plist_key_true() { local plist_file="$1" local key="$2" python3 - "$plist_file" "$key" <<'PY' import plistlib import re import sys from pathlib import Path path = Path(sys.argv[1]) key = sys.argv[2] data = path.read_bytes() try: value = plistlib.loads(data).get(key) if value is True: print("true") sys.exit(0) if value is None: print("missing") sys.exit(1) print(repr(value)) sys.exit(2) except Exception: text = data.decode("utf-8", errors="ignore") pattern = re.compile( r"\s*" + re.escape(key) + r"\s*\s*", re.IGNORECASE, ) if pattern.search(text): print("true") sys.exit(0) if key in text: print("present_but_not_true_or_unparsed") sys.exit(2) print("missing") sys.exit(1) PY } find_main_executable() { local app_dir="$1" local info_plist="${app_dir}/Info.plist" if [[ -f "${info_plist}" ]] && has_command python3; then local exe_name exe_name="$(python3 - "$info_plist" <<'PY' import plistlib import sys from pathlib import Path try: info = plistlib.loads(Path(sys.argv[1]).read_bytes()) print(info.get("CFBundleExecutable", "")) except Exception: print("") PY )" if [[ -n "${exe_name}" && -f "${app_dir}/${exe_name}" ]]; then printf '%s\n' "${app_dir}/${exe_name}" return 0 fi fi find "${app_dir}" -maxdepth 1 -type f ! -name '*.plist' ! -name 'PkgInfo' -printf '%s %p\n' \ | sort -nr \ | awk 'NR==1 { sub(/^[0-9]+ /, ""); print }' } print_section() { echo echo "==== $* ====" } echo "iOS Hotspot Configuration Signature Check" echo "Started: $(date -Is)" echo "Script: ${BASH_SOURCE[0]}" echo "Log: ${LOG_FILE}" echo if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then print_usage exit 0 fi IPA_PATH="${1:-}" if [[ -z "${IPA_PATH}" ]]; then print_usage fail "Missing IPA path." fi if [[ ! -f "${IPA_PATH}" ]]; then fail "IPA not found: ${IPA_PATH}" fi for cmd in unzip find grep sed awk sort date mktemp tee; do if ! has_command "${cmd}"; then fail "Required command not found: ${cmd}" fi done if ! has_command python3; then fail "Required command not found: python3" fi if ! has_command openssl; then warn "openssl not found. embedded.mobileprovision will be parsed with a fallback XML extractor." fi WORK_DIR="$(mktemp -d)" EXTRACT_DIR="${WORK_DIR}/ipa" mkdir -p "${EXTRACT_DIR}" print_section "Input" info "IPA: ${IPA_PATH}" info "IPA size: $(du -h "${IPA_PATH}" | awk '{print $1}')" info "Work dir: ${WORK_DIR}" print_section "Extract IPA" unzip -q "${IPA_PATH}" -d "${EXTRACT_DIR}" || fail "Failed to unzip IPA." APP_DIR="$(find "${EXTRACT_DIR}/Payload" -maxdepth 1 -type d -name '*.app' | head -n 1)" if [[ -z "${APP_DIR}" ]]; then fail "No .app directory found under Payload." fi pass "Found app bundle: ${APP_DIR}" PROFILE_PATH="${APP_DIR}/embedded.mobileprovision" if [[ ! -f "${PROFILE_PATH}" ]]; then fail "embedded.mobileprovision not found in app bundle." fi pass "Found provisioning profile: ${PROFILE_PATH}" EXECUTABLE_PATH="$(find_main_executable "${APP_DIR}")" if [[ -z "${EXECUTABLE_PATH}" || ! -f "${EXECUTABLE_PATH}" ]]; then fail "Could not find main app executable." fi pass "Found main executable: ${EXECUTABLE_PATH}" print_section "Provisioning Profile Entitlements" PROFILE_PLIST="${WORK_DIR}/embedded.mobileprovision.plist" PROFILE_EXTRACTED=0 if has_command openssl; then if openssl smime -inform DER -verify -noverify -in "${PROFILE_PATH}" -out "${PROFILE_PLIST}" >/dev/null 2>&1; then PROFILE_EXTRACTED=1 pass "Decoded embedded.mobileprovision with openssl smime." elif openssl cms -inform DER -verify -noverify -in "${PROFILE_PATH}" -out "${PROFILE_PLIST}" >/dev/null 2>&1; then PROFILE_EXTRACTED=1 pass "Decoded embedded.mobileprovision with openssl cms." else warn "openssl could not decode embedded.mobileprovision; trying XML fallback." fi fi if [[ "${PROFILE_EXTRACTED}" -eq 0 ]]; then if extract_plist_from_binary "${PROFILE_PATH}" "${PROFILE_PLIST}"; then pass "Extracted plist XML from embedded.mobileprovision." else fail "Could not extract plist from embedded.mobileprovision." fi fi PROFILE_RESULT="$(check_plist_key_true "${PROFILE_PLIST}" "${ENTITLEMENT_KEY}" 2>&1)" PROFILE_STATUS=$? echo "Profile entitlement value: ${PROFILE_RESULT}" if [[ "${PROFILE_STATUS}" -eq 0 ]]; then pass "Provisioning profile contains ${ENTITLEMENT_KEY}=true" else fail "Provisioning profile does not contain ${ENTITLEMENT_KEY}=true" fi echo echo "Profile entitlement context:" grep -n -A2 -B1 "${ENTITLEMENT_KEY}" "${PROFILE_PLIST}" || true print_section "Signed App Entitlements" echo "Linux cannot run Apple's codesign. This is a best-effort scan of the app executable." if grep -a -q "${ENTITLEMENT_KEY}" "${EXECUTABLE_PATH}"; then pass "Executable contains entitlement key string: ${ENTITLEMENT_KEY}" else fail "Executable does not contain entitlement key string. Signed app entitlements likely do not include Hotspot Configuration." fi EXEC_ENTITLEMENTS_PLIST="${WORK_DIR}/signed-entitlements-heuristic.plist" if extract_plist_from_binary "${EXECUTABLE_PATH}" "${EXEC_ENTITLEMENTS_PLIST}"; then echo "Extracted executable plist candidate: ${EXEC_ENTITLEMENTS_PLIST}" EXEC_RESULT="$(check_plist_key_true "${EXEC_ENTITLEMENTS_PLIST}" "${ENTITLEMENT_KEY}" 2>&1)" EXEC_STATUS=$? echo "Executable entitlement value: ${EXEC_RESULT}" if [[ "${EXEC_STATUS}" -eq 0 ]]; then pass "Executable plist candidate contains ${ENTITLEMENT_KEY}=true" else warn "Executable contains entitlement key, but extracted plist candidate did not prove value=true." warn "Use macOS codesign for authoritative validation." fi echo echo "Executable entitlement context:" grep -n -A2 -B1 "${ENTITLEMENT_KEY}" "${EXEC_ENTITLEMENTS_PLIST}" || true else warn "Could not extract XML plist from executable. Key string was present, but value=true was not proven." warn "Use macOS codesign for authoritative validation." fi print_section "Summary" pass "Provisioning profile Hotspot entitlement: true" pass "Executable Hotspot entitlement key: present" echo echo "Full log written to:" echo " ${LOG_FILE}" echo echo "Authoritative macOS check, if available:" echo " codesign -d --entitlements :- Your.app"