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

libp2phttp: HTTP Peer ID Authentication #2854

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

Conversation

MarcoPolo
Copy link
Collaborator

This enables HTTP peers to authenticate each other's peer ID. This would allow users to use an http transport that has a peer id component (e.g. /dns/example.com/http/p2p/12Foo). I think it's nice to have this for completeness so that an http transport has the same semantics as a libp2p stream transport when doing HTTP with regard to Peer IDs.

There's more testing I want to do here, but I think this is more or less ready for a review.

For a high level overview of the authentication protocol refer to the overview in the spec: https://github.com/libp2p/specs/blob/45006f17d2fa0cede50b2db2311a55061011a3fc/http/peer-id-auth.md#mutual-client-and-server-peer-id-authentication-overview

@MarcoPolo

This comment was marked as outdated.

p2p/http/auth/client.go Outdated Show resolved Hide resolved
p2p/http/auth/client.go Outdated Show resolved Hide resolved
p2p/http/auth/client.go Outdated Show resolved Hide resolved
p2p/http/auth/auth.go Outdated Show resolved Hide resolved
p2p/http/auth/client.go Outdated Show resolved Hide resolved
p2p/http/auth/server.go Outdated Show resolved Hide resolved
p2p/http/auth/server.go Outdated Show resolved Hide resolved
p2p/http/auth/server.go Outdated Show resolved Hide resolved
p2p/http/auth/server.go Outdated Show resolved Hide resolved
p2p/http/auth/server.go Outdated Show resolved Hide resolved
@MarcoPolo
Copy link
Collaborator Author

MarcoPolo commented Aug 28, 2024

I've completely refactored this. The handshake logic is now neatly in internal/handshake. The Client API is simpler, and the Server API changed a little bit (in a way that makes it hopefully easier to use). The server uses HMAC to authenticate the opaque and the token. And it's also close to 10x faster (still could be improved, but this might be good enough for now).

The main missing thing is the test that generates the examples for the spec. I'll work on that next, but otherwise I think this is ready. Done.

when you get a chance, could I get a review here and in the spec libp2p/specs#564 @sukunrt .

p2p/http/auth/auth.go Show resolved Hide resolved
p2p/http/auth/client.go Outdated Show resolved Hide resolved
}
resp.Body.Close()

err = handshake.ParseHeader(resp.Header)
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to set the status code first?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

On the client? Or do you mean assert the status code is 401?

p2p/http/auth/internal/handshake/client.go Outdated Show resolved Hide resolved
Comment on lines 85 to 164
originalBody := req.Body

handshake.Run()
handshake.SetHeader(req.Header)

// Don't send the body before we've authenticated the server
req.Body = nil
resp, err := client.Do(req)
if err != nil {
return "", nil, err
}
resp.Body.Close()

err = handshake.ParseHeader(resp.Header)
if err != nil {
return "", nil, fmt.Errorf("failed to parse auth header: %w", err)
}
err = handshake.Run()
if err != nil {
return "", nil, fmt.Errorf("failed to run handshake: %w", err)
}

serverWasAuthenticated := false
_, err = handshake.PeerID()
if err == nil {
serverWasAuthenticated = true
}

req = req.Clone(req.Context())
if serverWasAuthenticated {
req.Body = originalBody
} else {
// Don't send the body before we've authenticated the server
req.Body = nil
}
handshake.SetHeader(req.Header)
resp, err = client.Do(req)
if err != nil {
return "", nil, fmt.Errorf("failed to do authenticated request: %w", err)
}

err = handshake.ParseHeader(resp.Header)
if err != nil {
resp.Body.Close()
return "", nil, fmt.Errorf("failed to parse auth info header: %w", err)
}
err = handshake.Run()
if err != nil {
resp.Body.Close()
return "", nil, fmt.Errorf("failed to run auth info handshake: %w", err)
}

serverPeerID, err := handshake.PeerID()
if err != nil {
resp.Body.Close()
return "", nil, fmt.Errorf("failed to get server's peer ID: %w", err)
}
a.tokenMapMu.Lock()
a.tokenMap[hostname] = tokenInfo{
token: handshake.BearerToken(),
insertedAt: time.Now(),
peerID: serverPeerID,
}
a.tokenMapMu.Unlock()

if serverWasAuthenticated {
return serverPeerID, resp, nil
}

// Server wasn't authenticated earlier.
// We need to make one final request with the body now that we authenticated
// the server.
req = req.Clone(req.Context())
req.Body = originalBody
handshake.SetHeader(req.Header)
resp, err = client.Do(req)
if err != nil {
return "", nil, fmt.Errorf("failed to do authenticated request: %w", err)
}
return serverPeerID, resp, nil
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a comment before each of the steps? This runs both Server initiated and client initiated handshake making things a bit complicated, comments would aid readability here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've changed this to be a for loop instead since it's all the same thing.

@sukunrt
Copy link
Member

sukunrt commented Sep 8, 2024

The new spec is much nicer! Thanks @MarcoPolo

p2p/http/auth/client.go Outdated 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
None yet
Development

Successfully merging this pull request may close these issues.

3 participants