Skip to content

Add draft authtoken spec#602

Open
skizzerz wants to merge 3 commits into
ircv3:masterfrom
skizzerz:authtoken
Open

Add draft authtoken spec#602
skizzerz wants to merge 3 commits into
ircv3:masterfrom
skizzerz:authtoken

Conversation

@skizzerz

Copy link
Copy Markdown
Contributor

This spec aims to be a replacement for the EXTJWT pull request by specifying a more general mechanism for generating and validating authentication tokens. Unlike EXTJWT, this does not limit the tokens to the JWT format, instead strongly preferring single-use OTPs (although JWTs are still permissible and supported by the spec).

Validation occurs through the IRC protocol as well, to reduce implementation burden on servers (it's just another command, rather than needing to spin up an httpd or other external service).

This spec aims to be a replacement for the EXTJWT pull request by
specifying a more general mechanism for generating and validating
authentication tokens. Unlike EXTJWT, this does not limit the tokens to
the JWT format, instead strongly preferring single-use OTPs (although
JWTs are still permissible and supported by the spec).

Validation occurs through the IRC protocol as well, to reduce
implementation burden on servers (it's just another command, rather than
needing to spin up an httpd or other external service).

Co-authored-by: whitequark <whitequark@whitequark.org>
@whitequark

Copy link
Copy Markdown

Ergochat has a PR implementing draft/AUTHTOKEN (ergochat/ergo#2385), I'll let @slingamn comment on it though.

@slingamn slingamn 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.

I updated my PR implementation. Overall I really like this spec!

Comment thread extensions/authtoken.md Outdated
Comment thread extensions/authtoken.md Outdated
Comment thread extensions/authtoken.md Outdated
Comment thread extensions/authtoken.md Outdated
Comment thread extensions/authtoken.md Outdated
Comment thread extensions/authtoken.md Outdated

- The service must validate that the token is intended for that service, to avoid token confusion attacks. Using URL-based validation (i.e. checking that one of the claims of the token matches the URL of the service) is a good way to do this. For JWT, this would be the "aud" claim.
- The service must validate that the token came from the IRC server it is expecting tokens to come from. For JWT, this would be the "iss" claim.
- The service must validate that the token is signed by the IRC server with a secure algorithm and that the signature is valid. The signing keys (recommended) or secrets (not recommended) should be pre-shared out of band.

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.

Is "secrets" intended to refer to symmetric signing keys used by, e.g. HS256? Discouraging this seems unwarranted to me.

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.

Yes, this section was going over what claims should be present in tokens that are non-interactively validated (i.e. the service is not using TOKEN VALIDATE). In such an event, HS256 or similar symmetric algorithms means that a secret key is being shared between multiple parties, since the specification makes no assumption that the ircd and external service are being run by the same operators. This in turn means that the external service could spoof its own properly-signed tokens without the ircd's input, for whatever that might allow it to do (in theory not much, but the risk exists that there are now multiple parties able to issue valid signed tokens).

If the ircd is intending for interactive validation only (i.e. external services must use TOKEN VALIDATE to get the claims list), then HS256 is perfectly appropriate because the secret key only lives in one place: on the ircd. Doing public key in such scenarios would be overkill and unnecessary.

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.

Makes sense. I was thinking that if you're doing one key per service, there would be no real disadvantage to using a symmetric secret key. But actually, with a symmetric key, the service would be able to sign its own tokens and then submit them for online validation, possibly learning some user data via TOKEN CLAIM that it might not normally be able to access. (If alice uses QDB and but bob never does, QDB would have normally have access to alice's TOKEN CLAIM outputs, but not bob's.) Probably not a big deal, but a real consideration.

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.

This suggests another hardening recommendation for JWTs: if interactive validation is not needed, it can be administratively disabled. And then there's really no problem with HS256 with unique keys: the service can forge whatever token it wants, but it's the only system that will accept the token.

If interactive validation is required, I think a unique asymmetric keypair per service should work (the ircd has the private key, the service has the public key, the general public has nothing).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This in turn means that the external service could spoof its own properly-signed tokens without the ircd's input

Is it realistic to expect there would be services that are hosted by a potentially unstrusted 3rd-party and yet require the IRCd as authorisation provider? That's a weird combo if the IRCd isn't a generic auth provider, and external services instead have to be manually added to the SERVICELIST by network operators.

What is the intent of this spec? Allowing networks to host additional services with SSO but without HTTP-dependent standards like OIDC, or is it turning networks into generic decentralised authentication providers, like a IRC-based Open-ID clone?

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.

The intent is for federated authentication and authorization with allowlisted services, which may or may not be run by 3rd parties. Partnering with a 3rd party for some service doesn't imply a full-trust relationship, and it makes sense to limit the blast radius of potential security issues as much as possible in the event that the 3rd party is compromised. HTTP-based mechanisms are not specified because IRC is not HTTP, and there are likely going to be non-HTTP-based services that wish to integrate with IRC for authn/authz decisions in this manner.

By specifying both the token generation and token validation layers, these services do not need to ship a validation implementation on a per-ircd basis. This increases interoperability by allowing network operators to choose services based on criteria other than "do they support the specific magic incantations needed to talk to the ircd I'm running"

As a note, while JWTs are popular (which is why there is a lot of space devoted to talking about them), the spec recommends single-use randomly generated OTPs and interactive validation. These are significantly more secure, more compact, and much easier to implement. The JWT discussion is primarily around how to avoid the numerous footguns that the JWT specification comes with and have resulted in security vulnerabilities in other services, without the implementer needing to be an expert in JWT security and/or having done all of that prior research. There are a lot of really bad JWT implementations out there and I don't want this spec to be another one in that pile.

Comment thread extensions/authtoken.md
Comment thread extensions/authtoken.md
BATCH -12345
```

### VALIDATE subcommand (batched)

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.

Ingesting C2S-batched VALIDATE was the hardest implementation step for me. I'm not sure there's a better option overall, but I wanted to air this issue: C2S batches might seem like they simplify server ingestion of multiline requests, but I don't think they do, because each type of C2S batch will have its own application-specific constraints, length limits, and abuse risks, so you can't really write a generic "ingest C2S batch" handler. (For example, in the Ergo implementation, we turn off fakelag when a draft/multiline batch is in progress, so we need to make sure we turn it back on on every fail path that ends the 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.

Another pain point for me was splitting input validation and permissions checking logic between the BATCH and VALIDATE handlers.

This would actually be cleaner for me if it were just:

VALIDATE TOKEN <servicename> <single-line token under 300 bytes>

or else

VALIDATE TOKEN <servicename> <multi-line token chunk exactly 300 bytes>
[repeat zero or more times]
VALIDATE TOKEN <servicename> <final token chunk under 300 bytes, possibly 0 bytes>

I invite comment as to whether this is cleaner in general, or just an idiosyncratic constraint imposed by my implementation :-) I would note in passing that VALIDATE TOKEN is not intended for use in "normal" IRC traffic, so the full extensibility of batch (e.g. allowing nested batches, allowing interspersed messages that are not part of the batch) is not directly relevant.

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.

My solanum C2S batch implementation involves a generic handler that works across every registered batch type to collect the batched lines together and fire off whatever function is supposed to handle the batch once the entire thing is received, so your inability to do that is certainly an ergo limitation rather than an inherent limitation of C2S batches (this is just an observation; I'm not saying that your argument lacks merit). For reference/comparison, solanum doesn't turn off fakelag for C2S batches. It might take a while to send it but once fully sent it gets burst out all at once.

I originally had it worded to be like AUTHENTICATE where each intermediate line was a fixed byte count and the final line was the leftovers (or blank), however I felt that C2S batches were a more elegant solution that requires fewer bespoke per-command multiline handlers on the client and server side -- AUTHENTICATE is 400 bytes, this could be 400 or might be 300, something else may choose some other limit, etc. I'd also like to drive more adoption of C2S batches in general as a useful framing tool for things that take up more than a line.

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.

Hmm, from the n=2 sample I have, I'm not convinced that C2S batching is a savings on implementation complexity. From my point of view, the server implementation still needs (1) custom validation rules, per batch type, about what can or can't be in the batch (2) custom length limits (which also have to be batch-type-aware). And then there generally seems to be a conservation effect, where any validation or limiting logic you try to make generic at the ingestion stage has to be made up later. (For example, you could try to implement generic length limits based on total byte size of the messages being queued, but you have to apply the real length limit later after doing whatever application-specific aggregation is required.)

One of the things that makes C2S batching compelling for draft/multiline is the interaction with labeled-response and echo-message (the client labels the initial BATCH line, then gets a labeled echo message after the whole batch is complete). What are some of the other use cases you're thinking about for C2S batching?

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 are some of the other use cases you're thinking about for C2S batching?

Rich content (aka "embeds" in discord or "blocks" in slack) is the main one I have in mind. Basically, a message that contains some title, content, optional image, optional buttons along the bottom, etc. I don't have anything written down for that yet but C2S batch would be the natural way of sending that and collecting all of those disparate things together as a single message.

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.

The other way of doing that is just a client tag. e.g. ircv3/ircv3-ideas#22 although the fallback mentioned there could potentially be batched along with it.

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.

I guess also if we wanted to go over 4 KB we would have to batch it?

- Reword a couple of sections to make motivations clearer.
- Make SERVICELIST the canonical place for services to live, avoiding
  duplication in ISUPPORT or CAP. The draft/authtoken cap was made a
  requirement to support this feature, although the C2S batch that it
  enables remains optional.
- Make SERVICELIST replies use a batch instead of NOTE for the standard
  case.
Comment thread extensions/authtoken.md
A user sends `TOKEN SERVICELIST` on a network where no services are configured. As such, they receive a note that no services are defined.

```
NOTE TOKEN NO_SERVICES :No services are defined for this network.

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.

Would TOKEN NO_SERVICES :No services are defined (i.e. NO_SERVICES as a new subcommand of TOKEN) make it easier for clients to route this message to a handler that clears the service list?

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 wouldn't be easier necessarily, but I agree that a NOTE might not be the right reply, since NOTE seems to generally be informational, but I do feel that using a Standard Reply here is right, perhaps WARN instead? Slightly more actionable than a NOTE in terms of client handling

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.

From a client handling perspective, I would imagine that they would clear the service list as part of sending the TOKEN SERVICELIST command, and therefore by not getting any batch as a response the list simply remains empty. This is further made easier if the server supports the draft/authtoken CAP as then the client will receive TOKEN NEW and TOKEN DEL notifications as the service list changes provided it enables that CAP.

The NOTE is purely informational and is the appropriate standard-reply (compared to WARN/FAIL) because a network having zero services isn't really an error condition (which is what WARN/FAIL both imply -- warn for non-fatal errors and FAIL for fatal errors).

If it would help client implementations, I could see us doing an empty batch instead or in addition to the NOTE, but I'd like client authors to chime in if that's the case.

Comment thread extensions/authtoken.md Outdated
TOKEN VALIDATE, TOKEN GENERATE, and the draft/authtoken batch previously
took a URL parameter which was meant to be validated server-side.
However, the spec currently enforces a 1:1 mapping between service key
and URL, so this additional validation was not really adding any value.
It did not protect against malicious clients either as the value was
fully user-specified. As such, remove it from these places, which in
turn gives a lot more space for the token to fit onto a single line.

Because URLs are now only exposed in SERVICELIST (and NEW
notifications), also remove the restriction on limiting URLs to 250
bytes.
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.

8 participants