Skip to content

Add client certificate management extension#584

Open
emersion wants to merge 1 commit into
ircv3:masterfrom
emersion:client-cert
Open

Add client certificate management extension#584
emersion wants to merge 1 commit into
ircv3:masterfrom
emersion:client-cert

Conversation

@emersion

@emersion emersion commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

This has been implemented as a vendor soju extension: https://codeberg.org/emersion/soju/src/branch/master/doc/ext/client-cert.md

Closes ircv3/ircv3-ideas#90

This has been implemented as a vendor soju extension:
https://codeberg.org/emersion/soju/src/branch/master/doc/ext/client-cert.md

References: ircv3/ircv3-ideas#90
Co-authored-by: Simon Ser <contact@emersion.fr>
Comment thread extensions/client-cert.md

C: CLIENTCERT LIST
S: :server BATCH +asdf draft/client-cert
S: @batch=ok CLIENTCERT LIST F70EC120E1603A58F5979A36221630D92AC75DBAEF8C205B5BB94463E1FBB73BB8E77172BB4CCC4B17FA9F7162AFA80F4E865798DF6CDE7F30E50B0362180E82 name=senpai\son\slinux

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be @batch=asdf?

Also, why is the tag-encoded data at the end of the line not in actual tags?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be @Batch=asdf?

Indeed.

why is the tag-encoded data at the end of the line not in actual tags?

The alternative would be to register each message tag, e.g. @batch=asdf;draft/client-cert-name=senpai\son\slinux CLIENTCERT LIST F70EC1…, which is longer and reads awkwardly.

@jwheare jwheare Feb 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you foresee a lot more of these key/values being added in future? I'd think it would be far more convenient for implementations to be able to lean on their existing tag parsing functionality than to add special case parsing in the rest of the message.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This format is easy to parse for clients, since they already have an appropriate parser for message tags. This format is already used by soju.im/bouncer-networks and soju.im/search.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name could also just be the trailing param, then no special parsing is needed: @batch=asdf CLIENTCERT LIST F70EC120E1603A58F5979A36221630D92AC75DBAEF8C205B5BB94463E1FBB73BB8E77172BB4CCC4B17FA9F7162AFA80F4E865798DF6CDE7F30E50B0362180E82 <ip> <time> :senpai on linux

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then the command is not extensible anymore.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any extensions would need to define new functionality anyway, at which point the new client cap would enable an additional parameter, or tag, or whatever. Tags wouldn't even need a new client cap. Those same issues would still exist with the current version (not able to add more data to the command without a new cap), but this way gets rid of needing special parsers and makes use of native IRC parsing without extra effort.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any extensions would need to define new functionality anyway, at which point the new client cap would enable an additional parameter, or tag, or whatever.

The spec as-is is extensible without introducing a new cap. Servers can add a new attribute without breaking clients, and clients can use the new attribute when it exists. No negotiation required.

gets rid of needing special parsers and makes use of native IRC parsing without extra effort

There isn't a special parser. This is the exact same parser as message-tags, which all clients already have implemented.

@skizzerz skizzerz Feb 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing in this spec requires message tags, so it's entirely feasible or reasonable for a client to implement this spec while not supporting message tags.

Putting encoded values in the message text itself along with the keys also hurts because it encroaches upon the 512 byte limit a lot faster. We're already using a lot of bytes on making the subcommand be part of every LIST line, along with a full sha-512 fingerprint (which I note isn't extensible in case a different hash algo comes around in the future, and some networks/services may use fingerprints other than sha-512 for certfp). Plus it just largely breaks IRC convention with how data is formatted/presented. I'm really not a fan of the current language and formatting and would very much rather see a different solution here. A WHOX-style query would be another option while remaining extensible; client specifies which fields they want in the response and the server responds with the selected fields in a well-defined ordering. (Edit: or as jwhere suggested just put them into actual tags)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skizzerz

Nothing in this spec requires message tags

Though not (yet?) required, batches require tag support (even without the message-tags cap).

@emersion

There isn't a special parser. This is the exact same parser as message-tags, which all clients already have implemented.

It's not a special format for implementations that already parse tags, but it is special parsing, because those implementations are not expecting that format to appear in positional arguments. Additional parsing logic is needed to decode them (even if that logic involves a decoder used elswhere)

By contrast, tags where tags belong will be parsed already for free by implementations that support tags. It might be "easy" to decode, but that doesn't make it correct.

This format is already used by soju.im/bouncer-networks and soju.im/search

Use by vendored specs doesn't really count as a precedent for an ircv3 spec.

Comment thread extensions/client-cert.md
Before sending any `CLIENTCERT` command, both following conditions MUST be
fulfilled:

- the `draft/client-cert` capability is successfully negotiated,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the advantage of defining a new client capability here? There is nothing in the spec that would involve the server sending unsolicited messages using these new commands, it's all just replies to the client sending the CLIENTCERT command. A server could advertise support for this command via a new ISUPPORT token instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Servers can send unsolicited CLIENTCERT commands, e.g. when the user updates certs via NickServ or similar, or when another client issues a CLIENTCERT command. See the quote below.
  2. Specs defining new commands generally define a new cap even if such commands are not sent unsolicited. For instance chathistory.

The server MAY send the CREATE subcommand on their own. The client SHOULD
handle receiving unprompted CREATE subcommands and remember it can
authenticate using SASL EXTERNAL in future IRC sessions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, missed that quote. If it can be sent unsolicited then yes a cap is needed. There is a real cost to adding new caps to a server, so they should not be added "by default" -- only when actually necessary. For example, I've recently worked on expanding cap code in solanum to be a 64-bit bitfield instead of 32-bit (meaning the maximum number of caps it supports is 63 instead of 31; one bit is reserved for detecting if we've hit the limit), but if every spec adds a new cap I can see even 63 becoming insufficient eventually sooner rather than later.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the phrasing here:

The client SHOULD handle receiving unprompted CREATE subcommands and remember it can authenticate using SASL EXTERNAL in future IRC sessions.

It seems unlikely that the client will possess that particular certificate if it wasn't already aware of it?

Comment thread extensions/client-cert.md

If the `batch` capability was negotiated, the server MUST reply to a `LIST`
subcommand using a single batch of type `draft/client-cert`. If the list of
pinned certificates is empty, the server MUST reply with an empty batch.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the server reply to an empty list if batch is NOT negotiated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it can't. I suppose we could just make batch required? Would that be too harsh and close the door to older servers?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a NOTE message may be appropriate in this case if we want to keep the batch dependency optional. e.g. NOTE CLIENTCERT NO_FINGERPRINTS. That way the client has something they can listen to instead of just timing out or something and figuring no response = no fingerprints to list.

If we go the NOTE route, would we want that to be the response even if batch is negotiated? Or should the no fingerprints case differ based on whether batch is enabled?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make batch mandatory, IMO

@progval progval left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea, thanks! I'll try to implement it in Limnoria as soon as a server supports it because that will be great for usability

Comment thread extensions/client-cert.md
Comment on lines +212 to +217
If the server fails to delete a pinned certificate for any other reason, it MUST
send a standard failure message of the following format:

```
FAIL CLIENTCERT INTERNAL_ERROR DELETE :...
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a bit strict. Can we at least add an other code for deletion rejected by policy? (eg. servers that don't allow clients to not have a cert or require some sort of confirmation to avoid being locked out)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the use-case for a separate code? How would client react differently?

@progval progval Feb 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

INTERNAL_ERROR implies it can be retried later (eg. because the server's DB connection is flaky). Either automatically, or with the client suggesting the users does it.

@emersion emersion mentioned this pull request Mar 22, 2026
Comment thread extensions/client-cert.md
Such certificates are called "pinned certificates".

Each pinned certificate is identified by what this document calls its
fingerprint. The fingerprint of a TLS client certificate is the SHA-512 digest

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason to choose SHA-512 here? I think most existing implementations (Unreal, Insp, Ergo) hardcode or default to SHA-256. (It's not an insuperable obstacle, just a source of friction, and I don't see a security concern given that this only needs second preimage resistance, which is unbroken even for MD5, let alone SHA-256.)

@sadiepowell sadiepowell Mar 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I don't see the need to define the hash or encoding method tbh. It can just be an opaque string allowing the IRCd and services to choose the encoding.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It saves the client from potentially having to store an association between the certificate and the server-pushed identifier...it's also more resilient to server software changes. (Not that these are decisive arguments, but I can see the point of specifying the hash algorithm.)

Comment thread extensions/client-cert.md
- `time`: the time of the last successful authentication attempt using the
pinned certificate. The value MUST follow the format of time values defined by
the [server-time] extension.
- `ip`: the IP of the last client that successfully authenticated using the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would servers which support TLS over UNIX sockets handle this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it is specified as MAY – but I assume this field is for user display purposes only? so if I were writing such an ircd, I'd ignore the fact that it's named ip and would have the server send a string like (local user $UID on $HOSTNAME) or unix/$UID@$HOSTNAME or something similar (i.e. socket peercred, maybe with a getpwuid lookup even). Or 0.0.0.0 as is tradition.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ergo falls back to 127.0.0.1 in this case if it doesn't have a proxied IP. Are there real use cases for TLS over Unix domain (without proxying)?

@sadiepowell sadiepowell Mar 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its a niche use case but its also something we support (InspIRCd's TLS support is entirely origin-agnostic) and we'd need to special case it here as users' client address is their UNIX socket path. Ideally the spec should be vague about the protocol used so servers can put whatever origin want in the field imo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add extension to manage CertFP

8 participants