sirp 2.0.0.pre → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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