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

Add optional secret for authenticating clients #1

Merged
merged 4 commits into from
Apr 8, 2022
Merged

Add optional secret for authenticating clients #1

merged 4 commits into from
Apr 8, 2022

Conversation

jtroo
Copy link
Contributor

@jtroo jtroo commented Apr 7, 2022

No description provided.

Copy link
Owner

@ekzhang ekzhang left a comment

Choose a reason for hiding this comment

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

Thanks for implementing this! It looks really great and quite cleanly implemented. I just have a couple minor comments.

I feel like passing a secret in this case would just be a very minor layer of authentication, since security is not particularly critical for this server, so passing it via CLI is probably fine + convenient (despite the issues of having passwords as command-line arguments).

However, I do want to have the protocol be correct if we add this feature. And in this case I believe the encryption does not add additional benefits, since an onlooker would still be able to access an encrypted secret that they could replay with the same nonce. I have two differing suggestions for how to adjust, and I'm open to either security model:

  • Send the secret in plaintext. I believe this is as secure as the current approach but would be simpler / has less code and dependencies.
  • Extend the protocol to have the server challenge with a random nonce first. This is more complex but is actually secure against TCP snooping (in that bystanders cannot learn how to connect to the server).

Also if we are assuming that someone can snoop on TCP connections, then it may be worth sending the authentication during Accept messages as well, otherwise a third party could intercept someone / accept the connection if they see the UUID.

README.md Outdated
Comment on lines 94 to 97
The client and server can optionally use secrets to protect the server from
being used by others. The secret is sent only by the client as encrypted base64
by using itself as a key with a randomly generated nonce. No additional traffic
is encrypted; only the secret.
Copy link
Owner

Choose a reason for hiding this comment

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

What benefit does encrypting the secret provide here? It seems like any client could replay the same encrypted secret + nonce combination, so this doesn't provide any security benefit beyond a plaintext exchange of secrets.

Copy link
Contributor Author

@jtroo jtroo Apr 7, 2022

Choose a reason for hiding this comment

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

Absolutely, I forgot to consider replay attacks. I'll add the server challenge to make encryption actually work properly, shouldn't be too hard to do.

src/auth.rs Outdated
Comment on lines 21 to 23
if sec_bytes.len() > KEY_LEN {
bail!("secret must be 32 bytes or fewer");
}
Copy link
Owner

Choose a reason for hiding this comment

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

Is it possible to just pass the secret through a SHA-2 digest, as the seed of an PRNG, or some other one-way key generation approach that removes the 32 byte restriction? That might be a slightly nicer interface.

src/client.rs Outdated
@@ -26,14 +27,23 @@ pub struct Client {

impl Client {
/// Create a new client.
pub async fn new(local_port: u16, to: &str, port: u16) -> Result<Self> {
pub async fn new(local_port: u16, to: &str, port: u16, secret: Option<String>) -> Result<Self> {
Copy link
Owner

Choose a reason for hiding this comment

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

Could be Option<&str> for consistency.

src/server.rs Outdated
Some(ClientMessage::Hello((port, secret))) => {
if let Err(e) = self.authenticate(secret) {
send_json(&mut stream, ServerMessage::ClientError(e.into())).await?;
return Ok(());
Copy link
Owner

Choose a reason for hiding this comment

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

Could this be given a more descriptive name like Unauthenticated or Invalid rather than ClientError?

src/server.rs Outdated
(Some(key), Some(cln_sec)) => {
if auth::secrets_match(&key, &cln_sec).is_err() {
warn!("incorrect secret provided");
Err("incorrect secret".into())
Copy link
Owner

Choose a reason for hiding this comment

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

.into() is a no-op for these Err variants

src/shared.rs Outdated
@@ -13,7 +13,7 @@ pub const CONTROL_PORT: u16 = 7835;
#[derive(Serialize, Deserialize)]
pub enum ClientMessage {
/// Initial client message specifying a port to forward.
Hello(u16),
Hello((u16, Option<String>)),
Copy link
Owner

Choose a reason for hiding this comment

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

This can just be a standard Hello(u16, Option<String>) variant, doesn't need the nested tuple!

Cargo.toml Outdated
@@ -25,3 +25,6 @@ tokio = { version = "1.17.0", features = ["full"] }
tracing = "0.1.32"
tracing-subscriber = "0.3.10"
uuid = { version = "0.8.2", features = ["serde", "v4"] }
chacha20poly1305 = { version = "0.9.0" }
rand = { version = "0.8.5" }
base64 = { version = "0.13.0" }
Copy link
Owner

Choose a reason for hiding this comment

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

Very minor but sort imports?

@ekzhang
Copy link
Owner

ekzhang commented Apr 8, 2022

Thank you for the contribution! I've changed the code fairly substantially to adjust the protocol and:

  1. Make it backwards compatible with v0.1
  2. Use HMAC instead of AEAD (I understand this algorithm better)
  3. Simplify semantics, minimize changes to client.rs and server.rs

Now it is only 400 lines of code instead of 600 lines of code.

@ekzhang ekzhang merged commit d5089ca into ekzhang:main Apr 8, 2022
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.

2 participants