rack-tctp 0.9.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
+ SHA1:
3
+ metadata.gz: 9603392ec9a82dbf2dfec8febe170418ed2643b0
4
+ data.tar.gz: 642244102dd91252cf6b32c248c4381bb15779ca
5
+ SHA512:
6
+ metadata.gz: 3e641bd533743f789a3259dc12de7c84bf613341f7478c5c427847384db4cd4c384defcf49e8e6871da0189aab4421f644df0c6c80638657afb804777e2fbeb9
7
+ data.tar.gz: 17e8a9056248af9091c43e23bc1c7c691f7e1d287ae2c70e53a075328a1e07f636745c47e516974e0f632d0d50839d41956eb9726a76642611148c5e4afab8fd
@@ -0,0 +1,116 @@
1
+ require 'openssl'
2
+ require 'socket'
3
+
4
+ # HTTP application layer encryption channel. Used for the Trusted Cloud Transfer Protocol (TCTP)
5
+ class HALEC
6
+ # The URL of this HALEC
7
+ attr_reader :url
8
+
9
+ # The plaintext socket
10
+ attr_reader :socket_here
11
+
12
+ # The SSL socket
13
+ attr_reader :ssl_socket
14
+
15
+ # The encrypted socket
16
+ attr_reader :socket_there
17
+
18
+ # The private key for a certificate (if any)
19
+ attr_reader :private_key
20
+
21
+ # A server or client certificate (if any)
22
+ attr_reader :certificate
23
+
24
+ # The TLS context
25
+ attr_reader :ctx
26
+
27
+ def initialize(options = {})
28
+ @url = options[:url] || ''
29
+ @ctx = options[:ssl_context] || OpenSSL::SSL::SSLContext.new()
30
+
31
+ @ctx.ssl_version = :TLSv1
32
+
33
+ @socket_here, @socket_there = socket_pair
34
+ [@socket_here, @socket_there].each do |socket|
35
+ socket.set_encoding(Encoding::BINARY)
36
+ end
37
+ end
38
+
39
+ private
40
+ def socket_pair
41
+ Socket.pair(:UNIX, :STREAM, 0) # Linux
42
+ rescue Errno::EAFNOSUPPORT
43
+ Socket.pair(:INET, :STREAM, 0) # Windows
44
+ end
45
+ end
46
+
47
+ # The Client end of an HALEC
48
+ class ClientHALEC < HALEC
49
+ def initialize(options = {})
50
+ super(options)
51
+
52
+ @ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket_here, @ctx)
53
+ end
54
+ end
55
+
56
+ # The Server end of an HALEC
57
+ class ServerHALEC < HALEC
58
+ def initialize(options = {})
59
+ super(options)
60
+
61
+ if(options[:private_key] && options[:certificate])
62
+ @private_key = options[:private_key]
63
+ @certificate = options[:certificate]
64
+ else
65
+ @private_key = ServerHALEC.default_key
66
+ @certificate = ServerHALEC.default_self_signed_certificate
67
+ end
68
+
69
+ @ctx.cert = @certificate
70
+ @ctx.key = @private_key
71
+
72
+ @ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket_here, @ctx)
73
+ Thread.new {
74
+ begin
75
+ s = @ssl_socket.accept
76
+ rescue Exception => e
77
+ puts e
78
+ end
79
+ }
80
+ end
81
+
82
+ class << self
83
+ @default_key
84
+ @default_self_signed_certificate
85
+
86
+ def initialize
87
+ default_key
88
+ default_self_signed_certificate
89
+
90
+ self
91
+ end
92
+
93
+ def default_key
94
+ @default_key ||= OpenSSL::PKey::RSA.new 2048
95
+ end
96
+
97
+ def default_self_signed_certificate
98
+ @default_self_signed_certificate ||= generate_self_signed_certificate
99
+ end
100
+
101
+ def generate_self_signed_certificate
102
+ name = OpenSSL::X509::Name.parse 'CN=tctp-server/DC=tctp'
103
+
104
+ cert = OpenSSL::X509::Certificate.new
105
+ cert.version = 2
106
+ cert.serial = 0
107
+ cert.not_before = Time.now
108
+ cert.not_after = Time.now + 3600
109
+
110
+ cert.public_key = @default_key.public_key
111
+ cert.subject = name
112
+
113
+ cert
114
+ end
115
+ end
116
+ end
data/lib/rack/tctp.rb ADDED
@@ -0,0 +1,185 @@
1
+ require 'radix'
2
+ require 'ruby-prof'
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
+
13
+ # The slug URI can contain any HTTP compatible characters
14
+ def self.slug_base
15
+ Radix::Base.new(Radix::BASE::B62 + ['-', '_'])
16
+ end
17
+
18
+ # Generate a new random slug (2^64 possibilities)
19
+ def self.new_slug
20
+ slug_base.convert(rand(2**64), 10)
21
+ end
22
+
23
+ # The TCTP sessions
24
+ attr_reader :sessions
25
+
26
+ # Initializes TCTP middleware
27
+ def initialize(app)
28
+ @app = app
29
+ @sessions = {}
30
+ end
31
+
32
+ # Middleware call. Supports all TCTP use cases:
33
+ # * TCTP discovery
34
+ # * HALEC creation
35
+ # * HALEC handshake
36
+ # * Decrypting TCTP secured entity-bodies
37
+ # * Encrypting entity-bodies using TCTP
38
+ def call(env)
39
+ begin
40
+ req = Rack::Request.new(env)
41
+
42
+ # Switch through TCTP use cases
43
+ case
44
+ when is_tctp_discovery?(req)
45
+ # TCTP discovery
46
+ # TODO Parameterize discovery information
47
+ [200, {"Content-Type" => TCTP_DISCOVERY_MEDIA_TYPE, "Content-Length" => DEFAULT_TCTP_DISCOVERY_INFORMATION.length.to_s}, DEFAULT_TCTP_DISCOVERY_INFORMATION]
48
+ when is_halec_creation?(req)
49
+ # HALEC creation
50
+ halec = ServerHALEC.new(url: '/halecs/' + TCTP::new_slug)
51
+
52
+ # TODO Allow creation using predefined cookie
53
+ session = TCTPSession.new
54
+
55
+ # Send client_hello to server HALEC and read handshake_response
56
+ client_hello = req.body.read
57
+ halec.socket_there.write(client_hello)
58
+ handshake_response = [halec.socket_there.recv(2048)]
59
+
60
+ # Set location header and content-length
61
+ header = {'Location' => halec.url, 'Content-Length' => handshake_response[0].length.to_s}
62
+
63
+ # Set the TCTP session cookie header
64
+ Rack::Utils.set_cookie_header!(header, "tctp_session_cookie", {:value => session.session_id, :path => '/', :expires => Time.now+24*60*60})
65
+
66
+ # Persist session and HALEC
67
+ session.halecs[halec.url] = halec
68
+ sessions[session.session_id] = session
69
+
70
+ [201, header, handshake_response]
71
+ when is_halec_handshake?(req)
72
+ # Get persisted server HALEC
73
+ halec = @sessions[req.cookies['tctp_session_cookie']].halecs[req.path_info]
74
+
75
+ # Write handshake message to server HALEC
76
+ halec.socket_there.write(req.body.read)
77
+
78
+ # Receive handshake response
79
+ handshake_response = halec.socket_there.recv(2048)
80
+
81
+ # Send back server HALEC response
82
+ [200, {'Content-Length' => handshake_response.length.to_s}, [handshake_response]]
83
+ else
84
+ # Decrypt TCTP secured bodies
85
+ if is_tctp_encrypted_body?(req) then
86
+ decrypted_body = StringIO.new
87
+
88
+ halec_url = req.body.readline.chomp
89
+
90
+ # Gets the HALEC
91
+ halec = @sessions[req.cookies['tctp_session_cookie']].halecs[halec_url]
92
+
93
+ halec.socket_there.write(req.body.read)
94
+ decrypted_body.write(halec.ssl_socket.readpartial(2 ** 26 - 1))
95
+
96
+ req.body.string = decrypted_body.string
97
+ end
98
+
99
+ status, headers, body = @app.call(env)
100
+
101
+ if is_tctp_response_requested?(req)
102
+ # Gets the first free server HALEC for encryption
103
+ # TODO Send error if cookie is missing
104
+ halec = @sessions[req.cookies['tctp_session_cookie']].free_halec
105
+
106
+ # The length of the content body
107
+ content_body_length = 0
108
+
109
+ # The first line
110
+ first_line = halec.url + "\r\n"
111
+ content_body_length += first_line.length
112
+
113
+ # Encrypt the body. The first line of the response specifies the used HALEC
114
+ encrypted_body = []
115
+ encrypted_body << first_line
116
+
117
+ # Encrypt each body fragment
118
+ body.each do |fragment|
119
+ bodyio = StringIO.new(fragment)
120
+
121
+ until bodyio.eof? do
122
+ chunk = bodyio.read(16 * 1024)
123
+ halec.ssl_socket.write(chunk)
124
+ encrypted_chunk = halec.socket_there.readpartial(32 * 1024)
125
+ encrypted_body << encrypted_chunk
126
+ content_body_length += encrypted_chunk.length
127
+ end
128
+ end
129
+
130
+ # Sets the content length and encoding
131
+ headers['Content-Length'] = content_body_length.to_s
132
+ headers['Content-Encoding'] = 'encrypted'
133
+
134
+ [status, headers, encrypted_body]
135
+ else
136
+ [status, headers, body]
137
+ end
138
+ end
139
+ rescue Exception => e
140
+ puts e
141
+ end
142
+ end
143
+
144
+ private
145
+ def is_tctp_discovery?(req)
146
+ req.options? && !req.env['HTTP_ACCEPT'].nil? && req.env['HTTP_ACCEPT'].eql?(TCTP_DISCOVERY_MEDIA_TYPE)
147
+ end
148
+
149
+ def is_halec_creation?(req)
150
+ req.post? && req.path_info.eql?('/halecs')
151
+ end
152
+
153
+ def is_halec_handshake?(req)
154
+ req.post? &&
155
+ !req.cookies.nil? &&
156
+ req.cookies.has_key?('tctp_session_cookie') &&
157
+ sessions.has_key?(req.cookies['tctp_session_cookie']) &&
158
+ sessions[req.cookies['tctp_session_cookie']].halecs.has_key?(req.path_info)
159
+ end
160
+
161
+ def is_tctp_response_requested? (req)
162
+ req.env['HTTP_ACCEPT_ENCODING'].eql?('encrypted')
163
+ end
164
+
165
+ def is_tctp_encrypted_body? (req)
166
+ req.env['HTTP_CONTENT_ENCODING'].eql?('encrypted')
167
+ end
168
+ end
169
+
170
+ class TCTPSession
171
+ attr_reader :session_id
172
+
173
+ attr_reader :halecs
174
+
175
+ def initialize(session_id = TCTP::new_slug)
176
+ @session_id = session_id
177
+ @halecs = {}
178
+ end
179
+
180
+ def free_halec
181
+ # TODO free HALEC handling
182
+ @halecs.first[1]
183
+ end
184
+ end
185
+ end
data/lib/rack-tctp.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'rack/tctp'
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-tctp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Mathias Slawik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-21 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Rack middleware for end-to-end security through TCTP
14
+ email: mathias.slawik@tu-berlin.de
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/rack-tctp.rb
20
+ - lib/rack/tctp.rb
21
+ - lib/rack/tctp/halec.rb
22
+ homepage: https://github.com/mathiasslawik/rack-tctp
23
+ licenses:
24
+ - Apache-2.0
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubyforge_project:
42
+ rubygems_version: 2.0.2
43
+ signing_key:
44
+ specification_version: 4
45
+ summary: Rack TCTP middleware
46
+ test_files: []