summaryrefslogtreecommitdiff
path: root/lib/srp/session.rb
blob: 53d9a3357513c8413254fac17c8da004632e6e04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
module SRP
  class Session
    include SRP::Util
    attr_accessor :user

    # params:
    # user: user object that represents and account (username, salt, verifier)
    # aa: SRPs A ephemeral value. encoded as a hex string.
    def initialize(user, aa=nil)
      @user = user
      aa ? initialize_server(aa) : initialize_client
    end

    # client -> server: I, A = g^a
    def handshake(server)
      @bb = server.handshake(user.username, aa)
    end

    # client -> server: M = H(H(N) xor H(g), H(I), s, A, B, K)
    def validate(server)
      server.validate(m)
    end

    def authenticate!(client_auth)
      authenticate(client_auth) || raise(SRP::WrongPassword)
    end

    def authenticate(client_auth)
      if(client_auth == m)
        @authenticated = true
        return @user
      end
    end

    def to_hash
      if @authenticated
        { :M2 => m2 }
      else
        { :B => bb,
#         :b => @b.to_s(16),    # only use for debugging
          :salt => @user.salt.to_s(16)
        }
      end
    end

    def to_json(options={})
      to_hash.to_json(options)
    end

    # for debugging use:
    def internal_state
      {
        username: @user.username,
        salt: @user.salt.to_s(16),
        verifier: @user.verifier.to_s(16),
        aa: aa,
        bb: bb,
        s: secret.to_s(16),
        k: k,
        m: m,
        m2: m2
      }
    end

    def aa
      @aa ||= modpow(GENERATOR, @a).to_s(16) # A = g^a (mod N)
    end

    # B = g^b + k v (mod N)
    def bb
      @bb ||= calculate_bb.to_s(16)
    end

    protected

    def calculate_bb
      (modpow(GENERATOR, @b) + multiplier * @user.verifier) % BIG_PRIME_N
    end

    # only seed b for testing purposes.
    def initialize_server(aa, ephemeral = nil)
      @aa = aa
      @b = ephemeral || bigrand(32).hex
    end

    def initialize_client
      @a = bigrand(32).hex
      # bb will be set during handshake.
    end

    def secret
      return client_secret if @a
      return server_secret if @b
    end

    # client: K = H( (B - kg^x) ^ (a + ux) )
    def client_secret
      base = bb.hex
      # base += BIG_PRIME_N * @multiplier
      base -= modpow(GENERATOR, @user.private_key) * multiplier
      base = base % BIG_PRIME_N
      modpow(base, @user.private_key * u.hex + @a)
    end

    # server: K = H( (Av^u) ^ b )
    # do not cache this - it's secret and someone might store the
    # session in a CookieStore
    def server_secret
      base = (modpow(@user.verifier, u.hex) * aa.hex) % BIG_PRIME_N
      modpow(base, @b)
    end

    # SRP 6a uses
    # M = H(H(N) xor H(g), H(I), s, A, B, K)
    def m
      @m ||= sha256_hex(n_xor_g_long, login_hash, @user.salt.to_s(16), aa, bb, k)
    end

    def m2
      @m2 ||= sha256_hex(aa, m, k)
    end

    def k
      @k ||= sha256_int(secret)
    end

    def n_xor_g_long
      @n_xor_g_long ||= hn_xor_hg.bytes.map{|b| "%02x" % b.ord}.join
    end

    def login_hash
      @login_hash ||= sha256_str(@user.username)
    end

    def u
      @u ||= sha256_hex(aa, bb)
    end

  end
end