OBS の obs-websocketプラグインの認証に関する仕様・試したことのメモ

## はじめに
先日、以下の記事でも書いた [OBS](https://obsproject.com/ja) を遠隔制御することができるプラグイン「[obs-websocket](https://github.com/Palakis/obs-websocket/)」の話です。

●OBS をスマホや M5GO（M5Stack）から遠隔制御 〜 MQTT や obs-websocket を利用 〜 - Qiita
　https://qiita.com/youtoy/items/23fb0e16f1a4428b5c9b

上記の記事に書いた仕組みでは、この記事で扱う「認証」の部分はプラグインのラッパー的な [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) が良い感じに処理してくれたので、詳しい仕様などは気にしていませんでした。

そして最近、下記の記事の Node-RED と OBS を使った仕組みを作ってみた際に、ラッパー的なものを使わずプラグインとの通信部分を作っていたのですが、認証部分がややこしそうで認証を無効にして対応しました。

●Node-RED を使って OBS を遠隔操作してみた話（obs-websocket を利用）｜豊田 陽介｜note
　https://note.com/youtoy/n/nf404040a91c0

この認証を自前で行えるようにもできればと思い、あらためて仕様を調べたり、認証部分の処理を試してみたりした際のメモです。

## obs-websocket に対するリクエスト処理について
認証の話に入る前に、obs-websocket へのリクエスト処理をする場合の仕様に関して補足します。

[公式のプロトコル仕様に関する説明](https://github.com/Palakis/obs-websocket/blob/4.x-current/docs/generated/protocol.md)で、以下の部分がリクエスト処理に関する概要を書いている部分です。
![obs-websocket_protocol_md_at_4_x-current_·_Palakis_obs-websocket.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/7e04d03e-f540-e070-d674-be3b161497ed.jpeg)

obs-websocket を使う流れとしては、最初に OBS にプラグインを追加し、適宜プラグインの設定を行います。その後、OBS を立ち上げた状態で WebSocketクライアントから OBS のプラグインに接続処理をします（例えば、デフォルト設定でローカルでの接続を行うなら、 `ws://localhost:4445` で接続）。そして、WebSocketクライアントから上で書かれていたような仕様の JSON を WebSocket で送ります。

JSON に記載する内容についてですが、「request-type」でリクエストしたい処理の名称を記載し、「message-id」には自分がリクエストした内容のレスポンスを識別するための文字列を設定します。

例えば、OBS に設定されているシーンのリストを取得する際は、「request-type」に「GetSceneList」を指定した、以下の JSON を WebSocket で送ります。

```JSON
{
    "request-type":"GetSceneList",
    "message-id":"【メッセージID用の文字列】"
}
```

request-type に指定できる内容は、[公式でのプロトコル仕様に関する説明](https://github.com/Palakis/obs-websocket/blob/4.x-current/docs/generated/protocol.md)をご参照ください。

## obs-websocket の認証について
### OBS上での認証の設定 
もし、obs-websocket の設定で認証を無効にしていれば、上で書いた「WebSocketクライアントからの接続 ⇒ JSON の送付 ⇒ 。。。」という処理を行うだけで遠隔での制御ができます。しかし、認証の設定を有効にしている場合、WebSocketクライアントからの接続後に認証用の文字列を生成して送るステップが必要になります。

OBS上での認証の設定は、OBS でのプラグインの設定を開いた際に、以下のように表示されている部分です。

![WebSockets_Server_Settings.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/7f9a477f-5839-3077-3f2b-6487a7ea441b.jpeg)

OBS上ではパスワードを設定するのですが、このパスワードを単純に WebSocket で送れば OK とはなっておらず、それが Node-RED からの制御を試した際に認証を無効にして試した理由でした。

### obs-websocket の認証処理の概要
obs-websocket の認証処理で、パスワードから認証用の文字列を生成するステップは以下のとおりです。

![obs-websocket_protocol_md_at_4_x-current_·_Palakis_obs-websocket2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/bd987702-9063-ac28-a01e-abcfb62961ef.jpeg)

こちらは、[公式のプロトコル仕様に関する説明](https://github.com/Palakis/obs-websocket/blob/4.x-current/docs/generated/protocol.md)から抜粋したものです。

認証用の文字列を生成する手順はおおまかには以下となります。

1. request-type を GetAuthRequired に設定したリクエスト処理を行い、そのレスポンスで「challenge」・「salt」の 2つの文字列を得る
2. OBS上で設定したパスワードと、手順1）で得られた「salt」とをつなげた文字列を作り、その文字列に処理を行って secret となる文字列を生成する
3. 手順2）で得た secret と、手順1）で得られた「challenge」とをつなげた文字列を作り、その文字列に処理を行って認証用の文字列を生成する
4. 手順3）で生成した認証用の文字列を、request-type を Authenticate に設定したリクエスト処理で送る

上記の手順3）や手順4）で「その文字列に処理を行って」と書いていた部分は、公式の説明では以下にあたる部分です。

```
secret_hash = binary_sha256(secret_string)
secret = base64_encode(secret_hash)

auth_response_hash = binary_sha256(auth_response_string)
auth_response = base64_encode(auth_response_hash)
```

こちらを JavaScript で実装してみた話を次に書いていきます。

### obs-websocket の認証処理の実装
今回、認証用の文字列を、パスワード・challenge・salt の 3つから生成する部分を、Node.js・JavaScript で実際に試してみました。なお、ここで実装したのは、obs-websocket とのやりとりを含む処理ではなく、認証処理について書かれた部分を抜粋した上の画像内で「Pseudo Code Example:」と書かれていた部分を、Node.js と HTML＋JavaScript で実装したものになります。

まず、Node.js での実装は冒頭にリンクを掲載していた記事を書いた時に使った obs-websocket-js のリポジトリの中に、そのまま使えるものがありました。
具体的には、「https://github.com/haganbmj/obs-websocket-js/blob/master/lib/util/authenticationHashing.js」に書かれた処理で、コメントを除いた部分は以下となります。

```JavaScript
const SHA256 = require('sha.js/sha256');

salt = '【取得した salt】';
challenge = '【取得した challenge】';
pass = '【OBS上で設定したパスワード】';

const hash = new SHA256()
    .update(pass)
    .update(salt)
    .digest('base64');

const resp = new SHA256()
    .update(hash)
    .update(challenge)
    .digest('base64');
```

手元で試すときは `npm install sha.js` でパッケージをインストールし、上記を動かしました。

また、HTML＋JavaScript での実装は、上記を参考に自分で作ってみました。

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>認証のテスト</title>
</head>

<body>
<h1>認証用の文字列を生成（コンソールへ）</h1>
<script src="./sha256.js"></script>

<script>
salt = '【取得した salt】';
challenge = '【取得した challenge】';
pass = '【OBS上で設定したパスワード】';

const textHash = new jsSHA("SHA-256", "TEXT");
textHash.update(pass);
textHash.update(salt);
const hash = textHash.getHash("B64");
console.log("hash: " + hash);

const textHash2 = new jsSHA("SHA-256", "TEXT");
textHash2.update(hash);
textHash2.update(challenge);
const resp = textHash2.getHash("B64");
console.log("resp: " + resp);

</script>
</body>
</html>
```

上記の 2種類のプログラムで認証用の文字列を生成し、実際に obs-websocket の認証を通せるのは確認しました。

## おわりに
今回、obs-websocket の認証処理の仕様を調べ、認証用の文字列生成を実際に試してみました。

これを実際に OBS の遠隔制御で利用する場合は、OBS へのリクエスト処理やレスポンスからの認証情報の取得なども実装する必要がありますが、その部分はこれから必要になった際に作っていこうと思います。

クラウド上など外部からアクセス可能な環境に OBS を置くような場合は別ですが、ローカルで obs-websocket を動作させてローカル間での通信をする使い方であれば（＋ OBS を動かしている PC にインターネット側から接続できるような設定をルーター等にしているなどということがなければ）、とりあえずは認証を OFF にして使うのが手軽かもしれません。