JSONのwalk
JSONファイルがいくつかあって、それらはスキーマは違っても取得したいキー名は固定であるなんてことがあるかもしれない。 そのときにJSONファイルのスキーマの違いを無視して特定キー名のプロパティを取得するようなことをしたいときに次のようなコード(Python)を利用すれば取得できる。
import json def walk_json(key, jo): # 配列が来た場合はその配列の各要素の中身を探る if type(jo) is list: return sum( list( map(walk_json, jo) ) ) # JSONは `true` のみでも立派にJSONとして成り立ってしまうので、 # `jo` がオブジェクト出ない場合は探したいキー名をプロパティには # 持っていない(そもそもプロパティを持ってない)のだから空リストを返す if type(jo) is not dict: return [] # ここまで来るとJSONオブジェクトしかここにきていないはず if key in jo: # JSONオブジェクト内に欲しいキーがあればそれを返す return [jo[key]] # JSONオブジェクト内に欲しいキーがなればプロパティの中をさらに探索 return sum( map( walk_json, filter( # JSONオブジェクトか配列以外はキーをプロパティに持つようなことはないため、 # 探索する必要はない lambda jo: type(jo) is dict or type(jo) list, jo.values() ), [] )
nodenvをgit cloneしただけだとinstallサブコマンドが現れない
この手のツールにありがち。
gitからクローンしたものだけだとinstall
サブコマンドが現れない。
$ nodenv install nodenv: no such command 'install' $ node help Usage: nodenv <command> [<args>] Some useful nodenv commands are: commands List all available nodenv commands local Set or show the local application-specific Node version global Set or show the global Node version shell Set or show the shell-specific Node version rehash Rehash nodenv shims (run this after installing executables) version Show the current Node version and its origin versions List installed Node versions which Display the full path to an executable whence List all Node versions that contain the given executable See 'nodenv help <command>' for information on a specific command. For full documentation, see: https://github.com/nodenv/nodenv#readme
解決方法
解決する手順は単純で、node-buildプラグインを所定の場所にインストールしてやるだけ。 詳しくはリポジトリを見ればインストール方法が書いてある。 もちろん、nodenvを入れてる前提。
$ mkdir -p (nodenv root)/plugins $ git clone https://github.com/nodenv/node-build.git (nodenv root)/plugins/node-build Cloning into '/home/hoge/.nodenv/plugins/node-build'... remote: Enumerating objects: 116, done. remote: Counting objects: 100% (116/116), done. remote: Compressing objects: 100% (90/90), done. remote: Total 19431 (delta 31), reused 45 (delta 15), pack-reused 19315 Receiving objects: 100% (19431/19431), 3.45 MiB | 3.33 MiB/s, done. Resolving deltas: 100% (12452/12452), done. $ nodenv help Usage: nodenv <command> [<args>] Some useful nodenv commands are: commands List all available nodenv commands local Set or show the local application-specific Node version global Set or show the global Node version shell Set or show the shell-specific Node version install Install a Node version using node-build uninstall Uninstall a specific Node version rehash Rehash nodenv shims (run this after installing executables) version Show the current Node version and its origin versions List installed Node versions which Display the full path to an executable whence List all Node versions that contain the given executable See 'nodenv help <command>' for information on a specific command. For full documentation, see: https://github.com/nodenv/nodenv#readme $ nodenv install --list 0.1.14 0.1.15 0.1.16 0.1.17 0.1.18 0.1.19 0.1.20 0.1.21 0.1.22 0.1.23 0.1.24 0.1.25 ...
C++とOpenSSLライブラリを利用してデータの暗号化・復号化をAES-CBCで行う
C++でまともに暗号化・復号化するサンプルをやっている例を見ないので書く。 実際にはsaltがどうだとかパディングがどうだとかストレッチングがどうだとか暗号化する前にデータを圧縮する話だとかは出さない。 運用上は重要だけど、そういうのは後付けできる。 問題はOpenSSLをC++でまともにあつかう例がない方なので、ここではこのはなしをざっくり切ってどのように記述するのかを解説していく。
方針
OpenSSLライブラリを利用する小さなAES-CBCモードに限定したopensslコマンドを実装する。
小さいといっても興が乗って300行程になってしまったが、問題はないだろう。
実際に重要なところは合わせても100行も行かない。
また、ここではC++17前提で実装を進める。
それ以前のC++を利用する方は適宜読み替えて欲しい。
ただ、OpenSSLのライブラリはCで書かれているので、中核となる部分はC++11までならコンパイルできるはず(試していない)。
ただ、それもcstdint
ライブラリ内にあるuint8_t
を用いるためなので、おそらくunsigned char
に変更すれば(char
が8 bitな環境であれば)C++98でもコンパイルできる...かもしれない。
興味があれば試して欲しい。
準備
さて、まずはいくつかデータ型を定義しておく。 まずはbyte型を定義する
#include <cstdint> using byte = uint8_t;
C++17ではstd::byteという型があるが、これよりは符号なし8 bit整数を利用することを明確にしたいのでuint8_tの方を採用する。
さらに、この型を用いてバイトストリーム用の型を定義する。
#include <vector> #include <cstdint> using byte_stream = std::vector<byte>;
8 bitな動的配列は全てこのbyte_stream
から作成する。
また、AES-CBCにおける鍵を表す型を定義する
#include <type_traits> #include <array> #include <cstddef> template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > using key = std::array<byte, key_size / 8>;
template
やstd::enable_if_t
がはいってきて何やらややこしい。
しかし、やりたいことは単純で、鍵長key_size
が128 bitか192 bitか256 bitなものしか鍵の型を生成できないように制限しているだけだ。
また、key_size / 8
をしているのはkey_size
の単位がbitでarrayが格納する要素の型は8 bit長なため、要素数はkey_size
bitを要素の型の長さ8 bitで割ったものとなるためだ。
最後にAES-CBCにはIVが必要となるので、これを表現する型を導入する。
#include <array> const auto BLOCK_SIZE = 16; using iv = std::array<byte, BLOCK_SIZE>;
BLOCK_SIZE == 16
なのはAESではブロック長が128 bit固定であり、IVもそのブロック長にする必要があるので、byte == uint8_t
からその要素数が128 / 8 == 16
となるため。
コマンドのインターフェース
今回用意するコマンドのインターフェースは次のようにしようと思う。
- 標準入力からデータを受け取って、受け取ったデータを暗号化した上で標準出力へ出力するサブコマンド
enc
を持つ - 標準入力からデータを受け取って、受け取ったデータを復号化した上で標準出力へ出力するサブコマンド
dec
を持つ
どちらも標準入力から受け取るので、main関数はこんな感じでいいだろう。
#include <iostream> int main(int argc, char* argv[]) { if (argc < 1) { std::cerr << "no subcommand" << std::endl; } auto subcommand = std::string(argv[1]); if (subcommand == "enc") { } else if (subcommand = "dec") { } }
ただ、これだけだとターミナルに移ったときに使い方がわからないので、help
サブコマンドとついでで-h
オプションをコマンドに追加しておこう。
#include <iostream> #include <unistd.h> #include <string> int main(int argc, char* argv[]) { int opt; // -hオプションを追加するためにgetopt関数を使った while ((opt = getopt(argc, argv, "h")) != -1) { switch (opt) { case 'h': usage(program_path); return 0; default: return 1; } } if (argc < 1) { std::cerr << "no subcommand" << std::endl; } auto subcommand = std::string(argv[1]); if (subcommand == "help") { usage(argv[0]); return 0; } if (subcommand == "enc") { } else if (subcommand = "dec") { } }
usage
という関数はプログラムのパスargv[0]
を受け取ってその使い方を表示してくれる。
#include <filesystem> #include <iostream> auto usage(const std::filesystem::path& program_path) -> void { const auto program_name = program_path.filename().generic_string(); std::cout << "OpenSSLライブラリの使用方法のサンプル" << std::endl << "Usage:" << std::endl << " " << program_name << " enc" << std::endl << " " << program_name << " dec" << std::endl << " " << program_name << " help" << std::endl << " " << program_name << " -h" << std::endl << "Arguments:" << std::endl << " enc" << std::endl << " 標準入力から受け取ったデータを暗号化し、" << std::endl << " 標準出力へ出力する" << std::endl << " dec" << std::endl << " 標準入力から受け取ったデータを復号化し、" << std::endl << " 標準出力へ出力する" << std::endl << " help" << std::endl << " ヘルプを表示する" << std::endl << "Options:" << std::endl << " -h" << std::endl << " ヘルプを表示する" << std::endl; }
AESの暗号化と復号化のインターフェースはいずれも鍵とIV、そしてデータを必要とするので、次のような形で良いだろう。
#include <type_traits> #include <cstddef> template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto encrypt( const key<key_size>& key, const byte_stream& data, const iv& iv ) -> byte_stream; template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto decrypt( const key<key_size>& key, const byte_stream& data, const iv& iv ) -> byte_stream;
いずれも鍵、IV、データを受け取ってデータを暗号化・復号化した結果であるbyte_stream
型なデータを返す。
インターフェースはどちらも同じなので、データを暗号化・復号化した結果を標準出力に返すために次のような関数を用意する
#include <type_traits> #include <functional> #include <stdexcept> #include <algorithm> #include <cstddef> template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto print_data_while_encoding( const byte_stream& original_key, const byte_stream& data, const byte_stream& original_iv, const std::function<byte_stream(const key<key_size>&, const byte_stream&, const iv&)>& encoder ) -> void { iv iv; key<key_size> key; if (iv.size() != original_iv.size()) { throw std::invalid_argument("iv length must be 128 bit "); } if (original_key.size() != key.size()) { throw std::invalid_argument("invalid key size"); } std::copy(original_iv.begin(), original_iv.end(), iv.begin()); std::copy(original_key.begin(), original_key.end(), key.begin()); auto encoded_data = encoder(key, data, iv); for (const auto byte_value : encoded_data) { std::cout << byte_value; } }
これは鍵、IV、データだけでなく、そららのデータを受け取って暗号化・復号化を行う関数encoder
を受け取っている。
途中、byte_stream
型で受け取った鍵とIVのデータ型を変換しているが、それ以外はencoder
に通し、その結果を表示する部分と引数のチェックを行っている部分とわかれている。
OpenSSLによるAES-CBC暗号化・復号化
本題のOpenSSLによる暗号化・復号化に入っていく。
暗号化
最初に暗号化を行うencrypt
関数の実装を行う。
まずは、次のように書く。
#include <openssl/evp.h> #include <type_traits> #include <cstddef> template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto encrypt( const key<key_size>& key, const byte_stream& data, const iv& iv ) -> byte_stream { EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); }
このctx
を暗号化が終わるまで一貫して用いる。
このctx
に対して暗号化モードや必要な設定・鍵・IVなどを保存した上でデータを暗号化・復号化するという仕組みになっている。
初期化コードを書いたらctx
に暗号化スイートを設定しておくようにコーディングする。
#include <openssl/evp.h> #include <type_traits> #include <cstddef> template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto encrypt( const key<key_size>& key, const byte_stream& data, const iv& iv ) -> byte_stream { EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); const EVP_CIPHER* cipher_suite; switch (key_size) { case 128: cipher_suite = EVP_aes_128_cbc(); break; case 192: cipher_suite = EVP_aes_192_cbc(); break; case 256: cipher_suite = EVP_aes_256_cbc(); break; } }
EVP_aes_(key-size)_cbc
という関数がAES-CBC暗号化スイートの設定を作成し、返してくれる。
スイートの設定を作成したら、その設定と鍵、IVをctx
に設定する。
#include <openssl/evp.h> #include <type_traits> #include <cstddef> template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto encrypt( const key<key_size>& key, const byte_stream& data, const iv& iv ) -> byte_stream { EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); const EVP_CIPHER* cipher_suite; switch (key_size) { case 128: cipher_suite = EVP_aes_128_cbc(); break; case 192: cipher_suite = EVP_aes_192_cbc(); break; case 256: cipher_suite = EVP_aes_256_cbc(); break; } EVP_EncryptInit(ctx, cipher_suite, key.data(), iv.data()); }
以上でAES-CBCによる暗号化を行う準備はととのった。 これから実際に暗号化処理を行う部分を実装するが、実は次のようなコードを追加するだけで大部分の実装が終わる。
#include <openssl/evp.h> #include <type_traits> #include <cstddef> template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto encrypt( const key<key_size>& key, const byte_stream& data, const iv& iv ) -> byte_stream { // 初期化処理は省略 auto encrypted_data = byte_stream( raw_data.size() + (BLOCK_SIZE - raw_data.size() % BLOCK_SIZE) ); int len = 0; EVP_EncryptUpdate(ctx, encrypted_data.data(), &len, data.data(), raw_data.size() }
encrypted_data
のデータサイズの調整をおこなっている式についてはすぐ後で触れるので、ここでは忘れて欲しい。
最後に、データの後処理を行う。
#include <openssl/evp.h> #include <type_traits> #include <cstddef> template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto encrypt( const key<key_size>& key, const byte_stream& data, const iv& iv ) -> byte_stream { // 初期化処理と暗号化処理は省略 EVP_EncryptFinal(ctx, encrypted_data.data() + len, &len) EVP_CIPHER_CTX_free(ctx); return encrypted_data; }
最後にこのような処理をしなければならないのはAESではデータサイズがブロック長単位になっている必要があり、その部分の暗号化処理を行う必要があるからだ。 AESではブロック長は128 bitであることを思いだそう。 AESでは暗号化・復号化処理は1ブロック毎に行われる。 つまり、AESで処理されるデータ長は128 bit単位になっていなければならない。 しかし、データ長のbit数がAESで処理する128 bit長で割り切れない、すなわちデータ長が128 bit単位になっていないとき、最後1つ手前の128 bit長のブロックを処理したあとにはみ出した分のデータを処理するときにそのはみ出したデータの長さはちょうど128 bitでない場合がある。 そもそもほとんどのデータはちょうどそのデータ長が128 bitで割り切れるということは稀だろう。 そこで、パディングというある一定のルールに乗っ取ったデータをデータの末尾に追加するという処理を施してデータ長を128 bit長に帳尻合わせする必要がでてくる。 しかし、ここではOpenSSLがどのパディングを使用するかやそのパディングの詳細については述べない。 各自興味があれば調べて欲しい。
実はデータ長がピッタリ128 bitのながさであってももう1ブロック分のパディング処理が行われる。 これには理由があって、パディングがあるデータとないデータの区別をするのを頑張るよりは、128 bit長ちょうどのデータに対しても1ブロック分のパディング処理を施しておくことで処理を共通して行える上にどこからどこまでがパディングでデータであるかの区別を見分けやすくできるというメリットがある。
とにかく、最後のはみだしたデータはパディングという処理を施した上でまだ暗号化されていないはみだしたデータを暗号化する必要がある。
そこで、len
に暗号化できたデータ長を保存しておき、encrypted_data
の先頭アドレスencrypted_data.data()
にlen
分足した、パディングが施されたはみだしているデータに対して暗号化するという形をとる。
先程のencrypted_data
のサイズを調整しているのはこの処理のためである。
元々はみだすデータサイズはデータサイズ % ブロック長(128 bit)
でわかるので、パディングされるデータはブロック長(128 bit) - データサイズ % ブロック長(128 bit)
で求められるので、パディング分だけ保存する配列を伸ばしておく必要があったのだ。
最後に、ctr
は動的にメモリー確保されてているのでEVP_CIPHER_CTX_free
でメモリーを開放する。
このとき、設定された鍵やIV以外のOpenSSL側で用意するEVP_aes_key-size_cbc
のような動的に確保されているメモリーが開放される。
まとめると、encrypt
関数は次のような形になる。
template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto encrypt( const key<key_size>& key, const byte_stream& raw_data, const iv& iv ) -> byte_stream { auto ctx = EVP_CIPHER_CTX_new(); if (ctx == nullptr) { throw "cannot initialize OpenSSL cipher context"; } const EVP_CIPHER* cipher_suite; switch (key_size) { case 128: cipher_suite = EVP_aes_128_cbc(); break; case 192: cipher_suite = EVP_aes_192_cbc(); break; case 256: cipher_suite = EVP_aes_256_cbc(); break; } if (cipher_suite == nullptr) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot get cipher suite"; } if (EVP_EncryptInit(ctx, cipher_suite, key.data(), iv.data()) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot construct OpenSSL cipher context"; } auto encrypted_data = byte_stream( raw_data.size() + (BLOCK_SIZE - raw_data.size() % BLOCK_SIZE) ); int len = 0; if (EVP_EncryptUpdate(ctx, encrypted_data.data(), &len, raw_data.data(), raw_data.size()) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot encrypt data"; } if (EVP_EncryptFinal(ctx, encrypted_data.data() + len, &len) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot padding"; } EVP_CIPHER_CTX_free(ctx); return encrypted_data; }
解説では省いていたエラー処理をここでは入れてある。
復号化
実は暗号化処理と復号化処理はほとんど同じような操作でできるので、解説することは少ない。 なので、いきなりコードを載せてしまう。
template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto decrypt( const key<key_size>& key, const byte_stream& encrypted_data, const iv& iv ) -> byte_stream { EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (ctx == nullptr) { throw "cannot initialize OpenSSL cipher context"; } const EVP_CIPHER* cipher_suite; switch (key_size) { case 128: cipher_suite = EVP_aes_128_cbc(); break; case 192: cipher_suite = EVP_aes_192_cbc(); break; case 256: cipher_suite = EVP_aes_256_cbc(); break; } if (cipher_suite == nullptr) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot get cipher suite"; } if (EVP_DecryptInit(ctx, cipher_suite, key.data(), iv.data()) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot construct OpenSSL cipher context"; } auto plain_data = byte_stream(encrypted_data.size()); int plain_data_len = 0; if (EVP_DecryptUpdate(ctx, plain_data.data(), &plain_data_len, encrypted_data.data(), encrypted_data.size()) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot encrypt data"; } int remain_plain_data_len; if (EVP_DecryptFinal(ctx, plain_data.data() + plain_data_len, &remain_plain_data_len) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot padding"; } plain_data.resize(plain_data_len + remain_plain_data_len); EVP_CIPHER_CTX_free(ctx); return plain_data; }
ほとんどEVP_EncryptXXX
がEVP_DecryptXXX
になっただけである。
ただ、最後にデータからパディングをはがす処理が異なっているだけである。
ここではそのパディングをはがす処理について解説する。
まず、plain_data_len
には暗号化時にははみだしていなかったデータの復号化後の長さが入っている。
また、remain_plain_data_len
にはパデイングデータをのぞいた暗号化時にはみだしていたデータ分の長さがはいる。
つまり、暗号化前のデータ長はplain_data_len + remain_plain_data_len
である。
パディング処理によってデータの末尾には余計なデータがくっついているので、byte_stream
がvetctor
であることを利用してresize
メソッドによってデータ長を暗号化前のデータ長に合わせてやる。
これによって真の復号化データを得ることができる。
コマンドの拡充
暗号化と復号化はできるようになったが、コマンドは最低限のインターフェースを供えただけで標準入力からデータを読み込んだり、出力したりIVや鍵を設定したりなどの部分が抜け落ちている。 したがってここではこれらを実装していくことにしよう。
まず、IVや鍵を設定しよう。
ただ、これは確認のためであれば全部を0埋めしたデータにしておけばよいだろうし、0埋めだけだと不安なので、デフォルトは0埋めにしておいて、あとでオプションから設定できれば良いだろう。
そこで-i
オプションでIVを受け取り、-k
で鍵を受け取るようにしよう。
ただし、これらはuint8_tな配列でなければならないので、これらオプションに指定する引数はhex string(16進数な数字の羅列)で受け取るようにする。
#include <iostream> #include <cstddef> #include <cstdint> #include <type_traits> #include <unistd.h> #include <string> #include <stdexcept> #include <algorithm> auto convert_hex_string(const std::string& hex_string) -> byte_stream { if (hex_string.size() % 2 != 0) { throw std::invalid_argument("invalid hex string"); } byte_stream byte_array(hex_string.size() / 2); for (std::size_t i = 0; i < hex_string.size(); i += 2) { auto&& byte_value = static_cast<uint8_t>( std::stoi(hex_string.substr(i, 2), nullptr, 16) ); byte_array[i / 2] = byte_value; } return byte_array; } int main(int argc, char* argv[]) { const auto program_path = argv[0]; byte_stream iv(BLOCK_SIZE, 0); byte_stream key(128 / 8, 0); int opt; while ((opt = getopt(argc, argv, "hi:k:V")) != -1) { switch (opt) { case 'h': usage(program_path); return 0; case 'i': try { iv = convert_hex_string(optarg); } catch (const std::invalid_argument& error) { std::cerr << "invalid iv format" << std::endl; return 1; } catch (const std::out_of_range& error) { std::cerr << error.what() << std::endl; return 1; } if (iv.size() != BLOCK_SIZE) { std::cerr << "iv must be 128 bit" << std::endl; } break; case 'k': try { key = convert_hex_string(optarg); } catch (const std::invalid_argument& error) { std::cout << "invalid key format" << std::endl; return 1; } catch (const std::out_of_range& error) { std::cerr << error.what() << std::endl; return 1; } if (!(key.size() == 128 / 8 || key.size() == 192 / 8 || key.size() == 256 / 8)) { std::cerr << "key size must be 128, 192 or 256 bit" << std::endl; } break; default: return 1; } } if (argc - optind < 1) { std::cerr << "no subcommand" << std::endl; return 1; } const auto subcommand = std::string(argv[optind]); if (subcommand == "help") { usage(program_path); return 0; } if (subcommand == "enc") { } else if (subcommand == "dec") { } else { std::cerr << "unknown command: " << subcommand << std::endl; return 1; } }
convert_hex_string
が受け取ったhex stringを符号無し8 bit整数配列に直す関数である。
この関数は2文字ずつ読んでいき、その2文字を8 bit整数を表す16進数文字列だと見なして符号無し8 bit整数に変換し、それをvectorにつめていくというものになっている。
また、-i
と-v
はそれぞれhex stringを引数として受け取り、convert_hex_string
にかけて符号無し8 bit整数配列に変換したうえで、各鍵長、IV長に当っているかをみている。
-i
と-v
オプションを追加したので、usage
関数をアップデートする。
auto usage(const std::filesystem::path& program_path) -> void { const auto program_name = program_path.filename().generic_string(); std::cout << "OpenSSLライブラリの使用方法のサンプル" << std::endl << "Usage:" << std::endl << " " << program_name << " enc" << std::endl << " " << program_name << " dec" << std::endl << " " << program_name << " help" << std::endl << " " << program_name << " -h" << std::endl << "Arguments:" << std::endl << " enc" << std::endl << " 標準入力から受け取ったデータを暗号化し、" << std::endl << " 標準出力へ出力する" << std::endl << " dec" << std::endl << " 標準入力から受け取ったデータを復号化し、" << std::endl << " 標準出力へ出力する" << std::endl << " help" << std::endl << " ヘルプを表示する" << std::endl << "Options:" << std::endl << " -h" << std::endl << " ヘルプを表示する" << std::endl << " -i <iv>" << std::endl << " <iv>で指定された値でIVを作成する。" << std::endl << " <iv>はHex String形式(例: AB01DF93BC)で" << std::endl << " 長さが128bitになるようにする必要がある" << std::endl << " -k <key>" << std::endl << " <key>で指定された値で鍵を作成する。" << std::endl << " <key>はhex String形式で長さが128 bit," << std::endl << " 192 bit, 256 bitのいずれかの" << std::endl << " 長さになる必要がある。" << std::endl; }
最後に、データを標準入力からうけとる部分と、暗号化・復号化したものを標準出力へ出力する部分を実装しよう。 データは符号無し8 bit配列に収めてやる。
#include <cstdio> #include <cstdint> int main(int argc, char* argv[]) { // オプション処理部分等は省略 byte_stream data; while (true) { auto byte_value = getc(stdin); if (byte_value == EOF) { break; } data.emplace_back(byte_value); } // データの暗号化・復号化部分は省略 }
データを暗号化・復号化した上で標準出力に出力するようにしてやる。
#include <iostream> #include <cstdint> int main(int argc, char* argv[]) { // オプション処理部分やデータの入力部分は省略 if (subcommand == "enc") { switch (key.size() * 8) { case 128: print_data_while_encoding<128>(key, data, iv, encrypt<128>); break; case 192: print_data_while_encoding<192>(key, data, iv, encrypt<192>); break; case 256: print_data_while_encoding<256>(key, data, iv, encrypt<256>); break; default: std::cout << "invalid key type" << std::endl; return 1; } } else if (subcommand == "dec") { switch (key.size() * 8) { case 128: print_data_while_encoding<128>(key, data, iv, decrypt<128>); break; case 192: print_data_while_encoding<192>(key, data, iv, decrypt<192>); break; case 256: print_data_while_encoding<256>(key, data, iv, decrypt<256>); break; default: std::cout << "invalid key type" << std::endl; return 1; } } else { std::cerr << "unknown command: " << subcommand << std::endl; return 1; } }
コードのまとめ
以上のコードをまとめると、最終的には次のようなコードができている
#include <openssl/evp.h> #include <iostream> #include <vector> #include <array> #include <cstdint> #include <cstddef> #include <type_traits> #include <unistd.h> #include <string> #include <filesystem> #include <stdexcept> #include <algorithm> #include <functional> #include <cstdio> const auto BLOCK_SIZE = 16; using byte = uint8_t; using byte_stream = std::vector<byte>; template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > using key = std::array<byte, key_size / 8>; using iv = std::array<byte, BLOCK_SIZE>; template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto encrypt( const key<key_size>& key, const byte_stream& raw_data, const iv& iv ) -> byte_stream { auto ctx = EVP_CIPHER_CTX_new(); if (ctx == nullptr) { throw "cannot initialize OpenSSL cipher context"; } const EVP_CIPHER* cipher_suite; switch (key_size) { case 128: cipher_suite = EVP_aes_128_cbc(); break; case 192: cipher_suite = EVP_aes_192_cbc(); break; case 256: cipher_suite = EVP_aes_256_cbc(); break; } if (cipher_suite == nullptr) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot get cipher suite"; } if (EVP_EncryptInit(ctx, cipher_suite, key.data(), iv.data()) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot construct OpenSSL cipher context"; } auto encrypted_data = byte_stream( raw_data.size() + (BLOCK_SIZE - raw_data.size() % BLOCK_SIZE) ); int len = 0; if (EVP_EncryptUpdate(ctx, encrypted_data.data(), &len, raw_data.data(), raw_data.size()) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot encrypt data"; } if (EVP_EncryptFinal(ctx, encrypted_data.data() + len, &len) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot padding"; } EVP_CIPHER_CTX_free(ctx); return encrypted_data; } template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto decrypt( const key<key_size>& key, const byte_stream& encrypted_data, const iv& iv ) -> byte_stream { EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (ctx == nullptr) { throw "cannot initialize OpenSSL cipher context"; } const EVP_CIPHER* cipher_suite; switch (key_size) { case 128: cipher_suite = EVP_aes_128_cbc(); break; case 192: cipher_suite = EVP_aes_192_cbc(); break; case 256: cipher_suite = EVP_aes_256_cbc(); break; } if (cipher_suite == nullptr) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot get cipher suite"; } if (EVP_DecryptInit(ctx, cipher_suite, key.data(), iv.data()) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot construct OpenSSL cipher context"; } auto plain_data = byte_stream(encrypted_data.size()); int plain_data_len = 0; if (EVP_DecryptUpdate(ctx, plain_data.data(), &plain_data_len, encrypted_data.data(), encrypted_data.size()) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot encrypt data"; } int remain_plain_data_len; if (EVP_DecryptFinal(ctx, plain_data.data() + plain_data_len, &remain_plain_data_len) == 0) { EVP_CIPHER_CTX_cleanup(ctx); throw "cannot padding"; } plain_data.resize(plain_data_len + remain_plain_data_len); EVP_CIPHER_CTX_free(ctx); return plain_data; } auto convert_hex_string(const std::string& hex_string) -> byte_stream { if (hex_string.size() % 2 != 0) { throw std::invalid_argument("invalid hex string"); } byte_stream byte_array(hex_string.size() / 2); for (std::size_t i = 0; i < hex_string.size(); i += 2) { auto&& byte_value = static_cast<uint8_t>( std::stoi(hex_string.substr(i, 2), nullptr, 16) ); byte_array[i / 2] = byte_value; } return byte_array; } auto usage(const std::filesystem::path& program_path) -> void { const auto program_name = program_path.filename().generic_string(); std::cout << "OpenSSLライブラリの使用方法のサンプル" << std::endl << "Usage:" << std::endl << " " << program_name << " enc" << std::endl << " " << program_name << " dec" << std::endl << " " << program_name << " help" << std::endl << " " << program_name << " -h" << std::endl << "Arguments:" << std::endl << " enc" << std::endl << " 標準入力から受け取ったデータを暗号化し、" << std::endl << " 標準出力へ出力する" << std::endl << " dec" << std::endl << " 標準入力から受け取ったデータを復号化し、" << std::endl << " 標準出力へ出力する" << std::endl << " help" << std::endl << " ヘルプを表示する" << std::endl << "Options:" << std::endl << " -h" << std::endl << " ヘルプを表示する" << std::endl << " -i <iv>" << std::endl << " <iv>で指定された値でIVを作成する。" << std::endl << " <iv>はHex String形式(例: AB01DF93BC)で" << std::endl << " 長さが128bitになるようにする必要がある" << std::endl << " -k <key>" << std::endl << " <key>で指定された値で鍵を作成する。" << std::endl << " <key>はhex String形式で長さが128 bit," << std::endl << " 192 bit, 256 bitのいずれかの" << std::endl << " 長さになる必要がある。" << std::endl; } template < size_t key_size, std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr > auto print_data_while_encoding( const byte_stream& original_key, const byte_stream& data, const byte_stream& original_iv, const std::function<byte_stream(const key<key_size>&, const byte_stream&, const iv&)>& encoder ) -> void { iv iv; key<key_size> key; if (iv.size() != original_iv.size()) { throw std::invalid_argument("iv length must be 128 bit "); } if (original_key.size() != key.size()) { throw std::invalid_argument("invalid key size"); } std::copy(original_iv.begin(), original_iv.end(), iv.begin()); std::copy(original_key.begin(), original_key.end(), key.begin()); auto encoded_data = encoder(key, data, iv); for (const auto byte_value : encoded_data) { std::cout << byte_value; } } int main(int argc, char* argv[]) { const auto program_path = argv[0]; byte_stream iv(BLOCK_SIZE, 0); byte_stream key(128 / 8, 0); int opt; while ((opt = getopt(argc, argv, "hi:k:V")) != -1) { switch (opt) { case 'h': usage(program_path); return 0; case 'i': try { iv = convert_hex_string(optarg); } catch (const std::invalid_argument& error) { std::cerr << "invalid iv format" << std::endl; return 1; } catch (const std::out_of_range& error) { std::cerr << error.what() << std::endl; return 1; } if (iv.size() != BLOCK_SIZE) { std::cerr << "iv must be 128 bit" << std::endl; } break; case 'k': try { key = convert_hex_string(optarg); } catch (const std::invalid_argument& error) { std::cout << "invalid key format" << std::endl; return 1; } catch (const std::out_of_range& error) { std::cerr << error.what() << std::endl; return 1; } if (!(key.size() == 128 / 8 || key.size() == 192 / 8 || key.size() == 256 / 8)) { std::cerr << "key size must be 128, 192 or 256 bit" << std::endl; } break; default: return 1; } } if (argc - optind < 1) { std::cerr << "no subcommand" << std::endl; return 1; } const auto subcommand = std::string(argv[optind]); if (subcommand == "help") { usage(program_path); return 0; } byte_stream data; while (true) { auto byte_value = getc(stdin); if (byte_value == EOF) { break; } data.emplace_back(byte_value); } try { if (subcommand == "enc") { switch (key.size() * 8) { case 128: print_data_while_encoding<128>(key, data, iv, encrypt<128>); break; case 192: print_data_while_encoding<192>(key, data, iv, encrypt<192>); break; case 256: print_data_while_encoding<256>(key, data, iv, encrypt<256>); break; default: std::cout << "invalid key type" << std::endl; return 1; } } else if (subcommand == "dec") { switch (key.size() * 8) { case 128: print_data_while_encoding<128>(key, data, iv, decrypt<128>); break; case 192: print_data_while_encoding<192>(key, data, iv, decrypt<192>); break; case 256: print_data_while_encoding<256>(key, data, iv, decrypt<256>); break; default: std::cout << "invalid key type" << std::endl; return 1; } } else { std::cerr << "unknown command: " << subcommand << std::endl; return 1; } } catch (const char* error) { std::cerr << error << std::endl; return 1; } }
ビルド
最後にビルドするが、ファイル名をmain.cpp
として保存した上で次のようなコマンドでビルドできる。
$ g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lcrypto -lssl main.cpp
OpenSSL 1.1ではlibssl
とlibcrypto
をリンクしなければならないが、OpenSSL 1.0では次のコマンドのようにlibsslだけリンクするようにしてビルドすれば良い。
$ g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lssl main.cpp
もしlibsslをリンクできなければfind / -name 'libssl.so*'
を、libcryptoをリンクできなければfind / -name 'libcrypto.so*'
を実行し、次のように実行することでリンクできる。
# libsslがリンクできなければ`-L libssl.so*のファイルがあるディレクトリへのパス`をコマンドに追加する(`*`はワイルドカード) $ g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lcrypto -lssl main.cpp -L libssl.so*のファイルがあるディレクトリのパス # libcryptoがリンクできなければ`-L libcrypto.so*のファイルがあるディレクトリへのパス`をコマンドに追加する $ g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lcrypto -lssl main.cpp -L libcrypto.so*のファイルがあるディレクトリのパス # どちらもなければ`-L libssl.so*のファイルがあるディレクトリへのパス -L libcrypto.so*のファイルがあるディレクトリのパス`をコマンドに追加する $ g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lcrypto -lssl main.cpp -L libcrypto.so*のファイルがあるディレクトリのパス -L libssl.so*のファイルがあるディレクトリへのパス
こうしてできたバイナリを実行する際には先程追加したディレクトリへのパスをLD_LIBRARY_PATHに指定してやる必要がある(LD_LIBRARY_PATH
は場合によっては優先度の高いディレクトリに差し替え用のライブラリを差し込まれて攻撃に利用される可能性があるため、十分に注意してとりあつかう)。
# libsslがリンクできなければこのコマンド $ env LD_LIBRARY_PATH=libssl.so*のファイルのあるディレクトリへのパス:$LD_LIBRARY_PATH ビルドしたコマンド # libcryptoがリンクできなければこのコマンド $ env LD_LIBRARY_PATH=libcrypto.so*のファイルのあるディレクトリへのパス:$LD_LIBRARY_PATH ビルドしたコマンド # どちらもない場合 $ env LD_LIBRARY_PATH=libssl.so*のファイルのあるディレクトリへのパス:libcrypto.so*のファイルのあるディレクトリへのパス:$LD_LIBRARY_PATH ビルドしたコマンド
もし、openssl/evp.h
をインクルードできなければ、find / -name 'evp.h'
を実行し、出力されたパスを元に-I openssl/evp.hのあるディレクトリのパス
というオプションを追加してビルドする
g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lcrypto -lssl main.cpp -I openssl/evp.hのあるディレクトリのパス
opensslコマンドによるテスト
最後に、実装した暗号化・復号化するコマンドの出力がopensslコマンドによって暗号化・復号化をできるかを試してみる。
まず暗号化。
$ bash $ iv="$(head -c 16 /dev/urandom | od -x -A n | tr -d ' \n')"; key="$(head -c 32 /dev/urandom | od -x -A n | tr -d ' \n')"; head -c 1000 /dev/urandom | base64 | tr -d '\n' | xargs echo | tee /dev/stderr | ./a.out -i "$iv" -k "$key" enc | openssl aes-256-cbc -nosalt -d -iv "$iv" -K "$key" | xargs echo 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
次に復号化。
$ bash $ iv="$(head -c 16 /dev/urandom | od -x -A n | tr -d ' \n')"; key="$(head -c 32 /dev/urandom | od -x -A n | tr -d ' \n')"; head -c 1000 /dev/urandom | base64 | tr -d '\n' | xargs echo | tee /dev/stderr | openssl aes-256-cbc -nosalt -e -iv "$iv" -K "$key" | ./a.out -i "$iv" -k "$key" dec | xargs echo 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
あっていそうだ。
スクリプトでやってることは簡単で
GoコードをバイナリにしてYoctoに入れる(ARM)
GoはARMバイナリを直接吐ける。 なので、Goのコンパイラをバイナリで引っ張ってビルド時に走らせればARMバイナリを簡単にビルドしてYoctoへ組み込める。
やるにはレシピに次ようなパッケージを追加して
LICENSE = "CLOSED" SRC_URI_append = " \ https://dl.google.com/go/go1.13.3.linux-amd64.tar.gz;name=go \ file://main.go \ " # go 1.13.3(linux x64版)のチェックサム SRC_URI[go.md5sum] = "e0b36adf4dbb7fa53b477df5d7b1dd8c" SRC_URI[go.sha256sum] = "0804bf02020dceaa8a7d7275ee79f7a142f1996bfd0c39216ccb405f93f994c0" # そのままバイナリをイメージに入れようとすると # 出来上がったバイナリをstripしようとしてイメージの # ビルドに失敗するので、stripしないように設定する INHIBIT_PACKAGE_STRIP = "1" INHIBIT_PACKAGE_DEBUG_SPLIT = "1" export PROG_PATH="${WORKDIR}/${PN}" do_compile() { # ターゲットOSはlinuxに export GOOS=linux # ターゲットのアーキテクチャをARMに export GOARCH=arm # ここでは命令セットはarmv7にしている # 他の命令セットは次を参照のこと # ref. https://github.com/golang/go/wiki/GoArm export GOARM=7 # goコンパイラは${WORKDIR}/go/binにある ${WORKDIR}/go/bin/go build -o ${PROG_PATH} ${WORKDIR}/main.go } do_install() { install -d ${D}/usr/local/bin install -m 755 ${PROG_PATH} ${D}/usr/local/bin/. } FILES_${PN} = " \ /usr/local/bin/${PN} \ "
あとはIMAGE_INSTALL
にパッケージを追加してlayerにレシピを追加すればGoで吐いたバイナリーをイメージに組込むことができる
I2C Toolsの簡単な使い方について
I2C Toolsには
- i2cget
- i2cset
- i2cdetect
- i2ctransfer
- i2cdump
というコマンドが同梱されているが、この内
- i2cget
- i2cset
- i2cdetect
についてコマンドの簡単な使い方を記しておく。
i2cdetect
i2cdetectの文法は次の通り:
i2cdetect -V i2cdetect -l i2cdetect -F i2cbus i2cdetect [-y] [-a] [-q|-r] i2cbus [first last]
i2cdetect -V
はバージョン情報を表示して終了する。
i2cdetect -l
はI2Cのバスを列挙してくれる。
$ i2cdetect -l
i2c-1 i2c 21a4000.i2c I2C adapter
i2c-2 i2c 21a8000.i2c I2C adapter
i2c-0 i2c 21a0000.i2c I2C adapter
この表示されるi2c-1
やi2c-2
などのi2c-x
のx
の部分が接続されているI2Cバスの識別子となっている。
後のコマンドでi2cbus
で指定すべきものはこのI2Cバスの識別子となっている。
i2cdetect -F i2cbus
はi2cbus
に指定されたI2Cバスが行える機能についての情報を表示してくれる
$ i2cdetect -F 1 I2C yes SMBus Quick Command yes SMBus Send Byte yes SMBus Receive Byte yes SMBus Write Byte yes SMBus Read Byte yes SMBus Write Word yes SMBus Read Word yes SMBus Process Call yes SMBus Block Write yes SMBus Block Read yes SMBus Block Process Call no SMBus PEC yes I2C Block Write yes I2C Block Read yes
i2cdetect [-y] [-a] [-q|-r] i2cbus [first last]
は指定したi2cbus
に対して(first
とlast
が0x18などの16進数アドレスで指定されていればfirst
からlast
まで)Write要求を発行して、反応があったスレーブアドレスを表示する。
-y
は対話形式にしないようにするためのオプションで、-a
は特別な理由がない限り使わないので無視してよい。
-q
はSMBusのQuick Command機能を利用してWriteするように試みているが、必要でなければ使わないほうがよい。
-r
はSMBusのRead Byte機能を利用してWrite要求ではなくRead要求でスキャンを試みるているが、Write onlyなデバイスに対してRead要求を行うとロックされて反応が返ってこなくなる可能性があるため、必要にならない限りは使わない方が良い。
したがって基本的な使い方は
$ i2cdetect -y 1
となる。これを実行すると次のような結果が返ってくる
0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- UU -- UU -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- 4a -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
4bitを境として、一番左の列はアドレスの上位桁(0x18
なら0x10
)を表しており、一番上の行はアドレスの下位4桁(0xf8
なら0x0x8)
を表している。
表中に表示されている4a
は反応のあったアドレスを表しており、上位桁と下位桁を併せた表記がなされている。
逆に--
は反応がなかったことを表している。
また、UU
は反応があったが、ドライバなどによって使用されていることを表している。
i2cget
i2cgetの文法は次の通り:
i2cget -V i2cget [-f] [-y] i2cbus chip-address [data-address [mode]]
-V
はバージョン情報を表示して終了するだけなので、2つ目の文法についてここでは説明する。
i2cbus
は/sys/bus/i2c/devices
の下にあるi2c-x
(x
は数字)というディレクトリのx
部分か、i2cdetect -l
を実行して表示されるi2c-x
のx
を指定する。
たとえば、自分の環境であれば、
$ ls /sys/bus/i2c/devices
i2c-0 i2c-1 i2c-2 i2c-3 i2c-4 i2c-5 i2c-6
があったので、i2cbus
に1
から6
までを指定することができる。
-f
は強制的に実行する際に用いる。
例えばi2cdetect
でUU
と表示されたアドレスに対して強制的にそのアドレスにあるスレーブデバイスから強制的に値を読みたい場合などに利用される。
-y
はi2cdetect
と同じで対話モードを無視するためのオプションとなっている。
chip-address
にはi2cdetect
で調べたときに反応があったスレーブアドレスを指定する。
例えば
$ i2cdetect -y 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- UU -- UU -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- 4a -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
となっている場合には0x4a
を指定したりする。
data-address
は利用できるスレーブアドレス下にあるデバイスに対してRead/Writeを行いたいアドレスを指定する。たとえば、WHO_AM_Iレジスタ(0x0f
)を読みたい場合は次のようにする
$ i2cdetect -y 1 0x4a 0x0f
最後のmode
には
b
: 1バイト読み込むw
: 1ワード(16bit)読み込むc
: 1ワード書き込んで読み込む
を指定でき、それぞれにプリフィクスとしてp
をつけることができる(例: cp
)。
p
はPEC送信を有効にする場合につける。
i2cset
i2cset
の文法は次の通り:
i2cset -V i2cset [-f] [-y] [-m mask] [-r] i2cbus chip-address data-address [value] ... [mode]
i2cset -V
は他と同様バージョン情報を表示して終了し。-f
、-y
オプションはi2cget
の-f
、-y
と同じものとなっている。
また、i2cbus
やchip-address
、data-address
もi2cget
と意味は同じため割愛する。
i2cset
はレジスターに値をセットするために使用されるコマンドだが、使用する値はvalue
を指定することで実現する。
ただし、複数のバイトを指定したいときにはmode
にs
かi
を指定する必要がある。
-m mask
にはマスク値を指定することができる。
このオプションを指定すると、value
に指定されている値全てに指定したmask
値がマスクされるようになっている。
最後のmode
に
- b: 1バイト書き込む
- w: 1ワード(16bit)書き込む
- s: SMBusブロックモードによる書き込み
- i: I2Cブロックモードによる書き込み
を指定することができ、i
を除く3つにはp
をプリフィクスとして付けることができる(例: cp
)。
p
はPEC送信を有効にする際に利用する。
i3で通知を出す
meta-qtにおけるQtWebEngineで音を出す方法
概要
ALSAが既にレイヤーに追加されていても、
QtWebEngine側にWebRTCの設定を入れる必要があるため、そのままgetUserMedia
などでメディアを取得することはできない。
ここでは、その追加手順について説明する。
手順
まず、レシピファイルを追加する(ここでは、自分のレイヤー名をmeta-hoge
としておく)。
ディレクトリの構成はmeta-qt5ではqtwebengineのレシピはrecipies-qt/qt5
の下にあるので、それに合わせる。
$ mkdir -p meta-hoge/recipies-qt/qt5 $ touch meta-hoge/recipies-qt/qt5/qtwebengine_git.bbappend
meta-hoge/recipies-qt/qt5/qtwebengine_git.bbappend
の中には次のように書く。
PKGCONFIG += "webrtc"