OpenSSL::Providerの使い方(雰囲気)

先日社のブログのお手伝いして、その中でruby/opensslへのPRをじま..紹介した。 https://tech.smarthr.jp/entry/2023/07/12/115326
https://github.com/ruby/openssl/pull/635

実際のアップデート作業は周りの人が優秀過ぎてあんまり書くことない。Providerがどうとか気づいたのも僕ではないので詳しくは記事を読んでください。

OpenSSL::Providerというクラスそのものについては書けることがありそうなのと、Ruby Orgに貢献できたのが嬉しいので調子にのってブログにも残して置く。
偉そうなこと言ってるが全部雰囲気で書いた雰囲気野郎です。

このブログは公式情報でもなんでもなく、この機能がリリースされている時にはまったく違う状態になっている可能性があるので注意!

OpenSSL::Providerとは

OpenSSL3系からProviderという新しい概念が導入された。
https://www.openssl.org/docs/man3.0/man7/migration_guide.html

アルゴリズムの実装を集めてプラガブルにしているものっぽい。このProviderによってOpenSSLが対応するアルゴリズムが変わる。
ruby/opensslにはProviderに関する機能が無かった。そこでProviderを設定するために生まれたクラスがOpenSSL::Providerである。

OpenSSL::Providerの使い方

以下の様にOpenSSL::Provider.loadを使って名前指定でProviderを読み込むことができる。

OpenSSL::Cipher.new("RC4")
=> `initialize': unsupported (OpenSSL::Cipher::CipherError)
  from (irb):5:in `new'
  from (irb):5:in `<main>'
~~~~~ snip ~~~~~
OpenSSL::Provider.load("legacy")
=> #<OpenSSL::Provider name="legacy">
cipher = OpenSSL::Cipher.new("RC4")
=> #<OpenSSL::Cipher:0x0000000108eb3648>

それぞれのメソッドについて書いていく。

OpenSSL::Provider.load(provider_name)

指定した名前のProviderを読み込むメソッド。中身は OSSL_PROVIDER_loadである。 返り値はOSSL_PROVIDERという構造体を参照するOpenSSL::Providerのインスタンス。後述するが、このインスタンスがGCされても参照しているProvider自体にはなにもしない。

OpenSSL::Provider.provider_names

現在ロードされているプロバイダーの名前の一覧を持つArrayを返す。

OpenSSL::Provider.provider_names
=> ["default", "legacy"]

なぜインスタンスの一覧ではなく名前の一覧かというと、OSSL_PROVIDER構造体への参照カウントを増やしつつインスタンスの一覧を返す方法が無かったから。このメソッドの中身はOSSL_PROVIDER_do_allという関数で、この関数は参照カウントを増やしてくれるわけではない。
OSSL_PROVIDER_Loadで参照カウントを増やせるのだが、既に読み込まれているOSSL_PROVIDER構造体をまた読み込むのはおかしいし、構造体への参照カウントを増やさないと後述するOpenSSL::Provider#unloadがメモリ的に危険な操作になってしまう。
なのでインスタンス一覧ではなく、名前だけ返すようにした。
詳しくはこの会話を見てくれ!
https://github.com/ruby/openssl/pull/635#discussion_r1214355587

OpenSSL::Provider#unload

中身はOSSL_PROVIDER_unload

legacy_provider = OpenSSL::Provider.load("legacy")
legacy_provider.unload
=> true

返り値はtrue
このメソッドはOpenSSL::Provider::ProviderErrorが発生する可能性がある。
1つ目はなんらかの理由でOSSL_PROVIDER_unloadが失敗した場合。
2つ目は同じOpenSSL::Providerインスタンスを2回unloadした場合。
2つ目についてはメモリ安全の為である。OSSL_PROVIDER_unloadはOSSL_PROVIDER構造体を開放するため、インスタンスが持っている構造体への参照を元にunloadを再度呼び出すと危険な操作になる。なので、2回unloadされた際にはOSSL_PROVIDER_unloadは行わずに例外を出している。
OpenSSL::ProviderインスタンスがGCされた場合は内部で参照している構造体にはなにもしない。また、構造体をunloadすることなく開放するAPIは公開されていないっぽい。

OpenSSL::Provider#name

参照しているOSSL_PROVIDER構造体のnameをStringで返す。 unloadされたインスタンスに対してこのメソッドを呼び出すとOpenSSL::Provider::ProviderErrorが発生する。

OpenSSL::Provider#inspect

こんな感じのStringを返す。

#<OpenSSL::Provider name="legacy">

unloadされたインスタンスはこんな感じ。

#<OpenSSL::Provider unloaded provider>

謝辞

C言語なんもわからん人間が周りのコードを見て雰囲気で書いた酷いコードをここまで丁寧にレビューしてもらえて感謝しかありません。 メンテナの方々のおかげで快適にRubyでコーディングできております! あとこのRubyの拡張ライブラリの作り方というドキュメントはマジで神なのでみんな読もう!

関連記事

OpenSSL3のProviderを設定するAPIを提供するGemをリリースした - QWYNG.dev