diff --git a/.yardopts b/.yardopts new file mode 100644 index 0000000..e06e717 --- /dev/null +++ b/.yardopts @@ -0,0 +1 @@ +--no-private lib/**/*.rb - README.md LICENSE.txt CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..1fa5cd9 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,49 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of +fostering an open and welcoming community, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating +documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free +experience for everyone, regardless of level of experience, gender, gender +identity and expression, sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic + addresses, without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to +fairly and consistently applying these principles to every aspect of managing +this project. Project maintainers who do not follow or enforce the Code of +Conduct may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting a project maintainer at glenn@rempe.us. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. Maintainers are +obligated to maintain confidentiality with regard to the reporter of an +incident. + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.3.0, available at +[http://contributor-covenant.org/version/1/3/0/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/3/0/ diff --git a/lib/sirp/client.rb b/lib/sirp/client.rb index 630143a..6237882 100644 --- a/lib/sirp/client.rb +++ b/lib/sirp/client.rb @@ -3,21 +3,30 @@ class Client include SIRP attr_reader :N, :g, :k, :a, :A, :S, :K, :M, :H_AMK, :hash + # @param group [Integer] the group size in bits def initialize(group = 2048) # select modulus (N) and generator (g) @N, @g, @hash = Ng(group) @k = calc_k(@N, @g, hash) end + # Phase 1 : Start the authentication process by generating the 'A' value + # and send it, along with the username, to the server. + # + # @return [String] the value of 'A' in hex def start_authentication # Generate a/A private and public components @a ||= SecureRandom.hex(32).hex @A = num_to_hex(calc_A(@a, @N, @g)) end - # Process initiated authentication challenge. - # Returns M if authentication is successful, false otherwise. - # Salt and B should be given in hex. + # Phase 1 : Process the salt and B value provided by the server. + # + # @param username [String] the client provided authentication username + # @param password [String] the client provided authentication password + # @param xsalt [String] the server provided salt for the username in hex + # @param xbb [String] the server verifier 'B' value in hex + # @return [String] the client 'M' value in hex def process_challenge(username, password, xsalt, xbb) bb = xbb.to_i(16) @@ -43,6 +52,14 @@ def process_challenge(username, password, xsalt, xbb) @M end + # Phase 2 : Verify that the server provided H(A,M,K) value matches + # the client generated version. This is the last step of mutual + # authentication and confirms that the client and server have + # completed the auth process. + # + # @param server_HAMK [String] the server provided H_AMK in hex + # @return [true,false] returns true if the server and client agree on the + # H_AMK value, false if not def verify(server_HAMK) return false unless @H_AMK # Secure constant time comparison, hash the params to ensure diff --git a/lib/sirp/sirp.rb b/lib/sirp/sirp.rb index 20a35fe..a5f5da8 100644 --- a/lib/sirp/sirp.rb +++ b/lib/sirp/sirp.rb @@ -19,14 +19,14 @@ def sha_str(s, hash_klass) end # Constant time string comparison. - # Extracted from Rack::Utils - # https://github.com/rack/rack/blob/master/lib/rack/utils.rb + # Extracted from Rack::Utils + # https://github.com/rack/rack/blob/master/lib/rack/utils.rb # - # NOTE: the values compared should be of fixed length, such as strings - # that have already been processed by HMAC. This should not be used - # on variable length plaintext strings because it could leak length info - # via timing attacks. The user provided value should always be passed - # in as the second parameter so as not to leak info about the secret. + # NOTE: the values compared should be of fixed length, such as strings + # that have already been processed by HMAC. This should not be used + # on variable length plaintext strings because it could leak length info + # via timing attacks. The user provided value should always be passed + # in as the second parameter so as not to leak info about the secret. # # @param a [String] the private value # @param b [String] the user provided value diff --git a/lib/sirp/verifier.rb b/lib/sirp/verifier.rb index 9e4c61f..7ac56e3 100644 --- a/lib/sirp/verifier.rb +++ b/lib/sirp/verifier.rb @@ -9,9 +9,17 @@ def initialize(group = 2048) @k = calc_k(@N, @g, hash) end - # Initial user creation for the persistance layer. - # Not part of the authentication process. - # Returns { , , } + # Phase 0 ; Generate a verifier and salt client-side. This should be + # used during the initial user registration process. All three values + # should be provided as attributes in the user registration process. The + # verifier and salt should be persisted server-side. The verifier + # should be protected and never made public or returned to the user. + # The salt should be returned to the user to start Phase 1 of the + # authentication process. + # + # @param username [String] the authentication username + # @param password [String] the authentication password + # @return [Hash] a Hash of the username, verifier, and salt def generate_userauth(username, password) @salt ||= SecureRandom.hex(10) x = calc_x(username, password, @salt, hash) @@ -19,9 +27,14 @@ def generate_userauth(username, password) { username: username, verifier: num_to_hex(v), salt: @salt } end - # Authentication phase 1 - create challenge. - # Returns Hash with challenge for client and proof to be stored on server. - # Parameters should be given in hex. + # Phase 1 - Create a challenge for the client, and a proof to be stored + # on the server for later use when verifying the client response. + # + # @param username [String] the client provided authentication username + # @param xverifier [String] the server stored verifier for the username in hex + # @param xsalt [String] the server stored salt for the username in hex + # @param xaa [String] the client provided 'A' value in hex + # @return [Hash] a Hash with the challenge for the client and a proof for the server def get_challenge_and_proof(username, xverifier, xsalt, xaa) # SRP-6a safety check return false if (xaa.to_i(16) % @N).zero? @@ -34,9 +47,20 @@ def get_challenge_and_proof(username, xverifier, xsalt, xaa) } end - # returns H_AMK on success, false on failure - # User -> Host: M = H(H(N) xor H(g), H(I), s, A, B, K) - # Host -> User: H(A, M, K) + # Phase 2 - Use the server stored proof and the client provided 'M' value. + # Calculates a server 'M' value and compares it to the client provided one, + # and if they match the client and server have negotiated equal secrets. + # Returns a H(A, M, K) value on success and false on failure. + # + # Sets the @K value, which is the client and server negotiated secret key + # if verification succeeds. This can be used to derive strong encryption keys + # for later use. The client independently calculates the same @K value as well. + # + # If authentication fails the H_AMK value must not be provided to the client. + # + # @param proof [Hash] the server stored proof Hash with keys A, B, b, I, s, v + # @param client_M [String] the client provided 'M' value in hex + # @return [String, false] the H_AMK value in hex for the client, or false if verification failed def verify_session(proof, client_M) @A = proof[:A] @B = proof[:B] @@ -65,8 +89,8 @@ def verify_session(proof, client_M) end end - # generates challenge - # input verifier in hex + # @param xverifier [String] the server stored verifier for the username in hex + # @return [String] the B value in hex def generate_B(xverifier) v = xverifier.to_i(16) @b ||= SecureRandom.hex(32).hex