ruby-srp 0.1.6 → 0.1.7

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