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
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の拡張ライブラリの作り方というドキュメントはマジで神なのでみんな読もう!