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 support for SCRAM-SHA-* #5

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

singpolyma
Copy link

No -PLUS yet, no support for channel binding.
Also supports SCRAM-* for any OpenSSL-supported digest function with just one
line of code in the future.

No -PLUS yet, no support for channel binding.
Also supports SCRAM-* for any OpenSSL-supported digest function with just one
line of code in the future.
@nevans
Copy link
Owner

nevans commented May 21, 2022

Thanks! This is great!

I haven't really given this project the attention I'd hoped. But I'm glad someone else sees the value in it. I'll take a closer look at all three PRs next week!

Copy link
Owner

@nevans nevans left a comment

Choose a reason for hiding this comment

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

FYI, I merged most of your changes with a bunch of updates of my own in some ruby/net-imap branches. I've pushed one PR but I'll send in some others that correspond to all of your other PRs.

Adding in a pure-ruby saslprep has been the main thing holding me back from finishing all this. The other big thing holding me back is that I have some fundamental (but backwards-compatible) API changes I needed to make, so the SASL gem can more generally useful than the embedded net/imap API: standardizing properties across all mechanisms, and property callbacks. I wanted to finish than before I could merge your PRs. Actually, I've used your PRs to help guide the design choices.

Once I'm happy with the net-imap PRs, I'll merge all of the changes back into net-sasl. I'll keep your PRs open until then. :) Thanks!

@@ -28,5 +28,6 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]

spec.add_dependency "digest"
spec.add_dependency "idn-ruby"
Copy link
Owner

Choose a reason for hiding this comment

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

I'd prefer a pure-ruby saslprep, to avoid dependencies. I'm hoping to replace the SASL implementations in net/imap and net/pop and net/smtp, which are all bundled gems. Adding another dependency might prevent that from happening; especially a C dependency, which isn't ideal for for JRuby and TruffleRuby.

My plan is to allow a swap-able saslprep library, for performance. But that will need benchmarks.

Copy link
Owner

@nevans nevans left a comment

Choose a reason for hiding this comment

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

FWIW, I wrote this up a while back, but never submitted the review! Oops. I recently finished a pure ruby SASLprep (I want some more test-cases before I'm happy with it), which generates Regexps directly from the stringprep RFC's tables.

I think I'll have time to address all of these comments before the end of the week!


# responds to the server's challenges
def process(challenge)
return "n,#{'a=' + @authzid if @authzid},#{initial_message}" if challenge.nil?
Copy link
Owner

Choose a reason for hiding this comment

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

We can't rely on nil, because not every protocol supports initial response. E.g. IMAP4rev1 without the SASL-IR extension. Per the SCRAM RFC, it should just be an empty string in that case. But it's still better to just explicitly track the state, IMO. The first time the method is called, challenge will be ignored and the initial response will be sent.


bare = "c=biws,r=#{sparams['r']}"
salted_password = OpenSSL::KDF.pbkdf2_hmac(
IDN::Stringprep.with_profile(@password.encode("utf-8"), "SASLprep"),
Copy link
Owner

Choose a reason for hiding this comment

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

I have a pure ruby stdlib implementation of SASLprep in mind for this but it isn't ready yet. nearly done, but not quite. The RFC does give us an official way to cheat: only allow ASCII strings. ;) So this could just guard raise unless password.ascii_only? and I'd replace it with my Net::SASL::SASLprep.saslprep when it's done.

protected

def initial_message
"n=#{@username},r=#{@cnonce}"
Copy link
Owner

Choose a reason for hiding this comment

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

we should do some error checking or formatting on these values, here or in #initialize? eg "\0" or "," chars.

add_authenticator "SCRAM-SHA-224", ScramAuthenticator.for("SHA224")
add_authenticator "SCRAM-SHA-256", ScramAuthenticator.for("SHA256")
add_authenticator "SCRAM-SHA-384", ScramAuthenticator.for("SHA384")
add_authenticator "SCRAM-SHA-512", ScramAuthenticator.for("SHA512")
Copy link
Owner

@nevans nevans Jul 19, 2022

Choose a reason for hiding this comment

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

I like that it only takes one line of code. But, even though plugging in alternate hashes is fairly straightforward and predictable, I'd prefer to only include official SASL mechanisms in the default set. I think that the IETF drafts for 512 (or at least, the ones that I knew about) recently expired, although I haven't researched why.

For any that are left out, we can include documentation so that users can add it themselves, if they really need it sooner.

# This should generally be instantiated via Net::SASL.authenticator.
def initialize(username, password, authzid = nil, hash:, **options)
super
@hash = OpenSSL::Digest.new(hash)
Copy link
Owner

@nevans nevans Jul 19, 2022

Choose a reason for hiding this comment

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

I think I'd prefer to define hash on the subclasses. It loses the nice one liner for new SCRAM-* mechanisms, but it'll be simpler and only like... three lines.

class SCRAMAuthenticator
  def hash; @hash ||= OpenSSL::Digest.new(self.class::HASH) end
end

# Defined in RFC5802, etc etc etc
class SCRAM256Authenticator < SCRAMAuthenticator
  HASH = "SHA256"
end

The one-liner could be recreated with only a little bit of extra work. But I'm not sure it's worth it, especially because I prefer an explicitly named class constant to which we can add rdoc for each specific mechanism.

@Neustradamus
Copy link

@nevans: What is the status of this PR (net-sasl)?

cc: @hsbt, @shugo.

nevans added a commit to ruby/net-imap that referenced this pull request Sep 7, 2023
Based on the implementation by @singpolyma at
nevans/net-sasl#5

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 10, 2023
Based on the implementation by @singpolyma at
nevans/net-sasl#5

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 10, 2023
Based on the implementation by @singpolyma at
nevans/net-sasl#5

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 11, 2023
Based on the implementation by @singpolyma at
nevans/net-sasl#5

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 12, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 12, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 12, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 13, 2023
Based on the implementation by @singpolyma at
nevans/net-sasl#5

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 15, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 16, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

New authenticators for any digest algorithms supported can be added by
subclassing ScramAuthenticator and adding a DIGEST_NAME constant (and
then registering with an Authenticators registry).

Co-authored-by: Stephen Paul Weber <[email protected]>
@nevans
Copy link
Owner

nevans commented Sep 16, 2023

I know it's taken me forever to finish this, but ruby/net-imap#172 should be merged shortly. After net-imap 0.4.0 is released (probably this week?), I'll copy all of those updates back here.

I still need to fix net-imap so it ensures the authenticator is #done?. As it is now, if the server returns OK before sending the final message, then we can't satisfy the following requirement from the RFC:

The client then authenticates the server by computing the
ServerSignature and comparing it to the value sent by the server. If
the two are different, the client MUST consider the authentication
exchange to be unsuccessful, and it might have to drop the
connection.

nevans added a commit to ruby/net-imap that referenced this pull request Sep 18, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

New authenticators for any digest algorithms supported can be added by
subclassing ScramAuthenticator and adding a DIGEST_NAME constant (and
then registering with an Authenticators registry).

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to nevans/net-imap that referenced this pull request Sep 20, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

New authenticators for any digest algorithms supported can be added by
subclassing ScramAuthenticator and adding a DIGEST_NAME constant (and
then registering with an Authenticators registry).

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 20, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

New authenticators for any digest algorithms supported can be added by
subclassing ScramAuthenticator and adding a DIGEST_NAME constant (and
then registering with an Authenticators registry).

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 21, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

New authenticators for any digest algorithms supported can be added by
subclassing ScramAuthenticator and adding a DIGEST_NAME constant (and
then registering with an Authenticators registry).

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 23, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

New authenticators for any digest algorithms supported can be added by
subclassing ScramAuthenticator and adding a DIGEST_NAME constant (and
then registering with an Authenticators registry).

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 24, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

New authenticators for any digest algorithms supported can be added by
subclassing ScramAuthenticator and adding a DIGEST_NAME constant (and
then registering with an Authenticators registry).

Co-authored-by: Stephen Paul Weber <[email protected]>
nevans added a commit to ruby/net-imap that referenced this pull request Sep 24, 2023
Loosely based on the implementation by @singpolyma at
nevans/net-sasl#5

New authenticators for any digest algorithms supported can be added by
subclassing ScramAuthenticator and adding a DIGEST_NAME constant (and
then registering with an Authenticators registry).

Co-authored-by: Stephen Paul Weber <[email protected]>
@Neustradamus
Copy link

@nevans: Good job for your work in net-imap!

But it is not possible to have an official net-sasl?
Some projects or people need net-sasl but not net-imap...

cc: @singpolyma.

@nevans
Copy link
Owner

nevans commented Sep 25, 2023

I'll update this gem with the latest net-imap implementation some time "soonish" (after net-imap v0.4.0 is released)

@nevans
Copy link
Owner

nevans commented Sep 27, 2023

But it is not possible to have an official net-sasl?
Some projects or people need net-sasl but not net-imap...

That's the goal, but it would add a new bundled gem to ruby. I wanted to release the basic implementation in the context of one project and then port it to at least one other before I seriously advance that proposal.

I've actually been using a fork of net-smtp in production for several months and I'm hoping to finish PRs for net-smtp and net-pop this week. If the maintainers of those projects are happy with it, then maybe there's a reasonable chance it might make it into ruby 3.3. 🤞🏻

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