libtls 0.0.1

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.
@@ -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