libtls 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.yardopts +3 -0
- data/Gemfile +4 -0
- data/LICENSE +13 -0
- data/README.md +279 -0
- data/Rakefile +9 -0
- data/lib/libtls.rb +9 -0
- data/lib/libtls/client.rb +195 -0
- data/lib/libtls/config.rb +112 -0
- data/lib/libtls/exn.rb +22 -0
- data/lib/libtls/raw.rb +759 -0
- data/lib/libtls/server.rb +128 -0
- data/lib/libtls/version.rb +8 -0
- data/libtls.gemspec +30 -0
- data/spec/fixtures/mike-burns.pem +116 -0
- data/spec/fixtures/theca.pem +13 -0
- data/spec/fixtures/thecert.crt +13 -0
- data/spec/fixtures/thecsr.csr +11 -0
- data/spec/fixtures/thekey.key +15 -0
- data/spec/fixtures/thekey.key.protected +18 -0
- data/spec/oo/client_spec.rb +28 -0
- data/spec/oo/server_spec.rb +75 -0
- data/spec/support/fixtures.rb +10 -0
- metadata +135 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'libtls/raw'
|
2
|
+
require 'libtls/exn'
|
3
|
+
|
4
|
+
module LibTLS
|
5
|
+
##
|
6
|
+
# A TLS configuration
|
7
|
+
#
|
8
|
+
# This object is an abstraction over the libtls configuration. It can be used
|
9
|
+
# as a shorthand for configuring the struct tls context.
|
10
|
+
#
|
11
|
+
# config = LibTLS::Config.new(
|
12
|
+
# ca_path: '/etc/ssl',
|
13
|
+
# key_mem: [key_ptr, 512]
|
14
|
+
# )
|
15
|
+
# LibTLS::Raw.tls_configure(ctx, config.as_raw)
|
16
|
+
# config.free
|
17
|
+
class Config
|
18
|
+
##
|
19
|
+
# Keys that can be configured
|
20
|
+
#
|
21
|
+
# This is derived from the +tls_config_set_*+ functions in {LibTLS::Raw}.
|
22
|
+
VALID_SET_CONFIGS = %i(
|
23
|
+
ca_file ca_path ca_mem cert_file cert_mem ciphers dheparams ecdhecurve
|
24
|
+
key_file key_mem protocols verify_depth
|
25
|
+
)
|
26
|
+
|
27
|
+
##
|
28
|
+
# Return a new instance of Config
|
29
|
+
#
|
30
|
+
# @param [Hash] config_hash the Ruby representation of the configuration. The
|
31
|
+
# keys are any of {VALID_SET_CONFIGS}; the value is either a scalar value,
|
32
|
+
# or an array. The array is splatted into the appropriate C function.
|
33
|
+
#
|
34
|
+
# @option config_hash [String] ca_file The filename used to load a file containing
|
35
|
+
# the root certificates. (_Client_)
|
36
|
+
# @option config_hash [String] ca_path The path (directory) which should be
|
37
|
+
# searched for root certificates. (_Client_)
|
38
|
+
# @option config_hash [[FFI::Pointer, Fixnum]] ca_mem Set the root certificates
|
39
|
+
# directly from memory. (_Client_)
|
40
|
+
# @option config_hash [String] cert_file Set file from which the public
|
41
|
+
# certificate will be read. (_Client_ _and_ _server_)
|
42
|
+
# @option config_hash [[FFI::Pointer, Fixnum]] cert_mem Set the public
|
43
|
+
# certificate directly from memory. (_Client_ _and_ _server_)
|
44
|
+
# @option config_hash [String] ciphers Set the list of ciphers that may be
|
45
|
+
# used. (_Client_ _and_ _server_)
|
46
|
+
# @option config_hash [String] dheparams Set the dheparams option to either
|
47
|
+
# "none" (0), "auto" (-1), or "legacy" (1024). The default is "none".
|
48
|
+
# (_Server_)
|
49
|
+
# @option config_hash [String] ecdhecurve Set the ecdhecurve option to one of
|
50
|
+
# "none" (+NID_undef+), "auto" (-1), or any NID value understood by
|
51
|
+
# OBJ_txt2nid (3). (_Server_)
|
52
|
+
# @option config_hash [String] keyfile Set the file from which the private
|
53
|
+
# key will be read. (_Server_)
|
54
|
+
# @option config_hash [[FFI::Pointer, Fixnum]] key_mem Directly set the
|
55
|
+
# private key from memory. (_Server_)
|
56
|
+
# @option config_hash [Fixnum] protocols Sets which versions of the protocol
|
57
|
+
# may be used, as documented in {LibTLS::Raw#tls_config_set_protocols}.
|
58
|
+
# (_Client_ _and_ _server_)
|
59
|
+
# @option config_hash [Fixnum] verify_depth Set the verify depth as
|
60
|
+
# documented under SSL_CTX_set_verify_depth(3). (_Client_)
|
61
|
+
def initialize(config_hash)
|
62
|
+
@config_hash = config_hash
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Convert this object into the C representation
|
67
|
+
#
|
68
|
+
# This builds a struct tls_config pointer, sets the values on it as dictated
|
69
|
+
# by the hash passed in, and returns the struct tls_config pointer.
|
70
|
+
#
|
71
|
+
# The return value must be freed using {#free}.
|
72
|
+
#
|
73
|
+
# @return [FFI::Pointer] the completed struct tls_config pointer
|
74
|
+
# @raise [LibTLS::UnknownCError] if +tls_config_new+ or the appropriate
|
75
|
+
# +tls_config_set_*+ fails
|
76
|
+
def as_raw
|
77
|
+
@raw_config ||= buld_raw_config
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Release any memory held on to by the C library
|
82
|
+
#
|
83
|
+
# This method must be called when finished.
|
84
|
+
def free
|
85
|
+
LibTLS::Raw.tls_config_free(as_raw)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def buld_raw_config
|
91
|
+
if (raw = LibTLS::Raw.tls_config_new).null?
|
92
|
+
raise LibTLS::UnknownCError, "tls_config_new"
|
93
|
+
end
|
94
|
+
|
95
|
+
valid_config_hash.each do |key, value|
|
96
|
+
ret = LibTLS::Raw.send("tls_config_set_#{key}", raw, *value)
|
97
|
+
|
98
|
+
if ret && ret < 0
|
99
|
+
raise LibTLS::UnknownCError, "tls_config_set_#{key}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
raw
|
104
|
+
end
|
105
|
+
|
106
|
+
def valid_config_hash
|
107
|
+
@config_hash.select do |key, value|
|
108
|
+
VALID_SET_CONFIGS.include?(key)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/libtls/exn.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module LibTLS
|
2
|
+
##
|
3
|
+
# An unknown error occured in the libtls library
|
4
|
+
#
|
5
|
+
# This exception is raised when a non-TLS issue occurs in the libtls library.
|
6
|
+
# If possible, it might be useful to inspect +errno+ when you rescue this
|
7
|
+
# exception.
|
8
|
+
class UnknownCError < RuntimeError
|
9
|
+
##
|
10
|
+
# A description of the error
|
11
|
+
#
|
12
|
+
# This description contains the C function name.
|
13
|
+
def to_s
|
14
|
+
"#{super.to_s} failed"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# A known error occured in the libtls library
|
20
|
+
class CError < RuntimeError
|
21
|
+
end
|
22
|
+
end
|
data/lib/libtls/raw.rb
ADDED
@@ -0,0 +1,759 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module LibTLS
|
4
|
+
##
|
5
|
+
# Direct access to C functions
|
6
|
+
#
|
7
|
+
# This module encapsulates the communication between Ruby and the C libtls
|
8
|
+
# library.
|
9
|
+
#
|
10
|
+
# We recommend that you consider the object-oriented API in {LibTLS::Client}
|
11
|
+
# and {LibTLS::Server} before reaching for these functions. However, not all
|
12
|
+
# functions are accessible via those two classes.
|
13
|
+
#
|
14
|
+
# The {LibTLS::Server}, {LibTLS::Client}, and {LibTLS::OpenedClient}
|
15
|
+
# instances provide access to the +FFI::Pointer+ that is used by functions in
|
16
|
+
# this module, so that you can mostly use the OO interface but directly access
|
17
|
+
# the C functions when needed.
|
18
|
+
#
|
19
|
+
# Using functions from here means that you are familiar with the tls_init(3)
|
20
|
+
# man page and the +FFI::MemoryPointer+ class.
|
21
|
+
#
|
22
|
+
# Much of the documentation in this file is taken from the tls_init(3) man
|
23
|
+
# page. Such documentation is copyright 2014 Ted Unangst, licensed under the
|
24
|
+
# ISC license.
|
25
|
+
module Raw
|
26
|
+
extend FFI::Library
|
27
|
+
|
28
|
+
ffi_lib "libtls.so"
|
29
|
+
|
30
|
+
##
|
31
|
+
# The version of the libtls API.
|
32
|
+
TLS_API = 20141031
|
33
|
+
|
34
|
+
##
|
35
|
+
# Select the TLS 1.0 protocol
|
36
|
+
TLS_PROTOCOL_TLSv1_0 = (1 << 1)
|
37
|
+
##
|
38
|
+
# Select the TLS 1.1 protocol
|
39
|
+
TLS_PROTOCOL_TLSv1_1 = (1 << 2)
|
40
|
+
##
|
41
|
+
# Select the TLS 1.2 protocol
|
42
|
+
TLS_PROTOCOL_TLSv1_2 = (1 << 3)
|
43
|
+
##
|
44
|
+
# Select any TLS 1.x protocol
|
45
|
+
TLS_PROTOCOL_TLSv1 = \
|
46
|
+
TLS_PROTOCOL_TLSv1_0 | TLS_PROTOCOL_TLSv1_1 | TLS_PROTOCOL_TLSv1_2
|
47
|
+
|
48
|
+
##
|
49
|
+
# Select any TLS protocol of any version
|
50
|
+
TLS_PROTOCOLS_ALL = TLS_PROTOCOL_TLSv1
|
51
|
+
##
|
52
|
+
# Select the default, suggested TLS protocol
|
53
|
+
#
|
54
|
+
# Do not use any protocol except this one unless you understand why you are
|
55
|
+
# doing so.
|
56
|
+
TLS_PROTOCOLS_DEFAULT = TLS_PROTOCOL_TLSv1_2
|
57
|
+
|
58
|
+
##
|
59
|
+
# A read operation is necessary to continue
|
60
|
+
TLS_READ_AGAIN = -2
|
61
|
+
##
|
62
|
+
# A write operation is necessary to continue
|
63
|
+
TLS_WRITE_AGAIN = -3
|
64
|
+
|
65
|
+
##
|
66
|
+
# @!method tls_init()
|
67
|
+
#
|
68
|
+
# Initialize the libtls library
|
69
|
+
#
|
70
|
+
# The +tls_init+ function should be called once before any function is used.
|
71
|
+
# It may be called more than once, but not concurrently.
|
72
|
+
#
|
73
|
+
# @return [Fixnum] 0 on success, -1 on error
|
74
|
+
attach_function :tls_init, [], :int
|
75
|
+
|
76
|
+
##
|
77
|
+
# @!method tls_error(ctx)
|
78
|
+
#
|
79
|
+
# Produce the error message on the context
|
80
|
+
#
|
81
|
+
# The +tls_error+ function may be used to retrieve a string containing more
|
82
|
+
# information about the most recent error.
|
83
|
+
#
|
84
|
+
# @param ctx [FFI::Pointer] the TLS context
|
85
|
+
# @return [String] the error message for the most recent error
|
86
|
+
attach_function :tls_error, [:pointer], :string
|
87
|
+
|
88
|
+
##
|
89
|
+
# @!group Create and free configuration objects
|
90
|
+
# @!method tls_config_new()
|
91
|
+
#
|
92
|
+
# Produce a new +tls_config+ context
|
93
|
+
#
|
94
|
+
# Before a connection is created, a configuration must be created. The
|
95
|
+
# +tls_config_new+ function returns a new default configuration that can be
|
96
|
+
# used for future connections. Several functions exist to change the
|
97
|
+
# options of the configuration; see below.
|
98
|
+
#
|
99
|
+
# @return [FFI::Pointer] a +tls_config+ context or +FFI::Pointer::NULL+ on
|
100
|
+
# failure. Check using +#null?+.
|
101
|
+
attach_function :tls_config_new, [], :pointer
|
102
|
+
# @!endgroup
|
103
|
+
|
104
|
+
##
|
105
|
+
# @!group Create and free configuration objects
|
106
|
+
# @!method tls_config_free(config)
|
107
|
+
#
|
108
|
+
# Free the +tls_config+ object
|
109
|
+
#
|
110
|
+
# When no more contexts are to be created, the +tls_config+ object should be
|
111
|
+
# freed by calling +tls_config_free+.
|
112
|
+
#
|
113
|
+
# @param config [FFI::Pointer] the TLS config
|
114
|
+
# @return [nil] +void+
|
115
|
+
attach_function :tls_config_free, [:pointer], :void
|
116
|
+
# @!endgroup
|
117
|
+
|
118
|
+
##
|
119
|
+
# @!method tls_config_parse_protocols(protocols, protostr)
|
120
|
+
#
|
121
|
+
# Parse a protocol string into a bitmask
|
122
|
+
#
|
123
|
+
# The +tls_config_parse_protocols+ function parses a protocol string and
|
124
|
+
# returns the corresponding value via the +protocols+ argument. This value
|
125
|
+
# can then be passed to the {#tls_config_set_protocols} function. The
|
126
|
+
# protocol string is a comma or colon separated list of keywords. Valid
|
127
|
+
# keywords are +tlsv1.0+, +tlsv1.1+, +tlsv1.2+, +all+ (all supported
|
128
|
+
# protocols), +default+ (an alias for +secure+), +legacy+ (an alias for
|
129
|
+
# +all+) and +secure+ (currently TLSv1.2 only). If a value has a negative
|
130
|
+
# prefix (in the form of a leading exclamation mark) then it is removed
|
131
|
+
# from the list of available protocols, rather than being added to it.
|
132
|
+
#
|
133
|
+
# @param protocols [FFI::Pointer] a +uint32_t+ pointer to store the parse
|
134
|
+
# result
|
135
|
+
# @param protostr [String] the protocol string
|
136
|
+
# @return [Fixnum] 0 on success, -1 on error
|
137
|
+
#
|
138
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
139
|
+
attach_function :tls_config_parse_protocols, [:uint32_t, :string], :int
|
140
|
+
|
141
|
+
##
|
142
|
+
# @!group Client configuration
|
143
|
+
# @!method tls_config_set_ca_file(config, ca_file)
|
144
|
+
#
|
145
|
+
# Use the given file as the certificate authority file
|
146
|
+
#
|
147
|
+
# +tls_config_set_ca_file+ sets the filename used to load a file containing
|
148
|
+
# the root certificates.
|
149
|
+
#
|
150
|
+
# This applies to clients.
|
151
|
+
#
|
152
|
+
# @param config [FFI::Pointer] the TLS config
|
153
|
+
# @param ca_file [String] absolute path to the file containing the root
|
154
|
+
# certificates
|
155
|
+
# @return [Fixnum] 0 on success, -1 on error
|
156
|
+
attach_function :tls_config_set_ca_file, [:pointer, :string], :int
|
157
|
+
|
158
|
+
##
|
159
|
+
# @!method tls_config_set_ca_path(config, ca_path)
|
160
|
+
#
|
161
|
+
# Use the directory specified to find the root certificate file
|
162
|
+
#
|
163
|
+
# +tls_config_set_ca_path+ sets the path (directory) which should be
|
164
|
+
# searched for root certificates.
|
165
|
+
#
|
166
|
+
# This applies to clients.
|
167
|
+
#
|
168
|
+
# @param config [FFI::Pointer] the TLS config
|
169
|
+
# @param ca_path [String] absolute path to the directory containing the
|
170
|
+
# root certificates
|
171
|
+
# @return [Fixnum] 0 on success, -1 on error
|
172
|
+
#
|
173
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
174
|
+
attach_function :tls_config_set_ca_path, [:pointer, :string], :int
|
175
|
+
|
176
|
+
##
|
177
|
+
# @!method tls_config_set_ca_mem(config, cert, len)
|
178
|
+
#
|
179
|
+
# Use the root certicate from memory
|
180
|
+
#
|
181
|
+
# +tls_config_set_ca_mem+ sets the root certificates directly from memory.
|
182
|
+
#
|
183
|
+
# This applies to clients.
|
184
|
+
#
|
185
|
+
# @param config [FFI::Pointer] the TLS config
|
186
|
+
# @param cert [FFI::Pointer] uint8_t pointer to the cert
|
187
|
+
# @param len [Fixnum] number of bytes in the cert
|
188
|
+
# @return [Fixnum] 0 on success, -1 on error
|
189
|
+
#
|
190
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
191
|
+
attach_function :tls_config_set_ca_mem, [:pointer, :pointer, :size_t], :int
|
192
|
+
# @!endgroup
|
193
|
+
|
194
|
+
##
|
195
|
+
# @!group Client and server configuration
|
196
|
+
# @!method tls_config_set_cert_file(config, cert_file)
|
197
|
+
#
|
198
|
+
# Use the given file as the certficate file
|
199
|
+
#
|
200
|
+
# +tls_config_set_ca_file+ sets sets file from which the public certificate
|
201
|
+
# will be read.
|
202
|
+
#
|
203
|
+
# This applies to clients and servers.
|
204
|
+
#
|
205
|
+
# @param config [FFI::Pointer] the TLS config
|
206
|
+
# @param cert_file [String] absolute path to the file containing the public
|
207
|
+
# certificate
|
208
|
+
# @return [Fixnum] 0 on success, -1 on error
|
209
|
+
attach_function :tls_config_set_cert_file, [:pointer, :string], :int
|
210
|
+
|
211
|
+
##
|
212
|
+
# @!method tls_config_set_cert_mem(config, cert, len)
|
213
|
+
#
|
214
|
+
# Use the given file as the public certificate
|
215
|
+
#
|
216
|
+
# +tls_config_set_cert_mem+ sets the public certificate directly from
|
217
|
+
# memory.
|
218
|
+
#
|
219
|
+
# This applies to clients and servers.
|
220
|
+
#
|
221
|
+
# @param config [FFI::Pointer] the TLS config
|
222
|
+
# @param cert [FFI::Pointer] uint8_t pointer to the cert
|
223
|
+
# @param len [Fixnum] number of bytes in the cert
|
224
|
+
# @return [Fixnum] 0 on success, -1 on error
|
225
|
+
#
|
226
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
227
|
+
attach_function :tls_config_set_cert_mem, [:pointer, :pointer, :size_t], :int
|
228
|
+
# @!endgroup
|
229
|
+
|
230
|
+
##
|
231
|
+
# @!group Client and server configuration
|
232
|
+
# @!method tls_config_set_ciphers(config, ciphers)
|
233
|
+
#
|
234
|
+
# Use the given list of ciphers
|
235
|
+
#
|
236
|
+
# +tls_config_set_ciphers+ sets the list of ciphers that may be used.
|
237
|
+
#
|
238
|
+
# This applies to clients and servers.
|
239
|
+
#
|
240
|
+
# @param config [FFI::Pointer] the TLS config
|
241
|
+
# @param ciphers [String] a list of ciphers to use
|
242
|
+
# @return [Fixnum] 0 on success, -1 on error
|
243
|
+
attach_function :tls_config_set_ciphers, [:pointer, :string], :int
|
244
|
+
# @!endgroup
|
245
|
+
|
246
|
+
##
|
247
|
+
# @!group Server configuration
|
248
|
+
# @!method tls_config_set_dheparams(config, params)
|
249
|
+
#
|
250
|
+
# Tune the dheparams
|
251
|
+
#
|
252
|
+
# This applies to servers.
|
253
|
+
#
|
254
|
+
# @param config [FFI::Pointer] the TLS config
|
255
|
+
# @param params [String] one of "none" (0), "auto" (-1), or "legacy"
|
256
|
+
# (1024). The default is "none".
|
257
|
+
# @return [Fixnum] 0 on success, -1 on error
|
258
|
+
#
|
259
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
260
|
+
attach_function :tls_config_set_dheparams, [:pointer, :string], :int
|
261
|
+
|
262
|
+
##
|
263
|
+
# @!method tls_config_set_ecdhecurve(config, name)
|
264
|
+
#
|
265
|
+
# Use the specified EC DHE curve
|
266
|
+
#
|
267
|
+
# This applies to servers.
|
268
|
+
#
|
269
|
+
# @param config [FFI::Pointer] the TLS config
|
270
|
+
# @param name one of "none" (+NID_undef+), "auto" (-1), or any NID value
|
271
|
+
# understood by OBJ_txt2nid (3).
|
272
|
+
# @return [Fixnum] 0 on success, -1 on error
|
273
|
+
#
|
274
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
275
|
+
attach_function :tls_config_set_ecdhecurve, [:pointer, :string], :int
|
276
|
+
|
277
|
+
##
|
278
|
+
# @!method tls_config_set_key_file(config, key_file)
|
279
|
+
#
|
280
|
+
# Set the private key via an absolute file path
|
281
|
+
#
|
282
|
+
# +tls_config_set_key_file+ sets the file from which the private key will
|
283
|
+
# be read.
|
284
|
+
#
|
285
|
+
# This applies to servers.
|
286
|
+
#
|
287
|
+
# @param config [FFI::Pointer] the TLS config
|
288
|
+
# @param key_file [String] absolute path to a private key in a file
|
289
|
+
# @return [Fixnum] 0 on success, -1 on error
|
290
|
+
attach_function :tls_config_set_key_file, [:pointer, :string], :int
|
291
|
+
|
292
|
+
##
|
293
|
+
# @!method tls_config_set_key_mem(config, key, len)
|
294
|
+
#
|
295
|
+
# Set the private key via a value in memory
|
296
|
+
#
|
297
|
+
# +tls_config_set_key_mem+ directly sets the private key from memory.
|
298
|
+
#
|
299
|
+
# This applies to servers.
|
300
|
+
#
|
301
|
+
# @param config [FFI::Pointer] the TLS config
|
302
|
+
# @param key [FFI::Pointer] a uint8_t pointer to the key
|
303
|
+
# @param len [Fixnum] number of bytes in the key
|
304
|
+
# @return [Fixnum] 0 on success, -1 on error
|
305
|
+
#
|
306
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
307
|
+
attach_function :tls_config_set_key_mem, [:pointer, :pointer, :size_t], :int
|
308
|
+
# @!endgroup
|
309
|
+
|
310
|
+
##
|
311
|
+
# @!group Client and server configuration
|
312
|
+
# @!method tls_config_set_protocols(config, protocols)
|
313
|
+
#
|
314
|
+
# Select which protocols are to be used
|
315
|
+
#
|
316
|
+
# +tls_config_set_protocols+ sets which versions of the protocol may be
|
317
|
+
# used. Possible values are the bitwise OR of:
|
318
|
+
#
|
319
|
+
# - {TLS_PROTOCOL_TLSv1_0}
|
320
|
+
# - {TLS_PROTOCOL_TLSv1_1}
|
321
|
+
# - {TLS_PROTOCOL_TLSv1_2}
|
322
|
+
#
|
323
|
+
# Additionally, the values {TLS_PROTOCOL_TLSv1} (TLSv1.0, TLSv1.1 and
|
324
|
+
# TLSv1.2), {TLS_PROTOCOLS_ALL} (all supported protocols) and
|
325
|
+
# {TLS_PROTOCOLS_DEFAULT} (TLSv1.2 only) may be used.
|
326
|
+
#
|
327
|
+
# This applies to clients and servers.
|
328
|
+
#
|
329
|
+
# @param config [FFI::Pointer] the TLS config
|
330
|
+
# @param protocols [Fixnum] bitmask of the protocols to select
|
331
|
+
# @return [nil] +void+
|
332
|
+
attach_function :tls_config_set_protocols, [:pointer, :uint], :void
|
333
|
+
# @!endgroup
|
334
|
+
|
335
|
+
##
|
336
|
+
# @!group Client configuration
|
337
|
+
# @!method tls_config_set_verify_depth(config, verify_depth)
|
338
|
+
#
|
339
|
+
# Set the maximum depth for the certificate chain
|
340
|
+
#
|
341
|
+
# See SSL_CTX_set_verify_depth(3) for details.
|
342
|
+
#
|
343
|
+
# This applies to clients.
|
344
|
+
#
|
345
|
+
# @param config [FFI::Pointer] the TLS config
|
346
|
+
# @param verify_depth [Fixnum] the maximum depth for certificate chain
|
347
|
+
# verification
|
348
|
+
# @return [nil] +void+
|
349
|
+
#
|
350
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
351
|
+
attach_function :tls_config_set_verify_depth, [:pointer, :int], :void
|
352
|
+
# @!endgroup
|
353
|
+
|
354
|
+
##
|
355
|
+
# @!group Server configuration
|
356
|
+
# @!method tls_config_clear_keys(config)
|
357
|
+
#
|
358
|
+
# Clear secret keys from memory
|
359
|
+
#
|
360
|
+
# +tls_config_clear_keys+ clears any secret keys from memory.
|
361
|
+
#
|
362
|
+
# This applies to servers.
|
363
|
+
#
|
364
|
+
# @param config [FFI::Pointer] the TLS config
|
365
|
+
# @return [nil] +void+
|
366
|
+
#
|
367
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
368
|
+
attach_function :tls_config_clear_keys, [:pointer], :void
|
369
|
+
# @!endgroup
|
370
|
+
|
371
|
+
##
|
372
|
+
# @!group Client configuration
|
373
|
+
# @!method tls_config_insecure_noverifycert(config)
|
374
|
+
#
|
375
|
+
# Insecurely disable certficate verification
|
376
|
+
#
|
377
|
+
# +tls_config_insecure_noverifycert+ disables certificate verification. Be
|
378
|
+
# extremely careful when using this option.
|
379
|
+
#
|
380
|
+
# This applies to clients.
|
381
|
+
#
|
382
|
+
# @param config [FFI::Pointer] the TLS config
|
383
|
+
# @return [nil] +void+
|
384
|
+
#
|
385
|
+
# @note This method should not be used, and is therefore untested.
|
386
|
+
attach_function :tls_config_insecure_noverifycert, [:pointer], :void
|
387
|
+
|
388
|
+
##
|
389
|
+
# @!method tls_config_insecure_noverifyname(config)
|
390
|
+
#
|
391
|
+
# Insecurely disable server name verification
|
392
|
+
#
|
393
|
+
# +tls_config_insecure_noverifyname+ disables server name verification. Be
|
394
|
+
# careful when using this option.
|
395
|
+
#
|
396
|
+
# This applies to clients.
|
397
|
+
#
|
398
|
+
# @param config [FFI::Pointer] the TLS config
|
399
|
+
# @return [nil] +void+
|
400
|
+
#
|
401
|
+
# @note This method should not be used, and is therefore untested.
|
402
|
+
attach_function :tls_config_insecure_noverifyname, [:pointer], :void
|
403
|
+
|
404
|
+
##
|
405
|
+
# @!method tls_config_verify(config)
|
406
|
+
#
|
407
|
+
# Restore default verification
|
408
|
+
#
|
409
|
+
# +tls_config_verify+ reenables server name and certificate verification.
|
410
|
+
#
|
411
|
+
# This applies to clients.
|
412
|
+
#
|
413
|
+
# @param config [FFI::Pointer] the TLS config
|
414
|
+
# @return [nil] +void+
|
415
|
+
#
|
416
|
+
# @note While this method is fine, getting to this state is not. This
|
417
|
+
# method is therefore untested.
|
418
|
+
attach_function :tls_config_verify, [:pointer], :void
|
419
|
+
# @!endgroup
|
420
|
+
|
421
|
+
##
|
422
|
+
# @!group Client and server configuration
|
423
|
+
# @!method tls_load_file(file, len, password)
|
424
|
+
#
|
425
|
+
# Load a certificate or key
|
426
|
+
#
|
427
|
+
# +tls_load_file+ loads a certificate or key from disk into memory to be
|
428
|
+
# loaded with {#tls_config_set_ca_mem}, {#tls_config_set_cert_mem} or
|
429
|
+
# {#tls_config_set_key_mem}. A private key will be decrypted if the
|
430
|
+
# optional +password+ argument is specified.
|
431
|
+
#
|
432
|
+
# This applies to clients and servers.
|
433
|
+
#
|
434
|
+
# @param file [String] the absolute filename
|
435
|
+
# @param len [FFI::Pointer] pointer to a +size_t+ storing the number of
|
436
|
+
# bytes loaded
|
437
|
+
# @param password [String] either +FFI::Pointer::NULL+ or the password for
|
438
|
+
# the private key
|
439
|
+
# @return [FFI::Pointer] pointer to +uint8_t+, the key
|
440
|
+
#
|
441
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
442
|
+
attach_function :tls_load_file, [:string, :pointer, :string], :pointer
|
443
|
+
# @!endgroup
|
444
|
+
|
445
|
+
##
|
446
|
+
# @!group Create, prepare, and free a connection context
|
447
|
+
# @!method tls_client()
|
448
|
+
#
|
449
|
+
# Create a client context
|
450
|
+
#
|
451
|
+
# A tls connection is represented as a context. A new context is created by
|
452
|
+
# either the {#tls_client} or {#tls_server} functions. The context can then
|
453
|
+
# be configured with the function {#tls_configure}. The same +tls_config+
|
454
|
+
# object can be used to configure multiple contexts.
|
455
|
+
#
|
456
|
+
# +tls_client+ creates a new tls context for client connections.
|
457
|
+
#
|
458
|
+
# @return [FFI::Pointer] a +tls+ context or +FFI::Pointer::NULL+ on
|
459
|
+
# failure. Check using +#null?+.
|
460
|
+
attach_function :tls_client, [], :pointer
|
461
|
+
|
462
|
+
##
|
463
|
+
# @!method tls_server()
|
464
|
+
#
|
465
|
+
# Create a server context
|
466
|
+
#
|
467
|
+
# A tls connection is represented as a context. A new context is created by
|
468
|
+
# either the {#tls_client} or {#tls_server} functions. The context can then
|
469
|
+
# be configured with the function {#tls_configure}. The same +tls_config+
|
470
|
+
# object can be used to configure multiple contexts.
|
471
|
+
#
|
472
|
+
# +tls_server+ creates a new tls context for server connections.
|
473
|
+
#
|
474
|
+
# @return [FFI::Pointer] a +tls+ context or +FFI::Pointer::NULL+ on
|
475
|
+
# failure. Check using +#null?+.
|
476
|
+
attach_function :tls_server, [], :pointer
|
477
|
+
|
478
|
+
##
|
479
|
+
# @!method tls_configure(ctx, config)
|
480
|
+
#
|
481
|
+
# Apply the configuration to the context
|
482
|
+
#
|
483
|
+
# +tls_configure+ readies a tls context for use by applying the
|
484
|
+
# configuration options.
|
485
|
+
#
|
486
|
+
# The same +tls_config+ object can be used to configure multiple contexts.
|
487
|
+
#
|
488
|
+
# @param ctx [FFI::Pointer] a TLS context
|
489
|
+
# @param config [FFI::Pointer] the TLS config
|
490
|
+
# @return [Fixnum] 0 on success, -1 on error
|
491
|
+
attach_function :tls_configure, [:pointer, :pointer], :int
|
492
|
+
|
493
|
+
##
|
494
|
+
# @!method tls_reset(ctx)
|
495
|
+
#
|
496
|
+
# Reset a context to a newly-initialized state
|
497
|
+
#
|
498
|
+
# @param ctx [FFI::Pointer] a TLS context
|
499
|
+
# @return [nil] +void+
|
500
|
+
#
|
501
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
502
|
+
attach_function :tls_reset, [:pointer], :void
|
503
|
+
|
504
|
+
##
|
505
|
+
# @!method tls_close(ctx)
|
506
|
+
#
|
507
|
+
# Close the TLS connection
|
508
|
+
#
|
509
|
+
# After use, a +tls+ context should be closed with +tls_close+, and then
|
510
|
+
# freed by calling {#tls_free}. When no more contexts are to be created, the
|
511
|
+
# +tls_config+ object should be freed by calling {#tls_config_free}.
|
512
|
+
#
|
513
|
+
# +tls_close+ closes a connection after use. If the connection was
|
514
|
+
# established using +#tls_connect_fds+, only the TLS layer will be closed
|
515
|
+
# and it is the caller's responsibility to close the file descriptors.
|
516
|
+
#
|
517
|
+
# @param ctx [FFI::Pointer] a TLS context
|
518
|
+
# @return [Fixnum] 0 on success, -1 on error, {TLS_READ_AGAIN} if a read
|
519
|
+
# opreation is necessary to continue, {TLS_WRITE_AGAIN} if a write
|
520
|
+
# operation is necessary to continue. In case of a {TLS_READ_AGAIN} or
|
521
|
+
# {TLS_WRITE_AGAIN}, the caller should repeat the call.
|
522
|
+
attach_function :tls_close, [:pointer], :int
|
523
|
+
|
524
|
+
##
|
525
|
+
# @!method tls_free(ctx)
|
526
|
+
#
|
527
|
+
# Free memory for the TLS context
|
528
|
+
#
|
529
|
+
# After use, a +tls+ context should be closed with +tls_close+, and then
|
530
|
+
# freed by calling {#tls_free}. When no more contexts are to be created, the
|
531
|
+
# +tls_config+ object should be freed by calling {#tls_config_free}.
|
532
|
+
#
|
533
|
+
# +tls_free+ frees a tls context after use.
|
534
|
+
#
|
535
|
+
# @param ctx [FFI::Pointer] a TLS context
|
536
|
+
attach_function :tls_free, [:pointer], :void
|
537
|
+
# @!endgroup
|
538
|
+
|
539
|
+
##
|
540
|
+
# @!group Initiate a connection and perform I/O
|
541
|
+
# @!method tls_connect(ctx, host, port)
|
542
|
+
#
|
543
|
+
# Open a TLS connection to the +host+ and +port+
|
544
|
+
#
|
545
|
+
# A client connection is initiated after configuration by calling
|
546
|
+
# +tls_connect+. This function will create a new socket, connect to the
|
547
|
+
# specified host and port, and then establish a secure connection.
|
548
|
+
#
|
549
|
+
# +tls_connect+ connects a client context to the server named by host. The
|
550
|
+
# port may be numeric or a service name. If it is +FFI::Pointer::NULL+ then
|
551
|
+
# a host of the format "hostname:port" is permitted.
|
552
|
+
#
|
553
|
+
# @param ctx [FFI::Pointer] a TLS context
|
554
|
+
# @param host [String] the server to connect to, as an IPv4 address, an
|
555
|
+
# IPv6 address, anything that can be resolved by +getaddrinfo+.
|
556
|
+
# @param port [String] the port as a number, service name, or NULL pointer.
|
557
|
+
# If it is a NULL pointer, the host is parsed for the port number.
|
558
|
+
# @return [Fixnum] 0 on success, -1 on error, {TLS_READ_AGAIN} if a read
|
559
|
+
# opreation is necessary to continue, {TLS_WRITE_AGAIN} if a write
|
560
|
+
# operation is necessary to continue. In case of a {TLS_READ_AGAIN} or
|
561
|
+
# {TLS_WRITE_AGAIN}, the caller should repeat the call.
|
562
|
+
attach_function :tls_connect, [:pointer, :string, :string], :int
|
563
|
+
|
564
|
+
##
|
565
|
+
# @!method tls_connect_fds(ctx, fd_read, fd_write, servername)
|
566
|
+
#
|
567
|
+
# Upgrade an existing connection to secure
|
568
|
+
#
|
569
|
+
# +tls_connect_fds+ connects a client context to a pair of existing file
|
570
|
+
# descriptors.
|
571
|
+
#
|
572
|
+
# @param ctx [FFI::Pointer] a TLS context
|
573
|
+
# @param fd_read [Fixnum] the read file descriptor
|
574
|
+
# @param fd_write [Fixnum] the write file descriptor
|
575
|
+
# @param servername [String] the name of the server, as matched in the
|
576
|
+
# certificate
|
577
|
+
# @return [Fixnum] 0 on success, -1 on error, {TLS_READ_AGAIN} if a read
|
578
|
+
# opreation is necessary to continue, {TLS_WRITE_AGAIN} if a write
|
579
|
+
# operation is necessary to continue. In case of a {TLS_READ_AGAIN} or
|
580
|
+
# {TLS_WRITE_AGAIN}, the caller should repeat the call.
|
581
|
+
#
|
582
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
583
|
+
attach_function :tls_connect_fds, [:pointer, :int, :int, :string], :int
|
584
|
+
|
585
|
+
##
|
586
|
+
# @!method tls_connect_servername(ctx, host, port, servername)
|
587
|
+
#
|
588
|
+
# Open a TLS connection to the +host+ and +port+, verifying against
|
589
|
+
# +servername+
|
590
|
+
#
|
591
|
+
# The +tls_connect_servername+ function has the same behaviour as
|
592
|
+
# {#tls_connect}, however the name to use for verification is explicitly
|
593
|
+
# provided, rather than being inferred from the host value.
|
594
|
+
#
|
595
|
+
# @param ctx [FFI::Pointer] a TLS context
|
596
|
+
# @param host [String] the server to connect to, as an IPv4 address, an
|
597
|
+
# IPv6 address, anything that can be resolved by +getaddrinfo+.
|
598
|
+
# @param port [String] the port as a number, service name, or NULL pointer.
|
599
|
+
# If it is a NULL pointer, the host is parsed for the port number.
|
600
|
+
# @param servername [String] the server name to verify the certificate
|
601
|
+
# against
|
602
|
+
# @return [Fixnum] 0 on success, -1 on error, {TLS_READ_AGAIN} if a read
|
603
|
+
# opreation is necessary to continue, {TLS_WRITE_AGAIN} if a write
|
604
|
+
# operation is necessary to continue. In case of a {TLS_READ_AGAIN} or
|
605
|
+
# {TLS_WRITE_AGAIN}, the caller should repeat the call.
|
606
|
+
#
|
607
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
608
|
+
attach_function :tls_connect_servername,
|
609
|
+
[:pointer, :string, :string, :string], :int
|
610
|
+
|
611
|
+
##
|
612
|
+
# @!method tls_connect_socket(ctx, s, servername)
|
613
|
+
#
|
614
|
+
# Upgrade an existing socket
|
615
|
+
#
|
616
|
+
# +tls_connect_socket+ connects a client context to an already established
|
617
|
+
# socket connection.
|
618
|
+
#
|
619
|
+
# @param ctx [FFI::Pointer] a TLS context
|
620
|
+
# @param s [Fixnum] the file descriptor for a socket
|
621
|
+
# @param servername [String] the server name to verify the certificate
|
622
|
+
# against
|
623
|
+
# @return [Fixnum] 0 on success, -1 on error, {TLS_READ_AGAIN} if a read
|
624
|
+
# opreation is necessary to continue, {TLS_WRITE_AGAIN} if a write
|
625
|
+
# operation is necessary to continue. In case of a {TLS_READ_AGAIN} or
|
626
|
+
# {TLS_WRITE_AGAIN}, the caller should repeat the call.
|
627
|
+
#
|
628
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
629
|
+
attach_function :tls_connect_socket, [:pointer, :int, :string], :int
|
630
|
+
|
631
|
+
##
|
632
|
+
# @!method tls_accept_fds(ctx, cctx, fd_read, fd_write)
|
633
|
+
#
|
634
|
+
# Perform the TLS handshake on an established pair of file descriptors
|
635
|
+
#
|
636
|
+
# +tls_accept_fds+ creates a new context suitable for reading and writing
|
637
|
+
# on an existing pair of file descriptors and returns it in +*cctx+. A
|
638
|
+
# configured server context should be passed in +ctx+ and +*cctx+ should be
|
639
|
+
# initialized to NULL.
|
640
|
+
#
|
641
|
+
# @see #tls_accept_socket
|
642
|
+
#
|
643
|
+
# @param ctx [FFI::Pointer] a TLS context
|
644
|
+
# @param cctx [FFI::Pointer] a reference to a TLS context
|
645
|
+
# @param fd_read [Fixnum] the read file descriptor
|
646
|
+
# @param fd_write [Fixnum] the write file descriptor
|
647
|
+
# @return [Fixnum] 0 on success, -1 on error, {TLS_READ_AGAIN} if a read
|
648
|
+
# opreation is necessary to continue, {TLS_WRITE_AGAIN} if a write
|
649
|
+
# operation is necessary to continue. In case of a {TLS_READ_AGAIN} or
|
650
|
+
# {TLS_WRITE_AGAIN}, the caller should repeat the call.
|
651
|
+
#
|
652
|
+
# @todo This is untested. Please {https://github.com/mike-burns/libtls.rb#contributing contribute a patch}.
|
653
|
+
attach_function :tls_accept_fds, [:pointer, :pointer, :int, :int], :int
|
654
|
+
|
655
|
+
##
|
656
|
+
# @!method tls_accept_socket(ctx, cctx, socket)
|
657
|
+
#
|
658
|
+
# Perform the TLS handshake on an established socket
|
659
|
+
#
|
660
|
+
# A server can accept a new client connection by calling
|
661
|
+
# +tls_accept_socket+ on an already established socket connection.
|
662
|
+
#
|
663
|
+
# +tls_accept_socket+ creates a new context suitable for reading and
|
664
|
+
# writing on an already established socket connection and returns it in
|
665
|
+
# +*cctx+. A configured server context should be passed in +ctx+ and
|
666
|
+
# +*cctx+ should be initialized to +NULL+.
|
667
|
+
#
|
668
|
+
# The pattern looks like this:
|
669
|
+
#
|
670
|
+
# cctx_ptr = FFI::MemoryPointer.new(:pointer)
|
671
|
+
# LibTLS::Raw.tls_accept_socket(ctx, cctx_ptr, client_sock.fileno)
|
672
|
+
# cctx = cctx_ptr.read_pointer
|
673
|
+
#
|
674
|
+
# @param ctx [FFI::Pointer] a TLS context
|
675
|
+
# @param cctx [FFI::Pointer] a reference to a TLS context
|
676
|
+
# @param socket [Int] the file descriptor of an established socket that is
|
677
|
+
# connected to a client. Use +Socket#fileno+ to get the file descriptor
|
678
|
+
# of a Socket instance.
|
679
|
+
# @return [Fixnum] 0 on success, -1 on error, {TLS_READ_AGAIN} if a read
|
680
|
+
# opreation is necessary to continue, {TLS_WRITE_AGAIN} if a write
|
681
|
+
# operation is necessary to continue. In case of a {TLS_READ_AGAIN} or
|
682
|
+
# {TLS_WRITE_AGAIN}, the caller should repeat the call.
|
683
|
+
attach_function :tls_accept_socket, [:pointer, :pointer, :int], :int
|
684
|
+
|
685
|
+
##
|
686
|
+
# @!method tls_read(ctx, buf, buflen, outlen)
|
687
|
+
#
|
688
|
+
# Read from the socket
|
689
|
+
#
|
690
|
+
# +tls_read+ reads +buflen+ bytes of data from the socket into +buf+. The
|
691
|
+
# amount of data read is returned in +outlen+.
|
692
|
+
#
|
693
|
+
# The pattern is as follows:
|
694
|
+
#
|
695
|
+
#
|
696
|
+
# READ_LEN = 1024
|
697
|
+
# FFI::MemoryPointer.new(:size_t) do |outlen|
|
698
|
+
# FFI::MemoryPointer.new(:uchar, READ_LEN, true) do |buf|
|
699
|
+
# loop do
|
700
|
+
# if LibTLS::Raw.tls_read(ctx, buf, READ_LEN, outlen) < 0
|
701
|
+
# raise LibTLS::CError, "tls_read: #{LibTLS::Raw.tls_error(ctx)}"
|
702
|
+
# end
|
703
|
+
#
|
704
|
+
# do_something_with( buf.get_string(0, outlen.get_int(0)) )
|
705
|
+
#
|
706
|
+
# if READ_LEN > outlen.get_int(0)
|
707
|
+
# break
|
708
|
+
# end
|
709
|
+
# end
|
710
|
+
# end
|
711
|
+
# end
|
712
|
+
#
|
713
|
+
# @param ctx [FFI::Pointer] a TLS context
|
714
|
+
# @param buf [FFI::Pointer] allocated memory for a +buflen+ number of
|
715
|
+
# +uchars+
|
716
|
+
# @param buflen [Fixnum] the number of bytes to read
|
717
|
+
# @param outlen [FFI::Pointer] the number of bytes read
|
718
|
+
# @return [Fixnum] 0 on success, -1 on error, {TLS_READ_AGAIN} if a read
|
719
|
+
# opreation is necessary to continue, {TLS_WRITE_AGAIN} if a write
|
720
|
+
# operation is necessary to continue. In the case of a {TLS_READ_AGAIN} the
|
721
|
+
# caller should repeat the call. In the case of a {TLS_WRITE_AGAIN}, the
|
722
|
+
# caller should call {#tls_write}.
|
723
|
+
attach_function :tls_read, [:pointer, :pointer, :size_t, :pointer], :int
|
724
|
+
|
725
|
+
##
|
726
|
+
# @!method tls_write(ctx, buf, buflen, outlen)
|
727
|
+
#
|
728
|
+
# Write data to the socket
|
729
|
+
#
|
730
|
+
# +tls_write+ writes +buflen+ bytes of data from +buf+ to the socket. The
|
731
|
+
# amount of data written is returned in +outlen+.
|
732
|
+
#
|
733
|
+
# The pattern is as follows:
|
734
|
+
#
|
735
|
+
# STR = "HELLO\r\n"
|
736
|
+
#
|
737
|
+
# FFI::MemoryPointer.new(:size_t) do |outlen|
|
738
|
+
# FFI::MemoryPointer.new(:uchar, STR.length + 1) do |str_ptr|
|
739
|
+
# str_ptr.put_string(0, STR)
|
740
|
+
#
|
741
|
+
# if LibTLS::Raw.tls_write(ctx, str_ptr, STR.length, outlen) < 0
|
742
|
+
# raise LibTLS::CError, "tls_write: #{LibTLS::Raw.tls_error(ctx)}"
|
743
|
+
# end
|
744
|
+
# end
|
745
|
+
# end
|
746
|
+
#
|
747
|
+
# @param ctx [FFI::Pointer] a TLS context
|
748
|
+
# @param buf [FFI::Pointer] a pointer to the string of +uchar+s
|
749
|
+
# @param buflen [Fixnum] the number of bytes to write
|
750
|
+
# @param outlen [FFI::Pointer] the number of bytes written
|
751
|
+
# @return [Fixnum] 0 on success, -1 on error, {TLS_READ_AGAIN} if a read
|
752
|
+
# opreation is necessary to continue, {TLS_WRITE_AGAIN} if a write
|
753
|
+
# operation is necessary to continue. In the case of a {TLS_READ_AGAIN} the
|
754
|
+
# caller should call {#tls_read}. In the case of a {TLS_WRITE_AGAIN}, the
|
755
|
+
# caller should repeat the call.
|
756
|
+
attach_function :tls_write, [:pointer, :pointer, :size_t, :pointer], :int
|
757
|
+
# @!endgroup
|
758
|
+
end
|
759
|
+
end
|