Add client certificate management extension#584
Conversation
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>
|
|
||
| C: CLIENTCERT LIST | ||
| S: :server BATCH +asdf draft/client-cert | ||
| S: @batch=ok CLIENTCERT LIST F70EC120E1603A58F5979A36221630D92AC75DBAEF8C205B5BB94463E1FBB73BB8E77172BB4CCC4B17FA9F7162AFA80F4E865798DF6CDE7F30E50B0362180E82 name=senpai\son\slinux |
There was a problem hiding this comment.
Should this be @batch=asdf?
Also, why is the tag-encoded data at the end of the line not in actual tags?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Then the command is not extensible anymore.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Nothing in this spec requires message tags
Though not (yet?) required, batches require tag support (even without the message-tags cap).
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.
| Before sending any `CLIENTCERT` command, both following conditions MUST be | ||
| fulfilled: | ||
|
|
||
| - the `draft/client-cert` capability is successfully negotiated, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
- 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.
- 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
CREATEsubcommand on their own. The client SHOULD
handle receiving unpromptedCREATEsubcommands and remember it can
authenticate using SASL EXTERNAL in future IRC sessions.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
|
|
||
| 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. |
There was a problem hiding this comment.
How does the server reply to an empty list if batch is NOT negotiated?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
progval
left a comment
There was a problem hiding this comment.
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
| 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 :... | ||
| ``` |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
What's the use-case for a separate code? How would client react differently?
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.)
| - `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 |
There was a problem hiding this comment.
How would servers which support TLS over UNIX sockets handle this?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
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