A few days ago, I encountered a new annoyance. Every time I launched an io.js server on my dev laptop, OS X’s firewall prompted me:
This was odd. Typically, such prompts are only seen the first time a new binary is executed. It’s expected after building a new version of io.js, but upgrading to v1.8.1 seemed to have changed this behavior. I checked my firewall rules and confirmed that io.js was explicitly allowed.
Confused? So was I. Why would OS X keep prompting for an allowed program? I needed to get work done, so rather than understand the problem, I tried to quickly fix it. Restarting didn’t help. Uninstalling and rebuilding io.js didn’t change anything. I finally stumbled on a clue when I attempted to remove and re-add the firewall rule. I could remove io.js, but I couldn’t re-add it. Clicking “allow” when prompted didn’t add it. Even manually adding /usr/local/bin/iojs
did nothing.
Poking around eventually paid off. I manually verified the code signature…
Not good! But I had work to do. I couldn’t spend time satisfying my curiosity. As a band-aid, I simply signed the current binary.
That was enough to tide me over until the weekend, but I wasn’t pleased at the prospect of doing this whenever I upgraded io.js. Also, I still had no idea how a program compiled on my own laptop could have an invalid signature.
Yesterday evening, I dove into the problem. First, I reinstalled io.js to reproduce the firewall prompting issue. Then I tried to figure out where things were going wrong. What follows is my thought process interspersed with terminal output.
Idea: Maybe my dev environment is borked. Let’s try compiling other programs to see if they pass signature checks.
Ag’s build process lacks any signing steps, so that seems fine. Searching through io.js’s build scripts, I notice some steps that could potentially try to sign the binary. What about something with a similar build process to io.js? Would building Node.js result in an invalid signature?
Hmm… nope. In fact, it’s not signed at all. What’s different here? Also, how exactly is io.js’s signature invalid? After looking at codesign
’s manpage again, I use --describe
:
Interesting info, but nothing really stands out to me. Maybe running codesign
with dtruss
can help me figure out how OS X verifies the signature:
Nothing?! I could have sworn OS X had a database of code signatures somewhere. codesign
should have opened it. In desperation, I search my entire hard drive for that unique-looking identifier: 5555494426c029279ed5393a9c5c43ac9796d090
.
Success! Opening the file in a text editor shews binary stuff interspersed with text. Hopefully, file
can figure it out.
Good ol’ file
, you never let me down. Except when you do. I’m relieved it’s a a SQLite DB, but what sort of horrid schema lurks inside?
Fortunately, the schema is pretty simple. That identifier
in the code
table looks promising. I bet it’s got something io.js-related.
Bingo. The table contains my band-aid signature and one from a week earlier. Though we shouldn’t be too hasty and stop gathering information. Is there anything important in the global
table?
No, but it was worth checking.
Now a hypothesis emerges. I’m still not sure how, but somehow an older version of io.js was signed on April 16th. When I upgraded, that signature was no longer valid. The second signature in the DB was created when I manually signed the binary. Not wanting to go further down this rabbit hole, I delete the signatures, restart my computer, and see if I’m back to normal behavior:
Victory! As expected, OS X’s firewall only prompts me once. After the first run, codesign
is happy:
I want to know why my old iojs signature failed, but I couldn’t find many clues from April 16th. That was just after I’d gotten my new laptop, but before I enabled Time Machine (April 21st).