summaryrefslogtreecommitdiff
path: root/lib/srp/session.rb
blob: abf91cc29cbca8cd232b4ca5e9bcf33d0a26f58f (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
module SRP
  class Session
    include SRP::Util
    attr_accessor :user

    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.to_s(16) }
      else
        { :B => bb.to_s(16),
#         :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.to_s(16),
        bb: bb.to_s(16),
        s: secret.to_s(16),
        k: k.to_s(16),
        m: m.to_s(16),
        m2: m2.to_s(16)
      }
    end

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

    # B = g^b + k v (mod N)
    def bb
      @bb ||= (modpow(GENERATOR, @b) + multiplier * @user.verifier) % BIG_PRIME_N
    end

    protected


    # 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
      # base += BIG_PRIME_N * @multiplier
      base -= modpow(GENERATOR, @user.private_key) * multiplier
      base = base % BIG_PRIME_N
      modpow(base, @user.private_key * u + @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) * aa) % 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_int(n_xor_g_long, login_hash, @user.salt, aa, bb, k).hex
    end

    def m2
      @m2 ||= sha256_int(aa, m, k).hex
    end

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

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

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

    def u
      @u ||= sha256_int(aa, bb).hex
    end

  end
end