srp-rb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,446 @@
1
+ # -*- encoding: utf-8 -*-
2
+ =begin
3
+ This is a pure Ruby implementation of the Secure Remote
4
+ Password protocol (SRP-6a). SRP is a cryptographically
5
+ strong authentication protocol for password-based, mutual
6
+ authentication over an insecure network connection.
7
+
8
+ References
9
+ * http://srp.stanford.edu/
10
+ * http://srp.stanford.edu/demo/demo.html
11
+
12
+ Copyright (c) 2012, Mikael Lammentausta
13
+ All rights reserved.
14
+
15
+ Following is the New BSD license:
16
+
17
+ Redistribution and use in source and binary forms, with or without
18
+ modification, are permitted provided that the following conditions are met:
19
+ * Redistributions of source code must retain the above copyright
20
+ notice, this list of conditions and the following disclaimer.
21
+ * Redistributions in binary form must reproduce the above copyright
22
+ notice, this list of conditions and the following disclaimer in the
23
+ documentation and/or other materials provided with the distribution.
24
+ * Neither the name of the Python Software Foundation nor the
25
+ names of its contributors may be used to endorse or promote products
26
+ derived from this software without specific prior written permission.
27
+
28
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
29
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
30
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31
+ DISCLAIMED. IN NO EVENT SHALL TOM COCAGNE BE LIABLE FOR ANY
32
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
33
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
35
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
+
39
+ =end
40
+ require "digest"
41
+ require "openssl"
42
+ require "srp-rb/version"
43
+
44
+ module SRP
45
+ class << self
46
+
47
+ def sha1_hex(h)
48
+ Digest::SHA1.hexdigest([h].pack('H*'))
49
+ end
50
+
51
+ def sha1_str(s)
52
+ Digest::SHA1.hexdigest(s)
53
+ end
54
+
55
+ def bigrand(bytes)
56
+ OpenSSL::Random.random_bytes(bytes).unpack("H*")[0]
57
+ end
58
+
59
+ # a^n (mod m)
60
+ def modpow(a, n, m)
61
+ r = 1
62
+ while true
63
+ r = r * a % m if n[0] == 1
64
+ n >>= 1
65
+ return r if n == 0
66
+ a = a * a % m
67
+ end
68
+ end
69
+
70
+ # SHA1 hashing function with padding.
71
+ # Input is prefixed with 0 to meet N hex width.
72
+ def H(n, *a)
73
+ nlen = 2 * ((('%x' % [n]).length * 4 + 7) >> 3)
74
+ hashin = a.map {|s|
75
+ next unless s
76
+ shex = s.class == String ? s : "%x" % s
77
+ if shex.length > nlen
78
+ raise "Bit width does not match - client uses different prime"
79
+ end
80
+ "0" * (nlen - shex.length) + shex
81
+ }.join('')
82
+ sha1_hex(hashin).hex % n
83
+ end
84
+
85
+ # Multiplier parameter
86
+ # k = H(N, g) (in SRP-6a)
87
+ def calc_k(n, g)
88
+ H(n, n, g)
89
+ end
90
+
91
+ # Private key (derived from username, raw password and salt)
92
+ # x = H(salt || H(username || ':' || password))
93
+ def calc_x(username, password, salt)
94
+ spad = if salt.length.odd? then '0' else '' end
95
+ sha1_hex(spad + salt + sha1_str([username, password].join(':'))).hex
96
+ end
97
+
98
+ # Random scrambling parameter
99
+ # u = H(A, B)
100
+ def calc_u(xaa, xbb, n)
101
+ H(n, xaa, xbb)
102
+ end
103
+
104
+ # Password verifier
105
+ # v = g^x (mod N)
106
+ def calc_v(x, n, g)
107
+ modpow(g, x, n)
108
+ end
109
+
110
+ # A = g^a (mod N)
111
+ def calc_A(a, n, g)
112
+ modpow(g, a, n)
113
+ end
114
+
115
+ # B = g^b + k v (mod N)
116
+ def calc_B(b, k, v, n, g)
117
+ (modpow(g, b, n) + k * v) % n
118
+ end
119
+
120
+ # Client secret
121
+ # S = (B - (k * g^x)) ^ (a + (u * x)) % N
122
+ def calc_client_S(bb, a, k, x, u, n, g)
123
+ modpow((bb - k * modpow(g, x, n)) % n, (a+ x * u), n)
124
+ end
125
+
126
+ # Server secret
127
+ # S = (A * v^u) ^ b % N
128
+ def calc_server_S(aa, b, v, u, n)
129
+ modpow((modpow(v, u, n) * aa), b, n)
130
+ end
131
+
132
+ # M = H(H(N) xor H(g), H(I), s, A, B, K)
133
+ def calc_M(username, xsalt, xaa, xbb, xkk, n, g)
134
+ hn = sha1_hex("%x" % n).hex
135
+ hg = sha1_hex("%x" % g).hex
136
+ hxor = "%x" % (hn ^ hg)
137
+ hi = sha1_str(username)
138
+ return H(n, hxor, hi, xsalt, xaa, xbb, xkk)
139
+ end
140
+
141
+ # H(A, M, K)
142
+ def calc_H_AMK(xaa, xmm, xkk, n, g)
143
+ H(n, xaa, xmm, xkk)
144
+ end
145
+
146
+ def Ng(group)
147
+ case group
148
+ when 1024
149
+ @N = %w{
150
+ EEAF0AB9 ADB38DD6 9C33F80A FA8FC5E8 60726187 75FF3C0B 9EA2314C
151
+ 9C256576 D674DF74 96EA81D3 383B4813 D692C6E0 E0D5D8E2 50B98BE4
152
+ 8E495C1D 6089DAD1 5DC7D7B4 6154D6B6 CE8EF4AD 69B15D49 82559B29
153
+ 7BCF1885 C529F566 660E57EC 68EDBC3C 05726CC0 2FD4CBF4 976EAA9A
154
+ FD5138FE 8376435B 9FC61D2F C0EB06E3
155
+ }.join.hex
156
+ @g = 2
157
+
158
+ when 1536
159
+ @N = %w{
160
+ 9DEF3CAF B939277A B1F12A86 17A47BBB DBA51DF4 99AC4C80 BEEEA961
161
+ 4B19CC4D 5F4F5F55 6E27CBDE 51C6A94B E4607A29 1558903B A0D0F843
162
+ 80B655BB 9A22E8DC DF028A7C EC67F0D0 8134B1C8 B9798914 9B609E0B
163
+ E3BAB63D 47548381 DBC5B1FC 764E3F4B 53DD9DA1 158BFD3E 2B9C8CF5
164
+ 6EDF0195 39349627 DB2FD53D 24B7C486 65772E43 7D6C7F8C E442734A
165
+ F7CCB7AE 837C264A E3A9BEB8 7F8A2FE9 B8B5292E 5A021FFF 5E91479E
166
+ 8CE7A28C 2442C6F3 15180F93 499A234D CF76E3FE D135F9BB
167
+ }.join.hex
168
+ @g = 2
169
+
170
+ when 2048
171
+ @N = %w{
172
+ AC6BDB41 324A9A9B F166DE5E 1389582F AF72B665 1987EE07 FC319294
173
+ 3DB56050 A37329CB B4A099ED 8193E075 7767A13D D52312AB 4B03310D
174
+ CD7F48A9 DA04FD50 E8083969 EDB767B0 CF609517 9A163AB3 661A05FB
175
+ D5FAAAE8 2918A996 2F0B93B8 55F97993 EC975EEA A80D740A DBF4FF74
176
+ 7359D041 D5C33EA7 1D281E44 6B14773B CA97B43A 23FB8016 76BD207A
177
+ 436C6481 F1D2B907 8717461A 5B9D32E6 88F87748 544523B5 24B0D57D
178
+ 5EA77A27 75D2ECFA 032CFBDB F52FB378 61602790 04E57AE6 AF874E73
179
+ 03CE5329 9CCC041C 7BC308D8 2A5698F3 A8D0C382 71AE35F8 E9DBFBB6
180
+ 94B5C803 D89F7AE4 35DE236D 525F5475 9B65E372 FCD68EF2 0FA7111F
181
+ 9E4AFF73
182
+ }.join.hex
183
+ @g = 2
184
+
185
+ when 3072
186
+ @N = %w{
187
+ FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08
188
+ 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B
189
+ 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9
190
+ A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6
191
+ 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8
192
+ FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
193
+ 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C
194
+ 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718
195
+ 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D
196
+ 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D
197
+ B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226
198
+ 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
199
+ BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC
200
+ E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF
201
+ }.join.hex
202
+ @g = 5
203
+
204
+ when 4096
205
+ @N = %w{
206
+ FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08
207
+ 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B
208
+ 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9
209
+ A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6
210
+ 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8
211
+ FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
212
+ 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C
213
+ 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718
214
+ 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D
215
+ 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D
216
+ B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226
217
+ 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
218
+ BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC
219
+ E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26
220
+ 99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB
221
+ 04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2
222
+ 233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127
223
+ D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199
224
+ FFFFFFFF FFFFFFFF
225
+ }.join.hex
226
+ @g = 5
227
+
228
+ when 6144
229
+ @N = %w{
230
+ FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08
231
+ 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B
232
+ 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9
233
+ A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6
234
+ 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8
235
+ FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
236
+ 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C
237
+ 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718
238
+ 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D
239
+ 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D
240
+ B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226
241
+ 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
242
+ BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC
243
+ E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26
244
+ 99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB
245
+ 04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2
246
+ 233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127
247
+ D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492
248
+ 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406
249
+ AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918
250
+ DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151
251
+ 2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03
252
+ F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F
253
+ BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA
254
+ CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B
255
+ B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632
256
+ 387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E
257
+ 6DCC4024 FFFFFFFF FFFFFFFF
258
+ }.join.hex
259
+ @g = 5
260
+
261
+ when 8192
262
+ @N = %w{
263
+ FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08
264
+ 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B
265
+ 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9
266
+ A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6
267
+ 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8
268
+ FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
269
+ 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C
270
+ 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718
271
+ 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D
272
+ 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D
273
+ B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226
274
+ 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
275
+ BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC
276
+ E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26
277
+ 99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB
278
+ 04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2
279
+ 233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127
280
+ D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492
281
+ 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406
282
+ AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918
283
+ DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151
284
+ 2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03
285
+ F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F
286
+ BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA
287
+ CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B
288
+ B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632
289
+ 387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E
290
+ 6DBE1159 74A3926F 12FEE5E4 38777CB6 A932DF8C D8BEC4D0 73B931BA
291
+ 3BC832B6 8D9DD300 741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C
292
+ 5AE4F568 3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9
293
+ 22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B 4BCBC886
294
+ 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A 062B3CF5 B3A278A6
295
+ 6D2A13F8 3F44F82D DF310EE0 74AB6A36 4597E899 A0255DC1 64F31CC5
296
+ 0846851D F9AB4819 5DED7EA1 B1D510BD 7EE74D73 FAF36BC3 1ECFA268
297
+ 359046F4 EB879F92 4009438B 481C6CD7 889A002E D5EE382B C9190DA6
298
+ FC026E47 9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71
299
+ 60C980DD 98EDD3DF FFFFFFFF FFFFFFFF
300
+ }.join.hex
301
+ @g = 19
302
+
303
+ else
304
+ raise NotImplementedError
305
+ end
306
+ return [@N, @g]
307
+ end
308
+ end
309
+
310
+
311
+ class Verifier
312
+ attr_reader :N, :g, :k, :A, :B, :b, :S, :K, :M, :H_AMK
313
+
314
+ def initialize group=1024
315
+ # select modulus (N) and generator (g)
316
+ @N, @g = SRP.Ng group
317
+ @k = SRP.calc_k(@N, @g)
318
+ end
319
+
320
+ # Initial user creation for the persistance layer.
321
+ # Not part of the authentication process.
322
+ # Returns { <username>, <password verifier>, <salt> }
323
+ def generate_userauth username, password
324
+ @salt ||= random_salt
325
+ x = SRP.calc_x(username, password, @salt)
326
+ v = SRP.calc_v(x, @N, @g)
327
+ return {:username => username, :verifier => "%x" % v, :salt => @salt}
328
+ end
329
+
330
+ # Authentication phase 1 - create challenge.
331
+ # Returns Hash with challenge for client and proof to be stored on server.
332
+ # Parameters should be given in hex.
333
+ def get_challenge_and_proof username, xverifier, xsalt, xaa
334
+ # SRP-6a safety check
335
+ return false if (xaa.to_i(16) % @N) == 0
336
+ generate_B(xverifier)
337
+ return {
338
+ :challenge => {:B => @B, :salt => xsalt},
339
+ :proof => {:A => xaa, :B => @B, :b => "%x" % @b,
340
+ :I => username, :s => xsalt, :v => xverifier}
341
+ }
342
+ end
343
+
344
+ # returns H_AMK on success, None on failure
345
+ # User -> Host: M = H(H(N) xor H(g), H(I), s, A, B, K)
346
+ # Host -> User: H(A, M, K)
347
+ def verify_session proof, client_M
348
+ @A = proof[:A]
349
+ @B = proof[:B]
350
+ @b = proof[:b].to_i(16)
351
+ username = proof[:I]
352
+ xsalt = proof[:s]
353
+ v = proof[:v].to_i(16)
354
+
355
+ u = SRP.calc_u(@A, @B, @N)
356
+ # SRP-6a safety check
357
+ return false if u == 0
358
+
359
+ # calculate session key
360
+ @S = "%x" % SRP.calc_server_S(@A.to_i(16), @b, v, u, @N)
361
+ @K = SRP.sha1_hex(@S)
362
+
363
+ # calculate match
364
+ @M = "%x" % SRP.calc_M(username, xsalt, @A, @B, @K, @N, @g)
365
+
366
+ if @M == client_M
367
+ # authentication succeeded
368
+ @H_AMK = "%x" % SRP.calc_H_AMK(@A, @M, @K, @N, @g)
369
+ return @H_AMK
370
+ end
371
+ return false
372
+ end
373
+
374
+ def random_salt
375
+ "%x" % SRP.bigrand(10).hex
376
+ end
377
+
378
+ def random_bignum
379
+ SRP.bigrand(32).hex
380
+ end
381
+
382
+ # generates challenge
383
+ # input verifier in hex
384
+ def generate_B xverifier
385
+ v = xverifier.to_i(16)
386
+ @b ||= random_bignum
387
+ @B = "%x" % SRP.calc_B(@b, k, v, @N, @g)
388
+ end
389
+ end # Verifier
390
+
391
+
392
+ class Client
393
+ attr_reader :N, :g, :k, :a, :A, :S, :K, :M, :H_AMK
394
+
395
+ def initialize group=1024
396
+ # select modulus (N) and generator (g)
397
+ @N, @g = SRP.Ng group
398
+ @k = SRP.calc_k(@N, @g)
399
+ end
400
+
401
+ def start_authentication
402
+ generate_A
403
+ end
404
+
405
+ # Process initiated authentication challenge.
406
+ # Returns M if authentication is successful, false otherwise.
407
+ # Salt and B should be given in hex.
408
+ def process_challenge username, password, xsalt, xbb
409
+ bb = xbb.to_i(16)
410
+ # SRP-6a safety check
411
+ return false if (bb % @N) == 0
412
+
413
+ x = SRP.calc_x(username, password, xsalt)
414
+ u = SRP.calc_u(@A, xbb, @N)
415
+
416
+ # SRP-6a safety check
417
+ return false if u == 0
418
+
419
+ # calculate session key
420
+ @S = "%x" % SRP.calc_client_S(bb, @a, @k, x, u, @N, @g)
421
+ @K = SRP.sha1_hex(@S)
422
+
423
+ # calculate match
424
+ @M = "%x" % SRP.calc_M(username, xsalt, @A, xbb, @K, @N, @g)
425
+
426
+ # calculate verifier
427
+ @H_AMK = "%x" % SRP.calc_H_AMK(@A, @M, @K, @N, @g)
428
+
429
+ return @M
430
+ end
431
+
432
+ def verify server_HAMK
433
+ return false unless @H_AMK
434
+ @H_AMK == server_HAMK
435
+ end
436
+
437
+ def random_bignum
438
+ SRP.bigrand(32).hex
439
+ end
440
+
441
+ def generate_A
442
+ @a ||= random_bignum
443
+ @A = "%x" % SRP.calc_A(@a, @N, @g)
444
+ end
445
+ end # Client
446
+ end
@@ -0,0 +1,5 @@
1
+ module Srp
2
+ module Rb
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require 'srp-rb'
@@ -0,0 +1,474 @@
1
+ # -*- encoding: utf-8 -*-
2
+ =begin
3
+
4
+ Copyright (c) 2012, Mikael Lammentausta
5
+ All rights reserved.
6
+
7
+ Following is the New BSD license:
8
+
9
+ Redistribution and use in source and binary forms, with or without
10
+ modification, are permitted provided that the following conditions are met:
11
+ * Redistributions of source code must retain the above copyright
12
+ notice, this list of conditions and the following disclaimer.
13
+ * Redistributions in binary form must reproduce the above copyright
14
+ notice, this list of conditions and the following disclaimer in the
15
+ documentation and/or other materials provided with the distribution.
16
+ * Neither the name of the Python Software Foundation nor the
17
+ names of its contributors may be used to endorse or promote products
18
+ derived from this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL TOM COCAGNE BE LIABLE FOR ANY
24
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ =end
32
+ require 'srp'
33
+
34
+ # monkey-patch API to define a, b and salt presetters
35
+ class SRP::Verifier
36
+ def set_b val
37
+ @b = val
38
+ end
39
+ def set_salt val
40
+ @salt = val
41
+ end
42
+ end
43
+ class SRP::Client
44
+ def set_a val
45
+ @a = val
46
+ end
47
+ end
48
+
49
+ describe SRP do
50
+ ###
51
+ ### Test SRP functions.
52
+ ### Values are from http://srp.stanford.edu/demo/demo.html
53
+ ### using 256 bit values.
54
+ ###
55
+ context "@module-functions" do
56
+ before :all do
57
+ @N = "115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3".to_i(16)
58
+ @g = 2
59
+ @username = "user"
60
+ @password = "password"
61
+ @salt = "16ccfa081895fe1ed0bb"
62
+ @a = "7ec87196e320a2f8dfe8979b1992e0d34439d24471b62c40564bb4302866e1c2".to_i(16)
63
+ @b = "8143e2f299852a05717427ea9d87c6146e747d0da6e95f4390264e55a43ae96".to_i(16)
64
+ end
65
+
66
+ it "should calculate k" do
67
+ k = SRP.calc_k(@N, @g)
68
+ ("%x" % k).should == "dbe5dfe0704fee4c85ff106ecd38117d33bcfe50"
69
+ ("%b" % k).length.should == 160
70
+ end
71
+
72
+ it "should calculate x" do
73
+ x = SRP.calc_x(@username, @password, @salt)
74
+ ("%x" % x).should == "bdd0a4e1c9df4082684d8d358b8016301b025375"
75
+ ("%b" % x).length.should == 160
76
+ end
77
+
78
+ it "should calculate verifier" do
79
+ x = "bdd0a4e1c9df4082684d8d358b8016301b025375".to_i(16)
80
+ v = SRP.calc_v(x, @N, @g)
81
+ ("%x" % v).should == "ce36e101ed8c37ed98ba4e441274dabd1062f3440763eb98bd6058e5400b6309"
82
+ ("%b" % v).length.should == 256
83
+ end
84
+
85
+ it "should calculate u" do
86
+ aa = "b1c4827b0ce416953789db123051ed990023f43b396236b86e12a2c69638fb8e"
87
+ bb = "fbc56086bb51e26ee1a8287c0a7f3fd4e067e55beb8530b869b10b961957ff68"
88
+ u = SRP.calc_u(aa, bb, @N)
89
+ ("%x" % u).should == "c60b17ddf568dd5743d0e3ba5621646b742432c5"
90
+ ("%b" % u).length.should == 160
91
+ end
92
+
93
+ it "should calculate public client value A" do
94
+ aa = SRP.calc_A(@a, @N, @g)
95
+ ("%x" % aa).should == "b1c4827b0ce416953789db123051ed990023f43b396236b86e12a2c69638fb8e"
96
+ ("%b" % aa).length.should == 256
97
+ end
98
+
99
+ it "should calculate public server value B" do
100
+ k = "dbe5dfe0704fee4c85ff106ecd38117d33bcfe50".to_i(16)
101
+ v = "ce36e101ed8c37ed98ba4e441274dabd1062f3440763eb98bd6058e5400b6309".to_i(16)
102
+ bb = SRP.calc_B(@b, k, v, @N, @g)
103
+ ("%x" % bb).should == "fbc56086bb51e26ee1a8287c0a7f3fd4e067e55beb8530b869b10b961957ff68"
104
+ ("%b" % bb).length.should == 256
105
+ end
106
+
107
+ it "should calculate session key from client params" do
108
+ bb = "fbc56086bb51e26ee1a8287c0a7f3fd4e067e55beb8530b869b10b961957ff68".to_i(16)
109
+ k = "dbe5dfe0704fee4c85ff106ecd38117d33bcfe50".to_i(16)
110
+ x = "bdd0a4e1c9df4082684d8d358b8016301b025375".to_i(16)
111
+ u = "c60b17ddf568dd5743d0e3ba5621646b742432c5".to_i(16)
112
+ a = @a
113
+ ss = SRP.calc_client_S(bb, a, k, x, u, @N, @g)
114
+ ("%x" % ss).should == "a606c182e364d2c15f9cdbeeeb63bb00c831d1da65eedc1414f21157d0312a5a"
115
+ ("%b" % ss).length.should == 256
116
+ end
117
+
118
+ it "should calculate session key from server params" do
119
+ aa = "b1c4827b0ce416953789db123051ed990023f43b396236b86e12a2c69638fb8e".to_i(16)
120
+ v = "ce36e101ed8c37ed98ba4e441274dabd1062f3440763eb98bd6058e5400b6309".to_i(16)
121
+ u = "c60b17ddf568dd5743d0e3ba5621646b742432c5".to_i(16)
122
+ b = @b
123
+ ss = SRP.calc_server_S(aa, b, v, u, @N)
124
+ ("%x" % ss).should == "a606c182e364d2c15f9cdbeeeb63bb00c831d1da65eedc1414f21157d0312a5a"
125
+ ("%b" % ss).length.should == 256
126
+ end
127
+
128
+ it "should calculate M" do
129
+ xaa = "b1c4827b0ce416953789db123051ed990023f43b396236b86e12a2c69638fb8e"
130
+ xbb = "fbc56086bb51e26ee1a8287c0a7f3fd4e067e55beb8530b869b10b961957ff68"
131
+ xss = "a606c182e364d2c15f9cdbeeeb63bb00c831d1da65eedc1414f21157d0312a5a"
132
+ xkk = SRP.sha1_hex(xss)
133
+ xkk.should == "5844898ea6e5f5d9b737bc0ba2fb9d5edd3f8e67"
134
+ mm = SRP.calc_M(@username, @salt, xaa, xbb, xkk, @N, @g)
135
+ ("%x" % mm).should == "2da30b225850c17720ed483ae6d04bcb67e4448e"
136
+ end
137
+
138
+ it "should calculate H(AMK)" do
139
+ xaa = "b1c4827b0ce416953789db123051ed990023f43b396236b86e12a2c69638fb8e"
140
+ xmm = "d597503056af882d5b27b419302ac7b2ea9d7468"
141
+ xkk = "5844898ea6e5f5d9b737bc0ba2fb9d5edd3f8e67"
142
+ h_amk = SRP.calc_H_AMK(xaa, xmm, xkk, @N, @g)
143
+ ("%x" % h_amk).should == "ffc955a9227f1bf1d87d66bebecba081f54dbb7a"
144
+ end
145
+ end
146
+
147
+
148
+ ###
149
+ ### Test predefined values for N and g.
150
+ ### Values are from vectors listed in RFC 5054 Appendix B.
151
+ ###
152
+ context "@predefined-Ng" do
153
+ it "should be 1024 bits" do
154
+ srp = SRP::Verifier.new(1024)
155
+ nn = srp.N
156
+ ("%x" % nn).should == "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3"
157
+ ("%b" % nn).length.should == 1024
158
+ srp.g.should == 2
159
+ end
160
+
161
+ it "should be 1536 bits" do
162
+ srp = SRP::Verifier.new(1536)
163
+ nn = srp.N
164
+ ("%b" % nn).length.should == 1536
165
+ srp.g.should == 2
166
+ end
167
+
168
+ it "should be 2048 bits" do
169
+ srp = SRP::Verifier.new(2048)
170
+ nn = srp.N
171
+ ("%b" % nn).length.should == 2048
172
+ srp.g.should == 2
173
+ end
174
+
175
+ it "should be 3072 bits" do
176
+ srp = SRP::Verifier.new(3072)
177
+ nn = srp.N
178
+ ("%b" % nn).length.should == 3072
179
+ srp.g.should == 5
180
+ end
181
+
182
+ it "should be 4096 bits" do
183
+ srp = SRP::Verifier.new(4096)
184
+ nn = srp.N
185
+ ("%b" % nn).length.should == 4096
186
+ srp.g.should == 5
187
+ end
188
+
189
+ it "should be 6144 bits" do
190
+ srp = SRP::Verifier.new(6144)
191
+ nn = srp.N
192
+ ("%b" % nn).length.should == 6144
193
+ srp.g.should == 5
194
+ end
195
+
196
+ it "should be 8192 bits" do
197
+ srp = SRP::Verifier.new(8192)
198
+ nn = srp.N
199
+ ("%b" % nn).length.should == 8192
200
+ srp.g.should == 19
201
+ end
202
+ end
203
+
204
+
205
+ ###
206
+ ### Test server-side Verifier.
207
+ ### Values are from http://srp.stanford.edu/demo/demo.html
208
+ ### using 1024 bit values.
209
+ ###
210
+ context "@verifier" do
211
+ before :all do
212
+ @username = "user"
213
+ @password = "password"
214
+ @salt = "16ccfa081895fe1ed0bb"
215
+ @a = "7ec87196e320a2f8dfe8979b1992e0d34439d24471b62c40564bb4302866e1c2"
216
+ @b = "8143e2f299852a05717427ea9d87c6146e747d0da6e95f4390264e55a43ae96"
217
+ end
218
+
219
+ it "should calculate k" do
220
+ k = SRP::Verifier.new(1024).k
221
+ k.should == "7556aa045aef2cdd07abaf0f665c3e818913186f".to_i(16)
222
+ end
223
+
224
+ it "should generate salt and verifier" do
225
+ auth = SRP::Verifier.new(1024).generate_userauth(@username, @password)
226
+ auth[:username].should == @username
227
+ auth[:verifier].should be
228
+ auth[:salt].should be
229
+ end
230
+
231
+ it "should calculate verifier with given salt" do
232
+ srp = SRP::Verifier.new(1024)
233
+ srp.set_salt @salt
234
+ auth = srp.generate_userauth(@username, @password)
235
+ v = auth[:verifier]
236
+ salt = auth[:salt]
237
+ salt.should == @salt
238
+ v.should == "321307d87ca3462f5b0cb5df295bea04498563794e5401899b2f32dd5cab5b7de9da78e7d62ea235e6d7f43a4ea09fea7c0dafdee6e79a1d12e2e374048deeaf5ba7c68e2ad952a3f5dc084400a7f1599a31d6d9d50269a9208db88f84090e8aa3c7b019f39529dcc19baa985a8d7ffb2d7628071d2313c9eaabc504d3333688"
239
+ end
240
+
241
+ it "should generate salt and calculate verifier" do
242
+ srp = SRP::Verifier.new(1024)
243
+ auth = srp.generate_userauth(@username, @password)
244
+ v = auth[:verifier]
245
+ salt = auth[:salt]
246
+ ("%b" % v.to_i(16)).length.should >= 1000
247
+ ("%b" % salt.to_i(16)).length.should >= 50
248
+ end
249
+
250
+ it "should generate B with predefined b" do
251
+ v = "321307d87ca3462f5b0cb5df295bea04498563794e5401899b2f32dd5cab5b7de9da78e7d62ea235e6d7f43a4ea09fea7c0dafdee6e79a1d12e2e374048deeaf5ba7c68e2ad952a3f5dc084400a7f1599a31d6d9d50269a9208db88f84090e8aa3c7b019f39529dcc19baa985a8d7ffb2d7628071d2313c9eaabc504d3333688"
252
+ srp = SRP::Verifier.new(1024)
253
+ srp.set_b @b.to_i(16)
254
+ bb = srp.generate_B(v)
255
+ bb.should == "56777d24af1121bd6af6aeb84238ff8d250122fe75ed251db0f47c289642ae7adb9ef319ce3ab23b6ecc97e5904749fc42f12bb016ecf39691db541f066667b8399bfa685c82b03ad8f92f75975ed086dbe0d470d4dd907ce11b19ee41b74aee72bd8445cde6b58c01f678e39ed9cd6b93c79382637df90777a96c10a768c510"
256
+ end
257
+
258
+ it "should generate B" do
259
+ srp = SRP::Verifier.new(1024)
260
+ bb = srp.generate_B("0")
261
+ ("%b" % bb.to_i(16)).length.should >= 1000
262
+ ("%b" % srp.b).length.should > 200
263
+ end
264
+
265
+ it "should calculate server session and key" do
266
+ # A is received in phase 1
267
+ aa = "165366e23a10006a62fb8a0793757a299e2985103ad2e8cdee0cc37cac109f3f338ee12e2440eda97bfa7c75697709a5dc66faadca7806d43ea5839757d134ae7b28dd3333049198cc8d328998b8cd8352ff4e64b3bd5f08e40148d69b0843bce18cbbb30c7e4760296da5c92717fcac8bddc7875f55302e55d90a34226868d2"
268
+ # B and b are saved from phase 1
269
+ bb = "56777d24af1121bd6af6aeb84238ff8d250122fe75ed251db0f47c289642ae7adb9ef319ce3ab23b6ecc97e5904749fc42f12bb016ecf39691db541f066667b8399bfa685c82b03ad8f92f75975ed086dbe0d470d4dd907ce11b19ee41b74aee72bd8445cde6b58c01f678e39ed9cd6b93c79382637df90777a96c10a768c510"
270
+ # v is from db
271
+ v = "321307d87ca3462f5b0cb5df295bea04498563794e5401899b2f32dd5cab5b7de9da78e7d62ea235e6d7f43a4ea09fea7c0dafdee6e79a1d12e2e374048deeaf5ba7c68e2ad952a3f5dc084400a7f1599a31d6d9d50269a9208db88f84090e8aa3c7b019f39529dcc19baa985a8d7ffb2d7628071d2313c9eaabc504d3333688"
272
+ _proof = {:A => aa, :B => bb, :b => @b,
273
+ :I => @username, :s => @salt, :v => v}
274
+ srp = SRP::Verifier.new(1024)
275
+ srp.verify_session(_proof, "match insignificant")
276
+ ss = srp.S
277
+ ss.should == "7f44592cc616e0d761b2d3309d513b69b386c35f3ed9b11e6d43f15799b673d6dcfa4117b4456af978458d62ad61e1a37be625f46d2a5bd9a50aae359e4541275f0f4bd4b4caed9d2da224b491231f905d47abd9953179aa608854b84a0e0c6195e73715932b41ab8d0d4a2977e7642163be6802c5907fb9e233b8c96e457314"
278
+ kk = srp.K
279
+ kk.should == "404bf923682abeeb3c8c9164d2cdb6b6ba21b64d"
280
+ end
281
+
282
+ it "should calculate verifier M and server proof" do
283
+ # A is received in phase 1
284
+ aa = "165366e23a10006a62fb8a0793757a299e2985103ad2e8cdee0cc37cac109f3f338ee12e2440eda97bfa7c75697709a5dc66faadca7806d43ea5839757d134ae7b28dd3333049198cc8d328998b8cd8352ff4e64b3bd5f08e40148d69b0843bce18cbbb30c7e4760296da5c92717fcac8bddc7875f55302e55d90a34226868d2"
285
+ # B and b are saved from phase 1
286
+ bb = "56777d24af1121bd6af6aeb84238ff8d250122fe75ed251db0f47c289642ae7adb9ef319ce3ab23b6ecc97e5904749fc42f12bb016ecf39691db541f066667b8399bfa685c82b03ad8f92f75975ed086dbe0d470d4dd907ce11b19ee41b74aee72bd8445cde6b58c01f678e39ed9cd6b93c79382637df90777a96c10a768c510"
287
+ # v is from db
288
+ v = "321307d87ca3462f5b0cb5df295bea04498563794e5401899b2f32dd5cab5b7de9da78e7d62ea235e6d7f43a4ea09fea7c0dafdee6e79a1d12e2e374048deeaf5ba7c68e2ad952a3f5dc084400a7f1599a31d6d9d50269a9208db88f84090e8aa3c7b019f39529dcc19baa985a8d7ffb2d7628071d2313c9eaabc504d3333688"
289
+ # S is validated
290
+ ss = "7f44592cc616e0d761b2d3309d513b69b386c35f3ed9b11e6d43f15799b673d6dcfa4117b4456af978458d62ad61e1a37be625f46d2a5bd9a50aae359e4541275f0f4bd4b4caed9d2da224b491231f905d47abd9953179aa608854b84a0e0c6195e73715932b41ab8d0d4a2977e7642163be6802c5907fb9e233b8c96e457314"
291
+ # K = H(S)
292
+ kk = SRP.sha1_hex(ss)
293
+ client_M = "52fb39fcacc2d909675ea3cf2b967980fc40ae0"
294
+ _proof = {:A => aa, :B => bb, :b => @b,
295
+ :I => @username, :s => @salt, :v => v}
296
+ srp = SRP::Verifier.new(1024)
297
+ srp.verify_session(_proof, client_M)
298
+ srp.M.should == client_M
299
+ srp.H_AMK.should == "d3668cebb1cba4b3d4a4cd8edde9d89279b9d1e9"
300
+ end
301
+ end
302
+
303
+
304
+ ###
305
+ ### Test Client.
306
+ ### Values are from http://srp.stanford.edu/demo/demo.html
307
+ ### using 1024 bit values.
308
+ ###
309
+ context "@client" do
310
+ before :all do
311
+ @username = "user"
312
+ @password = "password"
313
+ @salt = "16ccfa081895fe1ed0bb"
314
+ @a = "7ec87196e320a2f8dfe8979b1992e0d34439d24471b62c40564bb4302866e1c2"
315
+ @b = "8143e2f299852a05717427ea9d87c6146e747d0da6e95f4390264e55a43ae96"
316
+ end
317
+
318
+ it "should generate A from random a" do
319
+ srp = SRP::Client.new(1024)
320
+ aa1 = srp.generate_A
321
+ ("%b" % aa1.to_i(16)).length.should >= 1000
322
+ ("%b" % srp.generate_A.to_i(16)).length.should >= 200
323
+ srp = SRP::Client.new(1024)
324
+ aa2 = srp.generate_A
325
+ ("%b" % aa2.to_i(16)).length.should >= 1000
326
+ ("%b" % srp.generate_A.to_i(16)).length.should >= 200
327
+ aa1.should_not == aa2
328
+ end
329
+
330
+ it "should calculate A" do
331
+ srp = SRP::Client.new(1024)
332
+ srp.set_a @a.to_i(16)
333
+ aa = srp.generate_A
334
+ aa.should == "165366e23a10006a62fb8a0793757a299e2985103ad2e8cdee0cc37cac109f3f338ee12e2440eda97bfa7c75697709a5dc66faadca7806d43ea5839757d134ae7b28dd3333049198cc8d328998b8cd8352ff4e64b3bd5f08e40148d69b0843bce18cbbb30c7e4760296da5c92717fcac8bddc7875f55302e55d90a34226868d2"
335
+ end
336
+
337
+ it "should calculate client session and key" do
338
+ srp = SRP::Client.new(1024)
339
+ srp.set_a @a.to_i(16)
340
+ aa = srp.generate_A # created in phase 1
341
+ bb = "56777d24af1121bd6af6aeb84238ff8d250122fe75ed251db0f47c289642ae7adb9ef319ce3ab23b6ecc97e5904749fc42f12bb016ecf39691db541f066667b8399bfa685c82b03ad8f92f75975ed086dbe0d470d4dd907ce11b19ee41b74aee72bd8445cde6b58c01f678e39ed9cd6b93c79382637df90777a96c10a768c510"
342
+ mm = srp.process_challenge(@username, @password, @salt, bb)
343
+ ss = srp.S
344
+ ss.should == "7f44592cc616e0d761b2d3309d513b69b386c35f3ed9b11e6d43f15799b673d6dcfa4117b4456af978458d62ad61e1a37be625f46d2a5bd9a50aae359e4541275f0f4bd4b4caed9d2da224b491231f905d47abd9953179aa608854b84a0e0c6195e73715932b41ab8d0d4a2977e7642163be6802c5907fb9e233b8c96e457314"
345
+ kk = srp.K
346
+ kk.should == "404bf923682abeeb3c8c9164d2cdb6b6ba21b64d"
347
+ end
348
+ end
349
+
350
+
351
+ ###
352
+ ### Simulate actual authentication scenario over HTTP
353
+ ### when the server is RESTful and has to persist authentication
354
+ ### state between challenge and response.
355
+ ###
356
+ context "@authentication" do
357
+ before :all do
358
+ @username = "leonardo"
359
+ password = "icnivad"
360
+ @auth = SRP::Verifier.new(1024).generate_userauth(@username, password)
361
+ # imitate database persistance layer
362
+ @db = {@username => {
363
+ :verifier => @auth[:verifier],
364
+ :salt => @auth[:salt],
365
+ }}
366
+ end
367
+
368
+ it "should authenticate" do
369
+ client = SRP::Client.new(1024)
370
+ verifier = SRP::Verifier.new(1024)
371
+ # phase 1
372
+ # (client)
373
+ aa = client.generate_A
374
+ # (server)
375
+ v = @auth[:verifier]
376
+ salt = @auth[:salt]
377
+ bb = verifier.generate_B v
378
+ b = "%x" % verifier.b
379
+ # phase 2
380
+ # (client)
381
+ client_M = client.process_challenge(@username, "icnivad", salt, bb)
382
+ # (server)
383
+ _proof = {:A => aa, :B => bb, :b => b, :I => @username, :s => salt, :v => v}
384
+ server_H_AMK = verifier.verify_session(_proof, client_M)
385
+ server_H_AMK.should be
386
+ # (client)
387
+ client.H_AMK.should == server_H_AMK
388
+ end
389
+
390
+ it "should not authenticate" do
391
+ client = SRP::Client.new(1024)
392
+ verifier = SRP::Verifier.new(1024)
393
+ # phase 1
394
+ # (client)
395
+ aa = client.generate_A
396
+ # (server)
397
+ v = @auth[:verifier]
398
+ salt = @auth[:salt]
399
+ bb = verifier.generate_B v
400
+ b = "%x" % verifier.b
401
+ # phase 2
402
+ # (client)
403
+ client_M = client.process_challenge(@username, "wrong password", salt, bb)
404
+ # (server)
405
+ _proof = {:A => aa, :B => bb, :b => b, :I => @username, :s => salt, :v => v}
406
+ verifier.verify_session(_proof, client_M).should == false
407
+ verifier.H_AMK.should_not be
408
+ end
409
+
410
+ it "should be applied in async authentication with stateless server" do
411
+ username = @username
412
+
413
+ # client generates A and begins authentication process
414
+ client = SRP::Client.new(1024)
415
+ aa = client.start_authentication()
416
+
417
+
418
+ #
419
+ # username and A are received (client --> server)
420
+ #
421
+
422
+ # server finds user from "database"
423
+ _user = @db[username]
424
+ _user.should_not be_nil
425
+ v = _user[:verifier]
426
+ salt = _user[:salt]
427
+
428
+ # server generates B, saves A and B to database
429
+ srp = SRP::Verifier.new(1024)
430
+ _session = srp.get_challenge_and_proof username, v, salt, aa
431
+ _session[:challenge][:B].should == srp.B
432
+ _session[:challenge][:salt].should == salt
433
+ # store proof to memory
434
+ _user[:session_proof] = _session[:proof]
435
+ # clear variables to simulate end of phase 1
436
+ srp = username = v = bb = salt = nil
437
+ # server sends salt and B
438
+ client_response = _session[:challenge]
439
+
440
+
441
+ #
442
+ # client receives B and salt (server --> client)
443
+ #
444
+ bb = client_response[:B]
445
+ salt = client_response[:salt]
446
+ # client generates session key
447
+ # at this point _client_srp.a should be persisted!! calculate_client_key is stateful!
448
+ mmc = client.process_challenge @username, "icnivad", salt, bb
449
+ client.A.should be
450
+ client.M.should == mmc
451
+ client.K.should be
452
+ client.H_AMK.should be
453
+ # client sends M --> server
454
+ client_M = client.M
455
+
456
+ #
457
+ # server receives client session key (client --> server)
458
+ #
459
+ username = @username
460
+ _user = @db[username]
461
+ # retrive session from database
462
+ proof = _user[:session_proof]
463
+ srp = SRP::Verifier.new(1024)
464
+ verification = srp.verify_session(proof, client_M)
465
+ verification.should_not == false
466
+
467
+ # Now the two parties have a shared, strong session key K.
468
+ # To complete authentication, they need to prove to each other that their keys match.
469
+
470
+ client.verify(verification).should == true
471
+ verification.should == client.H_AMK
472
+ end
473
+ end
474
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: srp-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - lamikae
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: ! "\n Ruby implementation of the Secure Remote Password protocol (SRP-6a).\n
31
+ \ SRP is a cryptographically strong authentication protocol for \n password-based,
32
+ mutual authentication over an insecure network connection."
33
+ email:
34
+ - ''
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - lib/srp-rb/version.rb
40
+ - lib/srp-rb.rb
41
+ - lib/srp.rb
42
+ - spec/srp_spec.rb
43
+ homepage: ''
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project: srp-rb
63
+ rubygems_version: 1.8.25
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Secure Remote Password protocol SRP-6a.
67
+ test_files:
68
+ - spec/srp_spec.rb