Oct 18, 2023 Tags: cryptography, oss, security
This post was mostly written from an airplane. Please forgive any typos or small errors; I appreciate being notified of them and will make an effort to fix them.
Disclosure: I am professionally involved in the Sigstore community, and currently maintain Sigstore’s Python client. While I work on Sigstore professionally, the content below is my personal opinion and not necessarily the opinion of my employer, the Sigstore project, or anyone else in this general space.
Earlier this month, the Linux Foundation announced OpenPubKey as a new member project. You can read their announcement here. Some other resources, which have been around for a few weeks to months, came to my attention at around the same time:
I’ll start by saying that I think the idea behind OpenPubKey is extremely cool and demonstrates the (basic) workability of a technique (binding an ephemeral signing key to a semi-permanent identity in a globally verifiable way without additional trusted services) that I think is both extremely useful and powerful.
At the same time, I have concerns about how OpenPubKey’s privacy properties, its actual ability to provide reliable “keyless” signatures, and its compatibility with and implications for OIDC practices within IdPs. This post is an attempt to elaborate on those concerns.
Finally: I don’t believe these concerns are uniquely mine. There’s a post on the Sigstore blog that in particular covers some of the same privacy concerns, and probably others that I’ve missed.
At a very high level: Sigstore and OpenPubKey attempt to solve the same problem, namely: giving users the ability to produce signatures bound to a publicly verifiable identity. Doing this reliably solves two huge problems in “ordinary” distributed1 signing application:
Identity establishment. Traditional signing schemes punt identity verification to the end user, e.g. by expecting them to confirm that a certificate claiming to represent an entity is in fact controlled by that entity.
Binding key material directly to an identity credential sidesteps this: the user still needs to determine if they trust a particular identity, but an attacker can no longer easily2 impersonate that identity with key material that they wholly control.
Key management. Even when traditional signing schemes produce identity mappings that they are confident in, they must still perform manual key management: rotating out expired keys, periodically retrieving new keys for identities (and verifying them either in-band or out-of-band), &c. These tasks are manual, operationally complex, and error prone.
Using verifiable identities rather than the keys themselves sidesteps all of this and allows all parties to effectively be “keyless” at the end signature level: all private key material for signing operations can be discarded immediately, since binding a new ephemeral key to the same identity is relatively inexpensive.
Both Sigstore and OpenPubKey used OpenID Connect as their underlying source of identity and identity credentials, but their use of those credentials differs significantly:
Sigstore maintains a set of services, which it calls the “public good” instance. These services include a Certificate Authority (Fulcio) that’s responsible for accepting identity tokens, binding them to signing keys, and publishing the bound result as an X.509 certificate.
Sigstore also includes transparency services (a CT log for Fulcio itself, plus Rekor for artifact transparency) that are intended to keep Sigstore’s infrastructure auditable and honest, but using Sigstore does fundamentally involve placing trust in its CA.
OpenPubKey does not use a Certificate Authority, or any other external services besides the identity provider itself. Instead, it performs a trick (more on this in a bit) to bind a signing key directly to the identity credential produced by the identity provider.
Or, as a TL;DR: Sigstore’s design involves trusted (but transparent and publicly auditable) services that transform an identity credential and a public key into a new certificate, while OpenPubKey’s design involves producing an identity credential with a public key already bound to it.
As mentioned, OpenPubKey does not use a Certificate Authority or other form of indirection to bind a signing key to an identity token. Instead, the identity token itself is generated by the identity provider in a way that binds it to the key, and is directly shared with verifying parties (including potentially the public Internet).
This presents a problem: OIDC is intended to be a private credential format, with the expectation that only a few designated parties3 are trusted to receive and verify it.
Consequently, OIDC identity providers may include default claim values in their identity tokens that are neither required nor expected in a public key binding: things like secondary email addresses, service account identifiers, account statuses (e.g. whether using 2FA, whether suspended), gender, birth date, &c. OpenID Connect even includes a standard claim for mailing addresses!
More generally (and this will be a recurring theme), OpenID Connect providers are not bound to only exposing a particular set of claims over time: providers may (and, in practice, regularly do) choose to change their claim sets and claim contents over time. In other words: an identity credential that previously only exposed an email address could suddenly being exposing DOB and gender claims, with no advance notice (or particular visibility, unless the user is manually inspecting the JWT claims on each signing operation).
At best, this is extremely surprising: OpenPubKey users may believe that they’re only exposing a “sufficient” amount of identity information for verification purposes, when in reality they’re at the mercy of the (largely default) claim behavior of third-party IdPs.
At worst, this can be a serious privacy and security concern, especially if composed with a key transparency or logging scheme: a user seeking to make public signatures may accidentally end up permanently disclosing personal details (or account security posture information).
How does Sigstore fare, by contrast? It too exposes some OIDC claim values, but in a much more controlled manner: claims are filtered through per-IdP allowlists before being embedded in issued certificates. As a result, neither the entire claim set nor any surprise claim additions by an IdP ultimately surface in Sigstore-issued certificates. Critically, the identity token itself is never shared beyond Sigstore’s services.
As mentioned above: OpenPubKey essentially performs a hack to “trick” an identity provider into binding an identity credential to a user-controlled public key.
That “trick” is to stuff a public key identifier along with some other
fields4 into a pre-existing user-controllable claim. The whitepaper
mentions using the
nonce claim for this purpose, but the reference
implementation appears to use
This discrepancy isn’t particularly noteworthy, except for what it implies:
that the OpenPubKey developers discovered that many OIDC IdPs don’t actually
support a user-controlled
nonce at all, frequently because identity token
retrieval is done through a dedicated REST API endpoint rather than a full
More generally, however:
audclaim, in particular, is not always exposed as a fully user-controlled claim by many IdPs: it may instead be subject to an allowlist of audiences known to the OIDC provider, may have length or format restrictions, or may not even be a string (OIDC does not require this, and some IdPs prefer to encode it as an array of audiences).
Combined, these make for a somewhat shaky basis for a distributed verification
system. Sigstore sidesteps these issues again through its use of trusted
services: discrepancies between claim formats are handled by Fulcio’s use of
Dex, and Sigstore currently only requires
aud="sigstore" in terms of
deoting the public good instance as the intended audience.
I don’t have much to say about this, except for highlighting this section as probably a bad idea:
To enable verifying parties to enforce expiration if they so wish while avoiding the bad user experience of forcing the user to run the Authorization Code Flow every time the ID Token in their PK Token expires, we use the following alternative expiration mechanism. We do not expire a PK Token when the underlying ID Token expires according to its exp claim. Instead verifiers wishing to enforce expiration inspect the iat (issued at) claim, which specifies when the OP issued the ID Token, and reject the PK Token if it is older than two weeks. That is, if expiration is enforced, a PK Token expires two weeks after it is issued. (Page 9, S. 3.5.2)
Implicit expiration times like this are pretty dangerous, especially when describes in “may” language. See below for further thoughts on how this is likely to interact poorly with JWKS rotation practices, as well as difficult to reconcile with revocation.
I’ve put this section last because it isn’t fully clear to me that long-term signatures are an actual goal of the OpenPubKey design5. As such, the notes and concerns here may not be applicable. However, because the preprint mentions an “archival log” for handling verifications of older signatures, I felt that it’s appropriate to include my thoughts as well6.
One of the building blocks for OIDC is the JSON Web Key Set (JWKS) for a given
service; this set is (typically) found through a
.well-known discovery procedure
and is comprised of individual JSON Web Key-formatted public keys.
JWKS allows OIDC to sidestep the hard problem of timely, reliable key rotation: if an IdP discovers that their signing material is compromised, they can remove the corresponding public key from their JWKS and effectively revoke it for all current users7.
Notably, JSON Web Keys do not carry their own expiries, revealing the other edge of the sword: an IdP may choose to rotate their keys at any point, and are not required to adhere to any particular schedule or sensible cadence.
Given that an OIDC IdP can choose to rotate their keys at any time and thereby render the current “batch” of OpenPubKey-bound tokens invalid, how does OpenPubKey ensure that signatures remain verifiable? This is what the preprint says:
The main challenge facing archival verifiers is that the OP public key necessary to verify the PK Token may not longer be available at the OP’s JWKS endpoint. Most OP’s (sic) rotate their signing keys out of JWKS endpoint between every two weeks to a four times a year. To ensure the verifier has the OP’s public key for the PK Token sent with the OSMs, the verifier must create and maintain an archival log of OP public keys. This archival log must cover the time period that verifier wishes to verify PK Tokens over. The verifier builds this archival log by regularly downloading the public keys from the OP’s JWKS endpoint. The verifier not only records the public keys but also the time at which the public keys were downloaded. (Page 9, S. 3.5.3)
This scheme is concerning for three reasons:
Of these concerns, (2) is (in my opinion) the most serious: OIDC itself contains no real mechanisms for long-term revocation, because its underlying key establishment mechanism is intended to avoid those problems in the first place. By reintroducing long-term public key handling in the form of an (unverifiable?) archival log, OpenPubKey effectively subverts OIDC’s primary mechanism for handling key material compromise.
Again, I’d like to emphasize that I think the idea behind OpenPubKey is extremely interesting and fundamentally valuable10. Moreover, I believe it deserves further attention (and development).
At the same time, it’s my current opinion that the current design of OpenPubKey does not inspire confidence, particularly with respect to long-term verification: there appear to be a lot of hacks that assume that arbitrary third-party OIDC IdPs are relatively static, predictable, and permissive of claim shenanigans, hacks that OIDC IdPs themselves are unlikely to be willing to ossify into guarantees (or, more precisely, cannot ossify without compromising their security practices).
“Distributed” here just meaning “many independent signers, many independent verifiers.” ↩
Generally speaking, the attacker would need to (1) either control the identity themselves, or (2) compromise the identity provider for that identity. ↩
The audience, in OAuth and OIDC parlance ↩
The paper refers to these as the “Client Instance Claims,” reflecting the fact that they’re effectively subject claims made by the key holder and not the identity provider. ↩
Long-term verification is mentioned in the preprint, but is also contrasted with other use cases (like short-term verification, assuming that the window of verification does not overlap with JWKS rotation). ↩
The preprint also mentions the OpenID Connect UserInfo Verifiable Credentials draft standard, which would (as stated) eliminate the need for an archival log by verifying “signed” JWKs instead. This is conceptually appealing (since the “signed” JWKs are chained back up to the Web PKI), but raises separate logistical questions (the IdP must now use their end-entity certificate to sign JWKS, effectively an off-label purpose). ↩
In practice, some users may cache the JWKS, e.g. for as long as the HTTP caching headers dictate. But OIDC tokens themselves are short-lived, so this does not impede timely revocation substantially. ↩
And, if they wanted, OpenPubKey could use Rekor (or any other transparency log service) for exactly this purpose. But this would require reintroducing one of Sigstore’s largest online dependencies, begging the question of why not to just use Sigstore outright. ↩
On another read-through, they receive a short mention in Appendix C (page 18). This appendix says that a verifier “can” use this approach, but doesn’t (to my reading) do a good job of explaining the security benefits (and disadvantages) to doing so. The appendix also mentions that “OP public keys are available on the JWKS URI for at least one week,” which (to my knowledge) is not a guarantee that any OIDC IdP will make (or should make, because it would hamper post-compromise recovery). ↩
In the sense that exists in contrast to a scheme like Sigstore, which contains multiple online parties with subtle trust relationships between them. ↩