sirp 2.0.0.pre → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +4 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/README.md +170 -36
- data/lib/sirp.rb +3 -1
- data/lib/sirp/client.rb +67 -21
- data/lib/sirp/parameters.rb +171 -0
- data/lib/sirp/sirp.rb +141 -261
- data/lib/sirp/verifier.rb +87 -36
- data/lib/sirp/version.rb +1 -1
- data/sirp.gemspec +4 -3
- metadata +32 -24
- metadata.gz.sig +0 -0
- data/examples/Gemfile +0 -6
- data/examples/README.md +0 -34
- data/examples/clients/javascript/.gitignore +0 -1
- data/examples/clients/javascript/app.js +0 -59
- data/examples/clients/javascript/index.html +0 -23
- data/examples/clients/javascript/package.json +0 -15
- data/examples/clients/ruby/client.rb +0 -48
- data/examples/server.rb +0 -88
data/lib/sirp/verifier.rb
CHANGED
@@ -1,72 +1,123 @@
|
|
1
1
|
module SIRP
|
2
2
|
class Verifier
|
3
|
+
include SIRP
|
3
4
|
attr_reader :N, :g, :k, :A, :B, :b, :S, :K, :M, :H_AMK, :hash
|
4
5
|
|
6
|
+
# Select modulus (N), generator (g), and one-way hash function (SHA1 or SHA256)
|
7
|
+
#
|
8
|
+
# @param group [Integer] the group size in bits
|
5
9
|
def initialize(group = 2048)
|
6
|
-
|
7
|
-
|
8
|
-
|
10
|
+
raise ArgumentError, 'must be an Integer' unless group.is_a?(Integer)
|
11
|
+
raise ArgumentError, 'must be a known group size' unless [1024, 1536, 2048, 3072, 4096, 6144, 8192].include?(group)
|
12
|
+
|
13
|
+
@N, @g, @hash = Ng(group)
|
14
|
+
@k = calc_k(@N, @g, hash)
|
9
15
|
end
|
10
16
|
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
17
|
+
# Phase 0 ; Generate a verifier and salt client-side. This should only be
|
18
|
+
# used during the initial user registration process. All three values
|
19
|
+
# should be provided as attributes in the user registration process. The
|
20
|
+
# verifier and salt should be persisted server-side. The verifier
|
21
|
+
# should be protected and never made public or given to any user.
|
22
|
+
# The salt should be returned to any user requesting it to start
|
23
|
+
# Phase 1 of the authentication process.
|
24
|
+
#
|
25
|
+
# @param username [String] the authentication username
|
26
|
+
# @param password [String] the authentication password
|
27
|
+
# @return [Hash] a Hash of the username, verifier, and salt
|
14
28
|
def generate_userauth(username, password)
|
29
|
+
raise ArgumentError, 'username must be a string' unless username.is_a?(String) && !username.empty?
|
30
|
+
raise ArgumentError, 'password must be a string' unless password.is_a?(String) && !password.empty?
|
31
|
+
|
15
32
|
@salt ||= SecureRandom.hex(10)
|
16
|
-
x =
|
17
|
-
v =
|
18
|
-
{ username: username, verifier:
|
33
|
+
x = calc_x(username, password, @salt, hash)
|
34
|
+
v = calc_v(x, @N, @g)
|
35
|
+
{ username: username, verifier: num_to_hex(v), salt: @salt }
|
19
36
|
end
|
20
37
|
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
38
|
+
# Phase 1 : Step 2 : Create a challenge for the client, and a proof to be stored
|
39
|
+
# on the server for later use when verifying the client response.
|
40
|
+
#
|
41
|
+
# @param username [String] the client provided authentication username
|
42
|
+
# @param xverifier [String] the server stored verifier for the username in hex
|
43
|
+
# @param xsalt [String] the server stored salt for the username in hex
|
44
|
+
# @param xaa [String] the client provided 'A' value in hex
|
45
|
+
# @return [Hash] a Hash with the challenge for the client and a proof for the server
|
24
46
|
def get_challenge_and_proof(username, xverifier, xsalt, xaa)
|
47
|
+
raise ArgumentError, 'username must be a string' unless username.is_a?(String) && !username.empty?
|
48
|
+
raise ArgumentError, 'xverifier must be a string' unless xverifier.is_a?(String)
|
49
|
+
raise ArgumentError, 'xverifier must be a hex string' unless xverifier =~ /^[a-fA-F0-9]+$/
|
50
|
+
raise ArgumentError, 'xsalt must be a string' unless xsalt.is_a?(String)
|
51
|
+
raise ArgumentError, 'xsalt must be a hex string' unless xsalt =~ /^[a-fA-F0-9]+$/
|
52
|
+
raise ArgumentError, 'xaa must be a string' unless xaa.is_a?(String)
|
53
|
+
raise ArgumentError, 'xaa must be a hex string' unless xaa =~ /^[a-fA-F0-9]+$/
|
54
|
+
|
25
55
|
# SRP-6a safety check
|
26
|
-
return false if (xaa.to_i(16) % @N)
|
27
|
-
|
56
|
+
return false if (xaa.to_i(16) % @N).zero?
|
57
|
+
|
58
|
+
# Generate b and B
|
59
|
+
v = xverifier.to_i(16)
|
60
|
+
@b ||= SecureRandom.hex(32).hex
|
61
|
+
@B = num_to_hex(calc_B(@b, k, v, @N, @g))
|
28
62
|
|
29
63
|
{
|
30
64
|
challenge: { B: @B, salt: xsalt },
|
31
|
-
proof: { A: xaa, B: @B, b:
|
65
|
+
proof: { A: xaa, B: @B, b: num_to_hex(@b), I: username, s: xsalt, v: xverifier }
|
32
66
|
}
|
33
67
|
end
|
34
68
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
69
|
+
#
|
70
|
+
# Phase 2 : Step 1 : See Client#start_authentication
|
71
|
+
#
|
72
|
+
|
73
|
+
# Phase 2 : Step 2 : Use the server stored proof and the client provided 'M' value.
|
74
|
+
# Calculates a server 'M' value and compares it to the client provided one,
|
75
|
+
# and if they match the client and server have negotiated equal secrets.
|
76
|
+
# Returns a H(A, M, K) value on success and false on failure.
|
77
|
+
#
|
78
|
+
# Sets the @K value, which is the client and server negotiated secret key
|
79
|
+
# if verification succeeds. This can be used to derive strong encryption keys
|
80
|
+
# for later use. The client independently calculates the same @K value as well.
|
81
|
+
#
|
82
|
+
# If authentication fails the H_AMK value must not be provided to the client.
|
83
|
+
#
|
84
|
+
# @param proof [Hash] the server stored proof Hash with keys A, B, b, I, s, v
|
85
|
+
# @param client_M [String] the client provided 'M' value in hex
|
86
|
+
# @return [String, false] the H_AMK value in hex for the client, or false if verification failed
|
38
87
|
def verify_session(proof, client_M)
|
88
|
+
raise ArgumentError, 'proof must be a hash' unless proof.is_a?(Hash)
|
89
|
+
# gracefully handle string or symbol keys
|
90
|
+
Hashie.symbolize_keys!(proof)
|
91
|
+
raise ArgumentError, 'proof must have required hash keys' unless proof.keys == [:A, :B, :b, :I, :s, :v]
|
92
|
+
raise ArgumentError, 'client_M must be a string' unless client_M.is_a?(String)
|
93
|
+
raise ArgumentError, 'client_M must be a hex string' unless client_M =~ /^[a-fA-F0-9]+$/
|
94
|
+
|
39
95
|
@A = proof[:A]
|
40
96
|
@B = proof[:B]
|
41
97
|
@b = proof[:b].to_i(16)
|
42
98
|
v = proof[:v].to_i(16)
|
43
99
|
|
44
|
-
u =
|
100
|
+
u = calc_u(@A, @B, @N, hash)
|
45
101
|
|
46
102
|
# SRP-6a safety check
|
47
|
-
return false if u
|
103
|
+
return false if u.zero?
|
48
104
|
|
49
|
-
#
|
50
|
-
@S =
|
51
|
-
@K =
|
105
|
+
# Calculate session key 'S' and secret key 'K'
|
106
|
+
@S = num_to_hex(calc_server_S(@A.to_i(16), @b, v, u, @N))
|
107
|
+
@K = sha_hex(@S, hash)
|
52
108
|
|
53
|
-
#
|
54
|
-
@M =
|
109
|
+
# Calculate the 'M' matcher
|
110
|
+
@M = calc_M(@A, @B, @K, hash)
|
55
111
|
|
56
|
-
|
57
|
-
|
58
|
-
|
112
|
+
# Secure constant time comparison, hash the params to ensure
|
113
|
+
# that both strings being compared are equal length 32 Byte strings.
|
114
|
+
if secure_compare(Digest::SHA256.hexdigest(@M), Digest::SHA256.hexdigest(client_M))
|
115
|
+
# Authentication succeeded, Calculate the H(A,M,K) verifier
|
116
|
+
@H_AMK = num_to_hex(calc_H_AMK(@A, @M, @K, hash))
|
59
117
|
else
|
118
|
+
# Authentication failed
|
60
119
|
false
|
61
120
|
end
|
62
121
|
end
|
63
|
-
|
64
|
-
# generates challenge
|
65
|
-
# input verifier in hex
|
66
|
-
def generate_B(xverifier)
|
67
|
-
v = xverifier.to_i(16)
|
68
|
-
@b ||= SecureRandom.hex(32).hex
|
69
|
-
@B = SIRP.num_to_hex(SIRP.calc_B(@b, k, v, @N, @g))
|
70
|
-
end
|
71
122
|
end
|
72
123
|
end
|
data/lib/sirp/version.rb
CHANGED
data/sirp.gemspec
CHANGED
@@ -36,10 +36,11 @@ Gem::Specification.new do |spec|
|
|
36
36
|
|
37
37
|
# See : https://bugs.ruby-lang.org/issues/9569
|
38
38
|
spec.add_runtime_dependency 'rbnacl-libsodium', '~> 1.0'
|
39
|
-
spec.add_runtime_dependency '
|
39
|
+
spec.add_runtime_dependency 'sysrandom', '~> 1.0'
|
40
|
+
spec.add_runtime_dependency 'hashie', '~> 3.4'
|
40
41
|
|
41
|
-
spec.add_development_dependency 'bundler'
|
42
|
-
spec.add_development_dependency 'rake'
|
42
|
+
spec.add_development_dependency 'bundler'
|
43
|
+
spec.add_development_dependency 'rake'
|
43
44
|
spec.add_development_dependency 'rspec', '~> 3.4'
|
44
45
|
spec.add_development_dependency 'pry', '~> 0.10'
|
45
46
|
spec.add_development_dependency 'coveralls', '~> 0.8'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sirp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Glenn Rempe
|
@@ -31,7 +31,7 @@ cert_chain:
|
|
31
31
|
zieXiXZSAojfFx9g91fKdIrlPbInHU/BaCxXSLBwvOM0drE+c2ue9X8gB55XAhzX
|
32
32
|
37oBiw==
|
33
33
|
-----END CERTIFICATE-----
|
34
|
-
date: 2016-
|
34
|
+
date: 2016-09-21 00:00:00.000000000 Z
|
35
35
|
dependencies:
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rbnacl-libsodium
|
@@ -48,47 +48,61 @@ dependencies:
|
|
48
48
|
- !ruby/object:Gem::Version
|
49
49
|
version: '1.0'
|
50
50
|
- !ruby/object:Gem::Dependency
|
51
|
-
name:
|
51
|
+
name: sysrandom
|
52
52
|
requirement: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
54
|
- - "~>"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version:
|
56
|
+
version: '1.0'
|
57
57
|
type: :runtime
|
58
58
|
prerelease: false
|
59
59
|
version_requirements: !ruby/object:Gem::Requirement
|
60
60
|
requirements:
|
61
61
|
- - "~>"
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
version:
|
63
|
+
version: '1.0'
|
64
64
|
- !ruby/object:Gem::Dependency
|
65
|
-
name:
|
65
|
+
name: hashie
|
66
66
|
requirement: !ruby/object:Gem::Requirement
|
67
67
|
requirements:
|
68
68
|
- - "~>"
|
69
69
|
- !ruby/object:Gem::Version
|
70
|
-
version: '
|
71
|
-
type: :
|
70
|
+
version: '3.4'
|
71
|
+
type: :runtime
|
72
72
|
prerelease: false
|
73
73
|
version_requirements: !ruby/object:Gem::Requirement
|
74
74
|
requirements:
|
75
75
|
- - "~>"
|
76
76
|
- !ruby/object:Gem::Version
|
77
|
-
version: '
|
77
|
+
version: '3.4'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: bundler
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
type: :development
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
78
92
|
- !ruby/object:Gem::Dependency
|
79
93
|
name: rake
|
80
94
|
requirement: !ruby/object:Gem::Requirement
|
81
95
|
requirements:
|
82
|
-
- - "
|
96
|
+
- - ">="
|
83
97
|
- !ruby/object:Gem::Version
|
84
|
-
version: '
|
98
|
+
version: '0'
|
85
99
|
type: :development
|
86
100
|
prerelease: false
|
87
101
|
version_requirements: !ruby/object:Gem::Requirement
|
88
102
|
requirements:
|
89
|
-
- - "
|
103
|
+
- - ">="
|
90
104
|
- !ruby/object:Gem::Version
|
91
|
-
version: '
|
105
|
+
version: '0'
|
92
106
|
- !ruby/object:Gem::Dependency
|
93
107
|
name: rspec
|
94
108
|
requirement: !ruby/object:Gem::Requirement
|
@@ -173,7 +187,9 @@ files:
|
|
173
187
|
- ".gitignore"
|
174
188
|
- ".rubocop.yml"
|
175
189
|
- ".travis.yml"
|
190
|
+
- ".yardopts"
|
176
191
|
- CHANGELOG.md
|
192
|
+
- CODE_OF_CONDUCT.md
|
177
193
|
- Gemfile
|
178
194
|
- LICENSE.txt
|
179
195
|
- README.md
|
@@ -184,16 +200,9 @@ files:
|
|
184
200
|
- certs/gem-public_cert_grempe.pem
|
185
201
|
- docs/rfc2945.txt
|
186
202
|
- docs/rfc5054.txt
|
187
|
-
- examples/Gemfile
|
188
|
-
- examples/README.md
|
189
|
-
- examples/clients/javascript/.gitignore
|
190
|
-
- examples/clients/javascript/app.js
|
191
|
-
- examples/clients/javascript/index.html
|
192
|
-
- examples/clients/javascript/package.json
|
193
|
-
- examples/clients/ruby/client.rb
|
194
|
-
- examples/server.rb
|
195
203
|
- lib/sirp.rb
|
196
204
|
- lib/sirp/client.rb
|
205
|
+
- lib/sirp/parameters.rb
|
197
206
|
- lib/sirp/sirp.rb
|
198
207
|
- lib/sirp/verifier.rb
|
199
208
|
- lib/sirp/version.rb
|
@@ -213,9 +222,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
213
222
|
version: 2.1.0
|
214
223
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
215
224
|
requirements:
|
216
|
-
- - "
|
225
|
+
- - ">="
|
217
226
|
- !ruby/object:Gem::Version
|
218
|
-
version:
|
227
|
+
version: '0'
|
219
228
|
requirements: []
|
220
229
|
rubyforge_project:
|
221
230
|
rubygems_version: 2.5.1
|
@@ -223,4 +232,3 @@ signing_key:
|
|
223
232
|
specification_version: 4
|
224
233
|
summary: Secure (interoperable) Remote Password Auth (SRP-6a)
|
225
234
|
test_files: []
|
226
|
-
has_rdoc:
|
metadata.gz.sig
CHANGED
Binary file
|
data/examples/Gemfile
DELETED
data/examples/README.md
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
# SiRP : Secure (interoperable) Remote Password : Example
|
2
|
-
|
3
|
-
## Install Ruby Dependencies
|
4
|
-
|
5
|
-
```
|
6
|
-
$ cd examples
|
7
|
-
$ bundle install
|
8
|
-
```
|
9
|
-
|
10
|
-
## Run Server
|
11
|
-
|
12
|
-
```sh
|
13
|
-
# run this in the first terminal window
|
14
|
-
$ ./server.rb
|
15
|
-
```
|
16
|
-
|
17
|
-
## Authenticate with Ruby Client
|
18
|
-
|
19
|
-
```sh
|
20
|
-
# run this in the second terminal window
|
21
|
-
$ cd clients/ruby/
|
22
|
-
$ ./client.rb
|
23
|
-
```
|
24
|
-
|
25
|
-
## Authenticate with JavaScript Client
|
26
|
-
|
27
|
-
```sh
|
28
|
-
# see the output of the JS client in the browser's JS console
|
29
|
-
$ cd clients/javascript/
|
30
|
-
$ npm install
|
31
|
-
$ open index.html
|
32
|
-
```
|
33
|
-
|
34
|
-
You can find other username : password combinations in `server.rb`
|
@@ -1 +0,0 @@
|
|
1
|
-
/node_modules/
|
@@ -1,59 +0,0 @@
|
|
1
|
-
var client = new jsrp.client()
|
2
|
-
var serverAddr = 'http://localhost:4567/authenticate'
|
3
|
-
var username = 'leonardo'
|
4
|
-
var password = 'capricciosa'
|
5
|
-
|
6
|
-
$(document).ready(function () {
|
7
|
-
'use strict'
|
8
|
-
|
9
|
-
console.log('Starting Authentication')
|
10
|
-
|
11
|
-
$(document).ajaxError(function (event, request, settings) {
|
12
|
-
console.log(event)
|
13
|
-
console.log(request)
|
14
|
-
console.log(settings)
|
15
|
-
})
|
16
|
-
|
17
|
-
client.init({ username: username, password: password, length: 4096 }, function () {
|
18
|
-
client.createVerifier(function (err, result) {
|
19
|
-
// result will contain the necessary values the server needs to
|
20
|
-
// authenticate this user in the future.
|
21
|
-
// sendSaltToServer(result.salt)
|
22
|
-
// sendVerifierToServer(result.verifier)
|
23
|
-
|
24
|
-
if (err) {
|
25
|
-
console.log(err.stack);
|
26
|
-
}
|
27
|
-
|
28
|
-
var A = client.getPublicKey()
|
29
|
-
|
30
|
-
// Auth Phase 1
|
31
|
-
console.log('P1 : Sending username and A to server')
|
32
|
-
$.post(serverAddr, { username: username, A: A }, function (data) {
|
33
|
-
console.log('P1 : Received salt : ', data.salt)
|
34
|
-
console.log('P1 : Received B : ', data.B)
|
35
|
-
client.setSalt(data.salt)
|
36
|
-
client.setServerPublicKey(data.B)
|
37
|
-
|
38
|
-
var clientM = client.getProof()
|
39
|
-
console.log('P1 : calculated client M : ', clientM)
|
40
|
-
|
41
|
-
// Auth Phase 2
|
42
|
-
console.log('P2 : Sending username and M to server')
|
43
|
-
$.post(serverAddr, { username: username, M: clientM }, function (data) {
|
44
|
-
console.log('P2 : Received server H_AMK : ', data.H_AMK)
|
45
|
-
if (client.checkServerProof(data.H_AMK)) {
|
46
|
-
console.log('P2 : Client and server H_AMK match. Authenticated!')
|
47
|
-
console.log('P2 : Client and server negotiated shared key K : ', client.getSharedKey())
|
48
|
-
$('#status').html('Authenticated!')
|
49
|
-
$('#username').html(username)
|
50
|
-
$('#K').html(client.getSharedKey())
|
51
|
-
} else {
|
52
|
-
$('#status').html('Authenticated FAILED')
|
53
|
-
console.log('P2 : Client and server H_AMK DO NOT match. Authentication failed!')
|
54
|
-
}
|
55
|
-
}, 'json')
|
56
|
-
}, 'json')
|
57
|
-
})
|
58
|
-
})
|
59
|
-
})
|
@@ -1,23 +0,0 @@
|
|
1
|
-
<!DOCTYPE html>
|
2
|
-
<html>
|
3
|
-
<head>
|
4
|
-
<meta charset="utf-8">
|
5
|
-
<title>Secure Remote Password JS Client Example</title>
|
6
|
-
</head>
|
7
|
-
<body>
|
8
|
-
<h1>Secure Remote Password JS Client Example</h1>
|
9
|
-
|
10
|
-
<p>
|
11
|
-
Open the JavaScript console to see more output. Any shared secret
|
12
|
-
negotiated on successful authentication will be displayed below.
|
13
|
-
</p>
|
14
|
-
|
15
|
-
<p id='status'></p>
|
16
|
-
<p id='username'></p>
|
17
|
-
<p id='K'></p>
|
18
|
-
|
19
|
-
<script type="text/javascript" src="node_modules/jquery/dist/jquery.min.js"></script>
|
20
|
-
<script type="text/javascript" src="node_modules/jsrp/jsrp-browser.js"></script>
|
21
|
-
<script type="text/javascript" src="app.js"></script>
|
22
|
-
</body>
|
23
|
-
</html>
|
@@ -1,15 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"name": "javascript",
|
3
|
-
"version": "1.0.0",
|
4
|
-
"description": "",
|
5
|
-
"main": "app.js",
|
6
|
-
"scripts": {
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
8
|
-
},
|
9
|
-
"author": "",
|
10
|
-
"license": "New BSD",
|
11
|
-
"dependencies": {
|
12
|
-
"jquery": "^2.2.3",
|
13
|
-
"jsrp": "^0.2.0"
|
14
|
-
}
|
15
|
-
}
|