DEV Community

orca forge
orca forge

Posted on Originally published at forge.workstyle.tech

Why codesign Fails After Copying a Mac App from a DMG

📝 Originally published (in Japanese) at forge.workstyle.tech.

TL;DR The app showed “is damaged and can’t be opened” after distribution. Four Japanese filenames containing dakuten changed from NFC to NFD after copying from the DMG. That made the copied names differ from the names sealed by the signature, so codesign verification failed.

TL;DR

In this Mac app, after copying it from the DMG, four Japanese filenames containing voiced sound marks (dakuten) were converted from NFC to NFD normalization, causing codesign --verify --deep --strict to fail with a sealed resource is missing or invalid. To resolve this, I bundled the Japanese-named files into a single ASCII-named file and updated the app to extract them using NFC filenames on the first launch. After this fix, I confirmed that code signature verification passes after copying.

What I checked when I encountered a signature error

First, I verified whether the signature was actually corrupted. I asked the user to run codesign --verify --deep --strict on their device, which returned the error: a sealed resource is missing or invalid. This indicated there was a discrepancy between the app contents and the signature, going beyond a simple UI warning.

Checking with xattr, I found that the app had the quarantine attribute—essentially, it was marked as being downloaded. In the signature verification results, the same filename appeared as both file added and file missing.

At this point, I began to doubt my initial hypothesis. Previously, I assumed the app was being flagged as "broken" because of adhoc signing. However, that alone didn't explain why the same name was being reported as both added and missing.

How Japanese Filenames Changed After Copying from a DMG

I tested copying the app via a DMG on my local machine. That's when I noticed that the filenames' internal representation—the way the computer stores the characters—had changed.

In Japanese, characters with dakuten (voiced sound marks) look identical on screen, but have two different internal representations: NFC and NFD. Right when the app was built, the filenames were still in NFC, so codesign --verify running immediately after the build succeeded.

However, once the app was copied via the DMG, four filenames containing dakuten were converted to NFD. Names without dakuten remained in NFC. Even though they looked identical visually, the filenames sealed during signing no longer matched the filenames after copying, causing the signature verification to fail.

The issue didn't surface during the build itself, but only after passing through the DMG. Because I was only checking right after the build, I had missed inspecting the exact step where the failure actually occurred.

Modified to Avoid Including Japanese Names in Distributables

In this update, I've changed the approach to avoid including files with Japanese names directly in the app. Multiple files, along with their contents, are now consolidated into a single file with an ASCII name (alphanumeric characters). Upon the app's first launch, necessary files are extracted from this consolidated file.

The extracted file names are normalized to NFC (Unicode Normalization Form C). Since file names created in the user's folder are not part of the signing process, Japanese names can be used. The goal is to avoid including Japanese names within the distributable package.

When modifying the original files, the consolidated data is updated using a generation command. Tests are in place to verify that the consolidated content matches the original files, that names are NFC-compliant, and that the original file set is not included in the distribution configuration.

After Fixing, Did the Copied App Pass Signature Verification?

I recreated the DMG for the fixed version, mounted it (opened and displayed its contents), and then copied the app. After that, running codesign --verify --deep --strict passed this time. It was necessary to verify the signature after copying the app, not while it was still inside the DMG.

On the other hand, the spctl assessment remained rejected. Officially, this is due to the result of ad-hoc signing. Additionally, I haven’t yet confirmed how the warning displayed on the actual device changes after the fix.

What I learned from this is that correctly signing the source and ensuring the distributed app remains in the same state are two different things. Even if the file names appear correct, the internal representation may not be the same. After creating the DMG, it’s essential to verify the signature of the copied app as well.

Key takeaways

  • Check quarantine attributes with xattr.
  • Verify the signature with codesign --verify --deep --strict.
  • If a sealed resource is missing or invalid appears, compare the filenames reported as added and missing.
  • Verify the app again after copying it from the DMG.
  • For Japanese filenames, check the difference between NFC and NFD.

Top comments (0)