この記事は個人開発 Advent Calendar 2020 6日目の記事です。
昨日は @yakipudding さんによる「Reactで作成したポートフォリオサイトをNextに移行した」でした。
改良後のデザインがおっしゃられるとおりとてもスッキリしていて、センスの良さが本当に羨ましかったです。
はじめに
作った理由はこちらに書いたとおりです
3ヶ月ぐらいでできるかと見積もっていたのですが実際はリリースまで6ヶ月もかかってしまい、自分の effort estimation の精度に自信をなくして落ち込んだりもしたのですが、単に私の productivity が普通のエンジニアの半分ぐらいしかなかっただけなのかもしれない1と気をとりなおして今は元気な私はソフトウェア工学屋崩れ2の野良犬です
(U^ω^)
関係ないのですが Software Engineering at Google 読んでて、エンピリカルなソフトウェア工学屋さんの言ういわゆる effort の事を一貫して endeavor って呼んでるのが珍しい言い方3だなとおもいました
こちらでは RPi の話しか書かなかったのですが、RPi4 だけでなく Linux でも Mac でも使える汎用5のコピープロテクションですので、皆様もこちらに書いたみたいな目にあったりされましたら(されないように)使っていただければと思いまして、老爺心ながら使い方をご紹介させていただきます次第です
サービスの紹介
Koshitno はSansi ライブラリと共にアプリケーションのコピープロテクションを提供するクライアント - サーバ型の No license file な application anti-piracy サービスです
コピープロテクションって一般にライセンス定義ファイルとかライセンスキーとかを使ってアプリケーションを unauthorized なコピーから保護するものなのですが、ストレージがむき出しな RPi だとそれが弱点になると考え、それらを使わないで実現するためにクライアント-サーバ型で実装しました
クライアント-サーバ型であることで、ネットワークに接続していないスタンドアロンなアプリでは利用できないというネガティブな副作用があるのですが、逆に、扱いが煩わしく失敗すると事故の原因になりかねないライセンスファイルをつかわないですむのでお気軽お手軽というポジティブな副作用も生じております。世の中捨てる神あれば拾う神あり、人間万事塞翁が馬ということなのでございましょう
koshinto と sansi のコピープロテクションの仕組み
こちらに書いたとおりなのですが、sansi ライブラリはユーザーがダウンロードする時からその内部にアプリケーションの束縛条件を一意に表す ID (以下、Bind_id と呼びます)をバイナリで埋め込んであります
sansi ライブラリの提供する confirm() 関数は、呼ばれると各種の環境情報(デバイスのシリアルナンバー、IP アドレス、Mac アドレス等)を収集して、Bind_id と一緒に Koshinto に送信します
koshinto サーバは sansi から送信された情報と、内部に保存するアプリケーションの束縛条件をチェックして、OK/NG の返答をセキュアに返します
koshinto からの OK/NG の応答を confirm() は戻り値でアプリケーションにします
以上、アプリケーションは起動時に confirm() を呼んで、戻り値が NG だったら exit するように一文を入れるだけで copy protection の実装が完了するわけでございます。これは簡単!
この仕組自体の anti-tamper についてはこちらに紹介いたしました
使用例
サンプルアプリケーション
10秒間の秒読みを行う、シチュエーションによってはとても practical なアプリケーションです
/*
* 10 second countdown, copy guarded by sansi
*
* @author Dr. Takeyuki UEDA
* @copyright Copyright© Atelier UEDA 2020 - All rights reserved.
*/
#include <stdio.h> // for printf
#include <unistd.h> // for seep
void tenseccount();
int main(){
tenseccount();
return 0;
}
void tenseccount(){
int i;
printf("start 10 second countdown\n");
for (i=10; i>0; i--){
printf("%d\n",i);
sleep(1);
}
printf("0!\n");
}
Practical なので悪意の第三者による意図せぬコピー、著作権侵害をふせぐために sansi を組み込んで保護しましょう
sansi の組み込み
/*
* 10 second countdown, copy guarded by sansi
*
* @author Dr. Takeyuki UEDA
* @copyright Copyright© Atelier UEDA 2020 - All rights reserved.
*/
#include "sansi.h" // for sansi libraries
#include <stdio.h> // for printf
#include <unistd.h> // for seep
void tenseccount();
int main(){
if (ok_confirmed == confirm(NULL, NULL, NULL)){
printf("OK\n");
} else {
printf("NG\n");
return -1;
}
tenseccount();
return 0;
}
void tenseccount(){
int i;
printf("start 10 second countdown\n");
for (i=10; i>0; i--){
printf("%d\n",i);
sleep(1);
}
printf("0!\n");
}
追加したのは以下の2点で
- 8行目: sansi.h のインクルード
- 16-21行目: アプリの最初に confirm() を呼び、戻り値が ok_confirmed でなければ異常終了
サンプルプロジェクトの clone
完成品はこちらにございますので、git clone して
MacBook-Air:tmp takeyuki$ git clone https://github.com/UedaTakeyuki/sansi_examples.git
sansi_examples/c フォルダに移動してください
MacBook-Air:tmp takeyuki$ cd sansi_examples/c
ls してみるといろいろなファイルがあります
MacBook-Air:c takeyuki$ ls
README.md compile.sh main.c main.simple.c sansi.h
左からそれぞれ README、 compile スクリプト、別のサンプルの main.c (本稿ではつかいません)、先程紹介した main.simple.c、そして sansi のヘッダーファイルです
ここでは紹介しませんが、C アプリの他に C++, go, python, bash の組込例も用意させていただきました
go は cgo で sansi をリンクする例をご紹介させていただきました。vlang も同様にできるように思っているのですけどまだ試せていません。どなたか pull req いただければ感謝の言葉もございません
python は nuitka でコンパイルして strip すること、bash は shc とかでバイナリ化する前提です
sansi のダウンロード
Koshinto にログインして Home から [Binds]-[All] を選択してください
アカウントを作った直後だと、一つだけですが Bind がすでにあるのでこれを選択して
メニューから Sansi library Download を開き
アプリケーションのターゲットに合わせてライブラリをダウンロードしてください
ダウンロードしたライブラリを先程 git clone したプロジェクトにコピーしてください
MacBook-Air:c takeyuki$ cp /Users/takeyuki/Downloads/libsansi_ZbPdGoGyrNkQ_mac_v1.1.o .
MacBook-Air:c takeyuki$ ls
README.md main.c
compile.sh main.simple.c
libsansi_ZbPdGoGyrNkQ_mac_v1.1.o sansi.h
アプリケーションのコンパイル
コンパイルスクリプトの使い方は
MacBook-Air:c takeyuki$ ./compile.sh -h
Usage: ./compile.sh [-h][-c][-g][-m][-o obj] [source] [libsansi]
[source]: compiling source file, default is 'main.sample.c'
[libsansi]: path for linking 'libsansi….o', default is found it in cwd automatically
[-h]: show this usage and exit
[-c]: compile for linux by clang
[-g]: compile for linux by gcc, this is default
[-m]: compile for mac by clang
[-o obj] set compiled object file name, default is a.out
MacBook-Air:c takeyuki$ ./compile.sh -m
デフォルトで main.sample.c をコンパイルして、デフォルトで a.out をつくります。デフォルトで sansi ライブラリをよしなにさがしてくれるので便利です
Mac 用のコンパイルの場合
Mac 用のコンパイルであることを指示する -m を指定します
MacBook-Air:c takeyuki$ ./compile.sh -m
source = main.simple.c
libsansi = libsansi_ZbPdGoGyrNkQ_mac_v1.1.o
compiler = clang
obj = a.out
compiling…
尚、sansi は OpenSSL に依存するのですが、Mac だと OpenSSL がインストールされていないかもしれません。その旨のエラーがでた場合はインストールしてください
Linux 用のコンパイルの場合
Linux の場合はなにも指定する必要がありません
pi@raspberrypi:~/sansi_examples/c $ ./compile.sh
source = main.simple.c
libsansi = libsansi_ZbPdGoGyrNkQ_arm_v1.1.o
compiler = gcc
obj = a.out
compiling…
-c を指定すると gcc の代わりに clang でコンパイルします
pi@raspberrypi:~/sansi_examples/c $ ./compile.sh -c
source = main.simple.c
libsansi = libsansi_ZbPdGoGyrNkQ_arm_v1.1.o
compiler = clang
obj = a.out
compiling…
例1. アプリケーションを Mac のシリアル番号に束縛する
Koshinto に戻って Bind のステータスを確認すると Not Active になっているはずです
アプリケーションを Bind する方法は以下の3通りがあります
- 直接、値を入力して Bind
- sansi が取得してきた値を確認して Bind
- sansi が取得してきた値に自動的に Bind
正常な Bind のステータスは5通りあって、上記の3つの方法によってステータスの遷移がかわります。詳細については
こちらを参照ください
1. 事前に取得した key の値に直接束縛する場合
Koshinto に戻って Bind の [Keys] を開きます
調べておいた Mac のシリアルナンバーを Platform Serial Number に入力して lock を✓し、右下の UPDATE をクリックします
Status を開き、Binding を選択して右下の UPDATE をクリックします
Mac に戻って a.out がブロックされずに実行できているのが確認できます
MacBook-Air:c takeyuki$ ./a.out
OK
start 10 second countdown
10
9
8
7
6
5
4
3
2
1
0!
2. sansi が取得して送信してきた値から選択して束縛する場合
Mac に戻り、先程コンパイルした a.out を実行します
結果は NG になりますが、sansi が取得したMac の環境の各種値が Koshinto に送信されて Bind に反映されているはずです
MacBook-Air:c takeyuki$ ./a.out
NG
現在の Bind の値を Koshinto からブラウザに反映させるために、Bind の右下のフローティング・アクション・ボタン1(グルグルみたいな奴)を一度クリックします
keys を開くと値が送信されてきています
Platform Serial Number の lock を✓して右下の UPDATE をクリックします
Status は Bind Waiting から Bind Requesting に遷移しています
Status を Binding に変更します
変更したら右下の UPDATE をクリックして変更を反映させます
Mac に戻って a.out を実行してみると、今度は正常に起動して10秒の秒読みが実行されます
MacBook-Air:c takeyuki$ ./a.out
OK
start 10 second countdown
10
9
8
7
6
5
4
3
2
1
0!
3. 何を key にするか決めておいて、sansi が送信してきた値に自動的に束縛する場合
アプリケーションが最初に起動された Mac のシリアルナンバーに自動的に束縛することにします
keys を開いて Platform Serial Number の lock を✓し、右下の UPDATE をクリックして変更を反映させます
現在の Status は Not Active なので
Auto Bind Waiting に変更します
Mac に戻って a.out を実行してみると、初回から正常に起動して10秒の秒読みが実行されます
MacBook-Air:c takeyuki$ ./a.out
OK
start 10 second countdown
10
9
8
7
6
5
4
3
2
1
0!
現在の Bind の値を Koshinto からブラウザに反映させるために、Bind の右下のフローティング・アクション・ボタン(グルグルみたいな奴)を一度クリックします
keys の値は sansi が収集して送信してきた値に更新されていて、Platform Serial Number も更新されています
Status は Binding に遷移しています
例2. アプリケーションを RaspberryPi の SDカードのシリアルIDに束縛する
Koshinto に戻って Bind のステータスを確認すると Not Active になっているはずです
アプリケーションを Bind する方法は以下の3通りがあります
- 直接、値を入力して Bind
- sansi が取得してきた値を確認して Bind
- sansi が取得してきた値に自動的に Bind
正常な Bind のステータスは5通りあって、上記の3つの方法によってステータスの遷移がかわります。詳細については
こちらを参照ください
1. 事前に取得した key の値に直接束縛する場合
Koshinto に戻って Bind の [Keys] を開きます
調べておいた SD カードのシリアルナンバーを Platform Serial Number に入力して lock を✓し、右下の UPDATE をクリックします
Status を開き、Binding を選択して右下の UPDATE をクリックします
Raspberry Pi に戻って a.out がブロックされずに実行できているのが確認できます
pi@raspberrypi:~/sansi_examples/c $ ./a.out
OK
start 10 second countdown
10
9
8
7
6
5
4
3
2
1
0!
2. sansi が取得して送信してきた値から選択して束縛する場合
Raspberry Pi に戻り、先程コンパイルした a.out を実行します
結果は NG になりますが、sansi が取得したMac の環境の各種値が Koshinto に送信されて Bind に反映されているはずです
pi@raspberrypi:~/sansi_examples/c $ ./a.out
NG
現在の Bind の値を Koshinto からブラウザに反映させるために、Bind の右下のフローティング・アクション・ボタン1(グルグルみたいな奴)を一度クリックします
keys を開くと値が送信されてきています
SD Card Serial ID の lock を✓して右下の UPDATE をクリックします
Status は Bind Waiting から Bind Requesting に遷移しています
Status を Binding に変更します
変更したら右下の UPDATE をクリックして変更を反映させます
Raspberry Pi に戻って a.out を実行してみると、今度は正常に起動して10秒の秒読みが実行されます
pi@raspberrypi:~/sansi_examples/c $ ./a.out
OK
start 10 second countdown
10
9
8
7
6
5
4
3
2
1
0!
3. 何を key にするか決めておいて、sansi が送信してきた値に自動的に束縛する場合
アプリケーションが最初に起動された Raspberry Pi に装着されている SD カードのシリアルIDに自動的に束縛することにします
keys を開いて Platform Serial Number の lock を✓し、右下の UPDATE をクリックして変更を反映させます
現在の Status は Not Active なので
Auto Bind Waiting に変更します
Raspberry Pi に戻って a.out を実行してみると、初回から正常に起動して10秒の秒読みが実行されます
MacBook-Air:c takeyuki$ ./a.out
OK
start 10 second countdown
10
9
8
7
6
5
4
3
2
1
0!
現在の Bind の値を Koshinto からブラウザに反映させるために、Bind の右下のフローティング・アクション・ボタン(グルグルみたいな奴)を一度クリックします
keys の値は sansi が収集して送信してきた値に更新されていて、SD Card Serial ID も更新されています
Status は Binding に遷移しています
例3. アプリケーションを Linux Server の Global IP address に束縛する
Koshinto に戻って Bind のステータスを確認すると Not Active になっているはずです
アプリケーションを Bind する方法は以下の3通りがあります
- 直接、値を入力して Bind
- sansi が取得してきた値を確認して Bind
- sansi が取得してきた値に自動的に Bind
正常な Bind のステータスは5通りあって、上記の3つの方法によってステータスの遷移がかわります。詳細については
こちらを参照ください
1. 事前に取得した key の値に直接束縛する場合
Koshinto に戻って Bind の [Keys] を開きます
調べておいた Global IP address
を Global IP に入力して lock を✓し、右下の UPDATE をクリックします
Status を開き、Binding を選択して右下の UPDATE をクリックします
Linux に戻って a.out がブロックされずに実行できているのが確認できます
ueda@amfortas:~/sansi_examples/c$ ./a.out
OK
start 10 second countdown
10
9
8
7
6
5
4
3
2
1
0!
2. sansi が取得して送信してきた値から選択して束縛する場合
Linux に戻り、先程コンパイルした a.out を実行します
結果は NG になりますが、sansi が取得したMac の環境の各種値が Koshinto に送信されて Bind に反映されているはずです
ueda@amfortas:~/sansi_examples/c$ ./a.out
NG
現在の Bind の値を Koshinto からブラウザに反映させるために、Bind の右下のフローティング・アクション・ボタン1(グルグルみたいな奴)を一度クリックします
keys を開くと値が送信されてきています
Global IP の lock を✓して右下の UPDATE をクリックします
Status は Bind Waiting から Bind Requesting に遷移しています
Status を Binding に変更します
変更したら右下の UPDATE をクリックして変更を反映させます
Linux に戻って a.out を実行してみると、今度は正常に起動して10秒の秒読みが実行されます
ueda@amfortas:~/sansi_examples/c$ ./a.out
OK
start 10 second countdown
10
9
8
7
6
5
4
3
2
1
0!
3. 何を key にするか決めておいて、sansi が送信してきた値に自動的に束縛する場合
アプリケーションが最初に起動された Raspberry Pi に装着されている SD カードのシリアルIDに自動的に束縛することにします
keys を開いて Platform Serial Number の lock を✓し、右下の UPDATE をクリックして変更を反映させます
現在の Status は Not Active なので
Auto Bind Waiting に変更します
Linux に戻って a.out を実行してみると、初回から正常に起動して10秒の秒読みが実行されます
ueda@amfortas:~/sansi_examples/c$ ./a.out
OK
start 10 second countdown
10
9
8
7
6
5
4
3
2
1
0!
現在の Bind の値を Koshinto からブラウザに反映させるために、Bind の右下のフローティング・アクション・ボタン(グルグルみたいな奴)を一度クリックします
keys の値は sansi が収集して送信してきた値に更新されていて、Global IP も更新されています
Status は Binding に遷移しています
詳説
1. Bind の5種類の状態と、アプリケーションを Bind する3通りの方法
正常な Bind のステータスは以下の5つのどれかになります。各ステータスで confirm() の通知を受けた際の戻り値と、koshinto 内の bind の key と status の変化を表にまとめました
status | return | koshinto の bind の key | koshinto 内の bind の status |
---|---|---|---|
Not Active | 常に ng_comfirmed | 変化なし | 変化なし |
Bind Waiting | 常に ng_comfirmed | confirm からの値で更新 | Bind Requestingに遷移 |
Bind Requesting | 常に ng_comfirmed | 変化なし | 変化なし |
Auto Bind Waiting | 常に ok_comfirmed | confirm からの値で更新 | Binding |
Binding | confirm からの値によって ng_comfirmed またはok_comfirmed | 変化なし | 変化なし |
Not Active が、Bind が使われていない状態です
Binding が、アプリケーションが束縛されている状態です
アプリケーションを Bind する方法は3種類あって
- キーの値を設定してロックし、状態を Binding に変更
- 状態を [Bind Waitingに変更]して sansi からの confirm()を待つ。bind の key が更新され、状態が [Bind Requesting] に変わるので、適切な key を選んでロックし、状態を [Binding]に変更
- 適切な key を選んでロックしてから状態を [Auto Bind Waitingに変更]して sansi からの confirm()を待つ。key の値が更新されて自動的に [Bind]状態になる
束縛したい key の値(今の場合は Mac のシリアルナンバー)を事前に知っていて(「このマックについて」で確認済で)、その値を使って束縛する場合に 1. の手順になります。調べるのってめんどくさかったり typo があったりすると思うので後述の 2. や 3. の手順をお薦めいたしますが、Bind の状態の推移はシンプルに次のようになります
[Not Active] -> [Bind]
束縛したい key の値を知らない、もしくは自分で調べるのがめんどくさいので sansi に任せたい場合、sansi が送ってくる値を確認して問題がなければその値に束縛する場合が 2. の手順になります。遷移は
[Not Active] -> [Bind Waiting] -> [Bind Requesting] ->[Bind]
確認せずに自動的にその環境に束縛するのが 3. の手順になります。遷移は
[Not Active] -> [Auto Bind Waiting] -> [Binding]
related works
こちらを参照いただければ幸いです
名前の由来
Koshinto と Sansi の名前の由来はこちらを参照賜われれば至福の至に存じます次第です
結び
長々と書いてしまい本当にどうもすみません、今は謹んで反省しております次第です、無駄に長生きした年寄りの話が無駄に長いのは相応というものでございますれば、エンジニアの情けで許してくだされ/(^o^)\
references
- Raspberry Pi のアドベントに記事を書きました
- セキュリティのアドベントに Koshinto 自身の anti-tamper について書きました
- Koshinto Docs Koshinto のドキュメント
- Koshinto Koshinto サービスの入り口
- Sansi_Example アプリケーション への Sansi の組み込み方の言語毎の解説とサンプル。現在、c, go, python, bash の例を用意しています
- 庚申信仰 蛇足ながら Wikipedia
明日は
明日は@azukisiromochiさんによる「アプリ名『焼き鳥』事件 😱😱😱」です!私は前日なのでメンションもらって限定公開の頃から読ませて頂いてたのですが、正直、ものすごくおもしろい、ためになるお話なんですよ!お楽しみに!
-
もともと RPi だけの、それも SD CARD と Board の ID だけのつもりだったのに、つい NIC を追加したら Mac や Linux 一般にも欲がでて、という途中からの仕様追加を個人開発なのにやりまくったのも悪かったかもしれません ↩ ↩2 ↩3 ↩4
-
無駄に工学博士持ってます。せっかく博士なんだから、何時の日か自分の戦隊から「博士を守るんだ!」とかやってもらうのが幼少の折より今に至る夢です。戦隊コスとかの人、本物の工学博士を守ってみませんか?ちなみに「博士、お薬の時間ですよ」とかは経験済です ↩
-
タイトルに反してそういう人達へのネガティブな感情が前提にあるのかと読んでて勘ぐりました ↩
-
Raspberry Pi 以外の各種 IoT ボードも Linux ベースであれば IP address や MAC IDに束縛することで利用可能です。ボード固有のIDのへの束縛も対応していきたく、リファレンスボードの donation を募集してます ↩
-
Windows は持ってないので、開発に使える PC の donation を募集しています ↩