NEURLFilter PIR status check fails with CipherML errors (Code 1100/1800)

Hello !

following up from a related thread (account transfer issue, now resolved: https://developer.apple.com/forums/thread/843546?answerId=911820022), but this is a distinct, new issue so starting fresh as suggested.

The extension launches fine, XPC handshake succeeds, and PIR registration completes successfully. But the subsequent NEPIRChecker status check fails every time (retried repeatedly over several minutes, same result each time):

PIR status returned error <Error Domain=com.apple.CipherML Code=1100 "Unable to query status due to errors: Error details were logged and redacted." UserInfo={NSLocalizedDescription=Unable to query status due to errors: Error details were logged and redacted., NSUnderlyingError=0x677037cf0 {Error Domain=com.apple.CipherML Code=1800 "Error details were logged and redacted." UserInfo={NSLocalizedDescription=Error details were logged and redacted.}}}>

Config being registered (confirmed from the logs):

pirServerURL = https://pir.dropbet.app/
pirPrivacyPassIssuerURL = https://pir.dropbet.app/issue

Both endpoints are live and were already validated as part of the original NEURL Filter configuration approval. I've captured a sysdiagnose from the device right when this was happening, so I have that ready to attach to a bug report if useful — just let me know and I'll file one.

Any idea what CipherML error 1100/1800 typically indicates?

Answered by RomainDropB in 914594022

Quick closing update on this one - tracked it down and fixed it.
Root cause: RFC 9458 §4.1 encapsulates requests with context.Seal("", request)
the AEAD's additional authenticated data is empty, the header only feeds into info and the wire envelope.
Our server was incorrectly using the header as the AEAD's AAD too, causing every real device request to fail authentication while our own (identically wrong) test script kept "succeeding" against it in lockstep, masking the bug for weeks. Fixed and deployed — the filter now shows "Running" on a real device for the first time. Thanks for your help narrowing down the Linux/OpenSSL HPKE issue earlier, that was a necessary step to get here.
Closing this thread & and thanks again for the help

Unfortunately those error codes don’t tell you much:

  • 1100 is basically a something failed error.
  • 1800 means something failed but we’re pretty sure that the underlying details went into the system log.

So the next step is to reproduce the problem, grab a system log snapshot, and then look up the for other log entries in the com.apple.CipherML subsystem.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi @DTS Engineer

Following up with a much more precise finding after digging into server-side logs.

The failure is happening in our PIR server's OHTTP gateway, specifically in HPKE request decapsulation:

[OHTTP] decapsulateRequest failed: Crypto.CryptoKitError.underlyingCoreCryptoError(error: 503316581)

This is thrown from HPKE.Recipient(privateKey:ciphersuite:info:encapsulatedKey:) / .open(ct, authenticating:) (DHKEM(X25519, HKDF-SHA256) / HKDF-SHA256 / AES-128-GCM, per RFC 9458).

What I've confirmed:

  • Not an entitlement/config issue
    The NEURL Filter configuration is correctly onboarded, and the client reaches this point successfully (handshake, registration all succeed first).

  • Reproducible independently of the iOS client
    I wrote a standalone Python script using a real HPKE library (pyhpke), fetched our server's published /.well-known/ohttp-gateway key, and built a byte-for-byte RFC 9458-compliant OHTTP request against it. It fails with the exact same CryptoKitError.underlyingCoreCryptoError(error: 503316581). So this isn't specific to anything the iOS client does.

  • Failure rate seems to fluctuate over server uptime
    Right after restarting the server process, a handful of real device requests briefly succeeded decryption (progressing to a later, unrelated parsing error), then it reverted to failing on essentially every attempt a few minutes later. This pattern makes me suspect a concurrency/thread-safety issue around the shared Curve25519.KeyAgreement.PrivateKey being used to construct a new HPKE.Recipient on every incoming request, rather than a static configuration problem.

Any idea what corecrypto error 503316581 typically indicates, or whether there's a known gotcha with concurrent HPKE.Recipient construction from a shared private key in swift-crypto? Happy to share the Python repro script and/or file a bug with logs attached if that would help narrow it down.

Thanks again for sticking with this one.

@RomainDropB do you see any more logs in the console other than PIR status returned error <Error Domain=com.apple.CipherML Code=1100 "Unable to query status due to errors: Error details were logged and redacted." UserInfo={NSLocalizedDescription=Unable to query status due to errors: Error details were logged and redacted., NSUnderlyingError=0x677037cf0 {Error Domain=com.apple.CipherML Code=1800 "Error details were logged and redacted." UserInfo={NSLocalizedDescription=Error details were logged and redacted.}}}>

Because I am also facing same issue and i found some more logs along with this error.

@Pushpak-Ambadkar123 hello,

yes, I've dug a lot further on this. The full client-side sequence (on-device, Console.app filtered by NEPIRChecker) looks like this before that error:

<NEPIRChecker>: -[NEPIRChecker start:responseQueue:completionHandler:] - Register with PIR Server (...) completed registration with PIR for Group ...
<NEPIRChecker>: -[NEPIRChecker start:responseQueue:completionHandler:]_block_invoke - PIR status returned error <Error Domain=com.apple.CipherML Code=1100 ...>

But the real root cause, if you also run your own PIR server and have access to its logs: on my server side, this traces back to an HPKE decapsulation failure in the OHTTP gateway

Crypto.CryptoKitError.underlyingCoreCryptoError(error: 503316581)

thrown from HPKE.Recipient(...)/.open(ct, authenticating:) when decrypting the incoming OHTTP request. I confirmed it's reproducible independently of the real client using a standalone HPKE test script, and that the failure rate seems to fluctuate with server uptime (worse after the process has been running a while) - pointing toward a possible concurrency issue around shared key material.

Are you running your own PIR server, or hitting this purely from the client side? If you have server logs too, it'd be great to compare notes; might help build an even stronger case for @DTS Engineer Quinn.

Written by RomainDropB in 912402022

Any idea what corecrypto error 503316581 typically indicates … ?

Well, that was fun.

I presume you’re running this server not on the Mac, and thus it’s using OpenSSL-based crypto. If that’s the case then you can follow this logic…

Error 503316581 is 0x1e000065. Within that, the first byte is the library and the bottom 12 bits are the error code. That means the library is 0x1e, or 30, or ERR_LIB_CIPHER, and the code is 0x065, or 101, or CIPHER_R_BAD_DECRYPT. My understanding is that this is coming out of an authenticated decryption algorithm, so I’m gonna presume that it means that the authentication part of that failed.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi @DTS Engineer Quinn

made real progress based on your BAD_DECRYPT lead, with a new, more specific finding.

I bypassed swift-crypto's HPKE.Recipient/HPKE.Sender entirely and implemented RFC 9180 base-mode HPKE manually using the lower-level primitives (Curve25519 key agreement, HKDF, AES-GCM) instead. Confirmed this resolves the platform-specific bug: a standalone HPKE test request I construct now decrypts successfully both locally on macOS and on the actual Fly.io/Linux deployment - identical result on both platforms now, where before it only worked on macOS.

However, real requests from the iOS device still fail - now with a cleaner, more specific error: a genuine AEAD authentication failure (HPKE open failed), not the generic library crash from before. So my implementation correctly handles a standard RFC 9180 request, but something about the actual request the device sends doesn't match what I'm computing.

Is there anything non-standard about how NEURLFilterManager/the PIR client constructs or routes these OHTTP requests that might not be obvious from the RFC - e.g., does it go through an Apple-operated OHTTP relay that re-encapsulates in transit, or is there a PIR-specific wrinkle in how info/aad are built that differs from generic OHTTP? Trying to figure out what to compare my reimplementation against, since I don't have visibility into the real device's raw request bytes.

Thanks again - your BAD_DECRYPT read got us this far.

Hi Quinn @DTS Engineer,

Just a gentle follow-up
It's been somes days since my last message and I haven't found a solution yet on my end either, so wanted to check in.

To recap where we're stuck: the manual HPKE reimplementation fixed the platform-specific Linux/OpenSSL CIPHER_R_BAD_DECRYPT issue you helped us trace (confirmed via self-constructed test requests succeeding consistently on both macOS and Linux now). But real requests coming from NEURLFilterManager on a physical iPhone still fail decapsulation with an AEAD authentication failure (HPKE open failed), while our own hand-built RFC 9458 requests against the same server succeed every time. So there's some remaining difference between how the real client constructs its OHTTP request and what we're decrypting on the server side that I haven't been able to identify from the spec alone.

I was considering setting up a MITM proxy (mitmproxy) with a trusted root certificate installed on the iPhone, to capture the actual raw OHTTP POST body the device sends to our gateway and compare it byte-for-byte against what our server expects. Before I sink a weekend into that:

  • Does that sound like a reasonable diagnostic approach to you, given NEURLFilterManager/the underlying network extension agent is a system-level component - is it likely to even respect a standard proxy/trust-store configuration, or does it bypass that layer entirely?

  • Is there anything non-standard about how Apple's real OHTTP client builds the request that wouldn't be obvious from RFC 9458/9180 alone - e.g. a different info/aad construction, an intermediate relay reshaping the request, or key rotation/caching behavior on the client side that could cause it to encrypt against a stale key config?

  • Or do you think I'm chasing the wrong thing entirely here, and the real issue is somewhere else (e.g. in how the key config or bloom prefilter is being fetched/cached on-device)?

Thanks again for your help on this.

Accepted Answer

Quick closing update on this one - tracked it down and fixed it.
Root cause: RFC 9458 §4.1 encapsulates requests with context.Seal("", request)
the AEAD's additional authenticated data is empty, the header only feeds into info and the wire envelope.
Our server was incorrectly using the header as the AEAD's AAD too, causing every real device request to fail authentication while our own (identically wrong) test script kept "succeeding" against it in lockstep, masking the bug for weeks. Fixed and deployed — the filter now shows "Running" on a real device for the first time. Thanks for your help narrowing down the Linux/OpenSSL HPKE issue earlier, that was a necessary step to get here.
Closing this thread & and thanks again for the help