add apns supports.
This commit is contained in:
1 parent
855d24bc64
commit
21c392db8f
6 files changed
+442
-31
No files matched your search
+144
-22
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
ENTITLEMENT_KEY="com.apple.developer.networking.HotspotConfiguration"
|
||||
HOTSPOT_ENTITLEMENT_KEY="com.apple.developer.networking.HotspotConfiguration"
|
||||
APNS_ENTITLEMENT_KEY="aps-environment"
|
||||
CHECK_SCRIPT_VERSION="2026-04-23.3"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TIMESTAMP="$(date '+%Y%m%d-%H%M%S')"
|
||||
@@ -23,8 +25,9 @@ Usage:
|
||||
$(basename "$0") /path/to/App.ipa
|
||||
|
||||
Checks:
|
||||
1. embedded.mobileprovision contains ${ENTITLEMENT_KEY}=true
|
||||
2. App executable appears to contain ${ENTITLEMENT_KEY}=true
|
||||
1. embedded.mobileprovision contains ${HOTSPOT_ENTITLEMENT_KEY}=true
|
||||
2. embedded.mobileprovision contains ${APNS_ENTITLEMENT_KEY}=development|production
|
||||
3. App executable appears to contain both signed entitlements
|
||||
|
||||
Notes:
|
||||
Linux cannot run Apple's codesign tool. The executable check is a best-effort
|
||||
@@ -59,22 +62,32 @@ has_command() {
|
||||
extract_plist_from_binary() {
|
||||
local source_file="$1"
|
||||
local target_file="$2"
|
||||
local needle="${3:-}"
|
||||
|
||||
python3 - "$source_file" "$target_file" <<'PY'
|
||||
python3 - "$source_file" "$target_file" "$needle" <<'PY'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1])
|
||||
target = Path(sys.argv[2])
|
||||
needle = sys.argv[3].encode("utf-8") if len(sys.argv) > 3 and sys.argv[3] else b""
|
||||
data = source.read_bytes()
|
||||
|
||||
start = data.find(b"<?xml")
|
||||
if start < 0:
|
||||
start = data.find(b"<plist")
|
||||
needle_index = data.find(needle) if needle else -1
|
||||
if needle_index >= 0:
|
||||
start = data.rfind(b"<?xml", 0, needle_index)
|
||||
if start < 0:
|
||||
start = data.rfind(b"<plist", 0, needle_index)
|
||||
end = data.find(b"</plist>", needle_index)
|
||||
else:
|
||||
start = data.find(b"<?xml")
|
||||
if start < 0:
|
||||
start = data.find(b"<plist")
|
||||
end = data.find(b"</plist>", start if start >= 0 else 0)
|
||||
|
||||
if start < 0:
|
||||
sys.exit(2)
|
||||
|
||||
end = data.find(b"</plist>", start)
|
||||
if end < 0:
|
||||
sys.exit(3)
|
||||
|
||||
@@ -97,7 +110,28 @@ key = sys.argv[2]
|
||||
data = path.read_bytes()
|
||||
|
||||
try:
|
||||
value = plistlib.loads(data).get(key)
|
||||
plist = plistlib.loads(data)
|
||||
|
||||
values = []
|
||||
|
||||
def collect(obj):
|
||||
if isinstance(obj, dict):
|
||||
if key in obj:
|
||||
values.append(obj.get(key))
|
||||
entitlements = obj.get("Entitlements")
|
||||
if isinstance(entitlements, dict) and key in entitlements:
|
||||
values.append(entitlements.get(key))
|
||||
for value in obj.values():
|
||||
if isinstance(value, (dict, list, tuple)):
|
||||
collect(value)
|
||||
elif isinstance(obj, (list, tuple)):
|
||||
for value in obj:
|
||||
if isinstance(value, (dict, list, tuple)):
|
||||
collect(value)
|
||||
|
||||
collect(plist)
|
||||
|
||||
value = values[0] if values else None
|
||||
if value is True:
|
||||
print("true")
|
||||
sys.exit(0)
|
||||
@@ -123,6 +157,63 @@ except Exception:
|
||||
PY
|
||||
}
|
||||
|
||||
check_plist_key_string_allowed() {
|
||||
local plist_file="$1"
|
||||
local key="$2"
|
||||
shift 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]
|
||||
allowed = set(sys.argv[3:])
|
||||
data = path.read_bytes()
|
||||
|
||||
def finish(value):
|
||||
if value is None:
|
||||
print("missing")
|
||||
sys.exit(1)
|
||||
print(value)
|
||||
if str(value) in allowed:
|
||||
sys.exit(0)
|
||||
sys.exit(2)
|
||||
|
||||
try:
|
||||
plist = plistlib.loads(data)
|
||||
values = []
|
||||
|
||||
def collect(obj):
|
||||
if isinstance(obj, dict):
|
||||
if key in obj:
|
||||
values.append(obj.get(key))
|
||||
entitlements = obj.get("Entitlements")
|
||||
if isinstance(entitlements, dict) and key in entitlements:
|
||||
values.append(entitlements.get(key))
|
||||
for value in obj.values():
|
||||
if isinstance(value, (dict, list, tuple)):
|
||||
collect(value)
|
||||
elif isinstance(obj, (list, tuple)):
|
||||
for value in obj:
|
||||
if isinstance(value, (dict, list, tuple)):
|
||||
collect(value)
|
||||
|
||||
collect(plist)
|
||||
finish(values[0] if values else None)
|
||||
except Exception:
|
||||
text = data.decode("utf-8", errors="ignore")
|
||||
pattern = re.compile(
|
||||
r"<key>\s*" + re.escape(key) + r"\s*</key>\s*<string>\s*([^<]+?)\s*</string>",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
match = pattern.search(text)
|
||||
finish(match.group(1).strip() if match else None)
|
||||
PY
|
||||
}
|
||||
|
||||
find_main_executable() {
|
||||
local app_dir="$1"
|
||||
local info_plist="${app_dir}/Info.plist"
|
||||
@@ -157,7 +248,8 @@ print_section() {
|
||||
echo "==== $* ===="
|
||||
}
|
||||
|
||||
echo "iOS Hotspot Configuration Signature Check"
|
||||
echo "iOS Hotspot/APNs Signature Check"
|
||||
echo "Checker version: ${CHECK_SCRIPT_VERSION}"
|
||||
echo "Started: $(date -Is)"
|
||||
echo "Script: ${BASH_SOURCE[0]}"
|
||||
echo "Log: ${LOG_FILE}"
|
||||
@@ -246,43 +338,71 @@ if [[ "${PROFILE_EXTRACTED}" -eq 0 ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
PROFILE_RESULT="$(check_plist_key_true "${PROFILE_PLIST}" "${ENTITLEMENT_KEY}" 2>&1)"
|
||||
PROFILE_RESULT="$(check_plist_key_true "${PROFILE_PLIST}" "${HOTSPOT_ENTITLEMENT_KEY}" 2>&1)"
|
||||
PROFILE_STATUS=$?
|
||||
echo "Profile entitlement value: ${PROFILE_RESULT}"
|
||||
echo "Profile Hotspot entitlement value: ${PROFILE_RESULT}"
|
||||
if [[ "${PROFILE_STATUS}" -eq 0 ]]; then
|
||||
pass "Provisioning profile contains ${ENTITLEMENT_KEY}=true"
|
||||
pass "Provisioning profile contains ${HOTSPOT_ENTITLEMENT_KEY}=true"
|
||||
else
|
||||
fail "Provisioning profile does not contain ${ENTITLEMENT_KEY}=true"
|
||||
fail "Provisioning profile does not contain ${HOTSPOT_ENTITLEMENT_KEY}=true"
|
||||
fi
|
||||
|
||||
PROFILE_APNS_RESULT="$(check_plist_key_string_allowed "${PROFILE_PLIST}" "${APNS_ENTITLEMENT_KEY}" development production 2>&1)"
|
||||
PROFILE_APNS_STATUS=$?
|
||||
echo "Profile APNs entitlement value: ${PROFILE_APNS_RESULT}"
|
||||
if [[ "${PROFILE_APNS_STATUS}" -eq 0 ]]; then
|
||||
pass "Provisioning profile contains ${APNS_ENTITLEMENT_KEY}=${PROFILE_APNS_RESULT}"
|
||||
else
|
||||
fail "Provisioning profile does not contain ${APNS_ENTITLEMENT_KEY}=development|production"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Profile entitlement context:"
|
||||
grep -n -A2 -B1 "${ENTITLEMENT_KEY}" "${PROFILE_PLIST}" || true
|
||||
grep -n -A2 -B1 "${HOTSPOT_ENTITLEMENT_KEY}" "${PROFILE_PLIST}" || true
|
||||
grep -n -A2 -B1 "${APNS_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}"
|
||||
if grep -a -q "${HOTSPOT_ENTITLEMENT_KEY}" "${EXECUTABLE_PATH}"; then
|
||||
pass "Executable contains entitlement key string: ${HOTSPOT_ENTITLEMENT_KEY}"
|
||||
else
|
||||
fail "Executable does not contain entitlement key string. Signed app entitlements likely do not include Hotspot Configuration."
|
||||
fi
|
||||
|
||||
if grep -a -q "${APNS_ENTITLEMENT_KEY}" "${EXECUTABLE_PATH}"; then
|
||||
pass "Executable contains entitlement key string: ${APNS_ENTITLEMENT_KEY}"
|
||||
else
|
||||
fail "Executable does not contain entitlement key string. Signed app entitlements likely do not include APNs aps-environment."
|
||||
fi
|
||||
|
||||
EXEC_ENTITLEMENTS_PLIST="${WORK_DIR}/signed-entitlements-heuristic.plist"
|
||||
if extract_plist_from_binary "${EXECUTABLE_PATH}" "${EXEC_ENTITLEMENTS_PLIST}"; then
|
||||
if extract_plist_from_binary "${EXECUTABLE_PATH}" "${EXEC_ENTITLEMENTS_PLIST}" "${HOTSPOT_ENTITLEMENT_KEY}"; then
|
||||
echo "Extracted executable plist candidate: ${EXEC_ENTITLEMENTS_PLIST}"
|
||||
EXEC_RESULT="$(check_plist_key_true "${EXEC_ENTITLEMENTS_PLIST}" "${ENTITLEMENT_KEY}" 2>&1)"
|
||||
EXEC_RESULT="$(check_plist_key_true "${EXEC_ENTITLEMENTS_PLIST}" "${HOTSPOT_ENTITLEMENT_KEY}" 2>&1)"
|
||||
EXEC_STATUS=$?
|
||||
echo "Executable entitlement value: ${EXEC_RESULT}"
|
||||
echo "Executable Hotspot entitlement value: ${EXEC_RESULT}"
|
||||
if [[ "${EXEC_STATUS}" -eq 0 ]]; then
|
||||
pass "Executable plist candidate contains ${ENTITLEMENT_KEY}=true"
|
||||
pass "Executable plist candidate contains ${HOTSPOT_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
|
||||
|
||||
EXEC_APNS_RESULT="$(check_plist_key_string_allowed "${EXEC_ENTITLEMENTS_PLIST}" "${APNS_ENTITLEMENT_KEY}" development production 2>&1)"
|
||||
EXEC_APNS_STATUS=$?
|
||||
echo "Executable APNs entitlement value: ${EXEC_APNS_RESULT}"
|
||||
if [[ "${EXEC_APNS_STATUS}" -eq 0 ]]; then
|
||||
pass "Executable plist candidate contains ${APNS_ENTITLEMENT_KEY}=${EXEC_APNS_RESULT}"
|
||||
else
|
||||
warn "Executable contains APNs entitlement key, but extracted plist candidate did not prove value=development|production."
|
||||
warn "Use macOS codesign for authoritative validation."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Executable entitlement context:"
|
||||
grep -n -A2 -B1 "${ENTITLEMENT_KEY}" "${EXEC_ENTITLEMENTS_PLIST}" || true
|
||||
grep -n -A2 -B1 "${HOTSPOT_ENTITLEMENT_KEY}" "${EXEC_ENTITLEMENTS_PLIST}" || true
|
||||
grep -n -A2 -B1 "${APNS_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."
|
||||
@@ -290,7 +410,9 @@ fi
|
||||
|
||||
print_section "Summary"
|
||||
pass "Provisioning profile Hotspot entitlement: true"
|
||||
pass "Provisioning profile APNs entitlement: ${PROFILE_APNS_RESULT}"
|
||||
pass "Executable Hotspot entitlement key: present"
|
||||
pass "Executable APNs entitlement key: present"
|
||||
echo
|
||||
echo "Full log written to:"
|
||||
echo " ${LOG_FILE}"
|
||||
|
||||
Reference in new issue
Block a user