rack-tctp 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
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: