sirp 2.0.0.pre → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
}
|