aws-srp 0.1.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.
@@ -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: []