ruby-srp 0.1.6 → 0.1.7
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.
- data/lib/srp/session.rb +63 -29
- data/lib/srp/util.rb +1 -1
- data/ruby-srp.gemspec +1 -1
- data/test/fixtures/failed_js_login.json +12 -0
- data/test/fixtures/py_srp.json +11 -0
- data/test/fixtures/zero_padded_salt.json +10 -0
- data/test/session_test.rb +63 -34
- data/test/test_helper.rb +1 -0
- metadata +6 -3
data/lib/srp/session.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module SRP
|
|
2
2
|
class Session
|
|
3
3
|
include SRP::Util
|
|
4
|
-
attr_accessor :user
|
|
4
|
+
attr_accessor :user
|
|
5
5
|
|
|
6
6
|
def initialize(user, aa=nil)
|
|
7
7
|
@user = user
|
|
@@ -11,28 +11,27 @@ module SRP
|
|
|
11
11
|
# client -> server: I, A = g^a
|
|
12
12
|
def handshake(server)
|
|
13
13
|
@bb = server.handshake(user.username, aa)
|
|
14
|
-
@u = calculate_u
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
# client -> server: M = H(H(N) xor H(g), H(I), s, A, B, K)
|
|
18
17
|
def validate(server)
|
|
19
|
-
server.validate(
|
|
18
|
+
server.validate(m)
|
|
20
19
|
end
|
|
21
20
|
|
|
22
|
-
def authenticate!(
|
|
23
|
-
authenticate(
|
|
21
|
+
def authenticate!(client_auth)
|
|
22
|
+
authenticate(client_auth) || raise(SRP::WrongPassword)
|
|
24
23
|
end
|
|
25
24
|
|
|
26
|
-
def authenticate(
|
|
27
|
-
if(
|
|
28
|
-
@
|
|
25
|
+
def authenticate(client_auth)
|
|
26
|
+
if(client_auth == m)
|
|
27
|
+
@authenticated = true
|
|
29
28
|
return @user
|
|
30
29
|
end
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
def to_hash
|
|
34
|
-
if @
|
|
35
|
-
{ :M2 =>
|
|
33
|
+
if @authenticated
|
|
34
|
+
{ :M2 => m2.to_s(16) }
|
|
36
35
|
else
|
|
37
36
|
{ :B => bb.to_s(16),
|
|
38
37
|
# :b => @b.to_s(16), # only use for debugging
|
|
@@ -45,55 +44,90 @@ module SRP
|
|
|
45
44
|
to_hash.to_json(options)
|
|
46
45
|
end
|
|
47
46
|
|
|
47
|
+
# for debugging use:
|
|
48
|
+
def internal_state
|
|
49
|
+
{
|
|
50
|
+
username: @user.username,
|
|
51
|
+
salt: @user.salt.to_s(16),
|
|
52
|
+
verifier: @user.verifier.to_s(16),
|
|
53
|
+
aa: aa.to_s(16),
|
|
54
|
+
bb: bb.to_s(16),
|
|
55
|
+
s: secret.to_s(16),
|
|
56
|
+
k: k.to_s(16),
|
|
57
|
+
m: m.to_s(16),
|
|
58
|
+
m2: m2.to_s(16)
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def aa
|
|
63
|
+
@aa ||= modpow(GENERATOR, @a) # A = g^a (mod N)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# B = g^b + k v (mod N)
|
|
67
|
+
def bb
|
|
68
|
+
@bb ||= (modpow(GENERATOR, @b) + multiplier * @user.verifier) % BIG_PRIME_N
|
|
69
|
+
end
|
|
70
|
+
|
|
48
71
|
protected
|
|
49
72
|
|
|
50
73
|
|
|
51
74
|
# only seed b for testing purposes.
|
|
52
|
-
def initialize_server(aa,
|
|
75
|
+
def initialize_server(aa, ephemeral = nil)
|
|
53
76
|
@aa = aa
|
|
54
|
-
@b =
|
|
55
|
-
# B = g^b + k v (mod N)
|
|
56
|
-
@bb = (modpow(GENERATOR, @b) + multiplier * @user.verifier) % BIG_PRIME_N
|
|
57
|
-
@u = calculate_u
|
|
77
|
+
@b = ephemeral || bigrand(32).hex
|
|
58
78
|
end
|
|
59
79
|
|
|
60
80
|
def initialize_client
|
|
61
81
|
@a = bigrand(32).hex
|
|
62
|
-
|
|
82
|
+
# bb will be set during handshake.
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def secret
|
|
86
|
+
return client_secret if @a
|
|
87
|
+
return server_secret if @b
|
|
63
88
|
end
|
|
64
89
|
|
|
65
90
|
# client: K = H( (B - kg^x) ^ (a + ux) )
|
|
66
91
|
def client_secret
|
|
67
|
-
base =
|
|
92
|
+
base = bb
|
|
68
93
|
# base += BIG_PRIME_N * @multiplier
|
|
69
94
|
base -= modpow(GENERATOR, @user.private_key) * multiplier
|
|
70
95
|
base = base % BIG_PRIME_N
|
|
71
|
-
modpow(base, @user.private_key *
|
|
96
|
+
modpow(base, @user.private_key * u + @a)
|
|
72
97
|
end
|
|
73
98
|
|
|
74
99
|
# server: K = H( (Av^u) ^ b )
|
|
75
100
|
# do not cache this - it's secret and someone might store the
|
|
76
101
|
# session in a CookieStore
|
|
77
102
|
def server_secret
|
|
78
|
-
base = (modpow(@user.verifier,
|
|
103
|
+
base = (modpow(@user.verifier, u) * aa) % BIG_PRIME_N
|
|
79
104
|
modpow(base, @b)
|
|
80
105
|
end
|
|
81
106
|
|
|
82
|
-
#
|
|
107
|
+
# SRP 6a uses
|
|
83
108
|
# M = H(H(N) xor H(g), H(I), s, A, B, K)
|
|
84
|
-
def
|
|
85
|
-
@
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
109
|
+
def m
|
|
110
|
+
@m ||= sha256_int(n_xor_g_long, login_hash, @user.salt, aa, bb, k).hex
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def m2
|
|
114
|
+
@m2 ||= sha256_int(aa, m, k).hex
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def k
|
|
118
|
+
@k ||= sha256_int(secret).hex
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def n_xor_g_long
|
|
122
|
+
@n_xor_g_long ||= hn_xor_hg.bytes.map{|b| "%02x" % b.ord}.join.hex
|
|
89
123
|
end
|
|
90
124
|
|
|
91
|
-
def
|
|
92
|
-
|
|
125
|
+
def login_hash
|
|
126
|
+
@login_hash ||= sha256_str(@user.username).hex
|
|
93
127
|
end
|
|
94
128
|
|
|
95
|
-
def
|
|
96
|
-
sha256_int(
|
|
129
|
+
def u
|
|
130
|
+
@u ||= sha256_int(aa, bb).hex
|
|
97
131
|
end
|
|
98
132
|
|
|
99
133
|
end
|
data/lib/srp/util.rb
CHANGED
data/ruby-srp.gemspec
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"username": "blues",
|
|
3
|
+
"password": "justtest",
|
|
4
|
+
"salt": "6a6ef9ce5cb998eb",
|
|
5
|
+
"verifier": "a5da6d376d503e22d93385db0244c382d1413d9f721ad9866dfc5e895cf2a3331514ceec5f48aceab58b260651cc9ee1ba96d906f67a6b4a7414c82d1333607ebe96403ecc86050224dc4c17b1d30efdbb451a68d1b6a25cce10f0e844082329d3cb46e1c3d46298a0de2cd3b8c6acc1a80c206f0f10ec8cd3c050babdf338ba",
|
|
6
|
+
"aa": "4decb8543891f5a744b1e9b5bc375a474bfe3c5417e1db176cefcc7ba915338a14f309f8e0a4c7641bc9c9b9bd2e91c4d1beda1772c30d0350c9ba44f7c5911dfe6bb593ac2a2b30f1f6e5ec8a656cb4947c1907cf62f8d7283cbe32eb44b02158b51091ae130afa6063bb28cdea9ae159d4f222571e146f8715bfa31af09868",
|
|
7
|
+
"b": "f393e04f8a0463b90227742217d7e1bbba82241a43beb372c4fc90539d24bdaf",
|
|
8
|
+
"bb": "dee64fd54daafc18b338c5783ade3ff4275dfee8c97008e2d9fb445880a2e1d452c822a35e8e3f012bc6facaa28022f8de3fb1d632667d635abde0afc0ca4ed06c9197ea88f379042b10bc7b7f816a1ec14fefe6e9adef4ab904315b3a3f36749f3f6d1083b0eb0029173770f8e9342b098298389ba49a88d4ea6b78a7f576a4",
|
|
9
|
+
"m": "ccf0c492f715484dc8343e22cd5967c2c5d01de743c5f0a9c5cfd017db1804c",
|
|
10
|
+
"s": "50973f6e8134f95bd04f54f522e6e57d957d0640f91f0a989ff775712b81d5856ae3bdd2aa9c5eda8019e9db18065519c99c33a62c7f12f98e7aed60b153feee9ab73ba1272b4d76aa002da8cd47c6da733c88a0e70d4c3d6752fd366d66efe40870d26fd5d1755883b9489721e1881376628bf6ef89902f35e5e7e31227e2f",
|
|
11
|
+
"k": "dd93e648abfe2ac6c6d46e062ded60b31ec043e55ceca1946ec29508f4c68461"
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"username": "testuser",
|
|
3
|
+
"password": "password",
|
|
4
|
+
"salt": "4c78c3f8",
|
|
5
|
+
"verifier": "474c26aa42d11f20544a00f7bf9711c4b5cf7aab95ed448df82b95521b96668e7480b16efce81c861870302560ddf6604c67df54f1d04b99d5bb9d0f02c6051ada5dc9d594f0d4314e12f876cfca3dcd99fc9c98c2e6a5e04298b11061fb8549a22cde0564e91514080df79bca1c38c682214d65d590f66b3719f954b078b83c",
|
|
6
|
+
"aa": "9ff9d176b37d9100ad4d788b94ef887df6c88786f5fa2419c9a964001e1c1fa5cd22ea39dcf27682dac6cd8861d9de88184653451fd47f5654845ed24e828d531f95c44377c9bc3f5dd83a669716257c7b975a3a032d4d8adb605553cf4d45c483d7aceb7e6a23c5bd4b0aeeb2ef138b7fc75b27d9d706851c3ab9c721710272",
|
|
7
|
+
"b": "ce414b3b52d13a1f67416b7e00cdefb07c874291aed395efeab9435ec1ad6ac3",
|
|
8
|
+
"bb": "b2e852fe7af02d7931186f4958844b829d2976dd58c7bc7928ba3102ff269a9029c707112ab0b7cafdaf86a760f7b50ddd9c847e0c97f564d53cfd52daf61982f06582d49bbb3ea4ad6be55d513028eaf400a6d5a9d26b47689d3438a552716d65680d1b6ee77df3c9b3b6ba61023985562f2be4a6f1723282a2013160594565",
|
|
9
|
+
"m": "a0c066844117ffe7a7999f84356f3a7c8dce38e4e936eca2b6979ab0fce6ff6d",
|
|
10
|
+
"m2": "1f4a5ba9c5280b5b752465670f351bb1e61ff9ca06e02ad43c4418affeb3a1ef"
|
|
11
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"username": "RLNFB7",
|
|
3
|
+
"password": "NRH9NRT958BO",
|
|
4
|
+
"salt": "0401b02e",
|
|
5
|
+
"verifier": "943c7bf983b9afd0e08ba7d9c9da68cbf8bc88f05d564f002bd669130bb66ceb2b5aafa5c4a9cac09f42a17f7079b67a964365022283cc249446a165ca9e02855d188ca193bf0b4703d0d83254623e3e91576ba1f3b353981836226f3e9c36b7592a6a0daa608018273e7d3a3cb8615eee3606af9eec4a83e1947c8717f9415e",
|
|
6
|
+
"aa": "ea40a95b4ccf1934767e9098f0f5639f5b83321eb77137f3c7b50bb90323651ebbe14b08956e471d4b96ae12c96814fbc56bfe408afd4cffca17d53dc30653a2e9e0e57f5b97e8736a5a90470708a32f63e6417651303e331d6c3bf3d229379dd746fb9f47220ee52b6da008ce88710de27c058841d56644d58e98e1c8795371",
|
|
7
|
+
"b": "78e12fc099be1409e0fce3bf84484d89d58710bcc3d8a0e05227fb291be3fb28",
|
|
8
|
+
"bb": "d8d50a862b7e8a897f8b0554c4a474e8aa152bd08f23436773fbb977e81cbf5e8262937ffb7ad6b72e3aa7f72deec947cdb286ab466e490d7c544bf443331ad12657c8f9bb2aabf508b73ea1ed29d03a060f5f2a70baef858bdb79c5c878844c058fe10c2cc746b0fb701e98d8d6405ab7d0b65bb4f87cf8e47b25ae4ee6e53b",
|
|
9
|
+
"m": "d5cbec7254ce66f421ceddbfe8a0a8991b5be2aa9c25d868f073f4459dfc358b"
|
|
10
|
+
}
|
data/test/session_test.rb
CHANGED
|
@@ -3,48 +3,77 @@ require 'json'
|
|
|
3
3
|
|
|
4
4
|
class SessionTest < Test::Unit::TestCase
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Struct.new("Client", :username, :salt, :verifier)
|
|
7
7
|
|
|
8
|
-
def
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
def test_equivalance_to_py_srp
|
|
9
|
+
data = fixture(:py_srp)
|
|
10
|
+
client = stub_client(data)
|
|
11
|
+
session = init_session(client, data)
|
|
12
|
+
|
|
13
|
+
assert_same_values(data, session.internal_state)
|
|
14
|
+
assert_equal client, session.authenticate(data[:m].hex)
|
|
15
|
+
assert_equal({:M2 => data[:m2]}, session.to_hash)
|
|
16
|
+
assert_equal({'M2' => data[:m2]}.to_json, session.to_json)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_zero_padded_salt
|
|
20
|
+
data = fixture(:zero_padded_salt)
|
|
21
|
+
client = stub_client(data)
|
|
22
|
+
session = init_session(client, data)
|
|
23
|
+
state = session.internal_state
|
|
24
|
+
# Zero padding of the salt would cause next assertion to fail.
|
|
25
|
+
# But we are only interested in the calculated results anyway.
|
|
26
|
+
state.delete(:salt)
|
|
27
|
+
|
|
28
|
+
assert_same_values(data, state)
|
|
29
|
+
assert_equal client, session.authenticate(data[:m].hex)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_failing_js_login
|
|
33
|
+
data = fixture(:failed_js_login)
|
|
34
|
+
client = stub_client(data)
|
|
35
|
+
session = init_session(client, data)
|
|
36
|
+
|
|
37
|
+
assert_same_values(data, session.internal_state)
|
|
38
|
+
assert_equal client, session.authenticate(data[:m].hex)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def fixture(filename)
|
|
42
|
+
path = File.expand_path("../fixtures/#{filename}.json", __FILE__)
|
|
43
|
+
HashWithIndifferentAccess[JSON.parse(File.read(path))]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def stub_client(data)
|
|
49
|
+
@username = data[:username]
|
|
50
|
+
@password = data[:password]
|
|
51
|
+
@salt = data[:salt].hex
|
|
12
52
|
@client = SRP::Client.new @username,
|
|
13
53
|
:password => @password,
|
|
14
54
|
:salt => @salt
|
|
15
55
|
@verifier = @client.verifier
|
|
56
|
+
Struct::Client.new @username, @salt, @verifier
|
|
16
57
|
end
|
|
17
58
|
|
|
18
|
-
def
|
|
19
|
-
aa =
|
|
20
|
-
b
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
session
|
|
25
|
-
session.send(:initialize_server, aa, b) # seeding b to compare to py_srp
|
|
26
|
-
assert_equal bb.to_s(16), session.bb.to_s(16)
|
|
27
|
-
assert_equal self, session.authenticate(m)
|
|
28
|
-
assert_equal({'M2' => m2.to_s(16)}.to_json, session.to_json)
|
|
29
|
-
assert_equal({:M2 => m2.to_s(16)}, session.to_hash)
|
|
59
|
+
def init_session(client, data)
|
|
60
|
+
aa = data[:aa].hex
|
|
61
|
+
b = data[:b].hex
|
|
62
|
+
session = SRP::Session.new(client, aa)
|
|
63
|
+
# seed b to compare to py_srp
|
|
64
|
+
session.send(:initialize_server, aa, b)
|
|
65
|
+
session
|
|
30
66
|
end
|
|
31
67
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
client = SRP::Client.new @username,
|
|
42
|
-
:password => password,
|
|
43
|
-
:salt => @salt
|
|
44
|
-
assert_equal @verifier.to_s(16), client.verifier.to_s(16)
|
|
45
|
-
session = SRP::Session.new(self, aa)
|
|
46
|
-
session.send(:initialize_server, aa, b) # seeding b to compare to py_srp
|
|
47
|
-
assert_equal bb.to_s(16), session.bb.to_s(16)
|
|
48
|
-
assert session.authenticate(m)
|
|
68
|
+
# check all values in a hash against expectations.
|
|
69
|
+
#
|
|
70
|
+
# Note this will NOT assert all expected keys are set.
|
|
71
|
+
# So an empty Hash will always pass.
|
|
72
|
+
def assert_same_values(expected, actual)
|
|
73
|
+
actual.each_pair do |k,v|
|
|
74
|
+
next unless expected[k]
|
|
75
|
+
assert_equal expected[k], v, "Values for #{k} are not matching"
|
|
76
|
+
end
|
|
49
77
|
end
|
|
78
|
+
|
|
50
79
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby-srp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2013-
|
|
12
|
+
date: 2013-06-24 00:00:00.000000000 Z
|
|
13
13
|
dependencies: []
|
|
14
14
|
description: SRP client and server based on version 6 of the standard
|
|
15
15
|
email:
|
|
@@ -46,6 +46,9 @@ files:
|
|
|
46
46
|
- ruby-srp.gemspec
|
|
47
47
|
- test/auth_test.rb
|
|
48
48
|
- test/client_test.rb
|
|
49
|
+
- test/fixtures/failed_js_login.json
|
|
50
|
+
- test/fixtures/py_srp.json
|
|
51
|
+
- test/fixtures/zero_padded_salt.json
|
|
49
52
|
- test/session_test.rb
|
|
50
53
|
- test/test_helper.rb
|
|
51
54
|
- test/util_test.rb
|
|
@@ -69,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
69
72
|
version: 1.3.6
|
|
70
73
|
requirements: []
|
|
71
74
|
rubyforge_project:
|
|
72
|
-
rubygems_version: 1.8.
|
|
75
|
+
rubygems_version: 1.8.25
|
|
73
76
|
signing_key:
|
|
74
77
|
specification_version: 3
|
|
75
78
|
summary: Secure remote password library for ruby
|