sirp 2.0.0.pre → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 09ad55f348720fdef0cd77d90bc9f166db80cc59
4
- data.tar.gz: 0b664333742d32311c3e6a8b3269cdd482fa395f
3
+ metadata.gz: 5c52dff2045e6a63899115731fa0b6b47c211e83
4
+ data.tar.gz: b39d228ae76c0afb76e228203025ef9c3ebfd33d
5
5
  SHA512:
6
- metadata.gz: d89cecbbf3761e596eba3953adcb22af3ca25507068d0186728a6febb3fc72e6549885b04fa202d25eef736eab066eed22cdc59b4f2dfc59193bbd4a29a513c4
7
- data.tar.gz: 9e0b5e09f86ec07eb5db4ad2c61509749ae7e38c36645b39101fa21ce2dd0ddb6692a984e9a6896ba61e6cbe376172ba0a14beaa698b7c268954d7e82e13c1b4
6
+ metadata.gz: e13201e91458fd8f6d69e70482038a37007bd8faff54ca83c2b4ffa87be1f4906a49480ef3256152dde8b18fd1955566dbfc4b733ad1257fe159077aa8456f06
7
+ data.tar.gz: 2f129346aa66933aecbdb7ed76d26e32ac67a423a03ae291e3d995c9e8c1de6a3786bfa1c7975556f3d0d648da5097a88635aa2eff8dae544df1c2c121a58fe0
Binary file
data.tar.gz.sig CHANGED
Binary file
data/.gitignore CHANGED
@@ -9,3 +9,5 @@
9
9
  /tmp/
10
10
  /examples/.bundle/
11
11
  /examples/Gemfile.lock
12
+ .DS_Store
13
+
@@ -30,3 +30,7 @@ Style/FormatString:
30
30
 
31
31
  Style/MethodName:
32
32
  Enabled: false
33
+
34
+ Style/NumericLiterals:
35
+ Enabled: false
36
+
@@ -0,0 +1 @@
1
+ --no-private lib/**/*.rb - README.md LICENSE.txt CODE_OF_CONDUCT.md
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v2.0.0 (9/20/2016)
4
+
5
+ Initial release after shake-down in a real app.
6
+
3
7
  ## v2.0.0.pre (5/13/2016)
4
8
 
5
9
  This is the initial pre-release of the sirp gem.
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at glenn@rempe.us. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/README.md CHANGED
@@ -9,29 +9,47 @@
9
9
 
10
10
  Ruby Docs : [http://www.rubydoc.info/gems/sirp](http://www.rubydoc.info/gems/sirp)
11
11
 
12
-
13
12
  This is a pure Ruby implementation of the
14
13
  [Secure Remote Password](http://srp.stanford.edu/) protocol (SRP-6a),
15
14
  which is a 'zero-knowledge' mutual authentication system.
16
15
 
17
- SiRP is an authentication method that allows the use of user names and passwords
18
- over an insecure network connection without revealing the password. If either the
19
- client lacks the user's password or the server lacks the proper verification
20
- key, the authentication will fail. This approach is much more secure than the
21
- vast majority of authentication systems in daily use since the password is
22
- ***never*** sent over the wire, and is therefore impossible to intercept, and
23
- impossible to be revealed in a breach unless the verifier can be reversed. This
24
- attack would be of similar difficulty as deriving a private encryption key from
25
- its public key.
16
+ SRP is an protocol that allows for mutual authentication of a client and
17
+ server over an insecure network connection without revealing the password to the
18
+ server or an eavesdropper. If the client lacks the user's password, or the server
19
+ lacks the proper verification key, the authentication will fail. This approach
20
+ is much more secure than the vast majority of authentication systems in common use
21
+ since the password is never sent over the wire. The password is impossible to
22
+ intercept, or to be revealed in a server breach, unless the verifier can be
23
+ reversed. Since the verifier is derived from the password + salt through
24
+ cryptographic one-way hash functions and Modular Exponentiation. Attacking the
25
+ verifier to retrieve a password would be of similar difficulty
26
+ as deriving a private encryption key from its public key. Extremely difficult, if
27
+ not impossible.
26
28
 
27
29
  Unlike other common challenge-response authentication protocols, such as
28
- Kerberos and SSL, SiRP does not rely on an external infrastructure of trusted
30
+ Kerberos and SSL, SRP does not rely on an external infrastructure of trusted
29
31
  key servers or complex certificate management.
30
32
 
33
+ At the end of the authentication process both the client and the server will have
34
+ negotiated a shared strong encryption key suitable for encrypted session
35
+ communications. This key is negotiated through a modified Diffie-Hellman
36
+ key exchange and the key is never sent over the wire.
37
+
38
+ SiRP is designed to be interoperable with a Ruby client and server, or
39
+ with Ruby on the server side, and the [JSRP](https://github.com/alax/jsrp)
40
+ Javascript client running in a browser.
41
+
42
+ ## Live Demo
43
+
44
+ You can try out an interactive demo at
45
+ [https://sirp-demo.herokuapp.com/index.html](https://sirp-demo.herokuapp.com/index.html).
46
+
47
+ [Demo Source Code @ grempe/sirp-demo](https://github.com/grempe/sirp-demo)
48
+
31
49
  ## Documentation
32
50
 
33
51
  There is pretty extensive inline documentation. You can view the latest
34
- auto-generated docs at [http://www.rubydoc.info/gems/sirp](http://www.rubydoc.info/gems/sirp)
52
+ API docs at [http://www.rubydoc.info/gems/sirp](http://www.rubydoc.info/gems/sirp)
35
53
 
36
54
  You can check my documentation quality score at
37
55
  [http://inch-ci.org/github/grempe/sirp](http://inch-ci.org/github/grempe/sirp?branch=master)
@@ -40,9 +58,11 @@ You can check my documentation quality score at
40
58
 
41
59
  SiRP is continuously integration tested on the following Ruby VMs:
42
60
 
43
- * MRI 2.1, 2.2, 2.3
61
+ * MRI 2.1
62
+ * MRI 2.2
63
+ * MRI 2.3
44
64
 
45
- It may work on others as well.
65
+ Ruby versions < 2.1 are not supported.
46
66
 
47
67
  ## Installation
48
68
 
@@ -119,11 +139,83 @@ compliant third-party libraries:
119
139
 
120
140
  [JSRP / JavaScript](https://github.com/alax/jsrp)
121
141
 
142
+ ## SRP-6a Protocol Design
143
+
144
+ Extracted from [http://srp.stanford.edu/design.html](http://srp.stanford.edu/design.html)
145
+
146
+ ```
147
+ SRP is the newest addition to a new class of strong authentication protocols
148
+ that resist all the well-known passive and active attacks over the network.
149
+ SRP borrows some elements from other key-exchange and identification protcols
150
+ and adds some subtle modifications and refinements. The result is a protocol
151
+ that preserves the strength and efficiency of the EKE family protocols while
152
+ fixing some of their shortcomings.
153
+
154
+ The following is a description of SRP-6 and 6a, the latest versions of SRP:
155
+
156
+ N A large safe prime (N = 2q+1, where q is prime)
157
+ All arithmetic is done modulo N.
158
+ g A generator modulo N
159
+ k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6)
160
+ s User's salt
161
+ I Username
162
+ p Cleartext Password
163
+ H() One-way hash function
164
+ ^ (Modular) Exponentiation
165
+ u Random scrambling parameter
166
+ a,b Secret ephemeral values
167
+ A,B Public ephemeral values
168
+ x Private key (derived from p and s)
169
+ v Password verifier
170
+
171
+ The host stores passwords using the following formula:
172
+
173
+ x = H(s, p) (s is chosen randomly)
174
+ v = g^x (computes password verifier)
175
+
176
+ The host then keeps {I, s, v} in its password database. The authentication
177
+ protocol itself goes as follows:
178
+
179
+ User -> Host: I, A = g^a (identifies self, a = random number)
180
+ Host -> User: s, B = kv + g^b (sends salt, b = random number)
181
+
182
+ Both: u = H(A, B)
183
+
184
+ User: x = H(s, p) (user enters password)
185
+ User: S = (B - kg^x) ^ (a + ux) (computes session key)
186
+ User: K = H(S)
187
+
188
+ Host: S = (Av^u) ^ b (computes session key)
189
+ Host: K = H(S)
190
+
191
+ Now the two parties have a shared, strong session key K. To complete
192
+ authentication, they need to prove to each other that their keys match.
193
+ One possible way:
194
+
195
+ User -> Host: M = H(H(N) xor H(g), H(I), s, A, B, K)
196
+ Host -> User: H(A, M, K)
197
+
198
+ The two parties also employ the following safeguards:
199
+
200
+ * The user will abort if he receives B == 0 (mod N) or u == 0.
201
+ * The host will abort if it detects that A == 0 (mod N).
202
+ * The user must show his proof of K first. If the server detects that the
203
+ user's proof is incorrect, it must abort without showing its own proof of K.
204
+ ```
205
+
122
206
  ## Usage Example
123
207
 
124
208
  In this example the client and server steps are interleaved for demonstration
125
- purposes. See the `examples` dir for simple working client and server
126
- implementations.
209
+ purposes. See the [grempe/sirp-demo](https://github.com/grempe/sirp-demo)
210
+ repository for working sample code and a live demo. The phases of authentication
211
+ in this example are delineated by the HTTPS request/response between client and
212
+ server. The concept of 'phases' is something noted here for convenience. The
213
+ specification makes no mention of phases since it is implementation specific.
214
+
215
+ This example is useful for showing the ordering and arguments in the public
216
+ API and is not intended to be a 'copy & paste' code sample since the
217
+ client and server interaction is something left up to the implementer and likely
218
+ different in every case.
127
219
 
128
220
  ``` ruby
129
221
  require 'sirp'
@@ -132,22 +224,42 @@ username = 'user'
132
224
  password = 'password'
133
225
  prime_length = 2048
134
226
 
135
- # The salt and verifier should be stored on the server database.
227
+ # ~~~ Phase 0 : User Registration ~~~
228
+
229
+ # One time only! SRP is a form of TOFU (Trust On First Use) authentication
230
+ # where all is predicated on the client being able to register a verifier
231
+ # with the server upon initial registration. The server promises in turn to
232
+ # keep this verifier secret and never reveal it. If this first interaction
233
+ # is compromised then all is lost. If the verifier is revealed then there
234
+ # is a theoretical attack on the verifier which could reveal information
235
+ # about the password. It is likely cryptographically difficult though.
236
+ # It is important that the username and password combination be of
237
+ # high entropy.
238
+
239
+ # The salt and verifier should be persisted server-side, accessible by
240
+ # looking up via the username. The server must protect the verifier but
241
+ # will return the salt to any party requesting authentication who knows
242
+ # the username.
243
+
136
244
  @auth = SIRP::Verifier.new(prime_length).generate_userauth(username, password)
137
- # @auth is a hash containing :username, :verifier and :salt
245
+ # => {username: '...', verifier: '...', salt: '...'}
138
246
 
139
- # ~~~ Begin Authentication ~~~
247
+ # ~~~ Phase 1 : Challenge/Response ~~~
140
248
 
141
249
  client = SIRP::Client.new(prime_length)
142
250
  A = client.start_authentication
143
251
 
144
- # Client => Server: username, A
252
+ # HTTPS POST Client => Server: request includes 'username' and 'A'
145
253
 
146
- # Server retrieves user's verifier and salt from the database.
254
+ # Server retrieves user's verifier and salt from the database by
255
+ # looking up these values indexed by 'username'. Here simulated
256
+ # by using the @auth hash directly.
147
257
  v = @auth[:verifier]
148
258
  salt = @auth[:salt]
149
259
 
150
- # Server generates challenge for the client.
260
+ # Server generates a challenge for the client and a proof it will require
261
+ # in Phase 2 of the auth process. The challenge is given to the client, the
262
+ # proof is temporarily persisted.
151
263
  verifier = SIRP::Verifier.new(prime_length)
152
264
  session = verifier.get_challenge_and_proof(username, v, salt, A)
153
265
 
@@ -157,29 +269,49 @@ session = verifier.get_challenge_and_proof(username, v, salt, A)
157
269
  # Server sends the challenge containing salt and B to client.
158
270
  response = session[:challenge]
159
271
 
160
- # Server => Client: salt, B
272
+ # HTTPS Server => Client: response includes 'salt', and 'B'
161
273
 
162
- # Client calculates M as a response to the challenge.
274
+ # ~~~ Phase 2 : Continue Authentication ~~~
275
+
276
+ # Client calculates M as a response to the challenge using the
277
+ # username and password and the server provided 'salt' and 'B'.
163
278
  client_M = client.process_challenge(username, password, salt, B)
164
279
 
165
- # Client => Server: username, M
280
+ # HTTPS POST Client => Server: request includes 'username', and 'M'
166
281
 
167
282
  # Instantiate a new verifier on the server.
168
283
  verifier = SIRP::Verifier.new(prime_length)
169
284
 
170
- # Verify challenge response M.
171
- # The Verifier state is passed in @proof.
285
+ # Verify challenge response M against the Verifier proof stored earlier.
286
+ # server_H_AMK returned will be 'false' if verification failed.
172
287
  server_H_AMK = verifier.verify_session(@proof, client_M)
173
- # Is false if authentication failed.
174
-
175
- # At this point, the client and server should have a common session key
176
- # that is secure (i.e. not known to an outside party). To finish
177
- # authentication, they must prove to each other that their keys are
178
- # identical.
179
288
 
180
- # Server => Client: H(AMK)
181
-
182
- client.verify(server_H_AMK) == true
289
+ # At this point, the client and server should have a common session key (K)
290
+ # that is secure and unknown to any outside party. Before they can safely use
291
+ # it though they must prove to each other that their keys are identical by
292
+ # exchanging a hash H(A,M,K). This step allows both client and server to be
293
+ # certain they arrived at the same values independently.
294
+
295
+ # The server sends a response based on the results of verify_session.
296
+
297
+ if server_H_AMK
298
+ # HTTPS Server => Client: response includes server_H_AMK
299
+ else
300
+ # Do NOT include the server_H_AMK in the response.
301
+ # HTTPS Server => Client: 401 Unauthorized
302
+ end
303
+
304
+ # The client compares server_H_AMK response to its own calculated H(A,M,K).
305
+
306
+ if client.verify(server_H_AMK)
307
+ #### SUCCESS ####
308
+ # Client and server have mutually authenticated.
309
+ # Optional : Use this secret key to derive shared encryption keys for some
310
+ # other application specific use.
311
+ secret_key = client.K
312
+ else
313
+ # FAIL, THROW IT ALL AWAY AND START OVER!
314
+ end
183
315
 
184
316
  ```
185
317
 
@@ -200,6 +332,8 @@ interactive prompt that will allow you to experiment.
200
332
 
201
333
  To install this gem onto your local machine, run `bundle exec rake install`.
202
334
 
335
+ The formal release process can be found in [RELEASE.md](https://github.com/grempe/sirp/blob/master/RELEASE.md)
336
+
203
337
  ### Contributing
204
338
 
205
339
  Bug reports and pull requests are welcome on GitHub
@@ -1,8 +1,10 @@
1
1
  require 'openssl'
2
2
  require 'digest'
3
3
  require 'rbnacl/libsodium'
4
- require 'securer_randomer'
4
+ require 'sysrandom/securerandom'
5
+ require 'hashie'
5
6
  require 'sirp/sirp'
7
+ require 'sirp/parameters'
6
8
  require 'sirp/client'
7
9
  require 'sirp/verifier'
8
10
  require 'sirp/version'
@@ -1,50 +1,96 @@
1
1
  module SIRP
2
2
  class Client
3
+ include SIRP
3
4
  attr_reader :N, :g, :k, :a, :A, :S, :K, :M, :H_AMK, :hash
4
5
 
6
+ # Select modulus (N), generator (g), and one-way hash function (SHA1 or SHA256)
7
+ #
8
+ # @param group [Integer] the group size in bits
5
9
  def initialize(group = 2048)
6
- # select modulus (N) and generator (g)
7
- @N, @g, @hash = SIRP.Ng(group)
8
- @k = SIRP.calc_k(@N, @g, hash)
10
+ raise ArgumentError, 'must be an Integer' unless group.is_a?(Integer)
11
+ raise ArgumentError, 'must be a known group size' unless [1024, 1536, 2048, 3072, 4096, 6144, 8192].include?(group)
12
+
13
+ @N, @g, @hash = Ng(group)
14
+ @k = calc_k(@N, @g, hash)
9
15
  end
10
16
 
17
+ # Phase 1 : Step 1 : Start the authentication process by generating the
18
+ # client 'a' and 'A' values. Public 'A' should later be sent along with
19
+ # the username, to the server verifier to continue the auth process. The
20
+ # internal secret 'a' value should remain private.
21
+ #
22
+ # @return [String] the value of 'A' in hex
11
23
  def start_authentication
12
- # Generate a/A private and public components
13
24
  @a ||= SecureRandom.hex(32).hex
14
- @A = SIRP.num_to_hex(SIRP.calc_A(@a, @N, @g))
25
+ @A = num_to_hex(calc_A(@a, @N, @g))
15
26
  end
16
27
 
17
- # Process initiated authentication challenge.
18
- # Returns M if authentication is successful, false otherwise.
19
- # Salt and B should be given in hex.
28
+ #
29
+ # Phase 1 : Step 2 : See Verifier#get_challenge_and_proof(username, xverifier, xsalt, xaa)
30
+ #
31
+
32
+ # Phase 2 : Step 1 : Process the salt and B values provided by the server.
33
+ #
34
+ # @param username [String] the client provided authentication username
35
+ # @param password [String] the client provided authentication password
36
+ # @param xsalt [String] the server provided salt for the username in hex
37
+ # @param xbb [String] the server verifier 'B' value in hex
38
+ # @return [String] the client 'M' value in hex
20
39
  def process_challenge(username, password, xsalt, xbb)
40
+ raise ArgumentError, 'username must be a string' unless username.is_a?(String) && !username.empty?
41
+ raise ArgumentError, 'password must be a string' unless password.is_a?(String) && !password.empty?
42
+ raise ArgumentError, 'xsalt must be a string' unless xsalt.is_a?(String)
43
+ raise ArgumentError, 'xsalt must be a hex string' unless xsalt =~ /^[a-fA-F0-9]+$/
44
+ raise ArgumentError, 'xbb must be a string' unless xbb.is_a?(String)
45
+ raise ArgumentError, 'xbb must be a hex string' unless xbb =~ /^[a-fA-F0-9]+$/
46
+
47
+ # Convert the 'B' hex value to an Integer
21
48
  bb = xbb.to_i(16)
22
49
 
23
50
  # SRP-6a safety check
24
- return false if (bb % @N) == 0
51
+ return false if (bb % @N).zero?
25
52
 
26
- x = SIRP.calc_x(username, password, xsalt, hash)
27
- u = SIRP.calc_u(@A, xbb, @N, hash)
53
+ x = calc_x(username, password, xsalt, hash)
54
+ u = calc_u(@A, xbb, @N, hash)
28
55
 
29
56
  # SRP-6a safety check
30
- return false if u == 0
57
+ return false if u.zero?
31
58
 
32
- # calculate session key
33
- @S = SIRP.num_to_hex(SIRP.calc_client_S(bb, @a, @k, x, u, @N, @g))
34
- @K = SIRP.sha_hex(@S, hash)
59
+ # Calculate session key 'S' and secret key 'K'
60
+ @S = num_to_hex(calc_client_S(bb, @a, @k, x, u, @N, @g))
61
+ @K = sha_hex(@S, hash)
35
62
 
36
- # calculate match
37
- @M = SIRP.calc_M(@A, xbb, @K, hash)
63
+ # Calculate the 'M' matcher
64
+ @M = calc_M(@A, xbb, @K, hash)
38
65
 
39
- # calculate verifier
40
- @H_AMK = SIRP.num_to_hex(SIRP.calc_H_AMK(@A, @M, @K, hash))
66
+ # Calculate the H(A,M,K) verifier
67
+ @H_AMK = num_to_hex(calc_H_AMK(@A, @M, @K, hash))
41
68
 
69
+ # Return the 'M' matcher to be sent to the server
42
70
  @M
43
71
  end
44
72
 
73
+ #
74
+ # Phase 2 : Step 2 : See Verifier#verify_session(proof, client_M)
75
+ #
76
+
77
+ # Phase 2 : Step 3 : Verify that the server provided H(A,M,K) value
78
+ # matches the client generated version. This is the last step of mutual
79
+ # authentication and confirms that the client and server have
80
+ # completed the auth process. The comparison of local and server
81
+ # H_AMK values is done using a secure constant-time comparison
82
+ # method so as not to leak information.
83
+ #
84
+ # @param server_HAMK [String] the server provided H_AMK in hex
85
+ # @return [true,false] returns true if the server and client agree on the H_AMK value, false if not
45
86
  def verify(server_HAMK)
46
- return false unless @H_AMK
47
- @H_AMK == server_HAMK
87
+ return false unless @H_AMK && server_HAMK
88
+ return false unless server_HAMK.is_a?(String)
89
+ return false unless server_HAMK =~ /^[a-fA-F0-9]+$/
90
+
91
+ # Hash the comparison params to ensure that both strings
92
+ # being compared are equal length 32 Byte strings.
93
+ secure_compare(Digest::SHA256.hexdigest(@H_AMK), Digest::SHA256.hexdigest(server_HAMK))
48
94
  end
49
95
  end
50
96
  end