ronin-web-session_cookie 0.1.0.rc1

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,169 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
4
+ #
5
+ # ronin-web-session_cookie is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # ronin-web-session_cookie is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public License
16
+ # along with ronin-web-session_cookie. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ require 'ronin/web/session_cookie/cookie'
20
+ require 'ronin/support/encoding/base64'
21
+ require 'ronin/support/encoding/base62'
22
+
23
+ require 'python/pickle'
24
+
25
+ module Ronin
26
+ module Web
27
+ module SessionCookie
28
+ #
29
+ # Represents a Django signed session cookie (JSON or Pickle serialized).
30
+ #
31
+ # ## Examples
32
+ #
33
+ # Parse a Django JSON session cookie:
34
+ #
35
+ # Ronin::Web::SessionCookie.parse('sessionid=eyJmb28iOiJiYXIifQ:1pQcTx:UufiSnuPIjNs7zOAJS0UpqnyvRt7KET7BVes0I8LYbA')
36
+ # # =>
37
+ # # #<Ronin::Web::SessionCookie::Django:0x00007f29bb9c6b70
38
+ # # @hmac=
39
+ # # "R\xE7\xE2J{\x8F\"3l\xEF3\x80%-\x14\xA6\xA9\xF2\xBD\e{(D\xFB\x05W\xAC\xD0\x8F\va\xB0",
40
+ # # @params={"foo"=>"bar"},
41
+ # # @salt=1676070425>
42
+ #
43
+ # Parse a Django Pickled session cookie:
44
+ #
45
+ # Ronin::Web::SessionCookie.parse('sessionid=gAWVEAAAAAAAAAB9lIwDZm9vlIwDYmFylHMu:1pQcay:RjaK8DKN4xXQ_APIXXWEyFS08Q-PGo6UlRBFpedFk9M')
46
+ # # =>
47
+ # # #<Ronin::Web::SessionCookie::Django:0x00007f29b7aa6dc8
48
+ # # @hmac=
49
+ # # "F6\x8A\xF02\x8D\xE3\x15\xD0\xFC\x03\xC8]u\x84\xC8T\xB4\xF1\x0F\x8F\x1A\x8E\x94\x95\x10E\xA5\xE7E\x93\xD3",
50
+ # # @params={"foo"=>"bar"},
51
+ # # @salt=1676070860>
52
+ #
53
+ # @see https://docs.djangoproject.com/en/4.1/topics/http/sessions/#using-cookie-based-sessions
54
+ #
55
+ class Django < Cookie
56
+
57
+ # The salt used to sign the cookie.
58
+ #
59
+ # @return [Integer]
60
+ #
61
+ # @api public
62
+ attr_reader :salt
63
+
64
+ # The SHA256 HMAC of the Base64 encoded serialized {#params}.
65
+ #
66
+ # @return [String]
67
+ #
68
+ # @api public
69
+ attr_reader :hmac
70
+
71
+ #
72
+ # Initializes the Django cookie.
73
+ #
74
+ # @param [Hash{String => Object}] params
75
+ # The deserialized params of the session cookie.
76
+ #
77
+ # @param [Integer] salt
78
+ # The Base62 decoded timestamp that is used to salt the HMAC.
79
+ #
80
+ # @param [Integer] hmac
81
+ # The SHA256 HMAC of the Base64 encoded serialized {#params}.
82
+ #
83
+ # @api private
84
+ #
85
+ def initialize(params,salt,hmac)
86
+ super(params)
87
+
88
+ @salt = salt
89
+ @hmac = hmac
90
+ end
91
+
92
+ # Regular expression to match Django session cookies.
93
+ REGEXP = /\A(?:sessionid=)?#{URL_SAFE_BASE64_REGEXP}:#{URL_SAFE_BASE64_REGEXP}:#{URL_SAFE_BASE64_REGEXP}\z/
94
+
95
+ #
96
+ # Identifies if the cookie is a Django session cookie.
97
+ #
98
+ # @param [String] string
99
+ # The raw session cookie value.
100
+ #
101
+ # @return [Boolean]
102
+ # Indicates whether the session cookie value is a Django session
103
+ # cookie.
104
+ #
105
+ # @api public
106
+ #
107
+ def self.identify?(string)
108
+ string =~ REGEXP
109
+ end
110
+
111
+ #
112
+ # Parses a Django session cookie.
113
+ #
114
+ # @param [String] string
115
+ # The raw session cookie string to parse.
116
+ #
117
+ # @return [Django]
118
+ # The parsed and deserialized session cookie
119
+ #
120
+ # @api public
121
+ #
122
+ def self.parse(string)
123
+ # remove any 'sessionid' prefix.
124
+ string = string.sub(/\Asessionid=/,'')
125
+
126
+ # split the cookie
127
+ params, salt, hmac = string.split(':',3)
128
+
129
+ params = Support::Encoding::Base64.decode(params, mode: :url_safe)
130
+ params = if params.start_with?('{') && params.end_with?('}')
131
+ # JSON serialized cookie
132
+ JSON.parse(params)
133
+ else
134
+ # unpickle the Python Pickle serialized session cookie
135
+ Python::Pickle.load(params)
136
+ end
137
+
138
+ salt = Support::Encoding::Base62.decode(salt)
139
+ hmac = Support::Encoding::Base64.decode(hmac, mode: :url_safe)
140
+
141
+ return new(params,salt,hmac)
142
+ end
143
+
144
+ #
145
+ # Extracts the Django session cookie from the HTTP response.
146
+ #
147
+ # @param [Net::HTTPResponse] response
148
+ # The HTTP response object.
149
+ #
150
+ # @return [Django, nil]
151
+ # The parsed Django session cookie, or `nil` if there was no
152
+ # `Set-Cookie` header containing a Django session cookie.
153
+ #
154
+ # @api public
155
+ #
156
+ def self.extract(response)
157
+ if (set_cookie = response['Set-Cookie'])
158
+ cookie = set_cookie.split(';',2).first
159
+
160
+ if identify?(cookie)
161
+ return parse(cookie)
162
+ end
163
+ end
164
+ end
165
+
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
4
+ #
5
+ # ronin-web-session_cookie is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # ronin-web-session_cookie is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public License
16
+ # along with ronin-web-session_cookie. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ require 'ronin/web/session_cookie/cookie'
20
+
21
+ require 'base64'
22
+ require 'json'
23
+
24
+ module Ronin
25
+ module Web
26
+ module SessionCookie
27
+ #
28
+ # Represents a [JSON Web Token (JWT)][JWT].
29
+ #
30
+ # [JWT]: https://jwt.io
31
+ #
32
+ # ## Examples
33
+ #
34
+ # Ronin::Web::SessionCookie.parse('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c')
35
+ # # =>
36
+ # # #<Ronin::Web::SessionCookie::JWT:0x00007f18d5a45e58
37
+ # # @header={"alg"=>"HS256", "typ"=>"JWT"},
38
+ # # @hmac=
39
+ # # ":\x93\x92K\x0E\xDE\xE3\xCEK8\xFEO\xAF4\x9C\xC4v\xFBI\x1E\xAC\x00\xE3\x11rG\xC5\xC2.+\xA7\xBA",
40
+ # # @params={"id"=>123456789, "name"=>"Joseph"}>
41
+ #
42
+ # @see https://jwt.io/
43
+ #
44
+ class JWT < Cookie
45
+
46
+ # The parsed JWT header information.
47
+ #
48
+ # @return [Hash{String => Object}]
49
+ #
50
+ # @api public
51
+ attr_reader :header
52
+
53
+ # The SHA256 HMAC of the encoded {#header} + `.` + the encoded
54
+ # {#payload}.
55
+ #
56
+ # @return [String]
57
+ #
58
+ # @api public
59
+ attr_reader :hmac
60
+
61
+ alias payload params
62
+
63
+ #
64
+ # Initializes the parsed JWT session cookie.
65
+ #
66
+ # @param [Hash{String => Object}] header
67
+ # The parsed header information.
68
+ #
69
+ # @param [Hash{String => Object}] payload
70
+ # The parsed JWT payload.
71
+ #
72
+ # @param [String] hmac
73
+ # The SHA256 HMAC of the encoded header + `.` + the encoded payload.
74
+ #
75
+ # @api private
76
+ #
77
+ def initialize(header,payload,hmac)
78
+ @header = header
79
+
80
+ super(payload)
81
+
82
+ @hmac = hmac
83
+ end
84
+
85
+ # Regular expression to match JWT session cookies.
86
+ REGEXP = /\A(Bearer )?#{URL_SAFE_BASE64_REGEXP}\.#{URL_SAFE_BASE64_REGEXP}\.#{URL_SAFE_BASE64_REGEXP}\z/
87
+
88
+ #
89
+ # Identifies whether the string is a JWT session cookie.
90
+ #
91
+ # @param [String] string
92
+ # The raw session cookie value to identify.
93
+ #
94
+ # @return [Boolean]
95
+ # Indicates whether the session cookie value is a JWT session cookie.
96
+ #
97
+ # @api public
98
+ #
99
+ def self.identify?(string)
100
+ string =~ REGEXP
101
+ end
102
+
103
+ #
104
+ # Parses a JWT session cookie.
105
+ #
106
+ # @param [String] string
107
+ # The raw session cookie string to parse.
108
+ #
109
+ # @return [JWT]
110
+ # The parsed and deserialized session cookie
111
+ #
112
+ # @api public
113
+ #
114
+ def self.parse(string)
115
+ # remove any 'Bearer ' prefix.
116
+ string = string.sub(/\ABearer /,'')
117
+
118
+ # split the string
119
+ header, payload, hmac = string.split('.',3)
120
+
121
+ header = JSON.parse(Base64.decode64(header))
122
+ payload = JSON.parse(Base64.decode64(payload))
123
+ hmac = Base64.decode64(hmac)
124
+
125
+ return new(header,payload,hmac)
126
+ end
127
+
128
+ #
129
+ # Extracts the JWT session cookie from the HTTP response.
130
+ #
131
+ # @param [Net::HTTPResponse] response
132
+ # The HTTP response object.
133
+ #
134
+ # @return [JWT, nil]
135
+ # The parsed JWT session cookie, or `nil` if there was no
136
+ # `Authorization` header containing a JWT session cookie.
137
+ #
138
+ # @api public
139
+ #
140
+ def self.extract(response)
141
+ if (authorization = response['Authorization'])
142
+ if (match = authorization.match(REGEXP))
143
+ return parse(match[0])
144
+ end
145
+ end
146
+ end
147
+
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
4
+ #
5
+ # ronin-web-session_cookie is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # ronin-web-session_cookie is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public License
16
+ # along with ronin-web-session_cookie. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ require 'ronin/web/session_cookie/cookie'
20
+
21
+ require 'base64'
22
+ require 'delegate'
23
+ require 'rack/session/cookie'
24
+
25
+ module Ronin
26
+ module Web
27
+ module SessionCookie
28
+ #
29
+ # Represents a [Rack][rack-session] session cookie.
30
+ #
31
+ # [rack-session]: https://github.com/rack/rack-session
32
+ #
33
+ # ## Examples
34
+ #
35
+ # Ronin::Web::SessionCookie.parse('rack.session=BAh7CEkiD3Nlc3Npb25faWQGOgZFVG86HVJhY2s6OlNlc3Npb246OlNlc3Npb25JZAY6D0BwdWJsaWNfaWRJIkUyYWJkZTdkM2I0YTMxNDE5OThiYmMyYTE0YjFmMTZlNTNlMWMzYWJlYzhiYzc4ZjVhMGFlMGUwODJmMjJlZGIxBjsARkkiCWNzcmYGOwBGSSIxNHY1TmRCMGRVaklXdjhzR3J1b2ZhM2xwNHQyVGp5ZHptckQycjJRWXpIZz0GOwBGSSINdHJhY2tpbmcGOwBGewZJIhRIVFRQX1VTRVJfQUdFTlQGOwBUSSItOTkxNzUyMWYzN2M4ODJkNDIyMzhmYmI5Yzg4MzFmMWVmNTAwNGQyYwY7AEY%3D--02184e43850f38a46c8f22ffb49f7f22be58e272')
36
+ # # =>
37
+ # # #<Ronin::Web::SessionCookie::Rack:0x00007ff67455ee30
38
+ # # @params=
39
+ # # {"session_id"=>"2abde7d3b4a3141998bbc2a14b1f16e53e1c3abec8bc78f5a0ae0e082f22edb1",
40
+ # # "csrf"=>"4v5NdB0dUjIWv8sGruofa3lp4t2TjydzmrD2r2QYzHg=",
41
+ # # "tracking"=>{"HTTP_USER_AGENT"=>"9917521f37c882d42238fbb9c8831f1ef5004d2c"}}>
42
+ #
43
+ # @see https://github.com/rack/rack-session
44
+ #
45
+ class Rack < Cookie
46
+
47
+ # The HMAC for the deserialized and Base64 encoded session cookie.
48
+ #
49
+ # @return [String]
50
+ attr_reader :hmac
51
+
52
+ #
53
+ # Initializes the parsed Rack session cookie.
54
+ #
55
+ # @param [Hash{String => Object}] params
56
+ # The parsed params for the session cookie.
57
+ #
58
+ # @param [String] hmac
59
+ # The HMAC for the serialized and Base64 encoded session cookie.
60
+ #
61
+ # @api private
62
+ #
63
+ def initialize(params,hmac)
64
+ super(params)
65
+
66
+ @hmac = hmac
67
+ end
68
+
69
+ # Regular expression to match Rack session cookies.
70
+ REGEXP = /\A(rack\.session=)?(?:#{STRICT_BASE64_REGEXP}|#{URI_ENCODED_BASE64_REGEXP})--[0-9a-f]{40}\z/
71
+
72
+ #
73
+ # Identifies if the cookie is a Rack session cookie.
74
+ #
75
+ # @param [String] string
76
+ # The raw session cookie value to identify.
77
+ #
78
+ # @return [Boolean]
79
+ # Indicates whether the session cookie is a Rack session cookie.
80
+ #
81
+ # @api public
82
+ #
83
+ def self.identify?(string)
84
+ string =~ REGEXP
85
+ end
86
+
87
+ #
88
+ # Parses a Django session cookie.
89
+ #
90
+ # @param [String] string
91
+ # The raw session cookie string to parse.
92
+ #
93
+ # @return [Rack]
94
+ # The parsed and deserialized session cookie
95
+ #
96
+ # @api public
97
+ #
98
+ def self.parse(string)
99
+ # remove any 'rack.session' prefix.
100
+ string = string.sub(/\Arack\.session=/,'')
101
+
102
+ payload, hmac = string.split('--',2)
103
+
104
+ return new(Marshal.load(Base64.decode64(payload)),hmac)
105
+ end
106
+
107
+ #
108
+ # Extracts the Rack session cookie from the HTTP response.
109
+ #
110
+ # @param [Net::HTTPResponse] response
111
+ # The HTTP response object.
112
+ #
113
+ # @return [Rack, nil]
114
+ # The parsed Rack session cookie, or `nil` if there was no
115
+ # `Set-Cookie` header containing a Rack session cookie.
116
+ #
117
+ # @api public
118
+ #
119
+ def self.extract(response)
120
+ if (set_cookie = response['Set-Cookie'])
121
+ cookie = set_cookie.split(';',2).first
122
+
123
+ if identify?(cookie)
124
+ return parse(cookie)
125
+ end
126
+ end
127
+ end
128
+
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
4
+ #
5
+ # ronin-web-session_cookie is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # ronin-web-session_cookie is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public License
16
+ # along with ronin-web-session_cookie. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ module Ronin
20
+ module Web
21
+ module SessionCookie
22
+ # ronin-web-session_cookie version
23
+ VERSION = '0.1.0.rc1'
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
4
+ #
5
+ # ronin-web-session_cookie is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # ronin-web-session_cookie is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public License
16
+ # along with ronin-web-session_cookie. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ require 'ronin/web/session_cookie/rack'
20
+ require 'ronin/web/session_cookie/django'
21
+ require 'ronin/web/session_cookie/jwt'
22
+
23
+ module Ronin
24
+ module Web
25
+ #
26
+ # Namespace for `ronin-web-session_cookie`.
27
+ #
28
+ module SessionCookie
29
+ # All session cookie classes.
30
+ #
31
+ # @api private
32
+ CLASSES = [
33
+ Rack,
34
+ JWT,
35
+ Django
36
+ ]
37
+
38
+ #
39
+ # Parses the session cookie.
40
+ #
41
+ # @param [String] string
42
+ # The raw session cookie to parse.
43
+ #
44
+ # @return [Rack, Django, JWT, nil]
45
+ # The parsed and deserialized session cookie data.
46
+ # Returns `nil` if the session cookie did not match any of the supported
47
+ # formats.
48
+ #
49
+ # @api public
50
+ #
51
+ def self.parse(string)
52
+ CLASSES.each do |klass|
53
+ if klass.identify?(string)
54
+ return klass.parse(string)
55
+ end
56
+ end
57
+
58
+ return nil
59
+ end
60
+
61
+ #
62
+ # Extracts and parses the session cookie from the HTTP response.
63
+ #
64
+ # @param [Net::HTTPResponse] response
65
+ # The HTTP response object.
66
+ #
67
+ # @return [Rack, Django, JWT, nil]
68
+ # The parsed session cookie or `nil` if no session cookie could be
69
+ # detected.
70
+ #
71
+ # @api public
72
+ #
73
+ def self.extract(response)
74
+ CLASSES.each do |klass|
75
+ if (session_cookie = klass.extract(response))
76
+ return session_cookie
77
+ end
78
+ end
79
+
80
+ return nil
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'ronin/web/session_cookie/version'
14
+ Ronin::Web::SessionCookie::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+ gem.metadata = gemspec['metadata'] if gemspec['metadata']
24
+
25
+ glob = ->(patterns) { gem.files & Dir[*patterns] }
26
+
27
+ gem.files = `git ls-files`.split($/)
28
+ gem.files = glob[gemspec['files']] if gemspec['files']
29
+ gem.files += Array(gemspec['generated_files'])
30
+ # exclude test files from the packages gem
31
+ gem.files -= glob[gemspec['test_files'] || 'spec/{**/}*']
32
+
33
+ gem.executables = gemspec.fetch('executables') do
34
+ glob['bin/*'].map { |path| File.basename(path) }
35
+ end
36
+
37
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
38
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
39
+
40
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
41
+ %w[ext lib].select { |dir| File.directory?(dir) }
42
+ })
43
+
44
+ gem.requirements = gemspec['requirements']
45
+ gem.required_ruby_version = gemspec['required_ruby_version']
46
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
47
+ gem.post_install_message = gemspec['post_install_message']
48
+
49
+ split = ->(string) { string.split(/,\s*/) }
50
+
51
+ if gemspec['dependencies']
52
+ gemspec['dependencies'].each do |name,versions|
53
+ gem.add_dependency(name,split[versions])
54
+ end
55
+ end
56
+
57
+ if gemspec['development_dependencies']
58
+ gemspec['development_dependencies'].each do |name,versions|
59
+ gem.add_development_dependency(name,split[versions])
60
+ end
61
+ end
62
+ end