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 CHANGED
@@ -1,7 +1,7 @@
1
1
  module SRP
2
2
  class Session
3
3
  include SRP::Util
4
- attr_accessor :user, :aa, :bb
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(calculate_m(client_secret))
18
+ server.validate(m)
20
19
  end
21
20
 
22
- def authenticate!(m)
23
- authenticate(m) || raise(SRP::WrongPassword)
21
+ def authenticate!(client_auth)
22
+ authenticate(client_auth) || raise(SRP::WrongPassword)
24
23
  end
25
24
 
26
- def authenticate(m)
27
- if(m == calculate_m(server_secret))
28
- @m2 = calculate_m2
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 @m2
35
- { :M2 => @m2.to_s(16) }
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, b = nil)
75
+ def initialize_server(aa, ephemeral = nil)
53
76
  @aa = aa
54
- @b = b || bigrand(32).hex
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
- @aa = modpow(GENERATOR, @a) # A = g^a (mod N)
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 = @bb
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 * @u + @a)
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, @u) * @aa) % BIG_PRIME_N
103
+ base = (modpow(@user.verifier, u) * aa) % BIG_PRIME_N
79
104
  modpow(base, @b)
80
105
  end
81
106
 
82
- # this is outdated - SRP 6a uses
107
+ # SRP 6a uses
83
108
  # M = H(H(N) xor H(g), H(I), s, A, B, K)
84
- def calculate_m(secret)
85
- @k = sha256_int(secret).hex
86
- n_xor_g_long = hn_xor_hg.bytes.map{|b| "%02x" % b.ord}.join.hex
87
- username_hash = sha256_str(@user.username).hex
88
- @m = sha256_int(n_xor_g_long, username_hash, @user.salt, @aa, @bb, @k).hex
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 calculate_m2
92
- sha256_int(@aa, @m, @k).hex
125
+ def login_hash
126
+ @login_hash ||= sha256_str(@user.username).hex
93
127
  end
94
128
 
95
- def calculate_u
96
- sha256_int(@aa, @bb).hex
129
+ def u
130
+ @u ||= sha256_int(aa, bb).hex
97
131
  end
98
132
 
99
133
  end
data/lib/srp/util.rb CHANGED
@@ -54,7 +54,7 @@ d15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e5
54
54
  end
55
55
 
56
56
  def multiplier
57
- @k ||= calculate_multiplier
57
+ @muliplier ||= calculate_multiplier
58
58
  end
59
59
 
60
60
  protected
data/ruby-srp.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "ruby-srp"
3
- s.version = "0.1.6"
3
+ s.version = "0.1.7"
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.authors = ["Azul"]
6
6
  s.email = ["azul@leap.se"]
@@ -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
- attr_accessor :salt, :verifier, :username
6
+ Struct.new("Client", :username, :salt, :verifier)
7
7
 
8
- def setup
9
- @username = "testuser"
10
- @password = "password"
11
- @salt = '4c78c3f8'.hex
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 test_equivalance_to_py_srp
19
- aa = '9ff9d176b37d9100ad4d788b94ef887df6c88786f5fa2419c9a964001e1c1fa5cd22ea39dcf27682dac6cd8861d9de88184653451fd47f5654845ed24e828d531f95c44377c9bc3f5dd83a669716257c7b975a3a032d4d8adb605553cf4d45c483d7aceb7e6a23c5bd4b0aeeb2ef138b7fc75b27d9d706851c3ab9c721710272'.hex
20
- b = 'ce414b3b52d13a1f67416b7e00cdefb07c874291aed395efeab9435ec1ad6ac3'.hex
21
- bb = 'b2e852fe7af02d7931186f4958844b829d2976dd58c7bc7928ba3102ff269a9029c707112ab0b7cafdaf86a760f7b50ddd9c847e0c97f564d53cfd52daf61982f06582d49bbb3ea4ad6be55d513028eaf400a6d5a9d26b47689d3438a552716d65680d1b6ee77df3c9b3b6ba61023985562f2be4a6f1723282a2013160594565'.hex
22
- m = 'a0c066844117ffe7a7999f84356f3a7c8dce38e4e936eca2b6979ab0fce6ff6d'.hex
23
- m2 = '1f4a5ba9c5280b5b752465670f351bb1e61ff9ca06e02ad43c4418affeb3a1ef'.hex
24
- session = SRP::Session.new(self, aa)
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
- def test_zero_padded_salt
33
- @username = "RLNFB7"
34
- password = "NRH9NRT958BO"
35
- @salt = "0401b02e".hex
36
- @verifier = "943c7bf983b9afd0e08ba7d9c9da68cbf8bc88f05d564f002bd669130bb66ceb2b5aafa5c4a9cac09f42a17f7079b67a964365022283cc249446a165ca9e02855d188ca193bf0b4703d0d83254623e3e91576ba1f3b353981836226f3e9c36b7592a6a0daa608018273e7d3a3cb8615eee3606af9eec4a83e1947c8717f9415e".hex
37
- aa = "ea40a95b4ccf1934767e9098f0f5639f5b83321eb77137f3c7b50bb90323651ebbe14b08956e471d4b96ae12c96814fbc56bfe408afd4cffca17d53dc30653a2e9e0e57f5b97e8736a5a90470708a32f63e6417651303e331d6c3bf3d229379dd746fb9f47220ee52b6da008ce88710de27c058841d56644d58e98e1c8795371".hex
38
- b = "78e12fc099be1409e0fce3bf84484d89d58710bcc3d8a0e05227fb291be3fb28".hex
39
- bb = "d8d50a862b7e8a897f8b0554c4a474e8aa152bd08f23436773fbb977e81cbf5e8262937ffb7ad6b72e3aa7f72deec947cdb286ab466e490d7c544bf443331ad12657c8f9bb2aabf508b73ea1ed29d03a060f5f2a70baef858bdb79c5c878844c058fe10c2cc746b0fb701e98d8d6405ab7d0b65bb4f87cf8e47b25ae4ee6e53b".hex
40
- m = "d5cbec7254ce66f421ceddbfe8a0a8991b5be2aa9c25d868f073f4459dfc358b".hex
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
@@ -1,3 +1,4 @@
1
1
  require "rubygems"
2
2
  require 'test/unit'
3
+ require 'activesupport' # for HashWithIndifferentAccess
3
4
  require File.expand_path(File.dirname(__FILE__) + '/../lib/ruby-srp.rb')
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.6
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-02-25 00:00:00.000000000 Z
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.24
75
+ rubygems_version: 1.8.25
73
76
  signing_key:
74
77
  specification_version: 3
75
78
  summary: Secure remote password library for ruby