iodine 0.7.60 → 0.8.0

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.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.clang-format +15 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -3
  5. data/CHANGELOG.md +132 -12
  6. data/Gemfile +5 -7
  7. data/LICENSE.txt +1 -1
  8. data/README.md +147 -671
  9. data/Rakefile +9 -16
  10. data/examples/chatroom.nru +9 -0
  11. data/examples/client +74 -0
  12. data/examples/config-hello.nru +9 -0
  13. data/examples/config-hello.ru +11 -0
  14. data/examples/config-hijack.ru +13 -0
  15. data/examples/config-shootout.ru +43 -0
  16. data/examples/config.nru +120 -0
  17. data/examples/config.rb +37 -0
  18. data/examples/config.ru +42 -53
  19. data/examples/fake +24 -0
  20. data/examples/hello_neorack +137 -0
  21. data/examples/routes.nru +29 -0
  22. data/examples/routes.ru +42 -0
  23. data/exe/iodine +218 -258
  24. data/ext/iodine/extconf.rb +27 -100
  25. data/ext/iodine/fio-stl.h +128336 -0
  26. data/ext/iodine/fio-stl.md +26396 -0
  27. data/ext/iodine/iodine.c +213 -1393
  28. data/ext/iodine/iodine.h +250 -55
  29. data/ext/iodine/iodine_arg_helper.h +329 -0
  30. data/ext/iodine/iodine_caller.h +234 -25
  31. data/ext/iodine/iodine_cli.h +434 -0
  32. data/ext/iodine/iodine_compression.h +312 -0
  33. data/ext/iodine/iodine_connection.h +3484 -34
  34. data/ext/iodine/iodine_core.h +363 -0
  35. data/ext/iodine/iodine_crypto.h +1441 -0
  36. data/ext/iodine/iodine_defer.h +225 -4
  37. data/ext/iodine/iodine_json.h +738 -4
  38. data/ext/iodine/iodine_listener.h +263 -0
  39. data/ext/iodine/iodine_log.h +297 -0
  40. data/ext/iodine/iodine_minimap.h +543 -0
  41. data/ext/iodine/iodine_mustache.h +461 -4
  42. data/ext/iodine/iodine_pubsub_eng.h +729 -0
  43. data/ext/iodine/iodine_pubsub_history.h +547 -0
  44. data/ext/iodine/iodine_pubsub_msg.h +228 -0
  45. data/ext/iodine/iodine_redis.h +322 -0
  46. data/ext/iodine/iodine_store.h +418 -16
  47. data/ext/iodine/iodine_threads.h +279 -0
  48. data/ext/iodine/iodine_tls.h +515 -6
  49. data/ext/iodine/iodine_utils.h +923 -0
  50. data/iodine.gemspec +37 -42
  51. data/lib/iodine/benchmark.rb +1690 -0
  52. data/lib/iodine/documentation.rb +3085 -0
  53. data/lib/iodine/version.rb +17 -1
  54. data/lib/iodine.rb +54 -255
  55. data/logo.svg +36 -0
  56. data/mustache.md +55 -0
  57. data/sig/iodine.rbs +4 -0
  58. metadata +58 -184
  59. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  60. data/.github/workflows/ruby.yml +0 -42
  61. data/.gitignore +0 -21
  62. data/LIMITS.md +0 -41
  63. data/SPEC-PubSub-Draft.md +0 -159
  64. data/SPEC-WebSocket-Draft.md +0 -239
  65. data/bin/console +0 -22
  66. data/bin/info.md +0 -353
  67. data/bin/mustache_bench.rb +0 -100
  68. data/bin/poc/Gemfile.lock +0 -23
  69. data/bin/poc/README.md +0 -37
  70. data/bin/poc/config.ru +0 -66
  71. data/bin/poc/gemfile +0 -1
  72. data/bin/poc/www/index.html +0 -57
  73. data/examples/async_task.ru +0 -92
  74. data/examples/bates/README.md +0 -3
  75. data/examples/bates/config.ru +0 -342
  76. data/examples/bates/david+bold.pdf +0 -0
  77. data/examples/bates/public/drop-pdf.png +0 -0
  78. data/examples/bates/public/index.html +0 -600
  79. data/examples/echo.ru +0 -59
  80. data/examples/etag.ru +0 -16
  81. data/examples/hello.ru +0 -29
  82. data/examples/pubsub_engine.ru +0 -81
  83. data/examples/rack3.ru +0 -12
  84. data/examples/redis.ru +0 -70
  85. data/examples/shootout.ru +0 -73
  86. data/examples/sub-protocols.ru +0 -90
  87. data/examples/tcp_client.rb +0 -66
  88. data/examples/x-sendfile.ru +0 -14
  89. data/ext/iodine/fio.c +0 -11995
  90. data/ext/iodine/fio.h +0 -6371
  91. data/ext/iodine/fio_cli.c +0 -431
  92. data/ext/iodine/fio_cli.h +0 -189
  93. data/ext/iodine/fio_json_parser.h +0 -688
  94. data/ext/iodine/fio_siphash.c +0 -157
  95. data/ext/iodine/fio_siphash.h +0 -37
  96. data/ext/iodine/fio_tls.h +0 -129
  97. data/ext/iodine/fio_tls_missing.c +0 -649
  98. data/ext/iodine/fio_tls_openssl.c +0 -1106
  99. data/ext/iodine/fio_tmpfile.h +0 -50
  100. data/ext/iodine/fiobj.h +0 -44
  101. data/ext/iodine/fiobj4fio.h +0 -21
  102. data/ext/iodine/fiobj_ary.c +0 -333
  103. data/ext/iodine/fiobj_ary.h +0 -139
  104. data/ext/iodine/fiobj_data.c +0 -1187
  105. data/ext/iodine/fiobj_data.h +0 -167
  106. data/ext/iodine/fiobj_hash.c +0 -409
  107. data/ext/iodine/fiobj_hash.h +0 -176
  108. data/ext/iodine/fiobj_json.c +0 -622
  109. data/ext/iodine/fiobj_json.h +0 -68
  110. data/ext/iodine/fiobj_mem.h +0 -71
  111. data/ext/iodine/fiobj_mustache.c +0 -317
  112. data/ext/iodine/fiobj_mustache.h +0 -62
  113. data/ext/iodine/fiobj_numbers.c +0 -344
  114. data/ext/iodine/fiobj_numbers.h +0 -127
  115. data/ext/iodine/fiobj_str.c +0 -433
  116. data/ext/iodine/fiobj_str.h +0 -172
  117. data/ext/iodine/fiobject.c +0 -620
  118. data/ext/iodine/fiobject.h +0 -655
  119. data/ext/iodine/hpack.h +0 -1923
  120. data/ext/iodine/http.c +0 -2743
  121. data/ext/iodine/http.h +0 -1002
  122. data/ext/iodine/http1.c +0 -910
  123. data/ext/iodine/http1.h +0 -29
  124. data/ext/iodine/http1_parser.h +0 -878
  125. data/ext/iodine/http_internal.c +0 -1279
  126. data/ext/iodine/http_internal.h +0 -237
  127. data/ext/iodine/http_mime_parser.h +0 -350
  128. data/ext/iodine/iodine_caller.c +0 -230
  129. data/ext/iodine/iodine_connection.c +0 -941
  130. data/ext/iodine/iodine_defer.c +0 -420
  131. data/ext/iodine/iodine_fiobj2rb.h +0 -120
  132. data/ext/iodine/iodine_helpers.c +0 -282
  133. data/ext/iodine/iodine_helpers.h +0 -12
  134. data/ext/iodine/iodine_http.c +0 -1181
  135. data/ext/iodine/iodine_http.h +0 -23
  136. data/ext/iodine/iodine_json.c +0 -302
  137. data/ext/iodine/iodine_mustache.c +0 -567
  138. data/ext/iodine/iodine_pubsub.c +0 -580
  139. data/ext/iodine/iodine_pubsub.h +0 -26
  140. data/ext/iodine/iodine_rack_io.c +0 -273
  141. data/ext/iodine/iodine_rack_io.h +0 -20
  142. data/ext/iodine/iodine_store.c +0 -148
  143. data/ext/iodine/iodine_tcp.c +0 -346
  144. data/ext/iodine/iodine_tcp.h +0 -13
  145. data/ext/iodine/iodine_tls.c +0 -261
  146. data/ext/iodine/mustache_parser.h +0 -1546
  147. data/ext/iodine/redis_engine.c +0 -957
  148. data/ext/iodine/redis_engine.h +0 -79
  149. data/ext/iodine/resp_parser.h +0 -317
  150. data/ext/iodine/websocket_parser.h +0 -506
  151. data/ext/iodine/websockets.c +0 -743
  152. data/ext/iodine/websockets.h +0 -185
  153. data/lib/iodine/connection.rb +0 -61
  154. data/lib/iodine/json.rb +0 -42
  155. data/lib/iodine/mustache.rb +0 -113
  156. data/lib/iodine/pubsub.rb +0 -55
  157. data/lib/iodine/rack_utils.rb +0 -43
  158. data/lib/iodine/tls.rb +0 -16
  159. data/lib/rack/handler/iodine.rb +0 -33
  160. data/logo.png +0 -0
@@ -1,13 +1,522 @@
1
- #ifndef H_IODINE_TLS_H
2
- #define H_IODINE_TLS_H
1
+ #ifndef H___IODINE_TLS___H
2
+ /** ****************************************************************************
3
+ Iodine::TLS - TLS/SSL Context Management
3
4
 
5
+ This module provides TLS/SSL support for Iodine connections, wrapping the
6
+ facil.io TLS functionality and exposing it to Ruby.
7
+
8
+ ## TLS Backend Options
9
+
10
+ Iodine supports two TLS backends:
11
+
12
+ 1. **Embedded TLS 1.3** (`:iodine`) - Always available via fio-stl.h
13
+ - Lightweight, no external dependencies
14
+ - TLS 1.3 only (modern, secure)
15
+
16
+ 2. **OpenSSL** (`:openssl`) - Available if compiled with OpenSSL support
17
+ - Full TLS version support (1.0, 1.1, 1.2, 1.3)
18
+ - Broader compatibility with older clients
19
+
20
+ ## Ruby API
21
+
22
+ ### Iodine::TLS Class
23
+
24
+ tls = Iodine::TLS.new
25
+ tls.add_cert(name: "example.com")
26
+ tls.add_cert(cert: "cert.pem", key: "key.pem")
27
+ tls.add_cert(cert: "cert.pem", key: "key.pem", password: ENV['TLS_PASS'])
28
+
29
+ ### Class Methods
30
+
31
+ - `Iodine::TLS.default` - Returns the current default TLS backend (:iodine or
32
+ :openssl)
33
+ - `Iodine::TLS.default = :iodine` - Sets the default TLS backend
34
+
35
+ ### Instance Methods
36
+
37
+ - `add_cert(name:, cert:, key:, password:)` - Adds a certificate to the TLS
38
+ context. If only `name` is provided, a self-signed certificate is generated.
39
+ - `trust(path = nil)` - Adds a public certificate (PEM file) to the trust
40
+ list, enabling peer (client) certificate authentication. Without a path,
41
+ trusts the system's default trust registry.
42
+
43
+ ### Constants
44
+
45
+ - `Iodine::TLS::SUPPORTED` - Always `true` (embedded TLS always available)
46
+ - `Iodine::TLS::OPENSSL_AVAILABLE` - `true` if OpenSSL support was compiled in
47
+ - `Iodine::TLS::EMBEDDED_AVAILABLE` - Always `true`
48
+
49
+ ## Usage with Iodine.listen
50
+
51
+ # Using default TLS (auto-generates self-signed cert)
52
+ Iodine.listen(url: "https://0.0.0.0:3000", handler: app)
53
+
54
+ # Using custom TLS context
55
+ tls = Iodine::TLS.new
56
+ tls.add_cert(cert: "server.pem", key: "server.key")
57
+ Iodine.listen(url: "https://0.0.0.0:3000", handler: app, tls: tls)
58
+
59
+ # Specifying TLS backend
60
+ Iodine.listen(url: "https://0.0.0.0:3000", handler: app, tls_io: :openssl)
61
+
62
+ ***************************************************************************** */
63
+ #define H___IODINE_TLS___H
4
64
  #include "iodine.h"
5
65
 
6
- #include "fio_tls.h"
66
+ static VALUE iodine_rb_TLS_CERTIFICATE;
67
+
68
+ /* *****************************************************************************
69
+ TLS Wrapper - Internal Implementation
70
+ ***************************************************************************** */
71
+
72
+ static void iodine_tls_free(void *ptr_) {
73
+ fio_io_tls_s **p = (fio_io_tls_s **)ptr_;
74
+ fio_io_tls_free(*p);
75
+ }
76
+
77
+ static const rb_data_type_t IODINE_TLS_DATA_TYPE = {
78
+ .wrap_struct_name = "IodineTLS",
79
+ .function =
80
+ {
81
+ .dfree = iodine_tls_free,
82
+ },
83
+ .data = NULL,
84
+ };
85
+
86
+ static VALUE iodine_tls_alloc(VALUE klass) {
87
+ fio_io_tls_s **tls;
88
+ VALUE o =
89
+ TypedData_Make_Struct(klass, fio_io_tls_s *, &IODINE_TLS_DATA_TYPE, tls);
90
+ *tls = fio_io_tls_new();
91
+ if (!*tls)
92
+ rb_raise(rb_eNoMemError, "Couldn't allocate resources for TLS object");
93
+ return o;
94
+ }
95
+
96
+ static fio_io_tls_s *iodine_tls_get(VALUE self) {
97
+ fio_io_tls_s **p;
98
+ TypedData_Get_Struct(self, fio_io_tls_s *, &IODINE_TLS_DATA_TYPE, p);
99
+ return *p;
100
+ }
101
+
102
+ /* *****************************************************************************
103
+ The Functions to be Wrapped
104
+ ***************************************************************************** */
105
+
106
+ #if 0
107
+ /**
108
+ * Adds a certificate a new SSL/TLS context / settings object (SNI support).
109
+ *
110
+ * fio_io_tls_cert_add(tls, "www.example.com",
111
+ * "public_key.pem",
112
+ * "private_key.pem", NULL );
113
+ *
114
+ * NOTE: Except for the `tls` and `server_name` arguments, all arguments might
115
+ * be `NULL`, which a context builder (`fio_io_functions_s`) should treat as a
116
+ * request for a self-signed certificate. It may be silently ignored.
117
+ */
118
+ SFUNC fio_io_tls_s *fio_io_tls_cert_add(fio_io_tls_s *,
119
+ const char *server_name,
120
+ const char *public_cert_file,
121
+ const char *private_key_file,
122
+ const char *pk_password);
123
+
124
+ /**
125
+ * Adds an ALPN protocol callback to the SSL/TLS context.
126
+ *
127
+ * The first protocol added will act as the default protocol to be selected.
128
+ *
129
+ * A `NULL` protocol name will be silently ignored.
130
+ *
131
+ * A `NULL` callback (`on_selected`) will be silently replaced with a no-op.
132
+ */
133
+ SFUNC fio_io_tls_s *fio_io_tls_alpn_add(fio_io_tls_s *tls,
134
+ const char *protocol_name,
135
+ void (*on_selected)(fio_s *));
136
+
137
+ /** Calls the `on_selected` callback for the `fio_io_tls_s` object. */
138
+ SFUNC int fio_io_tls_alpn_select(fio_io_tls_s *tls,
139
+ const char *protocol_name,
140
+ size_t name_length,
141
+ fio_s *);
142
+
143
+ /**
144
+ * Adds a certificate to the "trust" list, which automatically adds a peer
145
+ * verification requirement.
146
+ *
147
+ * If `public_cert_file` is `NULL`, implementation is expected to add the
148
+ * system's default trust registry.
149
+ *
150
+ * Note: when the `fio_io_tls_s` object is used for server connections, this should
151
+ * limit connections to clients that connect using a trusted certificate.
152
+ *
153
+ * fio_io_tls_trust_add(tls, "google-ca.pem" );
154
+ */
155
+ SFUNC fio_io_tls_s *fio_io_tls_trust_add(fio_io_tls_s *, const char *public_cert_file);
156
+
157
+ /**
158
+ * Returns the number of `fio_io_tls_cert_add` instructions.
159
+ *
160
+ * This could be used when deciding if to add a NULL instruction (self-signed).
161
+ *
162
+ * If `fio_io_tls_cert_add` was never called, zero (0) is returned.
163
+ */
164
+ SFUNC uintptr_t fio_io_tls_cert_count(fio_io_tls_s *tls);
165
+
166
+ /**
167
+ * Returns the number of registered ALPN protocol names.
168
+ *
169
+ * This could be used when deciding if protocol selection should be delegated to
170
+ * the ALPN mechanism, or whether a protocol should be immediately assigned.
171
+ *
172
+ * If no ALPN protocols are registered, zero (0) is returned.
173
+ */
174
+ SFUNC uintptr_t fio_io_tls_alpn_count(fio_io_tls_s *tls);
175
+
176
+ /**
177
+ * Returns the number of `fio_io_tls_trust_add` instructions.
178
+ *
179
+ * This could be used when deciding if to disable peer verification or not.
180
+ *
181
+ * If `fio_io_tls_trust_add` was never called, zero (0) is returned.
182
+ */
183
+ SFUNC uintptr_t fio_io_tls_trust_count(fio_io_tls_s *tls);
184
+
185
+ /** Arguments (and info) for `fio_io_tls_each`. */
186
+ typedef struct fio_io_tls_each_s {
187
+ fio_io_tls_s *tls;
188
+ void *udata;
189
+ void *udata2;
190
+ int (*each_cert)(struct fio_io_tls_each_s *,
191
+ const char *server_name,
192
+ const char *public_cert_file,
193
+ const char *private_key_file,
194
+ const char *pk_password);
195
+ int (*each_alpn)(struct fio_io_tls_each_s *,
196
+ const char *protocol_name,
197
+ void (*on_selected)(fio_s *));
198
+ int (*each_trust)(struct fio_io_tls_each_s *, const char *public_cert_file);
199
+ } fio_io_tls_each_s;
200
+
201
+ /** Calls callbacks for certificate, trust certificate and ALPN added. */
202
+ SFUNC int fio_io_tls_each(fio_io_tls_each_s);
203
+
204
+ /** `fio_io_tls_each` helper macro, see `fio_io_tls_each_s` for named arguments. */
205
+ #define fio_io_tls_each(tls_, ...) \
206
+ fio_io_tls_each(((fio_io_tls_each_s){.tls = tls_, __VA_ARGS__}))
207
+
208
+ /** If `NULL` returns current default, otherwise sets it. */
209
+ SFUNC fio_io_functions_s fio_io_tls_default_io_functions(fio_io_functions_s *);
210
+ #endif
211
+
212
+ /* *****************************************************************************
213
+ Wrapper API
214
+ ***************************************************************************** */
7
215
 
8
- void iodine_init_tls(void);
9
- fio_tls_s *iodine_tls2c(VALUE self);
216
+ // clang-format off
217
+ /**
218
+ Assigns the TLS context a public certificate, allowing remote parties to
219
+ validate the connection's identity.
10
220
 
11
- extern VALUE IodineTLSClass;
221
+ A self signed certificate is automatically created if the `name` argument
222
+ is specified and either (or both) of the `cert` (public certificate) or `key`
223
+ (private key) arguments are missing.
12
224
 
225
+ Some implementations allow servers to have more than a single certificate, which
226
+ will be selected using the SNI extension. I believe the existing OpenSSL
227
+ implementation supports this option (untested).
228
+
229
+ Iodine::TLS#add_cert(name = nil,
230
+ cert = nil,
231
+ key = nil,
232
+ password = nil)
233
+
234
+ Certificates and keys should be String objects leading to a PEM file.
235
+
236
+ This method also accepts named arguments. i.e.:
237
+
238
+ tls = Iodine::TLS.new
239
+ tls.add_cert name: "example.com"
240
+ tls.add_cert cert: "my_cert.pem", key: "my_key.pem"
241
+ tls.add_cert cert: "my_cert.pem", key: "my_key.pem", password: ENV['TLS_PASS']
242
+
243
+ Since TLS setup is crucial for security, an initialization error will result in
244
+ Iodine crashing with an error message. This is expected behavior.
245
+ */ // clang-format on
246
+ static VALUE iodine_tls_cert_add(int argc, VALUE *argv, VALUE self) {
247
+ fio_io_tls_s *tls = iodine_tls_get(self);
248
+ fio_buf_info_s server_name = FIO_BUF_INFO1((char *)"localhost");
249
+ fio_buf_info_s public_cert_file = FIO_BUF_INFO0;
250
+ fio_buf_info_s private_key_file = FIO_BUF_INFO0;
251
+ fio_buf_info_s pk_password = FIO_BUF_INFO0;
252
+ iodine_rb2c_arg(argc,
253
+ argv,
254
+ IODINE_ARG_BUF(server_name, 0, "name", 0),
255
+ IODINE_ARG_BUF(public_cert_file, 0, "cert", 0),
256
+ IODINE_ARG_BUF(private_key_file, 0, "key", 0),
257
+ IODINE_ARG_BUF(pk_password, 0, "password", 0));
258
+ fio_io_tls_cert_add(tls,
259
+ server_name.buf,
260
+ public_cert_file.buf,
261
+ private_key_file.buf,
262
+ pk_password.buf);
263
+ return self;
264
+ }
265
+
266
+ /** @deprecated use {Iodine::TLS.add_cert}. */
267
+ static VALUE iodine_tls_cert_add_old_name(int argc, VALUE *argv, VALUE self) {
268
+ return iodine_tls_cert_add(argc, argv, self);
269
+ }
270
+
271
+ /* *****************************************************************************
272
+ TLS Trust Store (Peer Certificate Authentication)
273
+ ***************************************************************************** */
274
+
275
+ // clang-format off
276
+ /**
277
+ Adds a public certificate (or certificate bundle) to the "trust" list,
278
+ enabling peer (client) certificate authentication / authorization.
279
+
280
+ tls = Iodine::TLS.new
281
+ tls.trust("clients-ca.pem")
282
+
283
+ Calling `trust` without a path (or with `nil`) trusts the system's default
284
+ trust registry.
285
+
286
+ Once the trust list is non-empty, servers using this TLS context request a
287
+ certificate from connecting clients and verify its chain against the trusted
288
+ certificates. The peer's certificate chain can then be reviewed by the
289
+ application using {Iodine::Connection#certificate} and
290
+ {Iodine::Connection#each_certificate}.
291
+
292
+ @param path [String, nil] path to a PEM file with one or more trusted
293
+ certificates (`nil` for the system's trust registry).
294
+ @return [Iodine::TLS] returns itself, so calls may be chained.
295
+ */ // clang-format on
296
+ static VALUE iodine_tls_trust(int argc, VALUE *argv, VALUE self) {
297
+ fio_io_tls_s *tls = iodine_tls_get(self);
298
+ fio_buf_info_s path = FIO_BUF_INFO0;
299
+ iodine_rb2c_arg(argc, argv, IODINE_ARG_BUF(path, 0, "path", 0));
300
+ /* NULL path marks the system's default trust registry as trusted */
301
+ fio_io_tls_trust_add(tls, path.buf);
302
+ return self;
303
+ }
304
+
305
+ /* *****************************************************************************
306
+ TLS Default Backend Getter/Setter
307
+ ***************************************************************************** */
308
+
309
+ /** Getter for Iodine::TLS.default - returns the current default TLS backend */
310
+ static VALUE iodine_tls_default_get(VALUE klass) {
311
+ /* Query the current C-level default */
312
+ fio_io_functions_s current = fio_io_tls_default_functions(NULL);
313
+
314
+ /* Get the known backend function pointers for comparison */
315
+ fio_io_functions_s tls13 = fio_tls13_io_functions();
316
+
317
+ /* Compare build_context function pointer to determine active backend */
318
+ if (current.build_context == tls13.build_context) {
319
+ return ID2SYM(rb_intern("iodine"));
320
+ }
321
+
322
+ #ifdef HAVE_OPENSSL
323
+ fio_io_functions_s openssl = fio_openssl_io_functions();
324
+ if (current.build_context == openssl.build_context) {
325
+ return ID2SYM(rb_intern("openssl"));
326
+ }
13
327
  #endif
328
+
329
+ /* Fallback - should not reach here */
330
+ rb_raise(rb_eStandardError, "known values not found (:iodine or :openssl)");
331
+ return ID2SYM(rb_intern("err"));
332
+ (void)klass;
333
+ }
334
+
335
+ /** Setter for Iodine::TLS.default= - validates and sets the default backend */
336
+ static VALUE iodine_tls_default_set(VALUE klass, VALUE backend) {
337
+ ID backend_id;
338
+ fio_io_functions_s current = fio_io_tls_default_functions(NULL);
339
+
340
+ if (!RB_TYPE_P(backend, RUBY_T_SYMBOL))
341
+ rb_raise(rb_eTypeError, "default must be a Symbol (:iodine or :openssl)");
342
+
343
+ backend_id = rb_sym2id(backend);
344
+ if (backend_id == rb_intern("iodine")) {
345
+ /* Set embedded TLS 1.3 as default */
346
+ current = fio_tls13_io_functions();
347
+ } else if (backend_id == rb_intern("openssl")) {
348
+ #ifndef HAVE_OPENSSL
349
+ rb_raise(rb_eRuntimeError, "OpenSSL not available (not compiled in)");
350
+ #else
351
+ /* Set OpenSSL as default */
352
+ current = fio_openssl_io_functions();
353
+ #endif
354
+ } else {
355
+ rb_raise(rb_eArgError, "default must be :iodine or :openssl");
356
+ }
357
+ fio_io_tls_default_functions(&current);
358
+ return backend;
359
+ (void)klass;
360
+ }
361
+
362
+ /* *****************************************************************************
363
+ Iodine::TLS::Certificate - Peer Certificate Snapshot
364
+ ***************************************************************************** */
365
+
366
+ /** Maps a public key algorithm to a Ruby Symbol. */
367
+ static VALUE iodine_tls_certificate_key_algo_sym(fio_x509_key_algo_e algo) {
368
+ switch (algo) {
369
+ case FIO_X509_KEY_RSA: return ID2SYM(rb_intern("rsa"));
370
+ case FIO_X509_KEY_ECDSA_P256: return ID2SYM(rb_intern("ecdsa_p256"));
371
+ case FIO_X509_KEY_ECDSA_P384: return ID2SYM(rb_intern("ecdsa_p384"));
372
+ case FIO_X509_KEY_ED25519: return ID2SYM(rb_intern("ed25519"));
373
+ default: return ID2SYM(rb_intern("unknown"));
374
+ }
375
+ }
376
+
377
+ /** Maps a signature algorithm to a Ruby Symbol. */
378
+ static VALUE
379
+ iodine_tls_certificate_signature_algo_sym(fio_x509_signature_algo_e algo) {
380
+ switch (algo) {
381
+ case FIO_X509_SIGNATURE_RSA_PKCS1_SHA256:
382
+ return ID2SYM(rb_intern("rsa_pkcs1_sha256"));
383
+ case FIO_X509_SIGNATURE_RSA_PKCS1_SHA384:
384
+ return ID2SYM(rb_intern("rsa_pkcs1_sha384"));
385
+ case FIO_X509_SIGNATURE_RSA_PKCS1_SHA512:
386
+ return ID2SYM(rb_intern("rsa_pkcs1_sha512"));
387
+ case FIO_X509_SIGNATURE_RSA_PSS_SHA256:
388
+ return ID2SYM(rb_intern("rsa_pss_sha256"));
389
+ case FIO_X509_SIGNATURE_RSA_PSS_SHA384:
390
+ return ID2SYM(rb_intern("rsa_pss_sha384"));
391
+ case FIO_X509_SIGNATURE_RSA_PSS_SHA512:
392
+ return ID2SYM(rb_intern("rsa_pss_sha512"));
393
+ case FIO_X509_SIGNATURE_ECDSA_SHA256:
394
+ return ID2SYM(rb_intern("ecdsa_sha256"));
395
+ case FIO_X509_SIGNATURE_ECDSA_SHA384:
396
+ return ID2SYM(rb_intern("ecdsa_sha384"));
397
+ case FIO_X509_SIGNATURE_ED25519: return ID2SYM(rb_intern("ed25519"));
398
+ default: return ID2SYM(rb_intern("unknown"));
399
+ }
400
+ }
401
+
402
+ /** Converts a (possibly empty) buffer view to a Ruby String (or `nil`). */
403
+ #define IODINE___CERT_BUF2STR(bufinfo) \
404
+ ((bufinfo).buf ? rb_str_new((const char *)(bufinfo).buf, \
405
+ (long)(bufinfo).len) \
406
+ : Qnil)
407
+
408
+ /**
409
+ * Builds an Iodine::TLS::Certificate snapshot from a parsed X.509
410
+ * certificate.
411
+ *
412
+ * All fields are copied eagerly, since the `fio_x509_cert_s` views are
413
+ * transient (valid only until the next `fio_io_peer_info_next` call on ANY
414
+ * connection or until the connection is closed).
415
+ */
416
+ static VALUE iodine_tls_certificate_new(const fio_x509_cert_s *cert) {
417
+ VALUE o = rb_obj_alloc(iodine_rb_TLS_CERTIFICATE);
418
+ rb_ivar_set(o, rb_intern("@der"), IODINE___CERT_BUF2STR(cert->der));
419
+ rb_ivar_set(o, rb_intern("@serial"), IODINE___CERT_BUF2STR(cert->serial));
420
+ rb_ivar_set(o, rb_intern("@subject"), IODINE___CERT_BUF2STR(cert->subject));
421
+ rb_ivar_set(o, rb_intern("@issuer"), IODINE___CERT_BUF2STR(cert->issuer));
422
+ rb_ivar_set(o, rb_intern("@cn"), IODINE___CERT_BUF2STR(cert->cn));
423
+ rb_ivar_set(o,
424
+ rb_intern("@signature"),
425
+ IODINE___CERT_BUF2STR(cert->signature));
426
+ rb_ivar_set(o, rb_intern("@san_dns"), IODINE___CERT_BUF2STR(cert->san_dns));
427
+ rb_ivar_set(o, rb_intern("@san_ip"), IODINE___CERT_BUF2STR(cert->san_ip));
428
+ rb_ivar_set(o,
429
+ rb_intern("@not_before"),
430
+ rb_time_new((time_t)cert->not_before, 0));
431
+ rb_ivar_set(o,
432
+ rb_intern("@not_after"),
433
+ rb_time_new((time_t)cert->not_after, 0));
434
+ rb_ivar_set(o,
435
+ rb_intern("@fingerprint"),
436
+ rb_str_new((const char *)cert->fingerprint, 32));
437
+ rb_ivar_set(o,
438
+ rb_intern("@key_algo"),
439
+ iodine_tls_certificate_key_algo_sym(cert->key_algo));
440
+ rb_ivar_set(o,
441
+ rb_intern("@signature_algo"),
442
+ iodine_tls_certificate_signature_algo_sym(cert->signature_algo));
443
+ rb_ivar_set(o,
444
+ rb_intern("@key_usage"),
445
+ cert->has_key_usage ? UINT2NUM(cert->key_usage) : Qnil);
446
+ rb_ivar_set(o, rb_intern("@version"), INT2FIX(cert->version));
447
+ rb_ivar_set(o, rb_intern("@chain_index"), INT2FIX(cert->chain_index));
448
+ rb_ivar_set(o, rb_intern("@verified"), cert->verified ? Qtrue : Qfalse);
449
+ rb_ivar_set(o, rb_intern("@ca"), cert->is_ca ? Qtrue : Qfalse);
450
+ return rb_obj_freeze(o);
451
+ }
452
+
453
+ #undef IODINE___CERT_BUF2STR
454
+
455
+ /** Returns `true` if the TLS backend verified this certificate's chain. */
456
+ static VALUE iodine_tls_certificate_verified_p(VALUE self) {
457
+ return rb_ivar_get(self, rb_intern("@verified"));
458
+ }
459
+
460
+ /** Returns `true` if the certificate is a CA (Basic Constraints). */
461
+ static VALUE iodine_tls_certificate_ca_p(VALUE self) {
462
+ return rb_ivar_get(self, rb_intern("@ca"));
463
+ }
464
+
465
+ /* *****************************************************************************
466
+ Initialize Iodine::TLS
467
+ ***************************************************************************** */
468
+
469
+ static void Init_Iodine_TLS(void) { /** Initialize Iodine::TLS */
470
+ /** Used to setup a TLS contexts for connections (incoming / outgoing). */
471
+ VALUE m = iodine_rb_IODINE_TLS =
472
+ rb_define_class_under(iodine_rb_IODINE, "TLS", rb_cObject);
473
+ rb_define_alloc_func(m, iodine_tls_alloc);
474
+ rb_define_method(m, "add_cert", iodine_tls_cert_add, -1);
475
+ rb_define_method(m, "use_certificate", iodine_tls_cert_add_old_name, -1);
476
+ rb_define_method(m, "trust", iodine_tls_trust, -1);
477
+
478
+ /* TLS default backend getter/setter methods */
479
+ rb_define_singleton_method(m, "default", iodine_tls_default_get, 0);
480
+ rb_define_singleton_method(m, "default=", iodine_tls_default_set, 1);
481
+
482
+ /* TLS backend availability constants */
483
+ rb_const_set(m,
484
+ rb_intern("SUPPORTED"),
485
+ Qtrue); /* Always true - embedded TLS always available */
486
+ #ifdef HAVE_OPENSSL
487
+ rb_const_set(m, rb_intern("OPENSSL_AVAILABLE"), Qtrue);
488
+ #else
489
+ rb_const_set(m, rb_intern("OPENSSL_AVAILABLE"), Qfalse);
490
+ #endif
491
+ /* Embedded TLS 1.3 is always available via fio-stl.h */
492
+ rb_const_set(m, rb_intern("EMBEDDED_AVAILABLE"), Qtrue);
493
+
494
+ /* Iodine::TLS::Certificate - peer certificate snapshot (value object) */
495
+ {
496
+ /** An immutable snapshot of an X.509 certificate. */
497
+ VALUE c = iodine_rb_TLS_CERTIFICATE =
498
+ rb_define_class_under(iodine_rb_IODINE_TLS, "Certificate", rb_cObject);
499
+ /* certificates originate from TLS connections, not Ruby code */
500
+ rb_undef_method(rb_singleton_class(c), "new");
501
+ rb_define_attr(c, "der", 1, 0);
502
+ rb_define_attr(c, "serial", 1, 0);
503
+ rb_define_attr(c, "subject", 1, 0);
504
+ rb_define_attr(c, "issuer", 1, 0);
505
+ rb_define_attr(c, "cn", 1, 0);
506
+ rb_define_attr(c, "signature", 1, 0);
507
+ rb_define_attr(c, "san_dns", 1, 0);
508
+ rb_define_attr(c, "san_ip", 1, 0);
509
+ rb_define_attr(c, "not_before", 1, 0);
510
+ rb_define_attr(c, "not_after", 1, 0);
511
+ rb_define_attr(c, "fingerprint", 1, 0);
512
+ rb_define_attr(c, "key_algo", 1, 0);
513
+ rb_define_attr(c, "signature_algo", 1, 0);
514
+ rb_define_attr(c, "key_usage", 1, 0);
515
+ rb_define_attr(c, "version", 1, 0);
516
+ rb_define_attr(c, "chain_index", 1, 0);
517
+ rb_define_method(c, "verified?", iodine_tls_certificate_verified_p, 0);
518
+ rb_define_method(c, "ca?", iodine_tls_certificate_ca_p, 0);
519
+ }
520
+ }
521
+
522
+ #endif /* H___IODINE_TLS___H */