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 +7 -0
- data/lib/rack/tctp/halec.rb +116 -0
- data/lib/rack/tctp.rb +185 -0
- data/lib/rack-tctp.rb +1 -0
- metadata +46 -0
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: []
|