Skip to content

Commit 0ff6d21

Browse files
committed
bio: add a BIO method that wraps IO-like object
Implement a 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 This will be used in a later commit with OpenSSL::SSL::SSLSocket.
1 parent a5ed9e6 commit 0ff6d21

File tree

4 files changed

+253
-0
lines changed

4 files changed

+253
-0
lines changed

ext/openssl/openssl_missing.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,26 @@ IMPL_PKEY_GETTER(EC_KEY, ec)
156156
#undef IMPL_PKEY_GETTER
157157
#undef IMPL_KEY_ACCESSOR2
158158
#undef IMPL_KEY_ACCESSOR3
159+
160+
// BIO
161+
static inline void *BIO_get_data(BIO *bio) { return bio->ptr; }
162+
static inline void BIO_set_data(BIO *bio, void *data) { bio->ptr = data; }
163+
static inline void BIO_set_init(BIO *bio, int init) { bio->init = init; }
164+
static inline BIO_METHOD *BIO_meth_new(int type, const char *name) {
165+
BIO_METHOD *meth = OPENSSL_malloc(sizeof(*meth));
166+
if (!meth)
167+
return NULL;
168+
memset(meth, 0, sizeof(*meth));
169+
meth->type = type;
170+
meth->name = name;
171+
return meth;
172+
}
173+
static inline void BIO_meth_free(BIO_METHOD *meth) { OPENSSL_free(meth); }
174+
static inline int BIO_meth_set_create(BIO_METHOD *meth, int (*f)(BIO *)) { meth->create = f; return 1; }
175+
static inline int BIO_meth_set_destroy(BIO_METHOD *meth, int (*f)(BIO *)) { meth->destroy = f; return 1; }
176+
static inline int BIO_meth_set_write(BIO_METHOD *meth, int (*f)(BIO *, const char *, int)) { meth->bwrite = f; return 1; }
177+
static inline int BIO_meth_set_read(BIO_METHOD *meth, int (*f)(BIO *, char *, int)) { meth->bread = f; return 1; }
178+
static inline int BIO_meth_set_ctrl(BIO_METHOD *meth, long (*f)(BIO *, int, long, void *)) { meth->ctrl = f; return 1; }
159179
#endif /* HAVE_OPAQUE_OPENSSL */
160180

161181
#if !defined(EVP_CTRL_AEAD_GET_TAG)

ext/openssl/ossl.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,7 @@ Init_openssl(void)
11501150
/*
11511151
* Init components
11521152
*/
1153+
Init_ossl_bio();
11531154
Init_ossl_bn();
11541155
Init_ossl_cipher();
11551156
Init_ossl_config();

ext/openssl/ossl_bio.c

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,230 @@ 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+
BIO *
54+
ossl_bio_new(VALUE io)
55+
{
56+
BIO *bio = BIO_new(ossl_bio_meth);
57+
if (!bio)
58+
ossl_raise(eOSSLError, "BIO_new");
59+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
60+
ctx->io = io;
61+
BIO_set_init(bio, 1);
62+
return bio;
63+
}
64+
65+
int
66+
ossl_bio_state(BIO *bio)
67+
{
68+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
69+
int state = ctx->state;
70+
ctx->state = 0;
71+
return state;
72+
}
73+
74+
static int
75+
bio_create(BIO *bio)
76+
{
77+
struct ossl_bio_ctx *ctx = OPENSSL_malloc(sizeof(*ctx));
78+
if (!ctx)
79+
return 0;
80+
memset(ctx, 0, sizeof(*ctx));
81+
BIO_set_data(bio, ctx);
82+
83+
return 1;
84+
}
85+
86+
static int
87+
bio_destroy(BIO *bio)
88+
{
89+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
90+
if (ctx) {
91+
OPENSSL_free(ctx);
92+
BIO_set_data(bio, NULL);
93+
}
94+
95+
return 1;
96+
}
97+
98+
struct bwrite_args {
99+
BIO *bio;
100+
struct ossl_bio_ctx *ctx;
101+
const char *data;
102+
int dlen;
103+
int written;
104+
};
105+
106+
static VALUE
107+
bio_bwrite0(VALUE args)
108+
{
109+
struct bwrite_args *p = (void *)args;
110+
BIO_clear_retry_flags(p->bio);
111+
112+
VALUE fargs[] = { rb_str_new_static(p->data, p->dlen), nonblock_kwargs };
113+
VALUE ret = rb_funcallv_public_kw(p->ctx->io, rb_intern("write_nonblock"),
114+
2, fargs, RB_PASS_KEYWORDS);
115+
116+
if (RB_INTEGER_TYPE_P(ret)) {
117+
p->written = NUM2INT(ret);
118+
return Qtrue;
119+
}
120+
else if (ret == sym_wait_readable) {
121+
BIO_set_retry_read(p->bio);
122+
return Qfalse;
123+
}
124+
else if (ret == sym_wait_writable) {
125+
BIO_set_retry_write(p->bio);
126+
return Qfalse;
127+
}
128+
else {
129+
rb_raise(rb_eTypeError, "write_nonblock must return an Integer, "
130+
":wait_readable, or :wait_writable");
131+
}
132+
}
133+
134+
static int
135+
bio_bwrite(BIO *bio, const char *data, int dlen)
136+
{
137+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
138+
struct bwrite_args args = { bio, ctx, data, dlen, 0 };
139+
int state;
140+
141+
if (ctx->state)
142+
return -1;
143+
144+
VALUE ok = rb_protect(bio_bwrite0, (VALUE)&args, &state);
145+
if (state) {
146+
ctx->state = state;
147+
return -1;
148+
}
149+
if (RTEST(ok))
150+
return args.written;
151+
return -1;
152+
}
153+
154+
struct bread_args {
155+
BIO *bio;
156+
struct ossl_bio_ctx *ctx;
157+
char *data;
158+
int dlen;
159+
int readbytes;
160+
};
161+
162+
static VALUE
163+
bio_bread0(VALUE args)
164+
{
165+
struct bread_args *p = (void *)args;
166+
BIO_clear_retry_flags(p->bio);
167+
168+
VALUE fargs[] = { INT2NUM(p->dlen), nonblock_kwargs };
169+
VALUE ret = rb_funcallv_public_kw(p->ctx->io, rb_intern("read_nonblock"),
170+
2, fargs, RB_PASS_KEYWORDS);
171+
172+
if (RB_TYPE_P(ret, T_STRING)) {
173+
int len = RSTRING_LENINT(ret);
174+
if (len > p->dlen)
175+
rb_raise(rb_eTypeError, "read_nonblock returned too much data");
176+
memcpy(p->data, RSTRING_PTR(ret), len);
177+
p->readbytes = len;
178+
return Qtrue;
179+
}
180+
else if (NIL_P(ret)) {
181+
// In OpenSSL 3.0 or later: BIO_set_flags(p->bio, BIO_FLAGS_IN_EOF);
182+
p->ctx->eof = 1;
183+
return Qtrue;
184+
}
185+
else if (ret == sym_wait_readable) {
186+
BIO_set_retry_read(p->bio);
187+
return Qfalse;
188+
}
189+
else if (ret == sym_wait_writable) {
190+
BIO_set_retry_write(p->bio);
191+
return Qfalse;
192+
}
193+
else {
194+
rb_raise(rb_eTypeError, "write_nonblock must return an Integer, "
195+
":wait_readable, or :wait_writable");
196+
}
197+
}
198+
199+
static int
200+
bio_bread(BIO *bio, char *data, int dlen)
201+
{
202+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
203+
struct bread_args args = { bio, ctx, data, dlen, 0 };
204+
int state;
205+
206+
if (ctx->state)
207+
return -1;
208+
209+
VALUE ok = rb_protect(bio_bread0, (VALUE)&args, &state);
210+
if (state) {
211+
ctx->state = state;
212+
return -1;
213+
}
214+
if (RTEST(ok))
215+
return args.readbytes;
216+
return -1;
217+
}
218+
219+
static VALUE
220+
bio_flush0(VALUE vctx)
221+
{
222+
struct ossl_bio_ctx *ctx = (void *)vctx;
223+
return rb_funcallv_public(ctx->io, rb_intern("flush"), 0, NULL);
224+
}
225+
226+
static long
227+
bio_ctrl(BIO *bio, int cmd, long larg, void *parg)
228+
{
229+
struct ossl_bio_ctx *ctx = BIO_get_data(bio);
230+
int state;
231+
232+
if (ctx->state)
233+
return 0;
234+
235+
switch (cmd) {
236+
case BIO_CTRL_EOF:
237+
return ctx->eof;
238+
case BIO_CTRL_FLUSH:
239+
rb_protect(bio_flush0, (VALUE)ctx, &state);
240+
ctx->state = state;
241+
return !state;
242+
default:
243+
return 0;
244+
}
245+
}
246+
247+
void
248+
Init_ossl_bio(void)
249+
{
250+
ossl_bio_meth = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "Ruby IO-like object");
251+
if (!ossl_bio_meth)
252+
ossl_raise(eOSSLError, "BIO_meth_new");
253+
if (!BIO_meth_set_create(ossl_bio_meth, bio_create) ||
254+
!BIO_meth_set_destroy(ossl_bio_meth, bio_destroy) ||
255+
!BIO_meth_set_write(ossl_bio_meth, bio_bwrite) ||
256+
!BIO_meth_set_read(ossl_bio_meth, bio_bread) ||
257+
!BIO_meth_set_ctrl(ossl_bio_meth, bio_ctrl)) {
258+
BIO_meth_free(ossl_bio_meth);
259+
ossl_bio_meth = NULL;
260+
ossl_raise(eOSSLError, "BIO_meth_set_*");
261+
}
262+
263+
nonblock_kwargs = rb_hash_new();
264+
rb_hash_aset(nonblock_kwargs, ID2SYM(rb_intern_const("exception")), Qfalse);
265+
rb_global_variable(&nonblock_kwargs);
266+
267+
sym_wait_readable = ID2SYM(rb_intern_const("wait_readable"));
268+
sym_wait_writable = ID2SYM(rb_intern_const("wait_writable"));
269+
}

ext/openssl/ossl_bio.h

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

16+
BIO *ossl_bio_new(VALUE io);
17+
int ossl_bio_state(BIO *bio);
18+
19+
void Init_ossl_bio(void);
20+
1621
#endif

0 commit comments

Comments
 (0)