libtls 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1566c4cd86920d7e089b2087c3b98f62b5d43217
4
+ data.tar.gz: 3618c5cc4bcd47205a61664c8b52bbeba6bc91df
5
+ SHA512:
6
+ metadata.gz: fc0d68830f69516be58bf616d81ab44b6bad1e30c9e6882b96dca089fe8a3bf8d6784e45a501098770fecde8922637a3ba6cac7aee083e4a4f1b573df37da50f
7
+ data.tar.gz: 74d0efa0af199bb1f938e8ee6a70a69d55915108d82c7a3d638c01750ee9d1fa7edb4c8ed66f9071497575db15d43d69e7fbf31ca842e8d5896661eca9731701
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
@@ -0,0 +1,3 @@
1
+ --exclude spec
2
+ --hide-void-return
3
+ --files LICENSE
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in libtls.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2015 Mike Burns <mike@mike-burns.com>
2
+
3
+ Permission to use, copy, modify, and distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,279 @@
1
+ # libtls for Ruby
2
+
3
+ This is a set of libtls bindings for Ruby, plus a nice object-oriented layer
4
+ atop the bindings.
5
+
6
+ ## Installation
7
+
8
+ This gem depends on the libtls library. Make sure you either run OpenBSD or
9
+ have [libressl-portable] installed.
10
+
11
+ Once libtls itself is installed, add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'libtls'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install libtls
24
+
25
+ [libressl-portable]: http://www.libressl.org/releases.html
26
+
27
+ ## Usage
28
+
29
+ This library provides the API on two levels: the raw C functions, and a nice
30
+ object-oriented layer atop it.
31
+
32
+ ### Raw
33
+
34
+ The raw functions are as follows; see `tls_init`(3) for more information on what
35
+ they do:
36
+
37
+ - `LibTLS::Raw.tls_init`
38
+ - `LibTLS::Raw.tls_error`
39
+ - `LibTLS::Raw.tls_config_new`
40
+ - `LibTLS::Raw.tls_config_free`
41
+ - `LibTLS::Raw.tls_config_parse_protocols`
42
+ - `LibTLS::Raw.tls_config_set_ca_file`
43
+ - `LibTLS::Raw.tls_config_set_ca_path`
44
+ - `LibTLS::Raw.tls_config_set_ca_mem`
45
+ - `LibTLS::Raw.tls_config_set_cert_file`
46
+ - `LibTLS::Raw.tls_config_set_cert_mem`
47
+ - `LibTLS::Raw.tls_config_set_ciphers`
48
+ - `LibTLS::Raw.tls_config_set_dheparams`
49
+ - `LibTLS::Raw.tls_config_set_ecdhecurve`
50
+ - `LibTLS::Raw.tls_config_set_key_file`
51
+ - `LibTLS::Raw.tls_config_set_key_mem`
52
+ - `LibTLS::Raw.tls_config_set_protocols`
53
+ - `LibTLS::Raw.tls_config_set_verify_depth`
54
+ - `LibTLS::Raw.tls_config_clear_keys`
55
+ - `LibTLS::Raw.tls_config_insecure_noverifycert`
56
+ - `LibTLS::Raw.tls_config_insecure_noverifyname`
57
+ - `LibTLS::Raw.tls_config_verify`
58
+ - `LibTLS::Raw.tls_load_file`
59
+ - `LibTLS::Raw.tls_client`
60
+ - `LibTLS::Raw.tls_server`
61
+ - `LibTLS::Raw.tls_configure`
62
+ - `LibTLS::Raw.tls_reset`
63
+ - `LibTLS::Raw.tls_close`
64
+ - `LibTLS::Raw.tls_free`
65
+ - `LibTLS::Raw.tls_connect`
66
+ - `LibTLS::Raw.tls_connect_fds`
67
+ - `LibTLS::Raw.tls_connect_servername`
68
+ - `LibTLS::Raw.tls_connect_socket`
69
+ - `LibTLS::Raw.tls_accept_fds`
70
+ - `LibTLS::Raw.tls_accept_socket`
71
+ - `LibTLS::Raw.tls_read`
72
+ - `LibTLS::Raw.tls_write`
73
+
74
+ Of particular note are those functions which take a pointer (`tls_read`,
75
+ `tls_write`, `tls_accept_socket`, and others). These must have an instance of
76
+ `FFI::MemoryPointer` passed to them:
77
+
78
+ ```ruby
79
+ FFI::MemoryPointer.new(:size_t) do |outlen|
80
+ FFI::MemoryPointer.new(:uchar, 1024, true) do |buf|
81
+
82
+ ret = LibTLS::Raw.tls_read(client, buf, 1024, outlen)
83
+
84
+ if ret < 0
85
+ raise "tls_read: #{LibTLS::Raw.tls_error(client)}"
86
+ end
87
+
88
+ end
89
+ end
90
+ ```
91
+
92
+ Additionally, instance of Ruby's `Socket` object must be converted to their
93
+ file descriptor before interfacing with the C function. The `tls_accept_socket`
94
+ function combines the `FFI::MemoryPointer` requirement with this file
95
+ descriptor requirement:
96
+
97
+ ```ruby
98
+ cctx_ptr = FFI::MemoryPointer.new(:pointer)
99
+
100
+ if tls_accept_socket(server, cctx_ptr, socket.fileno) == -1
101
+ raise "tls_accept_socket: #{LibTLS::Raw.tls_error(server)}"
102
+ end
103
+
104
+ cctx = cctx_ptr.read_pointer
105
+ ```
106
+
107
+ Constants from `tls.h` are manually re-exposed under the `LibTLS::Raw`
108
+ namespace:
109
+
110
+ - `LibTLS::Raw::TLS_API`
111
+ - `LibTLS::Raw::TLS_PROTOCOL_TLSv1_0`
112
+ - `LibTLS::Raw::TLS_PROTOCOL_TLSv1_1`
113
+ - `LibTLS::Raw::TLS_PROTOCOL_TLSv1_2`
114
+ - `LibTLS::Raw::TLS_PROTOCOL_TLSv1`
115
+ - `LibTLS::Raw::TLS_PROTOCOLS_ALL`
116
+ - `LibTLS::Raw::TLS_PROTOCOLS_DEFAULT`
117
+ - `LibTLS::Raw::TLS_READ_AGAIN`
118
+ - `LibTLS::Raw::TLS_WRITE_AGAIN`
119
+
120
+ ### Object-Oriented Wrapper
121
+
122
+ An object-oriented wrapper is provided. Here is an example of a client:
123
+
124
+ ```ruby
125
+ # Get the contents of the Web page hosted at https://#{hostname}:443#{path} .
126
+ def get(hostname, path)
127
+ # The return value: nil, or a string.
128
+ content = nil
129
+
130
+ # TLS configuration. The key is formed from the series of tls_config_set_*
131
+ # functions; the value is either the scalar value (int or string), or an
132
+ # array of the multiple values. For example, ca_mem takes an array with the
133
+ # FFI::MemoryPointer and the length of that pointer.
134
+ config = {
135
+ ciphers: "DES-CBC3-SHA",
136
+ protocols: LibTLS::Raw::TLS_PROTOCOLS_ALL
137
+ }
138
+
139
+ # Create a new libtls client. The block is then immediately run, and then the
140
+ # memory free'd.
141
+ LibTLS::Client.new(configure: config) do |client|
142
+ # Connect to the server on port 443. When the block finishes, disconnect.
143
+ content = client.connect("mike-burns.com", 443) do |c|
144
+ # Send a string to the server; in this case, a HTTP request.
145
+ c.write(http_get(hostname, path))
146
+ # Read all the data from the server, and return it. The return value of
147
+ # this block is the return value of Client#connect.
148
+ c.read
149
+ end
150
+ end
151
+
152
+ # Return the content.
153
+ content
154
+ end
155
+
156
+ # Generate a HTTP request string.
157
+ def http_get(hostname, path)
158
+ ["GET #{path} HTTP/1.1",
159
+ "User-Agent: libtls.rb/0.1",
160
+ "Host: #{hostname}"].join("\r\n") +
161
+ "\r\n"
162
+ end
163
+ ```
164
+
165
+ And here is an example of a simple echo server:
166
+
167
+ ```ruby
168
+ # Reply to the socket's clients with their own string.
169
+ def echo_server(socket)
170
+ # Encrypt communications using the key and cert as generated by e.g.
171
+ # LibreSSL.
172
+ config = {
173
+ key_file: "thekey.key",
174
+ cert_file: "thecert.crt"
175
+ }
176
+
177
+ # Create and configure a new server object. The block is then immediately
178
+ # run, and then the memory is free'd.
179
+ LibTLS::Server.new(configure: config) do |server|
180
+ # Block until a client connects on client_socket.
181
+ client_socket, _ = socket.accept
182
+
183
+ # Loop forever; this allows another client to connect after this one.
184
+ loop do
185
+ # Handle the TLS handshake on the client socket. This takes a block,
186
+ # which is run immediately after the handshake has completed
187
+ # successfully. After the block finishes, disconnect and clean up. The
188
+ # block takes an opened client object.
189
+ server.accept(client_socket) do |c|
190
+ # Loop so that the client can write until they disconnect.
191
+ loop do
192
+ # Read the entirety of the client's string.
193
+ str = c.read
194
+ # Write exactly what the client sent.
195
+ c.write(str)
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+ ```
202
+
203
+ The underlying `struct tls *` object is exposed through the `#ctx` method; it
204
+ can be passed to any `LibTLS::Raw` method, for example.
205
+
206
+ These methods can raise instances of `LibTLS::UnknownCError` and
207
+ `LibTLS::CError`. Instances of the first are raised when we do not have access
208
+ to the underlying issue, and instances of the second attempt to include the
209
+ error string from libtls.
210
+
211
+ ## Contributing
212
+
213
+ As contributors and maintainers of this project, we will respect all people
214
+ who contribute in any fashion. We are committed to making participation in this
215
+ project a harassment-free experience for everyone, regardless of who they are.
216
+
217
+ The project maintainers have the right and responsibility to remove, edit, or
218
+ reject comments, commits, code, wiki edits, issues, and other contributions
219
+ that are not aligned to this code of conduct. Project maintainers who do not
220
+ follow the code of conduct may be removed from the project team.
221
+
222
+ Instances of unacceptable behavior may be reported by [opening an
223
+ issue][issues] or contacting [Mike Burns](mailto:mike@mike-burns.com)
224
+ ([PGP key][Mike PGP key]).
225
+
226
+ [issues]: https://github.com/mike-burns/libtls.rb/issues
227
+ [Mike PGP key]: http://pgp.mit.edu/pks/lookup?op=get&search=0x3E6761F72846B014
228
+
229
+ ### To contribute a feature
230
+
231
+ 1. Fork it ( https://github.com/mike-burns/libtls.rb/fork )
232
+ 2. Make sure the tests pass (`rake`)
233
+ 3. Create your feature branch (`git checkout -b my-new-feature`)
234
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
235
+ 5. Push to the branch (`git push origin my-new-feature`)
236
+ 6. Make sure the tests pass (`rake`)
237
+ 7. Make sure documentation is complete (`yard`)
238
+ 8. Create a new Pull Request
239
+
240
+ *Feature requests without patches will be closed*.
241
+
242
+ ### To report a security issue
243
+
244
+ If the issue should be kept quiet for security reasons, email
245
+ [Mike Burns](mailto:mike@mike-burns.com) directly. His PGP key id is
246
+ [0x2846b014][Mike PGP key], fingerprint:
247
+
248
+ 5FD8 2CE6 A646 3285 538F
249
+ C3A5 3E67 61F7 2846 B014
250
+
251
+ ## Credits
252
+
253
+ libtls for Ruby is by [Mike Burns]. It is released under the
254
+ [ISC license][LICENSE].
255
+
256
+ It would have been impossible to make this library so quickly without the
257
+ knowledge gained on [erltls] with [Rebecca Meritz].
258
+
259
+ GNU help was provided by [Matt Horan].
260
+
261
+ The [ffi] gem has also proven crucial to this project; thanks to
262
+ [Wayne Meissner, et al.][ffi credits], for their amazing work on that.
263
+
264
+ The code of conduct is adapted from the [Contributor Covenant],
265
+ [version 1.1.0][coc110].
266
+
267
+ [Donate to the OpenBSD Foundation][donate]. Without them, none of this would
268
+ exist.
269
+
270
+ [Mike Burns]: https://mike-burns.com
271
+ [Rebecca Meritz]: http://rebecca.meritz.com/
272
+ [Matt Horan]: https://matthoran.com/
273
+ [LICENSE]: LICENSE
274
+ [donate]: http://www.openbsdfoundation.org/donations.html
275
+ [ffi]: https://github.com/ffi/ffi/wiki
276
+ [ffi credits]: https://github.com/ffi/ffi/#credits
277
+ [erltls]: https://github.com/meritz-burns/erltls
278
+ [Contributor Covenant]: http://contributor-covenant.org
279
+ [coc110]: http://contributor-covenant.org/version/1/1/0/
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require "rspec/core/rake_task"
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
8
+ rescue LoadError
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'libtls/version'
2
+ require 'libtls/raw'
3
+ require 'libtls/client'
4
+ require 'libtls/server'
5
+
6
+ ##
7
+ # {include:file:README.md}
8
+ module LibTLS
9
+ end
@@ -0,0 +1,195 @@
1
+ require 'libtls/config'
2
+ require 'libtls/raw'
3
+
4
+ module LibTLS
5
+ ##
6
+ # This class represents a TLS client connecting to a server. Here is a sample
7
+ # HTTPS session; this #get method will produce the content at the specified
8
+ # path on the hostname:
9
+ #
10
+ # def get(hostname, path)
11
+ # content = nil
12
+ # config = { ca_file: '/etc/ssl/cert.pem' }
13
+ #
14
+ # LibTLS::Client.new(configure: config) do |client|
15
+ # content = client.connect("mike-burns.com", 443) do |c|
16
+ # c.write(http_get(hostname, path))
17
+ # c.read
18
+ # end
19
+ # end
20
+ #
21
+ # content
22
+ # end
23
+ #
24
+ # def http_get(hostname, path)
25
+ # ["GET #{path} HTTP/1.1",
26
+ # "User-Agent: libtls.rb/0.1",
27
+ # "Host: #{hostname}"].join("\r\n") +
28
+ # "\r\n"
29
+ # end
30
+ class Client
31
+ ##
32
+ # The FFI wrapper around the struct tls object
33
+ #
34
+ # This is only useful for calling any of the {LibTLS::Raw} methods.
35
+ attr_reader :ctx
36
+
37
+ ##
38
+ # Construct a new [Client] instance
39
+ #
40
+ # Once constructed, it runs the block. When the block finishes, it calls
41
+ # {#finish}.
42
+ #
43
+ # @param configure [Hash] a mapping from setting name to value. The setting
44
+ # name is any of {LibTLS::Config::VALID_SET_CONFIGS}; the value is either a
45
+ # scalar value passed through to the C function, or an array of values. For
46
+ # example:
47
+ # { ca_file: 'ca.pem', key_mem: [key_ptr, 48] }
48
+ # @yieldparam [Client] self an initialized and configured instance of self
49
+ # @raise [LibTLS::UnknownCError] if +tls_init+ or +tls_client+ fails
50
+ # @raise [LibTLS::CError] if +tls_configure+ fails
51
+ def initialize(configure:, &block)
52
+ if LibTLS::Raw.tls_init < 0
53
+ raise LibTLS::UnknownCError, "tls_init"
54
+ end
55
+
56
+ @config = Config.new(configure)
57
+
58
+ if (@ctx = LibTLS::Raw.tls_client).null?
59
+ raise LibTLS::UnknownCError, "tls_client"
60
+ end
61
+
62
+ if LibTLS::Raw::tls_configure(ctx, @config.as_raw) < 0
63
+ raise LibTLS::CError, "tls_configure: #{LibTLS::Raw.tls_error(ctx)}"
64
+ end
65
+
66
+ if block
67
+ begin
68
+ block.call(self)
69
+ ensure
70
+ self.finish
71
+ end
72
+ end
73
+ end
74
+
75
+ ##
76
+ # Open a connection with the server
77
+ #
78
+ # This method negotiates the TLS connection with the +hostname+, at the
79
+ # +port+. Once connected, it passes the connected client to the block. Once
80
+ # the block finishes, it calls {OpenedClient#close} on the connection.
81
+ #
82
+ # @param hostname [String] the server to connect to, as an IPv4 address, an
83
+ # IPv6 address, or anything that can be resolved by +getaddrinfo+.
84
+ # @param port [#to_s] the port on the server to connect to
85
+ # @yieldparam [OpenedClient] client a connected client
86
+ # @raise [LibTLS::CError] if the +tls_connect+ fails
87
+ # @return the result of the block
88
+ def connect(hostname, port, &block)
89
+ opened_client = nil
90
+
91
+ begin
92
+ if LibTLS::Raw.tls_connect(ctx, hostname, port.to_s) < 0
93
+ raise LibTLS::CError, "tls_connect: #{LibTLS::Raw.tls_error(ctx)}"
94
+ end
95
+
96
+ opened_client = OpenedClient.new(ctx)
97
+ block.call(opened_client)
98
+ ensure
99
+ opened_client && opened_client.close
100
+ end
101
+ end
102
+
103
+ ##
104
+ # Release any memory held on to by the C library
105
+ #
106
+ # This method must be called either implicitly by passing a block to
107
+ # {#initialize}, or explicitly by you.
108
+ def finish
109
+ @config.free
110
+ LibTLS::Raw.tls_free(ctx)
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ ##
117
+ # A TLS client connected to a server
118
+ #
119
+ # This class must be instantiated only by {LibTLS::Client#connect} and
120
+ # {LibTLS::Server#accept}.
121
+ #
122
+ # When finished, {#close} must be called. This is implicitly handled for you by
123
+ # passing a block to the methods mentioned above.
124
+ class OpenedClient
125
+ READ_LEN = 1024
126
+ private_constant :READ_LEN
127
+
128
+ ##
129
+ # The FFI wrapper around the struct tls object
130
+ #
131
+ # This is only useful for calling any of the {LibTLS::Raw} methods.
132
+ attr_reader :ctx
133
+
134
+ ##
135
+ # @api private
136
+ def initialize(ctx)
137
+ @ctx = ctx
138
+ end
139
+
140
+ ##
141
+ # Close this connection
142
+ #
143
+ # This method must be called either implicitly by passing a block to
144
+ # {LibTLS::Client#connect} or {LibTLS::Server#accept}, or explicitly by you.
145
+ def close
146
+ if LibTLS::Raw.tls_close(ctx) < 0
147
+ raise LibTLS::CError, "tls_close: #{LibTLS::Raw.tls_error(ctx)}"
148
+ end
149
+ end
150
+
151
+ ##
152
+ # Write the string to the connection
153
+ #
154
+ # @param [String] str the string to write
155
+ # @raise [LibTLS::CError] if +tls_write+ fails
156
+ def write(str)
157
+ FFI::MemoryPointer.new(:size_t) do |outlen|
158
+ FFI::MemoryPointer.new(:uchar, str.length + 1) do |str_ptr|
159
+ str_ptr.put_string(0, str)
160
+
161
+ if LibTLS::Raw.tls_write(ctx, str_ptr, str.length, outlen) < 0
162
+ raise LibTLS::CError, "tls_write: #{LibTLS::Raw.tls_error(ctx)}"
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ ##
169
+ # Read a string from the connection
170
+ #
171
+ # @raise [LibTLS::CError] if +tls_read+ fails
172
+ # @return [String] the accumulated buffer
173
+ def read
174
+ str = ""
175
+
176
+ FFI::MemoryPointer.new(:size_t) do |outlen|
177
+ FFI::MemoryPointer.new(:uchar, READ_LEN, true) do |buf|
178
+ loop do
179
+ if LibTLS::Raw.tls_read(ctx, buf, READ_LEN, outlen) < 0
180
+ raise LibTLS::CError, "tls_read: #{LibTLS::Raw.tls_error(ctx)}"
181
+ end
182
+
183
+ str += buf.get_string(0, outlen.get_int(0))
184
+
185
+ if READ_LEN > outlen.get_int(0)
186
+ break
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ str
193
+ end
194
+ end
195
+ end