OpenSSL の BIO を自作するには

# 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 に設定するという作法だと思います。

（書きかけ）