rea-reverse

iOS provisioning profile and IPA viewer

Drop a .mobileprovision, .provisionprofile, an .ipa, a zipped .app or .xcarchive, an Info.plist or an .entitlements file to see what it says: profile type, expiry, team, devices and certificates; the bundle id, privacy keys and frameworks of the app; and the entitlements the executable is actually signed with, checked against the profile.

Your profiles and builds never leave your browser. Files are read locally in a Web Worker that has no network access. An IPA is read through its ZIP directory: only the plists, the embedded profile and the executable's headers and signature are opened. How to verify this yourself

Drop a provisioning profile, IPA or plist here

or

.mobileprovision, .provisionprofile, .ipa, zipped .app or .xcarchive (ZIP64 too, up to 16 GiB), Info.plist (XML or binary), .entitlements, PrivacyInfo.xcprivacy, or a bare Mach-O executable.

What this tool does not do

  • It does not verify signatures: neither Apple's CMS signature on the profile nor the code signature of the app. A file that looks right here can still be rejected by the device if it was modified.
  • It does not decrypt, re-sign or modify anything. For App Store downloads (cryptid 1) only the headers and the signature are readable, which is all this tool reads.
  • Each app extension's own embedded profile and entitlements are not cross-checked; drop the extension's profile separately.

When an iOS build refuses to install, the error rarely says which part of the signing is wrong. “Unable to install”, “The executable was signed with invalid entitlements” (0xe8008016), “A valid provisioning profile for this executable was not found” (0xe8008015): each of these comes down to a handful of facts that live in two places, the provisioning profile and the code signature of the executable. On a Mac you would read them with:

security cms -D -i embedded.mobileprovision
codesign -d --entitlements :- Payload/MyApp.app

This page reads the same data from the file itself, in your browser, and puts the two side by side. It is meant for checking your own builds and profiles: why a build will not install on a test device, which profile an exported IPA actually contains, whether the push entitlement says development or production.

What you can drop here

  • Provisioning profiles: .mobileprovision (iOS, iPadOS, tvOS, watchOS, visionOS) and macOS .provisionprofile. Profiles are CMS SignedData wrapping an XML plist; both the definite-length encoding current profiles use and the BER indefinite-length form found in older ones and produced by openssl cms -stream are read. The XML that security cms -D prints works too.
  • Builds: an .ipa, a zipped .app (Finder’s “Compress”), or a zipped .xcarchive. Only the ZIP central directory is read up front; then just the app’s Info.plist, embedded.mobileprovision, PrivacyInfo.xcprivacy, each extension’s Info.plist, and the main executable. A multi-gigabyte IPA is not loaded into memory; ZIP64 archives are supported.
  • Single files: Info.plist in XML or binary (bplist00) form, an .entitlements file, a PrivacyInfo.xcprivacy, or a bare Mach-O executable (thin or universal) to list the entitlements in its signature.

What you get

The profile: name, App ID name, App ID (application-identifier, with the team prefix), UUID, team ID and name, platform, creation and expiry date with days left, whether Xcode manages it, the full Entitlements dictionary, the list of provisioned device UDIDs with a search box, the developer certificates (common name, team from the OU field, SHA-1, validity) and the certificates of the CMS signer.

The profile type is not stored in the file. It is derived the way Xcode and fastlane label profiles:

Profile contains Type
ProvisionsAllDevices = true Enterprise (In-House); on macOS, Developer ID
ProvisionedDevices and get-task-allow = true Development
ProvisionedDevices, get-task-allow = false Ad Hoc
neither App Store (Mac App Store on macOS)

The app: bundle id, version and build, minimum OS, SDK and Xcode version, device family, supported platforms, URL schemes, required device capabilities, App Transport Security settings, every NS…UsageDescription with its text, the privacy manifest (tracking flag, tracking domains, collected data types, and each required-reason API category with its reason codes such as CA92.1), embedded frameworks and dylibs (with a flag for those that ship their own privacy manifest) and app extensions with their bundle ids and extension points.

The executable: architecture slices (arm64, arm64e, …) with platform and minimum OS from LC_BUILD_VERSION, the cryptid of LC_ENCRYPTION_INFO_64 (1 means an App Store download, whose code is encrypted), the signing identifier and team from the CodeDirectory, whether the signature is ad hoc, the signing certificate, and the entitlements from both blobs of the code signature: the XML plist (slot 5) and the DER form (slot 7) that iOS 15 and later evaluate. If the two disagree, the page says so.

The checks

With a build that contains its profile, the page runs the checks behind the usual install failures:

  • Expiry: an expired profile, or one that expires within 30 days.
  • Bundle id: CFBundleIdentifier must match the profile’s App ID after the team prefix, where * is a wildcard (TEAMID.com.example.*).
  • Entitlements: every entitlement the executable is signed with must be allowed by the profile. Strings and arrays may use the profile’s wildcards (TEAMID.* for keychain groups, * for associated domains); get-task-allow true in an App Store or Ad Hoc build, aps-environment development against a production profile, a keychain group of another team, or a capability the profile does not have (iCloud, App Groups) each fail with the key and both values. On macOS, sandbox and hardened-runtime entitlements under com.apple.security. need no profile and are shown as such.
  • Team: the team of the signed application-identifier (or the CodeDirectory team) against the profile’s team.
  • Signing certificate: the certificate in the code signature must be one of the profile’s DeveloperCertificates, compared by SHA-1. This is the “profile doesn’t include the signing certificate” case.
  • Extensions: each .appex bundle id must start with the app’s bundle id plus a dot.

To check a device, paste its UDID into the box under the device list. The comparison runs on the page; nothing is sent.

How we checked it

The fixtures are synthetic and built on macOS 27.0.1 with Xcode 27.0 (iPhoneOS 27.0 SDK) and OpenSSL 4.0.3 by test/ios/build.sh: a throwaway CA, a “developer” certificate and a profile-signing certificate; five hand-written profiles (development, Ad Hoc in BER indefinite-length form, an expired App Store profile, enterprise with a * App ID, and a macOS development profile) signed with openssl cms -sign -binary -nodetach; and a small C program compiled for arm64 and arm64e, signed with codesign --entitlements --generate-entitlement-der and zipped as Payload/Fixture.app. A second build has the wrong bundle id, get-task-allow true and development push against the App Store profile, another team’s keychain group, an iCloud entitlement the profile lacks, a certificate the profile does not list, a misnamed widget extension, and cryptid set to 1 in one slice. The unit tests compare the results with security cms -D, plutil, codesign -d --entitlements (XML and --der), lipo -archs and otool -l. We also ran the profile parser over the 20 real profiles Xcode had downloaded on our Mac: name, UUID, team, expiry, device list, entitlements and certificate SHA-1s matched security cms -D for all 20 (none of that data is in this site or its tests).

Two things that cost us time while building the fixtures. plutil -convert json rejects a decoded profile with “Invalid object in plist for JSON format”, because profiles contain <date> and <data> values JSON cannot hold, so the build script falls back to plutil -p for its reference output. And codesign -dv does not print the Authority= lines at all; we only saw which certificate had signed a build with -dvvv. If you run security cms -D -i on the .ipa itself instead of the profile inside it, it fails with security: failed to add data to decoder: UNKNOWN (-8183(d): unzip first, or drop the IPA here.

How your files stay private

  • Files are read in a Web Worker on your device. Nothing is uploaded, and the page has no server component.
  • UDIDs, team ids, bundle ids and certificate details are shown on the page only. The UDID you paste is compared in the page and not stored.
  • Analytics events contain only coarse values: success or an error code, the kind of file (profile, ipa, …), the derived profile type, the number of failed checks as a bucket, and size and duration buckets. The full list is on the privacy page.
  • The tool page shows no ads.

How to verify this yourself

  1. Open your browser’s developer tools on the Network tab and drop an IPA. The only requests are page assets and the worker script /_astro/ios-worker-*.js.
  2. Or load the page, go offline, and drop a profile. It still works.
  3. The worker runs under the Content Security Policy default-src 'none'; script-src 'self'; connect-src 'none', so the browser blocks any request it tries to make.
  4. Compare with your own tools: security cms -D -i file.mobileprovision, codesign -d --entitlements :- MyApp.app and otool -l MyApp | grep -A4 LC_ENCRYPTION print the same values.

Limitations

  • Nothing is verified cryptographically. Neither Apple’s signature on the profile nor the app’s code signature is checked; a profile or binary edited after signing looks normal here but will be rejected by the device.
  • Nothing is decrypted, re-signed or modified. For App Store downloads, only the load commands and the signature are read.
  • App extensions are listed with their bundle ids; their own profiles and entitlements are not cross-checked. Drop an extension’s embedded.mobileprovision on its own to read it.
  • The entitlement rules cover the common cases (exact values, * wildcards, arrays as subsets). Apple’s installer has special cases this page does not model, for example entitlements granted to Apple-signed code only.
  • The device list comes from the profile. Whether a device is also registered and enabled in your developer account is not visible in the file.

FAQ

Which profile is inside my IPA?

The one at Payload/<App>.app/embedded.mobileprovision, which this page shows under Provisioning profile, with its name and UUID. Xcode’s export may pick a different profile than the one you expected, especially with automatic signing; the UUID tells you which one it was.

The profile allows the entitlement, but the install still fails.

Look at the Signed with column: the value in the binary must be allowed, not only the key. aps-environment development in a build signed with a distribution profile is the classic case, as is a keychain group with the wrong team prefix. Also check the signing certificate row: a profile regenerated after a certificate renewal no longer lists the old certificate.

How do I find my device’s UDID?

Connect it and look in Finder (click the text under the device name until the UDID appears) or in Xcode under Window > Devices and Simulators.