initial commit - testing srp auth
authorAzul <azul@leap.se>
Mon, 18 Jun 2012 10:34:11 +0000 (12:34 +0200)
committerAzul <azul@leap.se>
Mon, 18 Jun 2012 10:34:11 +0000 (12:34 +0200)
* This is lacking a few steps. We confirm the secret is the same but no key is generated from it and it is transfered over the wire in clear.
* this was inspired by https://gist.github.com/790048
* seperated util, client, server and test code

lib/srp.rb [new file with mode: 0644]
lib/srp/client.rb [new file with mode: 0644]
lib/srp/server.rb [new file with mode: 0644]
lib/srp/util.rb [new file with mode: 0644]
test/auth_test.rb [new file with mode: 0644]
test/test_helper.rb [new file with mode: 0644]

diff --git a/lib/srp.rb b/lib/srp.rb
new file mode 100644 (file)
index 0000000..999f9b6
--- /dev/null
@@ -0,0 +1,13 @@
+# Ruby library for the server side of the Secure Remote Password protocol
+
+# References
+# `The Stanford SRP Homepage',
+# http://srp.stanford.edu/
+# `SRP JavaScript Demo',
+# http://srp.stanford.edu/demo/demo.html
+
+$:.unshift File.dirname(__FILE__)
+module SRP
+  autoload :Client, 'srp/client'
+  autoload :Server, 'srp/server'
+end
diff --git a/lib/srp/client.rb b/lib/srp/client.rb
new file mode 100644 (file)
index 0000000..f4662e6
--- /dev/null
@@ -0,0 +1,48 @@
+require File.expand_path(File.dirname(__FILE__) + '/util')
+
+module SRP
+  class Client
+
+    include Util
+
+    attr_reader :salt, :verifier
+
+    def initialize(username, password)
+      @username = username
+      @password = password
+      @salt = bigrand(10).hex
+      @multiplier = multiplier # let's cache it
+      calculate_verifier
+    end
+
+    def authenticate(server, username, password)
+      x = calculate_x(username, password, salt)
+      a = bigrand(32).hex
+      aa = modpow(GENERATOR, a, PRIME_N) # A = g^a (mod N)
+      bb, u = server.initialize_auth(aa)
+      client_s = calculate_client_s(x, a, bb, u)
+      server.authenticate(aa, client_s)
+    end
+
+    protected
+    def calculate_verifier
+      x = calculate_x(@username, @password, @salt)
+      @verifier = modpow(GENERATOR, x, PRIME_N)
+    end
+
+    def calculate_x(username, password, salt)
+      shex = '%x' % [salt]
+      spad = if shex.length.odd? then '0' else '' end
+      sha1_hex(spad + shex + sha1_str([username, password].join(':'))).hex
+    end
+
+    def calculate_client_s(x, a, bb, u)
+      base = bb
+      base += PRIME_N * @multiplier
+      base -= modpow(GENERATOR, x, PRIME_N) * @multiplier
+      base = base % PRIME_N
+      modpow(base, x * u + a, PRIME_N)
+    end
+  end
+end
+
diff --git a/lib/srp/server.rb b/lib/srp/server.rb
new file mode 100644 (file)
index 0000000..a1189a1
--- /dev/null
@@ -0,0 +1,42 @@
+require File.expand_path(File.dirname(__FILE__) + '/util')
+
+module SRP
+  class Server
+
+    include Util
+
+    def initialize(salt, verifier)
+      @salt = salt
+      @verifier = verifier
+    end
+
+    def initialize_auth(aa)
+      @b = bigrand(32).hex
+      # B = g^b + k v (mod N)
+      @bb = (modpow(GENERATOR, @b, PRIME_N) + multiplier * @verifier) % PRIME_N
+      u = calculate_u(aa, @bb, PRIME_N)
+      return @bb, u
+    end
+
+    def authenticate(aa, client_s)
+      u = calculate_u(aa, @bb, PRIME_N)
+      base = (modpow(@verifier, u, PRIME_N) * aa) % PRIME_N
+      server_s = modpow(base, @b, PRIME_N)
+      return client_s == server_s
+    end
+
+
+    protected
+
+    def calculate_u(aa, bb, n)
+      nlen = 2 * ((('%x' % [n]).length * 4 + 7) >> 3)
+      aahex = '%x' % [aa]
+      bbhex = '%x' % [bb]
+      hashin = '0' * (nlen - aahex.length) + aahex \
+        + '0' * (nlen - bbhex.length) + bbhex
+      sha1_hex(hashin).hex
+    end
+  end
+end
+
+
diff --git a/lib/srp/util.rb b/lib/srp/util.rb
new file mode 100644 (file)
index 0000000..6792105
--- /dev/null
@@ -0,0 +1,53 @@
+require 'digest'
+require 'openssl'
+
+module SRP
+  module Util
+
+    # constants both sides know
+    PRIME_N = <<-EOS.split.join.hex # 1024 bits modulus (N)
+eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c25657
+6d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089da
+d15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e5
+7ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb
+06e3
+    EOS
+    GENERATOR = 2 # g
+
+    # a^n (mod m)
+    def modpow(a, n, m)
+      r = 1
+      while true
+        r = r * a % m if n[0] == 1
+        n >>= 1
+        return r if n == 0
+        a = a * a % m
+      end
+    end
+
+    def sha1_hex(h)
+      Digest::SHA1.hexdigest([h].pack('H*'))
+    end
+
+    def sha1_str(s)
+      Digest::SHA1.hexdigest(s)
+    end
+
+    def bigrand(bytes)
+      OpenSSL::Random.random_bytes(bytes).unpack("H*")[0]
+    end
+
+    def multiplier
+      n = PRIME_N
+      g = GENERATOR
+      nhex = '%x' % [n]
+      nlen = nhex.length + (nhex.length.odd? ? 1 : 0 )
+      ghex = '%x' % [g]
+      hashin = '0' * (nlen - nhex.length) + nhex \
+        + '0' * (nlen - ghex.length) + ghex
+      sha1_hex(hashin).hex % n
+    end
+  end
+
+end
+
diff --git a/test/auth_test.rb b/test/auth_test.rb
new file mode 100644 (file)
index 0000000..e6c4017
--- /dev/null
@@ -0,0 +1,25 @@
+require File.expand_path(File.dirname(__FILE__) + '/test_helper')
+
+class AuthTest < Test::Unit::TestCase
+
+  def setup
+    @username = 'user'
+    @password = 'opensasemi'
+    @client = SRP::Client.new(@username, @password)
+    @server = SRP::Server.new(@client.salt, @client.verifier)
+  end
+
+  def test_successful_auth
+    assert @client.authenticate(@server, @username, @password)
+  end
+
+  def test_wrong_password
+    assert !@client.authenticate(@server, @username, "password")
+  end
+
+  def test_wrong_username
+    assert !@client.authenticate(@server, "username", @password)
+  end
+end
+
+
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644 (file)
index 0000000..68d3acf
--- /dev/null
@@ -0,0 +1,3 @@
+require "rubygems"
+require 'test/unit'
+require File.expand_path(File.dirname(__FILE__) + '/../lib/srp.rb')