aws-srp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bc27e086070a47ca1d6035d3c058ac7865e72bc527c748e584f6edf3f2e65a77
4
+ data.tar.gz: f939b7bd528cd2d1aad17e5651eabf88581e9ed28c70ba4d01d1574c344a7c15
5
+ SHA512:
6
+ metadata.gz: 0e641bdbb5e55d8e9694f952a14cc16d2f6783e7f43787bb2ce7ce37f767d69a40c1c1aeeb135e072ab13273afc8b87bc6cd324ae4d1c41a97e7bbe1bbd9e624
7
+ data.tar.gz: f57c6d80d0fc372b2d70a329c60a78f928aa570e798b6272e806dc72fc83a34efd4b9f404a08e2b21f5ea4544b1b2ba2e09615f3d1763fcc4231de97d3c2a174
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.6.6
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in aws-srp.gemspec
4
+ gemspec
5
+
6
+ gem 'pry'
7
+ gem 'rake', '~> 12.0'
8
+ gem 'rspec', '~> 3.0'
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ aws-srp (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.3)
10
+ diff-lcs (1.4.4)
11
+ method_source (1.0.0)
12
+ pry (0.13.1)
13
+ coderay (~> 1.1)
14
+ method_source (~> 1.0)
15
+ rake (12.3.3)
16
+ rspec (3.9.0)
17
+ rspec-core (~> 3.9.0)
18
+ rspec-expectations (~> 3.9.0)
19
+ rspec-mocks (~> 3.9.0)
20
+ rspec-core (3.9.2)
21
+ rspec-support (~> 3.9.3)
22
+ rspec-expectations (3.9.2)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.9.0)
25
+ rspec-mocks (3.9.1)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.9.0)
28
+ rspec-support (3.9.3)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ aws-srp!
35
+ pry
36
+ rake (~> 12.0)
37
+ rspec (~> 3.0)
38
+
39
+ BUNDLED WITH
40
+ 2.1.4
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Tim Masliuchenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,60 @@
1
+ # aws-srp [![Build Status](https://travis-ci.org/timsly/aws-srp.svg?branch=master)](https://travis-ci.org/timsly/aws-srp)
2
+
3
+ AWS Cognito SRP Utility
4
+
5
+ This is a ruby implementation of [the AWS Cognito flow helpers](https://github.com/aws-amplify/amplify-js/blob/main/packages/amazon-cognito-identity-js/src/AuthenticationHelper.js)
6
+ using some ideas from [the SIRP gem](https://github.com/grempe/sirp)
7
+
8
+ By design the `AwsSRP::Flow` only processes params but doesn't trigger any requests.
9
+ Requests should be handled on the client side.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'aws-srp'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle install
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install aws-srp
26
+
27
+ ## Usage
28
+
29
+ ```ruby
30
+ flow = AwsSRP::Flow.new
31
+ init_params = flow.init_auth
32
+
33
+ # Do the init AWS Congnito request by sending `init_params`
34
+ # Parse the JSON response and pass it to the `AwsSRP::Flow#verify_password`
35
+
36
+ challange_request_params = flow.verify_password(challange_response_body)
37
+
38
+ # Send the `challange_request_params` and parse the response.
39
+ # The session token should be there
40
+ ```
41
+
42
+ ## TODO
43
+
44
+ - Test coverage for `AwsSRP::SRP` and `AwsSRP::Flow` classes
45
+ - Handle more AWS Cognito features(2FA, etc)
46
+
47
+ ## Development
48
+
49
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
50
+
51
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
52
+
53
+ ## Contributing
54
+
55
+ Bug reports and pull requests are welcome on GitHub at https://github.com/timsly/aws-srp.
56
+
57
+
58
+ ## License
59
+
60
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/aws_srp/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'aws-srp'
7
+ spec.version = AwsSRP::VERSION
8
+ spec.authors = ['Tim Masliuchenko']
9
+ spec.email = ['insside@gmail.com']
10
+
11
+ spec.summary = %q{AWS Cognito SRP Utility}
12
+ spec.description = %q{AWS Cognito SRP Utility}
13
+ spec.homepage = 'https://github.com/timsly/aws-srp'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/timsly/aws-srp'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ['lib']
28
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'aws-srp'
5
+
6
+ require 'pry'
7
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws_srp'
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+ require 'securerandom'
6
+
7
+ require 'aws_srp/version'
8
+ require 'aws_srp/hasher'
9
+ require 'aws_srp/hex'
10
+ require 'aws_srp/srp'
11
+ require 'aws_srp/flow'
12
+ require 'aws_srp/flow/password_verifier_response'
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsSRP
4
+ # AWS Cognito flow
5
+ class Flow
6
+ attr_reader :pool_id, :username, :password, :srp
7
+
8
+ def initialize(pool_id, username, password)
9
+ @pool_id = pool_id
10
+ @username = username
11
+ @password = password
12
+
13
+ @srp = SRP.new
14
+ end
15
+
16
+ def now
17
+ @now ||= Time.now.utc.strftime('%a %b %-e %H:%M:%S UTC %Y')
18
+ end
19
+
20
+ def init_auth
21
+ {
22
+ AuthParameters: {
23
+ USERNAME: username,
24
+ SRP_A: srp.aa.str
25
+ }
26
+ }
27
+ end
28
+
29
+ def verify_password(response)
30
+ response = PasswordVerifierResponse.new(response)
31
+
32
+ srp.username = [pool_id, response.user_id].join
33
+ srp.password = password
34
+ srp.salt = response.salt
35
+ srp.bb = response.bb
36
+
37
+ hmac = Hasher.new(srp.hkdf)
38
+ .update(pool_id)
39
+ .update(response.user_id)
40
+ .update(response.secret_block, base64: true)
41
+ .update(now)
42
+
43
+ {
44
+ ChallengeName: response.challenge_name,
45
+ ChallengeResponses: {
46
+ USERNAME: response.user_id,
47
+ PASSWORD_CLAIM_SECRET_BLOCK: response.secret_block,
48
+ TIMESTAMP: now,
49
+ PASSWORD_CLAIM_SIGNATURE: hmac.digest64
50
+ }
51
+ }
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsSRP
4
+ class Flow
5
+ # Password Verifier Challange response wrapper
6
+ class PasswordVerifierResponse
7
+ attr_reader :response
8
+
9
+ def initialize(response)
10
+ @response = response
11
+ end
12
+
13
+ def challenge_name
14
+ dig!('ChallengeName')
15
+ end
16
+
17
+ def salt
18
+ dig!('ChallengeParameters', 'SALT')
19
+ end
20
+
21
+ def secret_block
22
+ dig!('ChallengeParameters', 'SECRET_BLOCK')
23
+ end
24
+
25
+ def bb
26
+ dig!('ChallengeParameters', 'SRP_B')
27
+ end
28
+
29
+ def user_id
30
+ dig!('ChallengeParameters', 'USER_ID_FOR_SRP')
31
+ end
32
+
33
+ private
34
+
35
+ def dig!(*path)
36
+ response.dig(*path) ||
37
+ raise(ArgumentError, "#{path.join('/')} not found in the response")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsSRP
4
+ # Depends on the `msg` presence acts as a digest or a hmac
5
+ class Hasher
6
+ ALGORITHM = 'sha256'
7
+
8
+ def self.digest(key, msg = nil)
9
+ new(key, msg).digest
10
+ end
11
+
12
+ def self.hexdigest(key, msg = nil)
13
+ new(key, msg).hexdigest
14
+ end
15
+
16
+ def initialize(key, msg = nil)
17
+ @key = key
18
+ @digest = OpenSSL::Digest.new(ALGORITHM)
19
+
20
+ update(msg) if msg
21
+ end
22
+
23
+ def update(msg, base64: false)
24
+ msg = Base64.strict_decode64(msg) if base64
25
+ hmac.update(msg)
26
+
27
+ self
28
+ end
29
+
30
+ def digest
31
+ perform :digest
32
+ end
33
+
34
+ def hexdigest
35
+ perform :hexdigest
36
+ end
37
+
38
+ def digest64
39
+ Base64.strict_encode64(digest)
40
+ end
41
+
42
+ private
43
+
44
+ def hmac
45
+ @hmac ||= OpenSSL::HMAC.new(@key, @digest)
46
+ end
47
+
48
+ def perform(method)
49
+ @hmac ? hmac.public_send(method) : @digest.public_send(method, @key)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsSRP
4
+ # Hexadecimal number represntation.
5
+ class Hex
6
+ HEX_STRING_PATTERN = /^[a-fA-F0-9]+$/.freeze
7
+ DOUBLE_ZERO_PADDING_PATTERN = /^[a-fA-F89]/.freeze
8
+ ARITHMETIC_OPERATORS = %i[+ - * / %].freeze
9
+
10
+ # Return a hex string (e.g. \x9F)
11
+ def self.str(str)
12
+ [str].pack('H*')
13
+ end
14
+
15
+ attr_reader :str
16
+
17
+ def initialize(val)
18
+ @str, @int = if val.is_a?(Integer)
19
+ [val.to_s(16), val]
20
+ else
21
+ validate_str!(val.to_s)
22
+ end
23
+
24
+ @str = @str.downcase
25
+ end
26
+
27
+ def int
28
+ @int ||= str.hex
29
+ end
30
+ alias to_i int
31
+
32
+ def padding
33
+ if str.length.odd?
34
+ '0'
35
+ elsif str =~ DOUBLE_ZERO_PADDING_PATTERN
36
+ '00'
37
+ end
38
+ end
39
+
40
+ def to_s
41
+ "#{padding}#{str}"
42
+ end
43
+
44
+ # to hex string (e.g. \x9F)
45
+ def to_hs
46
+ self.class.str(to_s)
47
+ end
48
+
49
+ def concat(other)
50
+ to_s + other.to_s
51
+ end
52
+
53
+ ARITHMETIC_OPERATORS.each do |method|
54
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
55
+ def #{method}(other)
56
+ self.class.new(to_i.#{method}(other.to_i))
57
+ end
58
+ CODE
59
+ end
60
+
61
+ def zero?
62
+ to_i.zero?
63
+ end
64
+
65
+ def mod_exp(b, m)
66
+ self.class.new(_mod_exp(to_i, b.to_i, m.to_i))
67
+ end
68
+
69
+ private
70
+
71
+ def validate_str!(val)
72
+ return val if val =~ HEX_STRING_PATTERN
73
+
74
+ raise ArgumentError, "'#{val}' must be a hex string"
75
+ end
76
+
77
+ # Modular Exponentiation
78
+ # https://en.wikipedia.org/wiki/Modular_exponentiation
79
+ # http://rosettacode.org/wiki/Modular_exponentiation#Ruby
80
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5')
81
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
82
+ def _mod_exp(a, b, m)
83
+ a.to_bn.mod_exp(b, m).to_i
84
+ end
85
+ CODE
86
+ else
87
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
88
+ def _mod_exp(a, b, m)
89
+ a.pow(b, m)
90
+ end
91
+ CODE
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsSRP
4
+ # SRP related logic
5
+ # Titleized methods are not common in ruby so `A` becomes `aa`
6
+ class SRP
7
+ N = %w[
8
+ FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08
9
+ 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B
10
+ 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9
11
+ A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6
12
+ 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8
13
+ FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
14
+ 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C
15
+ 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718
16
+ 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D
17
+ 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D
18
+ B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226
19
+ 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
20
+ BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC
21
+ E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF
22
+ ].freeze
23
+ INFO_BITS = "Caldera Derived Key\u0001"
24
+
25
+ attr_reader :nn, :g, :a, :username, :password, :salt, :bb
26
+
27
+ def initialize
28
+ @nn = Hex.new(N.join)
29
+ @g = Hex.new(2)
30
+ @a = Hex.new(SecureRandom.hex(128))
31
+ end
32
+
33
+ # A = g^a (mod N)
34
+ def aa
35
+ @aa ||= g.mod_exp(a, nn)
36
+ end
37
+
38
+ # Multiplier parameter
39
+ # k = H(N, g) (in SRP-6a)
40
+ def k
41
+ @k ||= hash(nn.concat(g), hex: true)
42
+ end
43
+
44
+ # u = H(A, B)
45
+ def u
46
+ srp_6a_safety_check! do
47
+ hash(aa.concat(bb), hex: true)
48
+ end
49
+ end
50
+
51
+ def credentials_hash
52
+ hash([username, password].join(':'))
53
+ end
54
+
55
+ # Private key (derived from username, raw password and salt)
56
+ # x = H(salt || H(username || ':' || password))
57
+ def x
58
+ @x ||= hash(salt.concat(credentials_hash), hex: true)
59
+ end
60
+
61
+ # Client secret
62
+ # S = (B - (k * g^x)) ^ (a + (u * x)) % N
63
+ def ss
64
+ ((bb - k * g.mod_exp(x, nn)) % nn).mod_exp(a + x * u, nn)
65
+ end
66
+
67
+ def username=(val)
68
+ reset
69
+ @username = val
70
+ end
71
+
72
+ def password=(val)
73
+ reset
74
+ @password = val
75
+ end
76
+
77
+ def salt=(val)
78
+ reset
79
+ @salt = Hex.new(val)
80
+ end
81
+
82
+ def bb=(val)
83
+ bb = Hex.new(val)
84
+ srp_6a_safety_check!(bb % nn)
85
+
86
+ reset
87
+ @bb = bb
88
+ end
89
+
90
+ def hkdf
91
+ prk = Hasher.digest(u.to_hs, ss.to_hs)
92
+ Hasher.digest(prk, INFO_BITS)[0, 16]
93
+ end
94
+
95
+ def reset
96
+ @x = nil
97
+
98
+ self
99
+ end
100
+
101
+ private
102
+
103
+ def hash(str, hex: false)
104
+ str = Hex.str(str) if hex
105
+ hexdigest = Hasher.hexdigest(str)
106
+
107
+ hex ? Hex.new(hexdigest) : hexdigest
108
+ end
109
+
110
+ def srp_6a_safety_check!(val = nil)
111
+ val ||= yield
112
+
113
+ raise ArgumentError, 'SRP-6a safety check failed' if val.zero?
114
+
115
+ val
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsSRP
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws-srp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tim Masliuchenko
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-09-08 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: AWS Cognito SRP Utility
14
+ email:
15
+ - insside@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - ".travis.yml"
23
+ - Gemfile
24
+ - Gemfile.lock
25
+ - LICENSE.txt
26
+ - README.md
27
+ - Rakefile
28
+ - aws-srp.gemspec
29
+ - bin/console
30
+ - bin/setup
31
+ - lib/aws-srp.rb
32
+ - lib/aws_srp.rb
33
+ - lib/aws_srp/flow.rb
34
+ - lib/aws_srp/flow/password_verifier_response.rb
35
+ - lib/aws_srp/hasher.rb
36
+ - lib/aws_srp/hex.rb
37
+ - lib/aws_srp/srp.rb
38
+ - lib/aws_srp/version.rb
39
+ homepage: https://github.com/timsly/aws-srp
40
+ licenses:
41
+ - MIT
42
+ metadata:
43
+ homepage_uri: https://github.com/timsly/aws-srp
44
+ source_code_uri: https://github.com/timsly/aws-srp
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.3.0
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.0.3
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: AWS Cognito SRP Utility
64
+ test_files: []