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 SASL SCRAM-SHA-* mechanisms #172

Merged
merged 1 commit into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,17 @@ def starttls(**options)
#
# Login using clear-text username and password.
#
# +SCRAM-SHA-1+::
# +SCRAM-SHA-256+::
# See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator].
#
# Login by username and password. The password is not sent to the
# server but is used in a salted challenge/response exchange.
# +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
# Net::IMAP::SASL. New authenticators can easily be added for any other
# <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
# OpenSSL::Digest.
#
# +XOAUTH2+::
# See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator].
#
Expand Down
15 changes: 15 additions & 0 deletions lib/net/imap/sasl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ class IMAP
#
# Login using clear-text username and password.
#
# +SCRAM-SHA-1+::
# +SCRAM-SHA-256+::
# See ScramAuthenticator.
#
# Login by username and password. The password is not sent to the
# server but is used in a salted challenge/response exchange.
# +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
# Net::IMAP::SASL. New authenticators can easily be added for any other
# <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
# OpenSSL::Digest.
#
# +XOAUTH2+::
# See XOAuth2Authenticator.
#
Expand Down Expand Up @@ -114,11 +125,15 @@ module SASL
sasl_dir = File.expand_path("sasl", __dir__)
autoload :Authenticators, "#{sasl_dir}/authenticators"
autoload :GS2Header, "#{sasl_dir}/gs2_header"
autoload :ScramAlgorithm, "#{sasl_dir}/scram_algorithm"

autoload :AnonymousAuthenticator, "#{sasl_dir}/anonymous_authenticator"
autoload :ExternalAuthenticator, "#{sasl_dir}/external_authenticator"
autoload :OAuthBearerAuthenticator, "#{sasl_dir}/oauthbearer_authenticator"
autoload :PlainAuthenticator, "#{sasl_dir}/plain_authenticator"
autoload :ScramAuthenticator, "#{sasl_dir}/scram_authenticator"
autoload :ScramSHA1Authenticator, "#{sasl_dir}/scram_authenticator"
autoload :ScramSHA256Authenticator, "#{sasl_dir}/scram_authenticator"
autoload :XOAuth2Authenticator, "#{sasl_dir}/xoauth2_authenticator"

autoload :CramMD5Authenticator, "#{sasl_dir}/cram_md5_authenticator"
Expand Down
2 changes: 2 additions & 0 deletions lib/net/imap/sasl/authenticators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ def initialize(use_defaults: false)
add_authenticator "External"
add_authenticator "OAuthBearer"
add_authenticator "Plain"
add_authenticator "Scram-SHA-1"
add_authenticator "Scram-SHA-256"
add_authenticator "XOAuth2"
add_authenticator "Login" # deprecated
add_authenticator "Cram-MD5" # deprecated
Expand Down
1 change: 1 addition & 0 deletions lib/net/imap/sasl/gs2_header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module SASL
# several different mechanisms start with a GS2 header:
# * +GS2-*+ --- RFC5801[https://tools.ietf.org/html/rfc5801]
# * +SCRAM-*+ --- RFC5802[https://tools.ietf.org/html/rfc5802]
# (ScramAuthenticator)
# * +SAML20+ --- RFC6595[https://tools.ietf.org/html/rfc6595]
# * +OPENID20+ --- RFC6616[https://tools.ietf.org/html/rfc6616]
# * +OAUTH10A+ --- RFC7628[https://tools.ietf.org/html/rfc7628]
Expand Down
58 changes: 58 additions & 0 deletions lib/net/imap/sasl/scram_algorithm.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

module Net
class IMAP
module SASL

# For method descriptions,
# see {RFC5802 §2.2}[https://www.rfc-editor.org/rfc/rfc5802#section-2.2]
# and {RFC5802 §3}[https://www.rfc-editor.org/rfc/rfc5802#section-3].
module ScramAlgorithm
def Normalize(str) SASL.saslprep(str) end

def Hi(str, salt, iterations)
length = digest.digest_length
OpenSSL::KDF.pbkdf2_hmac(
str,
salt: salt,
iterations: iterations,
length: length,
hash: digest,
)
end

def H(str) digest.digest str end

def HMAC(key, data) OpenSSL::HMAC.digest(digest, key, data) end

def XOR(str1, str2)
str1.unpack("C*")
.zip(str2.unpack("C*"))
.map {|a, b| a ^ b }
.pack("C*")
end

def auth_message
[
client_first_message_bare,
server_first_message,
client_final_message_without_proof,
]
.join(",")
end

def salted_password
Hi(Normalize(password), salt, iterations)
end

def client_key; HMAC(salted_password, "Client Key") end
def server_key; HMAC(salted_password, "Server Key") end
def stored_key; H(client_key) end
def client_signature; HMAC(stored_key, auth_message) end
def server_signature; HMAC(server_key, auth_message) end
def client_proof; XOR(client_key, client_signature) end
end

end
end
end
Loading