Skip to content

Commit eca7ab8

Browse files
committed
bio: add a BIO method that wraps IO-like object
Implement a bare minimal BIO_METHOD required for SSL/TLS. The underlying IO-like object must implement the following methods: - #read_nonblock(len, exception: false) - #write_nonblock(str, exception: false) - #flush A later commit will wire it into OpenSSL::SSL::SSLSocket.
1 parent 3ff0961 commit eca7ab8

File tree

3 files changed

+278
-0
lines changed

3 files changed

+278
-0
lines changed

ext/openssl/ossl.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,7 @@ Init_openssl(void)
10331033
* Init components
10341034
*/
10351035
Init_ossl_asn1();
1036+
Init_ossl_bio();
10361037
Init_ossl_bn();
10371038
Init_ossl_cipher();
10381039
Init_ossl_config();

ext/openssl/ossl_bio.c

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,274 @@ ossl_membio2str(BIO *bio)
4040

4141
return ret;
4242
}
43+
44+
static BIO_METHOD *ossl_bio_meth;
45+
static VALUE nonblock_kwargs, sym_wait_readable, sym_wait_writable;
46+
47+
struct ossl_bio_ctx {
48+
VALUE io;
49+
int state;
50+
int eof;
51+
};
52+
53+
static void
54+
bio_free(void *ptr)
55+
{
56+
BIO_free(ptr);
57+
}
58+
59+
static void
60+
bio_mark(void *ptr)
61+
{
62+
struct ossl_bio_ctx *ctx = BIO_get_data(ptr);
63+
rb_gc_mark_movable(ctx->io);
64+
}
65+
66+
static void
67+
bio_compact(void *ptr)
68+
{
69+
struct ossl_bio_ctx *ctx = BIO_get_data(ptr);
70+
ctx->io = rb_gc_location(ctx->io);
71+
}
72+
73+
static const rb_data_type_t ossl_bio_type = {
74+
"OpenSSL/BIO",
75+
{
76+
.dmark = bio_mark,
77+
.dfree = bio_free,
78+
.dcompact = bio_compact,
79+
},
80+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
81+
};
82+
83+
VALUE
84+
ossl_bio_new(VALUE io)
85+
{
86+
VALUE obj = TypedData_Wrap_Struct(rb_cObject, &ossl_bio_type, NULL);
87+
BIO *bio = BIO_new(ossl_bio_meth);
88+
if (!bio)
89+
ossl_raise(eOSSLError, "BIO_new");
90+
91+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
92+
ctx->io = io;
93+
BIO_set_init(bio, 1);
94+
RTYPEDDATA_DATA(obj) = bio;
95+
return obj;
96+
}
97+
98+
BIO *
99+
ossl_bio_get(VALUE obj)
100+
{
101+
BIO *bio;
102+
TypedData_Get_Struct(obj, BIO, &ossl_bio_type, bio);
103+
return bio;
104+
}
105+
106+
int
107+
ossl_bio_state(VALUE obj)
108+
{
109+
BIO *bio;
110+
TypedData_Get_Struct(obj, BIO, &ossl_bio_type, bio);
111+
112+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
113+
int state = ctx->state;
114+
ctx->state = 0;
115+
return state;
116+
}
117+
118+
static int
119+
bio_create(BIO *bio)
120+
{
121+
struct ossl_bio_ctx *ctx = OPENSSL_malloc(sizeof(*ctx));
122+
if (!ctx)
123+
return 0;
124+
memset(ctx, 0, sizeof(*ctx));
125+
BIO_set_data(bio, ctx);
126+
127+
return 1;
128+
}
129+
130+
static int
131+
bio_destroy(BIO *bio)
132+
{
133+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
134+
if (ctx) {
135+
OPENSSL_free(ctx);
136+
BIO_set_data(bio, NULL);
137+
}
138+
139+
return 1;
140+
}
141+
142+
struct bwrite_args {
143+
BIO *bio;
144+
struct ossl_bio_ctx *ctx;
145+
const char *data;
146+
int dlen;
147+
int written;
148+
};
149+
150+
static VALUE
151+
bio_bwrite0(VALUE args)
152+
{
153+
struct bwrite_args *p = (void *)args;
154+
BIO_clear_retry_flags(p->bio);
155+
156+
VALUE fargs[] = { rb_str_new_static(p->data, p->dlen), nonblock_kwargs };
157+
VALUE ret = rb_funcallv_kw(p->ctx->io, rb_intern("write_nonblock"),
158+
2, fargs, RB_PASS_KEYWORDS);
159+
160+
if (RB_INTEGER_TYPE_P(ret)) {
161+
p->written = NUM2INT(ret);
162+
return Qtrue;
163+
}
164+
else if (ret == sym_wait_readable) {
165+
BIO_set_retry_read(p->bio);
166+
return Qfalse;
167+
}
168+
else if (ret == sym_wait_writable) {
169+
BIO_set_retry_write(p->bio);
170+
return Qfalse;
171+
}
172+
else {
173+
rb_raise(rb_eTypeError, "write_nonblock must return an Integer, "
174+
":wait_readable, or :wait_writable");
175+
}
176+
}
177+
178+
static int
179+
bio_bwrite(BIO *bio, const char *data, int dlen)
180+
{
181+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
182+
struct bwrite_args args = { bio, ctx, data, dlen, 0 };
183+
int state;
184+
185+
if (ctx->state)
186+
return -1;
187+
188+
VALUE ok = rb_protect(bio_bwrite0, (VALUE)&args, &state);
189+
if (state) {
190+
ctx->state = state;
191+
return -1;
192+
}
193+
if (RTEST(ok))
194+
return args.written;
195+
return -1;
196+
}
197+
198+
struct bread_args {
199+
BIO *bio;
200+
struct ossl_bio_ctx *ctx;
201+
char *data;
202+
int dlen;
203+
int readbytes;
204+
};
205+
206+
static VALUE
207+
bio_bread0(VALUE args)
208+
{
209+
struct bread_args *p = (void *)args;
210+
BIO_clear_retry_flags(p->bio);
211+
212+
VALUE fargs[] = { INT2NUM(p->dlen), nonblock_kwargs };
213+
VALUE ret = rb_funcallv_kw(p->ctx->io, rb_intern("read_nonblock"),
214+
2, fargs, RB_PASS_KEYWORDS);
215+
216+
if (RB_TYPE_P(ret, T_STRING)) {
217+
int len = RSTRING_LENINT(ret);
218+
if (len > p->dlen)
219+
rb_raise(rb_eTypeError, "read_nonblock returned too much data");
220+
memcpy(p->data, RSTRING_PTR(ret), len);
221+
p->readbytes = len;
222+
return Qtrue;
223+
}
224+
else if (NIL_P(ret)) {
225+
// In OpenSSL 3.0 or later: BIO_set_flags(p->bio, BIO_FLAGS_IN_EOF);
226+
p->ctx->eof = 1;
227+
return Qtrue;
228+
}
229+
else if (ret == sym_wait_readable) {
230+
BIO_set_retry_read(p->bio);
231+
return Qfalse;
232+
}
233+
else if (ret == sym_wait_writable) {
234+
BIO_set_retry_write(p->bio);
235+
return Qfalse;
236+
}
237+
else {
238+
rb_raise(rb_eTypeError, "write_nonblock must return an Integer, "
239+
":wait_readable, or :wait_writable");
240+
}
241+
}
242+
243+
static int
244+
bio_bread(BIO *bio, char *data, int dlen)
245+
{
246+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
247+
struct bread_args args = { bio, ctx, data, dlen, 0 };
248+
int state;
249+
250+
if (ctx->state)
251+
return -1;
252+
253+
VALUE ok = rb_protect(bio_bread0, (VALUE)&args, &state);
254+
if (state) {
255+
ctx->state = state;
256+
return -1;
257+
}
258+
if (RTEST(ok))
259+
return args.readbytes;
260+
return -1;
261+
}
262+
263+
static VALUE
264+
bio_flush0(VALUE vctx)
265+
{
266+
struct ossl_bio_ctx *ctx = (void *)vctx;
267+
return rb_funcallv(ctx->io, rb_intern("flush"), 0, NULL);
268+
}
269+
270+
static long
271+
bio_ctrl(BIO *bio, int cmd, long larg, void *parg)
272+
{
273+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
274+
int state;
275+
276+
if (ctx->state)
277+
return 0;
278+
279+
switch (cmd) {
280+
case BIO_CTRL_EOF:
281+
return ctx->eof;
282+
case BIO_CTRL_FLUSH:
283+
rb_protect(bio_flush0, (VALUE)ctx, &state);
284+
ctx->state = state;
285+
return !state;
286+
default:
287+
return 0;
288+
}
289+
}
290+
291+
void
292+
Init_ossl_bio(void)
293+
{
294+
ossl_bio_meth = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "Ruby IO-like object");
295+
if (!ossl_bio_meth)
296+
ossl_raise(eOSSLError, "BIO_meth_new");
297+
if (!BIO_meth_set_create(ossl_bio_meth, bio_create) ||
298+
!BIO_meth_set_destroy(ossl_bio_meth, bio_destroy) ||
299+
!BIO_meth_set_write(ossl_bio_meth, bio_bwrite) ||
300+
!BIO_meth_set_read(ossl_bio_meth, bio_bread) ||
301+
!BIO_meth_set_ctrl(ossl_bio_meth, bio_ctrl)) {
302+
BIO_meth_free(ossl_bio_meth);
303+
ossl_bio_meth = NULL;
304+
ossl_raise(eOSSLError, "BIO_meth_set_*");
305+
}
306+
307+
nonblock_kwargs = rb_hash_new();
308+
rb_hash_aset(nonblock_kwargs, ID2SYM(rb_intern_const("exception")), Qfalse);
309+
rb_global_variable(&nonblock_kwargs);
310+
311+
sym_wait_readable = ID2SYM(rb_intern_const("wait_readable"));
312+
sym_wait_writable = ID2SYM(rb_intern_const("wait_writable"));
313+
}

ext/openssl/ossl_bio.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,10 @@
1313
BIO *ossl_obj2bio(volatile VALUE *);
1414
VALUE ossl_membio2str(BIO*);
1515

16+
VALUE ossl_bio_new(VALUE io);
17+
BIO *ossl_bio_get(VALUE obj);
18+
int ossl_bio_state(VALUE obj);
19+
20+
void Init_ossl_bio(void);
21+
1622
#endif

0 commit comments

Comments
 (0)