Files
bluememe-cordova-utils/check-ios-hotspot-signature.sh
T
2026-04-23 12:44:42 +09:00

300 lines
7.8 KiB
Bash

#!/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 <<EOF
Usage:
$(basename "$0") /path/to/App.ipa
Checks:
1. embedded.mobileprovision contains ${ENTITLEMENT_KEY}=true
2. App executable appears to contain ${ENTITLEMENT_KEY}=true
Notes:
Linux cannot run Apple's codesign tool. The executable check is a best-effort
scan of the signed Mach-O payload. For authoritative signed entitlements,
verify on macOS with: codesign -d --entitlements :- Your.app
EOF
}
fail() {
echo "[FAIL] $*"
echo
echo "Full log: ${LOG_FILE}"
exit 1
}
warn() {
echo "[WARN] $*"
}
pass() {
echo "[PASS] $*"
}
info() {
echo "[INFO] $*"
}
has_command() {
command -v "$1" >/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"<?xml")
if start < 0:
start = data.find(b"<plist")
if start < 0:
sys.exit(2)
end = data.find(b"</plist>", start)
if end < 0:
sys.exit(3)
target.write_bytes(data[start:end + len(b"</plist>")])
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"<key>\s*" + re.escape(key) + r"\s*</key>\s*<true\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"