aws-cognito-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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a9d319dfe478786e4bda431aa7d660c10944737a7d66e95c8c253ab5ec75f691
4
+ data.tar.gz: 3741cfe9072feea72cc50d5c41ed5f5111cf9f28df32eaeefdd223908e00e880
5
+ SHA512:
6
+ metadata.gz: f6f713236d8e76dd635dda2a2437393d7a2b0b009e1f148bf138e53791cf0a1d90e077ffedc7a880f0caf878ab4d493ace2194a43d84b5c96bffcab8c07c5ba2
7
+ data.tar.gz: d28cb221df9702987bf37c1f7b1c4fd12af4b74182834435e8ca7833ffc3ad7ba772b3564c883d26d069af167abfa955bc42703d622a794762050617c6077eaa
@@ -0,0 +1,16 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - name: Set up Ruby
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 3.0.0
14
+ bundler-cache: true
15
+ - name: Run the default task
16
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,37 @@
1
+ *.a
2
+ *.bundle
3
+ *.gem
4
+ *.o
5
+ *.rbc
6
+ *.so
7
+
8
+ .bundle
9
+ /.bundle/
10
+
11
+ .yardoc
12
+ /.yardoc
13
+ /_yardoc/
14
+ _yardoc
15
+
16
+ .config
17
+ /coverage/
18
+ /doc/
19
+ /pkg/
20
+ /spec/reports/
21
+ /tmp/
22
+ Gemfile.lock
23
+ InstalledFiles
24
+ coverage
25
+ doc/
26
+ lib/bundler/man
27
+ mkmf.log
28
+ pkg
29
+ rdoc
30
+ spec/db/*
31
+ spec/reports
32
+ test/tmp
33
+ test/version_tmp
34
+ tmp
35
+
36
+ # rspec failure tracking
37
+ .rspec_status
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## Changelog for aws-cognito-srp-ruby
2
+
3
+ ### [0.1.0] - 2021-09-17
4
+
5
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Pedro Fayolle
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Aws::CognitoSrp for Ruby
2
+
3
+ An unofficial Ruby library implementing
4
+ [AWS Cognito's SRP authentication flow](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#Using-SRP-password-verification-in-custom-authentication-flow).
5
+
6
+ [Originally
7
+ translated](https://gist.github.com/jviney/5fd0fab96cd70d5d46853f052be4744c#file-aws_cognito_srp-rb-L4)
8
+ from Python's [Warrant](https://github.com/capless/warrant) by Jonathan Viney,
9
+ packaged into this gem by Pedro Carbajal.
10
+
11
+ ## Installation
12
+
13
+ In your Gemfile:
14
+
15
+ ```ruby
16
+ gem 'aws-cognito-srp'
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ require "aws-cognito-srp"
23
+
24
+ aws_srp = Aws::CognitoSrp.new(
25
+ username: "username",
26
+ password: "password",
27
+ pool_id: "pool-id",
28
+ client_id: "client-id",
29
+ aws_client: Aws::CognitoIdentityProvider::Client.new(region: "ap-southeast-2")
30
+ )
31
+
32
+ aws_srp.authenticate
33
+ ```
34
+
35
+ ## Development
36
+
37
+ After checking out the repo, run `bin/setup` to install dependencies. You can
38
+ also run `bin/console` for an interactive prompt that will allow you to
39
+ experiment.
40
+
41
+ To install this gem onto your local machine, run `bundle exec rake install`. To
42
+ release a new version, update the version number in `version.rb`, and then run
43
+ `bundle exec rake release`, which will create a git tag for the version, push
44
+ git commits and the created tag, and push the `.gem` file to
45
+ [rubygems.org](https://rubygems.org).
46
+
47
+ ## Contributing
48
+
49
+ Bug reports and pull requests are welcome on GitHub at
50
+ https://github.com/pilaf/aws-cognito-srp-ruby
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/aws/cognito_srp/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "aws-cognito-srp"
7
+ spec.version = Aws::CognitoSrp::VERSION
8
+ spec.authors = ["Jonathan Viney", "Pedro Carbajal", "The Warrant developers"]
9
+ spec.email = ["pedro_c@beezwax.net"]
10
+
11
+ spec.summary = "AWS Cognito SRP auth for Ruby"
12
+ spec.description = "Unofficial Ruby library implementing AWS Cognito's SRP authentication flow"
13
+ spec.homepage = "https://github.com/pilaf/aws-cognito-srp-ruby"
14
+ spec.license = "MIT"
15
+
16
+ # Specify which files should be added to the gem when it is released.
17
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
20
+ end
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "aws-sdk-cognitoidentityprovider"
24
+
25
+ spec.add_development_dependency "bundler", "~> 2.2.0"
26
+ spec.add_development_dependency "rake", "~> 13.0"
27
+ spec.add_development_dependency "ox", "~> 2.14.0"
28
+
29
+ # For more information and examples about making a new gem, checkout our
30
+ # guide at: https://bundler.io/guides/creating_gem.html
31
+ end
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "aws/cognito_srp"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ class CognitoSrp
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aws-sdk-cognitoidentityprovider"
4
+ require "aws/cognito_srp/version"
5
+
6
+ require "openssl"
7
+ require "digest"
8
+
9
+ module Aws
10
+ # Client for AWS Cognito Identity Provider using Secure Remote Password (SRP).
11
+ #
12
+ # Borrowed from:
13
+ # https://gist.github.com/jviney/5fd0fab96cd70d5d46853f052be4744c
14
+ #
15
+ # This code is a direct translation of the Python version found here:
16
+ # https://github.com/capless/warrant/blob/ff2e4793d8479e770f2461ef7cbc0c15ee784395/warrant/aws_srp.py
17
+ #
18
+ # Example usage:
19
+ #
20
+ # aws_srp = Aws::CognitoSrp.new(
21
+ # username: "username",
22
+ # password: "password",
23
+ # pool_id: "pool-id",
24
+ # client_id: "client-id",
25
+ # aws_client: Aws::CognitoIdentityProvider::Client.new(region: "us-west-2")
26
+ # )
27
+ #
28
+ # aws_srp.authenticate
29
+ #
30
+ class CognitoSrp
31
+ USER_SRP_AUTH = "USER_SRP_AUTH"
32
+ PASSWORD_VERIFIER = "PASSWORD_VERIFIER"
33
+ NEW_PASSWORD_REQUIRED = "NEW_PASSWORD_REQUIRED"
34
+
35
+ N_HEX = %w(
36
+ FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08
37
+ 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B
38
+ 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9
39
+ A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6
40
+ 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8
41
+ FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
42
+ 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C
43
+ 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718
44
+ 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D
45
+ 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D
46
+ B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226
47
+ 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
48
+ BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC
49
+ E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF
50
+ ).join.freeze
51
+
52
+ G_HEX = '2'
53
+
54
+ INFO_BITS = 'Caldera Derived Key'
55
+
56
+ def initialize(username:, password:, pool_id:, client_id:, aws_client:)
57
+ @username = username
58
+ @password = password
59
+ @pool_id = pool_id
60
+ @client_id = client_id
61
+ @aws_client = aws_client
62
+
63
+ @big_n = hex_to_long(N_HEX)
64
+ @g = hex_to_long(G_HEX)
65
+ @k = hex_to_long(hex_hash("00#{N_HEX}0#{G_HEX}"))
66
+ @small_a_value = generate_random_small_a
67
+ @large_a_value = calculate_a
68
+ end
69
+
70
+ def authenticate
71
+ init_auth_response = @aws_client.initiate_auth(
72
+ client_id: @client_id,
73
+ auth_flow: USER_SRP_AUTH,
74
+ auth_parameters: {
75
+ USERNAME: @username,
76
+ SRP_A: long_to_hex(@large_a_value)
77
+ }
78
+ )
79
+
80
+ raise unless init_auth_response.challenge_name == PASSWORD_VERIFIER
81
+
82
+ challenge_response = process_challenge(init_auth_response.challenge_parameters)
83
+
84
+ auth_response = @aws_client.respond_to_auth_challenge(
85
+ client_id: @client_id,
86
+ challenge_name: PASSWORD_VERIFIER,
87
+ challenge_responses: challenge_response
88
+ )
89
+
90
+ raise "new password required" if auth_response.challenge_name == NEW_PASSWORD_REQUIRED
91
+
92
+ auth_response.authentication_result
93
+ end
94
+
95
+ private
96
+
97
+ def generate_random_small_a
98
+ random_long_int = get_random(128)
99
+ random_long_int % @big_n
100
+ end
101
+
102
+ def calculate_a
103
+ big_a = @g.pow(@small_a_value, @big_n)
104
+ if big_a % @big_n == 0
105
+ raise "Safety check for A failed"
106
+ end
107
+
108
+ big_a
109
+ end
110
+
111
+ def get_password_authentication_key(username, password, server_b_value, salt)
112
+ u_value = calculate_u(@large_a_value, server_b_value)
113
+ if u_value == 0
114
+ raise "U cannot be zero."
115
+ end
116
+
117
+ username_password = "#{@pool_id.split("_")[1]}#{username}:#{password}"
118
+ username_password_hash = hash_sha256(username_password)
119
+
120
+ x_value = hex_to_long(hex_hash(pad_hex(salt) + username_password_hash))
121
+ g_mod_pow_xn = @g.pow(x_value, @big_n)
122
+ int_value2 = server_b_value - @k * g_mod_pow_xn
123
+ s_value = int_value2.pow(@small_a_value + u_value * x_value, @big_n)
124
+ hkdf = compute_hkdf(hex_to_bytes(pad_hex(s_value)), hex_to_bytes(pad_hex(long_to_hex(u_value))))
125
+ hkdf
126
+ end
127
+
128
+ def process_challenge(challenge_parameters)
129
+ user_id_for_srp = challenge_parameters.fetch("USER_ID_FOR_SRP")
130
+ salt_hex = challenge_parameters.fetch("SALT")
131
+ srp_b_hex = challenge_parameters.fetch("SRP_B")
132
+ secret_block_b64 = challenge_parameters.fetch("SECRET_BLOCK")
133
+
134
+ timestamp = Time.now.utc.strftime("%a %b %-d %H:%M:%S %Z %Y")
135
+
136
+ hkdf = get_password_authentication_key(user_id_for_srp, @password, srp_b_hex.to_i(16), salt_hex)
137
+ secret_block_bytes = Base64.strict_decode64(secret_block_b64)
138
+ msg = @pool_id.split("_")[1] + user_id_for_srp + secret_block_bytes + timestamp
139
+ hmac_digest = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), hkdf, msg)
140
+ signature_string = Base64.strict_encode64(hmac_digest).force_encoding('utf-8')
141
+
142
+ {
143
+ "TIMESTAMP" => timestamp,
144
+ "USERNAME" => user_id_for_srp,
145
+ "PASSWORD_CLAIM_SECRET_BLOCK" => secret_block_b64,
146
+ "PASSWORD_CLAIM_SIGNATURE" => signature_string
147
+ }
148
+ end
149
+
150
+ def hash_sha256(buf)
151
+ Digest::SHA256.hexdigest(buf)
152
+ end
153
+
154
+ def hex_hash(hex_string)
155
+ hash_sha256(hex_to_bytes(hex_string))
156
+ end
157
+
158
+ def hex_to_bytes(hex_string)
159
+ [hex_string].pack('H*')
160
+ end
161
+
162
+ def bytes_to_hex(bytes)
163
+ bytes.unpack1('H*')
164
+ end
165
+
166
+ def hex_to_long(hex_string)
167
+ hex_string.to_i(16)
168
+ end
169
+
170
+ def long_to_hex(long_num)
171
+ long_num.to_s(16)
172
+ end
173
+
174
+ def get_random(nbytes)
175
+ random_hex = bytes_to_hex(SecureRandom.bytes(nbytes))
176
+ hex_to_long(random_hex)
177
+ end
178
+
179
+ def pad_hex(long_int)
180
+ hash_str = if long_int.is_a?(String)
181
+ long_int
182
+ else
183
+ long_to_hex(long_int)
184
+ end
185
+
186
+ if hash_str.size % 2 == 1
187
+ hash_str = "0#{hash_str}"
188
+ elsif '89ABCDEFabcdef'.include?(hash_str[0])
189
+ hash_str = "00#{hash_str}"
190
+ end
191
+
192
+ hash_str
193
+ end
194
+
195
+ def compute_hkdf(ikm, salt)
196
+ prk = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), salt, ikm)
197
+ info_bits_update = INFO_BITS + 1.chr.force_encoding('utf-8')
198
+ hmac_hash = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), prk, info_bits_update)
199
+ hmac_hash[0, 16]
200
+ end
201
+
202
+ def calculate_u(big_a, big_b)
203
+ u_hex_hash = hex_hash(pad_hex(big_a) + pad_hex(big_b))
204
+ hex_to_long(u_hex_hash)
205
+ end
206
+ end
207
+ end
208
+
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aws/cognito_srp"
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aws/cognito_srp"
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws-cognito-srp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Viney
8
+ - Pedro Carbajal
9
+ - The Warrant developers
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2021-09-17 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: aws-sdk-cognitoidentityprovider
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: bundler
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: 2.2.0
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: 2.2.0
43
+ - !ruby/object:Gem::Dependency
44
+ name: rake
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '13.0'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '13.0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: ox
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: 2.14.0
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: 2.14.0
71
+ description: Unofficial Ruby library implementing AWS Cognito's SRP authentication
72
+ flow
73
+ email:
74
+ - pedro_c@beezwax.net
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - ".github/workflows/main.yml"
80
+ - ".gitignore"
81
+ - CHANGELOG.md
82
+ - Gemfile
83
+ - LICENSE
84
+ - README.md
85
+ - Rakefile
86
+ - aws-cognito-srp.gemspec
87
+ - bin/console
88
+ - bin/setup
89
+ - lib/aws-cognito-srp.rb
90
+ - lib/aws/cognito_srp.rb
91
+ - lib/aws/cognito_srp/version.rb
92
+ - lib/aws_cognito_srp.rb
93
+ homepage: https://github.com/pilaf/aws-cognito-srp-ruby
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubygems_version: 3.2.3
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: AWS Cognito SRP auth for Ruby
116
+ test_files: []