OpenSSL の BIO_METHOD について書きます
BIO_METHOD で検索しても BIO_METHOD を解説している情報が見つからないので書いてみます。
大事なことを最初に書いておきます。
BIO は入出力を抽象化したもので、BIO_METHOD構造体の定義が、BIO の実体となります。
BIO_METHOD構造体の定義を行うということは、bwrite などの関数を実装するということです。もし、OpenSSL が内部に実装していない種類の入出力を TLS プロトコルを使ったセキュアのものにしたいと考えたとき、あなたは、独自の BIO_METHOD構造体 を作ることで、それを実現できます。
IBMの OpenSSL API の説明ページ https://www.ibm.com/developerworks/jp/linux/library/l-openssl/index.html はとても素晴らしいと思うので一部引用します。「OpenSSL API のマニュアルは、記述が少し不明瞭です。」「OpenSSL の実装方法を学ぶ上での問題として、マニュアルが完全ではないということがあります。」これらは全く正しく、BIO_METHOD が何かを説明する情報はあまり見つかりません。
この解説ページで
bio = BIO_new_connect("hostname:port");
というコードが出てきます。この関数 BIO_new_connect のソースコードを見てみます。
(ソースは openssl-1.0.2k のものを使っています。)
BIO *BIO_new_connect(const char *str)
{
BIO *ret;
ret = BIO_new(BIO_s_connect());
if (ret == NULL)
return (NULL);
if (BIO_set_conn_hostname(ret, str))
return (ret);
else {
BIO_free(ret);
return (NULL);
}
}
とてもシンプルなソースとなっていて助かります。
この関数が返しているのは BIOポインタですが、"BIO OpenSSL" を検索すると解説記事は見つかります。書籍「OpenSSL―暗号・PKI・SSL/TLSライブラリの詳細」から引用すると、「BIOパッケージは、入出力を処理するための強力な抽象化機能を備えています。」とあります。つまり、入出力を処理するものが BIO です。
BIOポインタは、BIO_new で返されるものなので、BIO_set_conn_hostname は後回しにして、BIO_new(BIO_s_connect()) の実装を調べてみます。
BIO_new は、BIO_METHOD のポインタを引数に取ります。この引数が解説したい BIO_METHOD です。
BIO_s_connect() は、BIO_METHOD のポインタを返すはずですが、ソースコードを見てみます。
BIO_METHOD *BIO_s_connect(void)
{
return (&methods_connectp);
}
これまたシンプルなソースで、助かりますが、意味不明なので methods_connectp の定義も見てみます。
static BIO_METHOD methods_connectp = {
BIO_TYPE_CONNECT,
"socket connect",
conn_write,
conn_read,
conn_puts,
NULL, /* connect_gets, */
conn_ctrl,
conn_new,
conn_free,
conn_callback_ctrl,
};
BIO_METHOD 構造体をグローバルな領域に宣言しています。見ただけで、BIO_METHOD 構造体は、関数ポインタを集めた構造体だなというのが想像できます。
実際の構造体の宣言が以下です。
typedef struct bio_method_st {
int type;
const char *name;
int (*bwrite) (BIO *, const char *, int);
int (*bread) (BIO *, char *, int);
int (*bputs) (BIO *, const char *);
int (*bgets) (BIO *, char *, int);
long (*ctrl) (BIO *, int, long, void *);
int (*create) (BIO *);
int (*destroy) (BIO *);
long (*callback_ctrl) (BIO *, int, bio_info_cb *);
} BIO_METHOD;
methods_connectp は type に BIO_TYPE_CONNECT を使っていますが、それ以外の type は crypto/bio/bio.h に定義されています。どういう使い分けなのかわからないので、関数メンバーに注目します。
関数名は、bwrite や bread など、意味の取りやすい名前となっています。この BIO に対して書き込みを処理したい時には、bwrite が呼ばれるのだなというようなことが想像できます。分かりにくいのは ctrl の役割くらいでしょうか? create や destroy のタイミングも気になりますが、でも、まあ、気にしても仕方ない気もします。
これらの関数の中で最初に呼ばれるのは create だと思います。本当でしょうか?
BIO_new のソースは以下のようになっています。
BIO *BIO_new(BIO_METHOD *method)
{
BIO *ret = NULL;
ret = (BIO *)OPENSSL_malloc(sizeof(BIO));
if (ret == NULL) {
BIOerr(BIO_F_BIO_NEW, ERR_R_MALLOC_FAILURE);
return (NULL);
}
if (!BIO_set(ret, method)) {
OPENSSL_free(ret);
ret = NULL;
}
return (ret);
}
この関数も大したことはしておらず、メモリを確保して BIO_set 関数を呼び出しています。BIO_set 関数の中で create が呼ばれていますので、やはり最初に呼ばれる関数は create で合っていました。
methods_connectp の create の実体は conn_new 関数です。
conn_new の実装を見れば、create の関数に求められていることがわかると思います。
今はたまたま BIO_new_connect から始めましたが、OpenSSL 内部に実装している BIOオブジェクト(=BIO_METHOD の定義)は他にもあります。"static BIO_METHOD" で grep すると 26個の定義が見つかりました。かなり多いと感じました。
以下はメモリバッファに対する入出力を処理するものの定義です。
static BIO_METHOD mem_method = {
BIO_TYPE_MEM,
"memory buffer",
mem_write,
mem_read,
mem_puts,
mem_gets,
mem_ctrl,
mem_new,
mem_free,
NULL,
};
conn_new, mem_new の両方に共通することは、create の関数 は、BIO オブジェクトの初期化も行っているということです。BIO構造体の method ポインタは BIO_METHOD構造体を指していますので BIO は BIO_METHOD を所有していると言えますが、所有者である BIO を制御しているのは BIO_METHOD なのです。
BIO構造体を記載していませんでしたので以下に記載します。
例えば init は初期化済みかどうかのフラグとして使われていますが、定義には何のコメントもなく、init をどういう用途で使おうと、それは BIO_METHOD の実装者にまかされていると思われます。だけど、名前は大事なので、このフラグを他の用途に使うことは、ばかげていると思います。なので、BIO_METHOD を実装する時には、OpenSSLにすでに実装してある他の BIO_METHOD を参考にすることをお勧めします。
struct bio_st {
BIO_METHOD *method;
/* bio, mode, argp, argi, argl, ret */
long (*callback) (struct bio_st *, int, const char *, int, long, long);
char *cb_arg; /* first argument for the callback */
int init;
int shutdown;
int flags; /* extra storage */
int retry_reason;
int num;
void *ptr;
struct bio_st *next_bio; /* used by filter BIOs */
struct bio_st *prev_bio; /* used by filter BIOs */
int references;
unsigned long num_read;
unsigned long num_write;
CRYPTO_EX_DATA ex_data;
};
では、後回しにしていた BIO_set_conn_hostname を見ていきます。
# define BIO_set_conn_hostname(b,name) BIO_ctrl(b,BIO_C_SET_CONNECT,0,(char *)name)
BIO_ctrl 関数は、少し長い(25行)ので省略しますが、BIO の callback が定義されていれば、まずそれを呼び出します。次に BIO_METHOD の ctrl を呼び出します。
methods_connectp の場合、conn_new は BIO の callback に対し NULL を代入しますので、callback は呼ばれずに ctrl の実体である conn_ctrl が呼ばれます。
conn_ctrl がやっていることは、conn_new で確保していたメモリ領域にホスト名を保存することですが、そのあたりは、methods_connectp に固有の処理なので注目しません。ここで大事なことは、create 関数が、その BIO に固有な情報領域をメモリ確保し、確保したメモリアドレスを BIO の ptr に設定するという作法だと思います。
(書きかけ)