rack-tctp 0.9.4 → 0.9.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e17ba07a070105aa159112f1bc05b786470ca915
4
- data.tar.gz: 32d80313f686de0b04502360ef993833888ba95a
3
+ metadata.gz: b6b91b55890f0a473f132a0e6c2eec7fe5aa3254
4
+ data.tar.gz: 8ac625d2bb5a6576a5cc164fed0b7a928419d129
5
5
  SHA512:
6
- metadata.gz: bef07d9eced9cade393e20bbe8762e656c8b943ee292c5b89acf3e790e274ce9d0e24634bb27051bba01c5470b266516567623ca7f7605efb5949f57f9b645c2
7
- data.tar.gz: 3ad2c8e428775997f7300d60b13e6dd7ec77ff60ba551b6163ecdb8f16c82614be9f5f7dbe163c1bf49f4bf825b22119f9acb925f840e4c2ac74fe26031364f9
6
+ metadata.gz: d93b73d45ac80ad26c8c6f5d1b10964cf6f07d47a7a4a4fde519d96d7206c74613cae47301658d6e2d50ae44ed658666c8eb6bc5323b4c9d3eba15f8543577a8
7
+ data.tar.gz: b68e836d5e51dd6cfbc10ff89b7b67f98ed3adeee36f34faabab4dc117d7bcdbe61bae9bd4a36f1177f51b80ce40b61335ce60927381d6636de71dbe4375330c
@@ -0,0 +1,216 @@
1
+ //Modified from: https://github.com/puma/puma/blob/master/ext/puma_http11/mini_ssl.c
2
+ #define RSTRING_NOT_MODIFIED 1
3
+ #include <assert.h>
4
+ #include <ruby.h>
5
+ #include <rubyio.h>
6
+ #include <openssl/bio.h>
7
+ #include <openssl/ssl.h>
8
+ #include <openssl/err.h>
9
+
10
+ typedef struct {
11
+ BIO* read;
12
+ BIO* write;
13
+ SSL* ssl;
14
+ SSL_CTX* ctx;
15
+ } ms_conn;
16
+
17
+ void engine_free(ms_conn* conn) {
18
+ BIO_free(conn->read);
19
+ BIO_free(conn->write);
20
+
21
+ free(conn);
22
+ }
23
+
24
+ static VALUE eError;
25
+
26
+ void raise_error(SSL* ssl, int result) {
27
+ char buf[256];
28
+ u_long err;
29
+
30
+ while ((err = ERR_get_error()) != 0) {
31
+ ERR_error_string_n(err, buf, sizeof(buf));
32
+ printf("*** %s\n", buf);
33
+ }
34
+
35
+ ERR_clear_error();
36
+ rb_raise(eError, "OpenSSL error");
37
+ }
38
+
39
+ ms_conn* engine_alloc(VALUE klass, VALUE* obj) {
40
+ ms_conn* conn;
41
+
42
+ *obj = Data_Make_Struct(klass, ms_conn, 0, engine_free, conn);
43
+
44
+ conn->read = BIO_new(BIO_s_mem());
45
+ BIO_set_nbio(conn->read, 1);
46
+
47
+ conn->write = BIO_new(BIO_s_mem());
48
+ BIO_set_nbio(conn->write, 1);
49
+
50
+ conn->ssl = 0;
51
+ conn->ctx = 0;
52
+
53
+ return conn;
54
+ }
55
+
56
+ VALUE engine_init_server(VALUE self, VALUE key, VALUE cert) {
57
+ VALUE obj;
58
+ SSL_CTX* ctx;
59
+ SSL* ssl;
60
+ int use_certificate_file_ret, use_pk_file_ret;
61
+
62
+ ms_conn* conn = engine_alloc(self, &obj);
63
+
64
+ StringValue(key);
65
+ StringValue(cert);
66
+
67
+ ctx = SSL_CTX_new(TLSv1_server_method());
68
+ conn->ctx = ctx;
69
+
70
+ use_certificate_file_ret = SSL_CTX_use_certificate_file(ctx, RSTRING_PTR(cert), SSL_FILETYPE_PEM);
71
+ if(use_certificate_file_ret != 1) {
72
+ raise_error(conn->ssl, 0);
73
+ }
74
+
75
+ use_pk_file_ret = SSL_CTX_use_PrivateKey_file(ctx, RSTRING_PTR(key), SSL_FILETYPE_PEM);
76
+ if(use_pk_file_ret != 1) {
77
+ raise_error(conn->ssl, 0);
78
+ }
79
+
80
+ SSL_CTX_set_cipher_list(ctx, "ALL");
81
+ SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
82
+
83
+ ssl = SSL_new(ctx);
84
+ conn->ssl = ssl;
85
+
86
+ SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
87
+
88
+ SSL_set_bio(conn->ssl, conn->read, conn->write);
89
+
90
+ SSL_set_accept_state(ssl);
91
+ return obj;
92
+ }
93
+
94
+ VALUE engine_init_client(VALUE klass) {
95
+ VALUE obj;
96
+ ms_conn* conn = engine_alloc(klass, &obj);
97
+
98
+ conn->ctx = SSL_CTX_new(TLSv1_client_method());
99
+ SSL_CTX_set_cipher_list(conn->ctx, "ALL");
100
+
101
+ conn->ssl = SSL_new(conn->ctx);
102
+
103
+ SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL);
104
+
105
+ SSL_set_bio(conn->ssl, conn->read, conn->write);
106
+
107
+ SSL_set_connect_state(conn->ssl);
108
+ return obj;
109
+ }
110
+
111
+ VALUE engine_inject(VALUE self, VALUE str) {
112
+ ms_conn* conn;
113
+ long used;
114
+
115
+ Data_Get_Struct(self, ms_conn, conn);
116
+
117
+ StringValue(str);
118
+
119
+ used = BIO_write(conn->read, RSTRING_PTR(str), (int)RSTRING_LEN(str));
120
+
121
+ if(used == 0 || used == -1) {
122
+ return Qfalse;
123
+ }
124
+
125
+ return INT2FIX(used);
126
+ }
127
+
128
+ VALUE engine_read(VALUE self) {
129
+ ms_conn* conn;
130
+ char buf[512];
131
+ int bytes, n;
132
+
133
+ Data_Get_Struct(self, ms_conn, conn);
134
+
135
+ bytes = SSL_read(conn->ssl, (void*)buf, sizeof(buf));
136
+
137
+ if(bytes > 0) {
138
+ return rb_str_new(buf, bytes);
139
+ }
140
+
141
+ if(SSL_want_read(conn->ssl)) return Qnil;
142
+
143
+ if(SSL_get_error(conn->ssl, bytes) == SSL_ERROR_ZERO_RETURN) {
144
+ rb_eof_error();
145
+ }
146
+
147
+ raise_error(conn->ssl, bytes);
148
+
149
+ return Qnil;
150
+ }
151
+
152
+ VALUE engine_write(VALUE self, VALUE str) {
153
+ ms_conn* conn;
154
+ char buf[512];
155
+ int bytes;
156
+
157
+ Data_Get_Struct(self, ms_conn, conn);
158
+
159
+ StringValue(str);
160
+
161
+ bytes = SSL_write(conn->ssl, (void*)RSTRING_PTR(str), (int)RSTRING_LEN(str));
162
+ if(bytes > 0) {
163
+ return INT2FIX(bytes);
164
+ }
165
+
166
+ if(SSL_want_write(conn->ssl)) return Qnil;
167
+
168
+ raise_error(conn->ssl, bytes);
169
+
170
+ return Qnil;
171
+ }
172
+
173
+ VALUE engine_extract(VALUE self) {
174
+ ms_conn* conn;
175
+ int bytes;
176
+ size_t pending;
177
+ char buf[512];
178
+
179
+ Data_Get_Struct(self, ms_conn, conn);
180
+
181
+ pending = BIO_pending(conn->write);
182
+ if(pending > 0) {
183
+ bytes = BIO_read(conn->write, buf, sizeof(buf));
184
+ if(bytes > 0) {
185
+ return rb_str_new(buf, bytes);
186
+ } else if(!BIO_should_retry(conn->write)) {
187
+ raise_error(conn->ssl, bytes);
188
+ }
189
+ }
190
+
191
+ return Qnil;
192
+ }
193
+
194
+ void Init_engine() {
195
+ VALUE mod, eng, rack;
196
+
197
+ SSL_library_init();
198
+ OpenSSL_add_ssl_algorithms();
199
+ SSL_load_error_strings();
200
+ ERR_load_crypto_strings();
201
+
202
+ rack = rb_define_module("Rack");
203
+ mod = rb_define_class_under(rack, "TCTP", rb_cObject);
204
+ eng = rb_define_class_under(mod, "Engine", rb_cObject);
205
+
206
+ eError = rb_define_class_under(mod, "SSLError", rb_eStandardError);
207
+
208
+ rb_define_singleton_method(eng, "server", engine_init_server, 2);
209
+ rb_define_singleton_method(eng, "client", engine_init_client, 0);
210
+
211
+ rb_define_method(eng, "inject", engine_inject, 1);
212
+ rb_define_method(eng, "read", engine_read, 0);
213
+
214
+ rb_define_method(eng, "write", engine_write, 1);
215
+ rb_define_method(eng, "extract", engine_extract, 0);
216
+ }
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+
3
+ $defs.push '-Wno-deprecated-declarations'
4
+ $libs += ' -lssl -lcrypto '
5
+
6
+ create_makefile('rack/tctp/engine')
@@ -1,19 +1,16 @@
1
1
  require 'openssl'
2
2
  require 'socket'
3
+ require 'radix'
3
4
 
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
5
+ require 'rack/tctp/engine'
11
6
 
12
- # The SSL socket
13
- attr_reader :ssl_socket
7
+ # HTTP application layer encryption channel. Used for the Trusted Cloud Transfer Protocol (TCTP)
8
+ class Rack::TCTP::HALEC
9
+ # The SSL engine
10
+ attr_reader :engine
14
11
 
15
- # The encrypted socket
16
- attr_reader :socket_there
12
+ # The URL of this HALEC
13
+ attr_accessor :url
17
14
 
18
15
  # The private key for a certificate (if any)
19
16
  attr_reader :private_key
@@ -21,40 +18,68 @@ class HALEC
21
18
  # A server or client certificate (if any)
22
19
  attr_reader :certificate
23
20
 
24
- # The TLS context
25
- attr_reader :ctx
26
-
27
21
  def initialize(options = {})
28
- @url = options[:url] || ''
29
- @ctx = options[:ssl_context] || OpenSSL::SSL::SSLContext.new()
30
-
31
- @ctx.ssl_version = :TLSv1
22
+ @url = options[:url] || nil
23
+ end
32
24
 
33
- @socket_here, @socket_there = socket_pair
34
- [@socket_here, @socket_there].each do |socket|
35
- socket.set_encoding(Encoding::BINARY)
25
+ # Encrypts +plaintext+ data and either returns the encrypted data or calls a block with it.
26
+ # @param [String] plaintext The plaintext
27
+ # @return [String] The encrypted data
28
+ # @yield Gives the encrypted data to the block
29
+ # @yieldparam [String] The encrypted data
30
+ def encrypt_data(plaintext, &encrypted)
31
+ @engine.write plaintext
32
+
33
+ while(read_chunk = @engine.extract)
34
+ if(block_given?)
35
+ encrypted.call read_chunk
36
+ else
37
+ if read_data
38
+ read_data.write read_chunk
39
+ else
40
+ read_data = StringIO.new(read_chunk)
41
+ end
42
+ end
36
43
  end
44
+
45
+ read_data.string unless block_given?
37
46
  end
38
47
 
39
- private
40
- def socket_pair
41
- Socket.pair(:UNIX, :STREAM, 0) # Linux
42
- rescue Errno::EAFNOSUPPORT
43
- Socket.pair(:INET, :STREAM, 0) # Windows
48
+ # Decrypts +encrypted+ data and either returns the plaintext or calls a block with it.
49
+ # @param [String] encrypted The encrypted data
50
+ # @return [String] The plaintext
51
+ # @yield Gives the plaintext to the block
52
+ # @yieldparam [String] The plaintext
53
+ def decrypt_data(encrypted, &decrypted)
54
+ @engine.inject encrypted
55
+
56
+ while(read_chunk = @engine.read)
57
+ if(block_given?)
58
+ decrypted.call read_chunk
59
+ else
60
+ if read_data
61
+ read_data.write read_chunk
62
+ else
63
+ read_data = StringIO.new(read_chunk)
64
+ end
65
+ end
44
66
  end
67
+
68
+ read_data.string unless block_given?
69
+ end
45
70
  end
46
71
 
47
72
  # The Client end of an HALEC
48
- class ClientHALEC < HALEC
73
+ class Rack::TCTP::ClientHALEC < Rack::TCTP::HALEC
49
74
  def initialize(options = {})
50
75
  super(options)
51
76
 
52
- @ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket_here, @ctx)
77
+ @engine = Rack::TCTP::Engine.client
53
78
  end
54
79
  end
55
80
 
56
81
  # The Server end of an HALEC
57
- class ServerHALEC < HALEC
82
+ class Rack::TCTP::ServerHALEC < Rack::TCTP::HALEC
58
83
  def initialize(options = {})
59
84
  super(options)
60
85
 
@@ -62,21 +87,19 @@ class ServerHALEC < HALEC
62
87
  @private_key = options[:private_key]
63
88
  @certificate = options[:certificate]
64
89
  else
65
- @private_key = ServerHALEC.default_key
66
- @certificate = ServerHALEC.default_self_signed_certificate
90
+ @private_key = self.class.default_key
91
+ @certificate = self.class.default_self_signed_certificate
67
92
  end
68
93
 
69
- @ctx.cert = @certificate
70
- @ctx.key = @private_key
94
+ @private_key_file = Tempfile.new('rack_tctp_pk')
95
+ @private_key_file.write @private_key.to_s
96
+ @private_key_file.close
71
97
 
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
- }
98
+ @certificate_file = Tempfile.new('rack_tctp_cert')
99
+ @certificate_file.write @certificate.to_s
100
+ @certificate_file.close
101
+
102
+ @engine = Rack::TCTP::Engine.server(@private_key_file.path, @certificate_file.path)
80
103
  end
81
104
 
82
105
  class << self
@@ -112,5 +135,15 @@ class ServerHALEC < HALEC
112
135
 
113
136
  cert
114
137
  end
138
+
139
+ # The slug URI can contain any HTTP compatible characters
140
+ def slug_base
141
+ Radix::Base.new(Radix::BASE::B62 + ['-', '_'])
142
+ end
143
+
144
+ # Generate a new random slug (2^64 possibilities)
145
+ def new_slug
146
+ slug_base.convert(rand(2**64), 10)
147
+ end
115
148
  end
116
- end
149
+ end
data/lib/rack/tctp.rb CHANGED
@@ -26,8 +26,8 @@ module Rack
26
26
  # Initializes TCTP middleware
27
27
  def initialize(app, logger = nil)
28
28
  unless logger
29
- @logger = Kernel::Logger.new(STDOUT)
30
- @logger.level = Logger::FATAL
29
+ @logger = ::Logger.new(STDOUT)
30
+ @logger.level = ::Logger::FATAL
31
31
  else
32
32
  @logger = logger
33
33
  end
@@ -52,21 +52,22 @@ module Rack
52
52
  when is_tctp_discovery?(req)
53
53
  # TCTP discovery
54
54
  # TODO Parameterize discovery information
55
- [200, {"Content-Type" => TCTP_DISCOVERY_MEDIA_TYPE, "Content-Length" => DEFAULT_TCTP_DISCOVERY_INFORMATION.length.to_s}, DEFAULT_TCTP_DISCOVERY_INFORMATION]
55
+ [200, {"Content-Type" => TCTP_DISCOVERY_MEDIA_TYPE, "Content-Length" => DEFAULT_TCTP_DISCOVERY_INFORMATION.length.to_s}, [DEFAULT_TCTP_DISCOVERY_INFORMATION]]
56
56
  when is_halec_creation?(req)
57
57
  # HALEC creation
58
- halec = ServerHALEC.new(url: '/halecs/' + TCTP::new_slug)
58
+ halec = ServerHALEC.new(url: halec_uri(req.env, "/halecs/#{TCTP::new_slug}"))
59
59
 
60
60
  # TODO Allow creation using predefined cookie
61
61
  session = TCTPSession.new
62
62
 
63
63
  # Send client_hello to server HALEC and read handshake_response
64
64
  client_hello = req.body.read
65
- halec.socket_there.write(client_hello)
66
- handshake_response = [halec.socket_there.recv(2048)]
65
+ halec.engine.inject client_hello
66
+ halec.engine.read
67
+ handshake_response = [halec.engine.extract]
67
68
 
68
69
  # Set location header and content-length
69
- header = {'Location' => halec.url, 'Content-Length' => handshake_response[0].length.to_s}
70
+ header = {'Location' => halec.url.to_s, 'Content-Length' => handshake_response[0].length.to_s}
70
71
 
71
72
  # Set the TCTP session cookie header
72
73
  Rack::Utils.set_cookie_header!(header, "tctp_session_cookie", {:value => session.session_id, :path => '/', :expires => Time.now+24*60*60})
@@ -78,13 +79,14 @@ module Rack
78
79
  [201, header, handshake_response]
79
80
  when is_halec_handshake?(req)
80
81
  # Get persisted server HALEC
81
- halec = @sessions[req.cookies['tctp_session_cookie']].halecs[req.path_info]
82
+ halec = @sessions[req.cookies['tctp_session_cookie']].halecs[halec_uri(req.env, req.path_info)]
82
83
 
83
84
  # Write handshake message to server HALEC
84
- halec.socket_there.write(req.body.read)
85
+ halec.engine.inject req.body.read
85
86
 
86
87
  # Receive handshake response
87
- handshake_response = halec.socket_there.recv(2048)
88
+ halec.engine.read
89
+ handshake_response = halec.engine.extract
88
90
 
89
91
  # Send back server HALEC response
90
92
  [200, {'Content-Length' => handshake_response.length.to_s}, [handshake_response]]
@@ -96,10 +98,11 @@ module Rack
96
98
  halec_url = req.body.readline.chomp
97
99
 
98
100
  # Gets the HALEC
99
- halec = @sessions[req.cookies['tctp_session_cookie']].halecs[halec_url]
101
+ halec = @sessions[req.cookies['tctp_session_cookie']].halecs[URI(halec_url)]
100
102
 
101
- halec.socket_there.write(req.body.read)
102
- decrypted_body.write(halec.ssl_socket.readpartial(2 ** 26 - 1))
103
+ read_body = req.body.read
104
+
105
+ decrypted_body.write halec.decrypt_data(read_body)
103
106
 
104
107
  req.body.string = decrypted_body.string
105
108
  end
@@ -125,7 +128,7 @@ module Rack
125
128
  content_body_length = 0
126
129
 
127
130
  # The first line
128
- first_line = halec.url + "\r\n"
131
+ first_line = halec.url.to_s + "\r\n"
129
132
  content_body_length += first_line.length
130
133
 
131
134
  # Encrypt the body. The first line of the response specifies the used HALEC
@@ -134,15 +137,9 @@ module Rack
134
137
 
135
138
  # Encrypt each body fragment
136
139
  body.each do |fragment|
137
- bodyio = StringIO.new(fragment)
138
-
139
- until bodyio.eof? do
140
- chunk = bodyio.read(16 * 1024)
141
- halec.ssl_socket.write(chunk)
142
- encrypted_chunk = halec.socket_there.readpartial(32 * 1024)
143
- encrypted_body << encrypted_chunk
144
- content_body_length += encrypted_chunk.length
145
- end
140
+ encrypted_fragment = halec.encrypt_data fragment
141
+ encrypted_body << encrypted_fragment
142
+ content_body_length += encrypted_fragment.length
146
143
  end
147
144
 
148
145
  # Finding this bug took waaaay too long ...
@@ -190,7 +187,7 @@ module Rack
190
187
  !req.cookies.nil? &&
191
188
  req.cookies.has_key?('tctp_session_cookie') &&
192
189
  sessions.has_key?(req.cookies['tctp_session_cookie']) &&
193
- sessions[req.cookies['tctp_session_cookie']].halecs.has_key?(req.path_info)
190
+ sessions[req.cookies['tctp_session_cookie']].halecs.has_key?(halec_uri(req.env, req.path_info))
194
191
  end
195
192
 
196
193
  def is_tctp_response_requested? (req)
@@ -200,6 +197,14 @@ module Rack
200
197
  def is_tctp_encrypted_body? (req)
201
198
  req.env['HTTP_CONTENT_ENCODING'].eql?('encrypted')
202
199
  end
200
+
201
+ # Builds an URI object to be used as a HALEC +uri+
202
+ # @param [Hash] env A Rack environment hash
203
+ # @param [String] path A path
204
+ # @return [URI] An HALEC +uri+
205
+ def halec_uri(env, path)
206
+ URI("#{env['rack.url_scheme']}://#{env['HTTP_HOST']}:#{env['SERVER_PORT']}#{path}")
207
+ end
203
208
  end
204
209
 
205
210
  class TCTPSession
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-tctp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.4
4
+ version: 0.9.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mathias Slawik
@@ -97,12 +97,15 @@ dependencies:
97
97
  description: Rack middleware for end-to-end security through TCTP
98
98
  email: mathias.slawik@tu-berlin.de
99
99
  executables: []
100
- extensions: []
100
+ extensions:
101
+ - ext/engine/extconf.rb
101
102
  extra_rdoc_files: []
102
103
  files:
103
104
  - lib/rack-tctp.rb
104
105
  - lib/rack/tctp.rb
105
106
  - lib/rack/tctp/halec.rb
107
+ - ext/engine/engine.c
108
+ - ext/engine/extconf.rb
106
109
  homepage: https://github.com/mathiasslawik/rack-tctp
107
110
  licenses:
108
111
  - Apache-2.0
@@ -128,3 +131,4 @@ signing_key:
128
131
  specification_version: 4
129
132
  summary: Rack TCTP middleware
130
133
  test_files: []
134
+ has_rdoc: