rack-tctp 0.9.13 → 0.9.14
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
- data/ext/engine/engine.c +229 -229
- data/ext/engine/extconf.rb +6 -6
- data/lib/rack/tctp.rb +263 -260
- data/lib/rack/tctp/halec.rb +220 -212
- metadata +32 -18
data/ext/engine/extconf.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require 'mkmf'
|
2
|
-
|
3
|
-
$defs.push '-Wno-deprecated-declarations'
|
4
|
-
$libs += ' -lssl -lcrypto '
|
5
|
-
|
6
|
-
create_makefile('rack/tctp/engine')
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
$defs.push '-Wno-deprecated-declarations'
|
4
|
+
$libs += ' -lssl -lcrypto '
|
5
|
+
|
6
|
+
create_makefile('rack/tctp/engine')
|
data/lib/rack/tctp.rb
CHANGED
@@ -1,261 +1,264 @@
|
|
1
|
-
require 'radix'
|
2
|
-
require 'logger'
|
3
|
-
|
4
|
-
require_relative 'tctp/halec'
|
5
|
-
|
6
|
-
module Rack
|
7
|
-
# This middleware enables Rack to make use of the Trusted Cloud Transfer Protocol (TCTP) for HTTP end-to-end
|
8
|
-
# body confidentiality and integrity.
|
9
|
-
class TCTP
|
10
|
-
DEFAULT_TCTP_DISCOVERY_INFORMATION = '/.*:/halecs'
|
11
|
-
TCTP_DISCOVERY_MEDIA_TYPE = 'text/prs.tctp-discovery'
|
12
|
-
TCTP_MEDIA_TYPE = 'binary/prs.tctp'
|
13
|
-
|
14
|
-
# The slug URI can contain any HTTP compatible characters
|
15
|
-
def self.slug_base
|
16
|
-
Radix::Base.new(Radix::BASE::B62 + ['-', '_'])
|
17
|
-
end
|
18
|
-
|
19
|
-
# Generate a new random slug (2^64 possibilities)
|
20
|
-
def self.new_slug
|
21
|
-
slug_base.convert(rand(2**64), 10)
|
22
|
-
end
|
23
|
-
|
24
|
-
# The TCTP sessions
|
25
|
-
attr_reader :sessions
|
26
|
-
|
27
|
-
# Initializes TCTP middleware
|
28
|
-
def initialize(app, logger = nil)
|
29
|
-
unless logger
|
30
|
-
@logger = ::Logger.new(STDOUT)
|
31
|
-
@logger.level = ::Logger::FATAL
|
32
|
-
else
|
33
|
-
@logger = logger
|
34
|
-
end
|
35
|
-
|
36
|
-
@app = app
|
37
|
-
@sessions = {}
|
38
|
-
end
|
39
|
-
|
40
|
-
# Middleware call. Supports all TCTP use cases:
|
41
|
-
# * TCTP discovery
|
42
|
-
# * HALEC creation
|
43
|
-
# * HALEC handshake
|
44
|
-
# * Decrypting TCTP secured entity-bodies
|
45
|
-
# * Encrypting entity-bodies using TCTP
|
46
|
-
def call(env)
|
47
|
-
status, headers, body = nil, nil, nil
|
48
|
-
|
49
|
-
begin
|
50
|
-
req = Rack::Request.new(env)
|
51
|
-
|
52
|
-
case
|
53
|
-
when is_tctp_discovery?(req)
|
54
|
-
# TCTP discovery
|
55
|
-
# TODO Parameterize discovery information
|
56
|
-
[200, {"Content-Type" => TCTP_DISCOVERY_MEDIA_TYPE, "Content-Length" => DEFAULT_TCTP_DISCOVERY_INFORMATION.length.to_s}, [DEFAULT_TCTP_DISCOVERY_INFORMATION]]
|
57
|
-
when is_halec_creation?(req)
|
58
|
-
# HALEC creation
|
59
|
-
halec = ServerHALEC.new(url: halec_uri(req.env, "/halecs/#{TCTP::new_slug}"))
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
halec.engine.
|
67
|
-
halec.engine.
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
'
|
73
|
-
'Content-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
session.
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
halec.engine.
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
'Content-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
#
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
encrypted_body
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
headers['Content-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
sessions
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
#
|
223
|
-
# @param [
|
224
|
-
# @
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
1
|
+
require 'radix'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require_relative 'tctp/halec'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
# This middleware enables Rack to make use of the Trusted Cloud Transfer Protocol (TCTP) for HTTP end-to-end
|
8
|
+
# body confidentiality and integrity.
|
9
|
+
class TCTP
|
10
|
+
DEFAULT_TCTP_DISCOVERY_INFORMATION = '/.*:/halecs'
|
11
|
+
TCTP_DISCOVERY_MEDIA_TYPE = 'text/prs.tctp-discovery'
|
12
|
+
TCTP_MEDIA_TYPE = 'binary/prs.tctp'
|
13
|
+
|
14
|
+
# The slug URI can contain any HTTP compatible characters
|
15
|
+
def self.slug_base
|
16
|
+
Radix::Base.new(Radix::BASE::B62 + ['-', '_'])
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generate a new random slug (2^64 possibilities)
|
20
|
+
def self.new_slug
|
21
|
+
slug_base.convert(rand(2**64), 10)
|
22
|
+
end
|
23
|
+
|
24
|
+
# The TCTP sessions
|
25
|
+
attr_reader :sessions
|
26
|
+
|
27
|
+
# Initializes TCTP middleware
|
28
|
+
def initialize(app, logger = nil)
|
29
|
+
unless logger
|
30
|
+
@logger = ::Logger.new(STDOUT)
|
31
|
+
@logger.level = ::Logger::FATAL
|
32
|
+
else
|
33
|
+
@logger = logger
|
34
|
+
end
|
35
|
+
|
36
|
+
@app = app
|
37
|
+
@sessions = {}
|
38
|
+
end
|
39
|
+
|
40
|
+
# Middleware call. Supports all TCTP use cases:
|
41
|
+
# * TCTP discovery
|
42
|
+
# * HALEC creation
|
43
|
+
# * HALEC handshake
|
44
|
+
# * Decrypting TCTP secured entity-bodies
|
45
|
+
# * Encrypting entity-bodies using TCTP
|
46
|
+
def call(env)
|
47
|
+
status, headers, body = nil, nil, nil
|
48
|
+
|
49
|
+
begin
|
50
|
+
req = Rack::Request.new(env)
|
51
|
+
|
52
|
+
case
|
53
|
+
when is_tctp_discovery?(req)
|
54
|
+
# TCTP discovery
|
55
|
+
# TODO Parameterize discovery information
|
56
|
+
[200, {"Content-Type" => TCTP_DISCOVERY_MEDIA_TYPE, "Content-Length" => DEFAULT_TCTP_DISCOVERY_INFORMATION.length.to_s}, [DEFAULT_TCTP_DISCOVERY_INFORMATION]]
|
57
|
+
when is_halec_creation?(req)
|
58
|
+
# HALEC creation
|
59
|
+
halec = ServerHALEC.new(url: halec_uri(req.env, "/halecs/#{TCTP::new_slug}"))
|
60
|
+
|
61
|
+
session = sessions[req.cookies['tctp_session_cookie']] || TCTPSession.new
|
62
|
+
|
63
|
+
# Send client_hello to server HALEC and read handshake_response
|
64
|
+
client_hello = req.body.read
|
65
|
+
halec.engine.inject client_hello
|
66
|
+
halec.engine.read
|
67
|
+
handshake_response = [halec.engine.extract]
|
68
|
+
|
69
|
+
# Set location header and content-length
|
70
|
+
header = {
|
71
|
+
'Location' => halec.url.to_s,
|
72
|
+
'Content-Length' => handshake_response[0].length.to_s,
|
73
|
+
'Content-Type' => TCTP_MEDIA_TYPE
|
74
|
+
}
|
75
|
+
|
76
|
+
# Set the TCTP session cookie header
|
77
|
+
Rack::Utils.set_cookie_header!(header, "tctp_session_cookie", {:value => session.session_id, :path => '/', :expires => Time.now+24*60*60})
|
78
|
+
|
79
|
+
# Persist session and HALEC
|
80
|
+
session.push_halec(halec)
|
81
|
+
sessions[session.session_id] = session
|
82
|
+
|
83
|
+
[201, header, handshake_response]
|
84
|
+
when is_halec_handshake?(req)
|
85
|
+
# Get persisted server HALEC
|
86
|
+
halec = @sessions[req.cookies['tctp_session_cookie']].halecs[halec_uri(req.env, req.path_info)]
|
87
|
+
|
88
|
+
# Write handshake message to server HALEC
|
89
|
+
halec.engine.inject req.body.read
|
90
|
+
|
91
|
+
# Receive handshake response
|
92
|
+
halec.engine.read
|
93
|
+
handshake_response = halec.engine.extract
|
94
|
+
|
95
|
+
# Send back server HALEC response
|
96
|
+
[200, {
|
97
|
+
'Content-Length' => handshake_response.length.to_s,
|
98
|
+
'Content-Type' => TCTP_MEDIA_TYPE
|
99
|
+
}, [handshake_response]]
|
100
|
+
else
|
101
|
+
# Decrypt TCTP secured bodies
|
102
|
+
if is_tctp_encrypted_body?(req) then
|
103
|
+
decrypted_body = StringIO.new
|
104
|
+
|
105
|
+
halec_url = req.body.gets.chomp
|
106
|
+
|
107
|
+
# Gets the HALEC
|
108
|
+
halec = @sessions[req.cookies['tctp_session_cookie']].halecs[URI(halec_url)]
|
109
|
+
|
110
|
+
read_body = req.body.read
|
111
|
+
|
112
|
+
begin
|
113
|
+
decrypted_body.write halec.decrypt_data(read_body)
|
114
|
+
rescue Exception => e
|
115
|
+
error(e.message + e.backtrace.join("<br/>\n"))
|
116
|
+
end
|
117
|
+
|
118
|
+
decrypted_body.rewind
|
119
|
+
|
120
|
+
env['rack.input'] = decrypted_body
|
121
|
+
end
|
122
|
+
|
123
|
+
status, headers, body = @app.call(env)
|
124
|
+
|
125
|
+
if is_tctp_response_requested?(req) && status >= 200 && ![204, 205, 304].include?(status)
|
126
|
+
# Gets the first free server HALEC for encryption
|
127
|
+
# TODO Send error if cookie is missing
|
128
|
+
session = @sessions[req.cookies['tctp_session_cookie']]
|
129
|
+
|
130
|
+
unless session
|
131
|
+
return no_usable_halec_error
|
132
|
+
end
|
133
|
+
|
134
|
+
halec = session.pop_halec
|
135
|
+
|
136
|
+
unless halec
|
137
|
+
return no_usable_halec_error
|
138
|
+
end
|
139
|
+
|
140
|
+
# The length of the content body
|
141
|
+
content_body_length = 0
|
142
|
+
|
143
|
+
# The first line
|
144
|
+
first_line = halec.url.to_s + "\r\n"
|
145
|
+
content_body_length += first_line.length
|
146
|
+
|
147
|
+
# Encrypt the body. The first line of the response specifies the used HALEC
|
148
|
+
encrypted_body = []
|
149
|
+
encrypted_body << first_line
|
150
|
+
|
151
|
+
# Encrypt each body fragment
|
152
|
+
body.each do |fragment|
|
153
|
+
encrypted_fragment = halec.encrypt_data fragment
|
154
|
+
encrypted_body << encrypted_fragment
|
155
|
+
content_body_length += encrypted_fragment.length
|
156
|
+
end
|
157
|
+
|
158
|
+
encrypted_body.define_singleton_method :close do
|
159
|
+
session.push_halec halec
|
160
|
+
|
161
|
+
super() if self.class.superclass.respond_to? :close
|
162
|
+
end
|
163
|
+
|
164
|
+
# Finding this bug took waaaay too long ...
|
165
|
+
body.close if body.respond_to?(:close)
|
166
|
+
|
167
|
+
# Sets the content length and encoding
|
168
|
+
headers['Content-Length'] = content_body_length.to_s
|
169
|
+
headers['Content-Encoding'] = 'encrypted'
|
170
|
+
|
171
|
+
[status, headers, encrypted_body]
|
172
|
+
else
|
173
|
+
[status, headers, body]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
rescue Exception => e
|
177
|
+
# TODO Handle SSL Error
|
178
|
+
@logger.fatal e
|
179
|
+
|
180
|
+
error "Error in TCTP middleware. #{e} #{e.backtrace.inspect}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
def log_key
|
186
|
+
'TCTP Middleware'
|
187
|
+
end
|
188
|
+
|
189
|
+
def no_usable_halec_error
|
190
|
+
error 'No useable HALEC for encryption. Please perform Handshake.'
|
191
|
+
end
|
192
|
+
|
193
|
+
def error(message)
|
194
|
+
[500, {'Content-Type' => 'text/plain', 'Content-Length' => message.length.to_s}, [message]]
|
195
|
+
end
|
196
|
+
|
197
|
+
def is_tctp_discovery?(req)
|
198
|
+
req.options? && !req.env['HTTP_ACCEPT'].nil? && req.env['HTTP_ACCEPT'].eql?(TCTP_DISCOVERY_MEDIA_TYPE)
|
199
|
+
end
|
200
|
+
|
201
|
+
def is_halec_creation?(req)
|
202
|
+
req.post? && req.path_info.eql?('/halecs')
|
203
|
+
end
|
204
|
+
|
205
|
+
def is_halec_handshake?(req)
|
206
|
+
req.post? &&
|
207
|
+
!req.cookies.nil? &&
|
208
|
+
req.cookies.has_key?('tctp_session_cookie') &&
|
209
|
+
sessions.has_key?(req.cookies['tctp_session_cookie']) &&
|
210
|
+
sessions[req.cookies['tctp_session_cookie']].halecs.has_key?(halec_uri(req.env, req.path_info))
|
211
|
+
end
|
212
|
+
|
213
|
+
def is_tctp_response_requested? (req)
|
214
|
+
req.env['HTTP_ACCEPT_ENCODING'].eql?('encrypted')
|
215
|
+
end
|
216
|
+
|
217
|
+
def is_tctp_encrypted_body? (req)
|
218
|
+
req.env['HTTP_CONTENT_ENCODING'].eql?('encrypted')
|
219
|
+
end
|
220
|
+
|
221
|
+
# Builds an URI object to be used as a HALEC +uri+
|
222
|
+
# @param [Hash] env A Rack environment hash
|
223
|
+
# @param [String] path A path
|
224
|
+
# @return [URI] An HALEC +uri+
|
225
|
+
def halec_uri(env, path)
|
226
|
+
if(env['HTTP_HOST'])
|
227
|
+
URI("#{env['rack.url_scheme']}://#{env['HTTP_HOST']}#{path}")
|
228
|
+
else
|
229
|
+
URI("#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}")
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
class TCTPSession
|
235
|
+
attr_reader :session_id
|
236
|
+
|
237
|
+
attr_reader :halecs
|
238
|
+
|
239
|
+
attr_reader :halecs_mutex
|
240
|
+
|
241
|
+
def initialize(session_id = TCTP::new_slug)
|
242
|
+
@session_id = session_id
|
243
|
+
@halecs = {}
|
244
|
+
@halecs_mutex = Mutex.new
|
245
|
+
end
|
246
|
+
|
247
|
+
def pop_halec
|
248
|
+
free_halec = nil
|
249
|
+
|
250
|
+
@halecs_mutex.synchronize do
|
251
|
+
free_halec = @halecs.first {|url, halec| halec.engine.state.eql? 'SSLOK '}
|
252
|
+
|
253
|
+
@halecs.delete free_halec[0] if free_halec
|
254
|
+
end
|
255
|
+
return free_halec[1]
|
256
|
+
end
|
257
|
+
|
258
|
+
def push_halec(halec)
|
259
|
+
@halecs_mutex.synchronize do
|
260
|
+
@halecs[halec.url] = halec
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
261
264
|
end
|