Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP: Peer ID Authentication #564

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open

Conversation

MarcoPolo
Copy link
Contributor

@MarcoPolo MarcoPolo commented Aug 2, 2023

I've removed the peer id authentication scheme from the core spec of #508 for a couple reasons:

  1. Auth is optional, the MVP may not include it.
  2. This needs a bit more discussion since this is the first time we are doing a peer id auth scheme that relies on web PKI.
  3. I've also changed the auth scheme to not use Noise because we didn't get as much code reuse as I was hoping for and I think it adds unnecessary complexity to this. It's now a more traditional challenge-response scheme similar to SSH auth and WebAuthn and the first version of this auth.

On 2, this is the first time we are doing a peer id auth scheme that relies on web PKI, and thus has slightly different security properties than what we've done in the past. Instead of tying a peer id to an underlying encrypted channel we are tying it to a domain name. If the client can't trust the domain name (e.g. has enterprise root CAs installed) then their connection can be mitmd. In practice I don't think this is a serious concern because:

  • If a 3rd party has installed root CAs on a client, they probably also have root on the client. Thus they don't have to mitm, since they own the client.
  • If web PKI breaks, it will be very obvious since many other things will break. Libp2p users will still be able to use a different authentication scheme.

go-libp2p implementation: libp2p/go-libp2p#2854

http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
Copy link
Contributor

@thomaseizinger thomaseizinger left a comment

Choose a reason for hiding this comment

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

Thanks Marco!

A few thoughts. I think we could split this into two flows: Libp2p-Challenge-Client and Libp2p-Challenge-Server and specify that they can be combined as necessary or used standalone.

http/peer-id-auth.md Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
@MarcoPolo
Copy link
Contributor Author

I've update the spec with a couple changes:

  • A single auth scheme with parameters to control who is authenticated. This allows for mutual, client only, and server only auth.
  • Change the wording to allow for using a server-encrypted value as the challenge-client value.

Copy link
Contributor

@thomaseizinger thomaseizinger left a comment

Choose a reason for hiding this comment

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

Great work! I've made a few more comments but this looks sound from my end :)

http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
@MarcoPolo MarcoPolo mentioned this pull request Mar 18, 2024
Base automatically changed from http to master June 13, 2024 20:54
http/peer-id-auth.md Outdated Show resolved Hide resolved
* The client-chosen `challenge-server` SHOULD be at least 32 bytes.
* The client MUST use the same server-name as what is used for the TLS
session.
* If the client _only_ wants to authenticate the server and the server does
Copy link
Member

Choose a reason for hiding this comment

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

Can we make this a separate section?
Mutual Authentication
Client Only Authentication
Server Only Authentication

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense. Maybe an overview section and then separate sections for specifics.

Message To Sign in hex:
126f726967696e3d6578616d706c652e636f6d3d636c69656e742d6368616c6c656e67653d7171717171717171717171717171717171717171717171717171717171717171717171717171717171716f3d366368616c6c656e67653d414141414141414141414141414141414141414141414141414141414141414141414141414141414141413d
```
2. The server MUST verify the signature using the server name used in the TLS
Copy link
Member

Choose a reason for hiding this comment

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

How does the server get the client's public key corresponding to the peer id? I assume that's what the client is supposed to sign with to prove its peer id.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. If it can't be embedded in the peer id, the client/server should send it as a field in the header.

http/peer-id-auth.md Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
http/peer-id-auth.md Outdated Show resolved Hide resolved
Authorization: libp2p-PeerID peer-id=<string-representation-of-client-peer-id>, opaque=<opaque-from-server>, challenge-server=<base64-encoded-challenge-server>, sig=<base64-signature-bytes>
```

The `sig` param represents a signature over the parameters:
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice to include the certificate (or public key?) hash, if possible. That way, if the client has some way of validating it they can not rely on the CA system and instead validate the certificate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This could be easy to do, but I'm not sure it would be used. The optimal solution here is to authenticate with some TLS exported key material (RFC 5705). That would bind the peer id to the TLS session and can happen with no additional round trips. If a peer can access the certificate they likely can access the key material exporter.

The only problem with this approach is that the key material exporters aren't yet exposed by browsers. But I expect that to change soon-ish as https://datatracker.ietf.org/doc/draft-ietf-httpbis-unprompted-auth/ gets published and implemented by browsers.

My thought here is to focus on the browser use case, and, if it would be used and useful, create a new spec for the exported key material scenario.

Copy link
Member

Choose a reason for hiding this comment

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

The optimal solution here is to authenticate with some TLS exported key material (RFC 5705). That would bind the peer id to the TLS session and can happen with no additional round trips. If a peer can access the certificate they likely can access the key material exporter.

I agree, but that's likely harder to get at.

My thought here is to focus on the browser use case, and, if it would be used and useful, create a new spec for the exported key material scenario.

What about making the format extensible? I.e., allow the server to sign multiple things that can be extended later?

Copy link
Member

Choose a reason for hiding this comment

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

I agree, but that's likely harder to get at.

Ah, I see. That's what that proposal is about. Nice!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What about making the format extensible? I.e., allow the server to sign multiple things that can be extended later?

There is room in this spec to extend this later. A future version could for example define a new parameter that is passed by the client. Example: a SessionID parameter which is derived from keying material from the tls session just for this purpose, the server would check that it matches the expected SessionID it sees or fail the request.

Copy link
Member

Choose a reason for hiding this comment

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

This has to be covered by a signature. I.e., either:

  1. It's covered by the client signature and the server refuses to authenticate if it doesn't match
  2. It's covered by the server's signature and the client rejects the authentication if it doesn't match.

I want to make sure there's some sane upgrade path where we can add support for this later.

Copy link
Contributor Author

@MarcoPolo MarcoPolo Aug 21, 2024

Choose a reason for hiding this comment

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

Yes, sorry. The signature MUST include the SessionID as part of the parameters it signs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to be clear with the example for a future extension here:

  1. Each side derives a TLSSessionID using TLS' Keying Material exporters in a TBD way.
  2. Each side passes that as a parameter to the libp2p-PeerID auth scheme. And includes it as parameter in the signature.
  3. Each side MUST verify the provided TLSSessionID matches the expected value (as well as verifying the signature as before).
  4. Either side MUST fail the authentication if there is a mismatch.

Both sides need to sign and compare it.

Comment on lines 83 to 84
- `hostname`
- `challenge-client` in its base64 encoded form.
- `challenge-client`
Copy link
Member

Choose a reason for hiding this comment

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

If possible, I'd sign everything the client sent us (basically, each step of the handshake should witness the previous step).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is what the client signs and the server has sent (challenge-client is the challenge for the client). The only thing here not signed that was sent by the server is opaque field. Why do you think that needs to be signed?

On the other side, the only thing the server doesn't sign is the returned opaque field as well. (and the string form of the peer id, but the server MUST verify that is equivalent/derived to/from the public key bytes we do sign)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would you prefer if the wording were changed to say something like "all parameters must be covered by the signature except for the opaque parameter, and the peer-id parameter (as it's covered by the public key bytes)"?

@MarcoPolo
Copy link
Contributor Author

I'm considering making one more change to this spec: Allow the client to send a challenge in the initial request. This would allow the client to authenticate the server on the first round trip, and to send application data after only one round trip. It is similar in spirit to how the noise XX handshake works where the responder (server in our case) is authenticated after the first round trip.

I can't think of any compelling reason to not allow this, and many reasons to support it. The main reason being that in cases you know you need to authenticate, it removes a round trip.

Are there any compelling cases you can think of @Stebalien, @sukunrt, @aschmahmann?


Essentially supporting this flow:

sequenceDiagram
    C->>S: challenge-server
    S-->>C: challenge-client+server-sig [server authenticated]
    C->>S: client-sig + application data [client authenticated]
    S-->>C: resp
Loading

Alongside what is currently defined:

sequenceDiagram
    C->>S: initial request
    S-->>C: challenge-client
    C->>S: client-sig+challenge-server [client authenticated]
    S-->>C: server-sig [server authenticated]
    C->>S: application data
    S-->>C: resp
Loading

@Stebalien
Copy link
Member

I agree, the client should send its challenge in the first message.

http/peer-id-auth.md Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Todo
Development

Successfully merging this pull request may close these issues.

5 participants