From 20a8853854ce7c2b6352d0c694e3fb2ebd09a9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20L?= Date: Thu, 20 Apr 2017 23:26:41 +0200 Subject: [PATCH] Protocol spec: better readability --- PROTOCOL.md | 211 +++++++++++++++++++++++++++++----------------------- 1 file changed, 117 insertions(+), 94 deletions(-) diff --git a/PROTOCOL.md b/PROTOCOL.md index 0c03e8b2..c08ff818 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -6,77 +6,129 @@ Messages exchanged between the client and the server are JSON objects. The protocol in general is based on the OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio. ### Table of contents +* [Authentication](#authentication) * [Events](#events) - [Description](#description) - [Event Types](#event-types) - - ["SwitchScenes"](#switchscenes) - - ["ScenesChanged"](#sceneschanged) - - ["SourceOrderChanged"](#sourceorderchanged) - - ["SceneItemAdded"](#sceneitemadded) - - ["SceneItemRemoved"](#sceneitemremoved) - - ["SceneItemVisibilityChanged"](#sceneitemvisibilitychanged) - - ["SceneCollectionChanged"](#scenecollectionchanged) - - ["SceneCollectionListChanged"](#scenecollectionlistchanged) - - ["SwitchTransition"](#switchtransition) - - ["TransitionDurationChanged"](#transitiondurationchanged) - - ["TransitionListChanged"](#transitionlistchanged) - - ["TransitionBegin"](#transitionbegin) - - ["PreviewSceneChanged"](#previewscenechanged) - - ["StudioModeSwitched"](#studiomodeswitched) - - ["ProfileChanged"](#profilechanged) - - ["ProfileListChanged"](#profilelistchanged) - - ["StreamStarting"](#streamstarting) - - ["StreamStarted"](#streamstarted) - - ["StreamStopping"](#streamstopping) - - ["StreamStopped"](#streamstopped) - - ["RecordingStarting"](#recordingstarting) - - ["RecordingStarted"](#recordingstarted) - - ["RecordingStopping"](#recordingstopping) - - ["RecordingStopped"](#recordingstopped) - - ["StreamStatus"](#streamstatus) - - ["Exiting"](#exiting) + - **Scenes** + - ["SwitchScenes"](#switchscenes) + - ["ScenesChanged"](#sceneschanged) + - **Sources / Scene Items** + - ["SourceOrderChanged"](#sourceorderchanged) + - ["SceneItemAdded"](#sceneitemadded) + - ["SceneItemRemoved"](#sceneitemremoved) + - ["SceneItemVisibilityChanged"](#sceneitemvisibilitychanged) + - **Scene Collections** + - ["SceneCollectionChanged"](#scenecollectionchanged) + - ["SceneCollectionListChanged"](#scenecollectionlistchanged) + - **Transitions** + - ["SwitchTransition"](#switchtransition) + - ["TransitionDurationChanged"](#transitiondurationchanged) + - ["TransitionListChanged"](#transitionlistchanged) + - ["TransitionBegin"](#transitionbegin) + - **Studio Mode** + - ["PreviewSceneChanged"](#previewscenechanged) + - ["StudioModeSwitched"](#studiomodeswitched) + - **Profiles** + - ["ProfileChanged"](#profilechanged) + - ["ProfileListChanged"](#profilelistchanged) + - **Streaming** + - ["StreamStarting"](#streamstarting) + - ["StreamStarted"](#streamstarted) + - ["StreamStopping"](#streamstopping) + - ["StreamStopped"](#streamstopped) + - ["StreamStatus"](#streamstatus) + - **Recording** + - ["RecordingStarting"](#recordingstarting) + - ["RecordingStarted"](#recordingstarted) + - ["RecordingStopping"](#recordingstopping) + - ["RecordingStopped"](#recordingstopped) + - **Other** + - ["Exiting"](#exiting) * [Requests](#requests) - [Description](#description-1) - [Request Types](#request-types) - - ["GetVersion"](#getversion) - - ["GetAuthRequired"](#getauthrequired) - - ["Authenticate"](#authenticate) - - ["GetCurrentScene"](#getcurrentscene) - - ["SetCurrentScene"](#setcurrentscene) - - ["GetSceneList"](#getscenelist) - - ["SetSourceRender"](#setsourcerender) - - ["GetStudioModeStatus"](#getstudiomodestatus) - - ["SetPreviewScene"](#setpreviewscene) - - ["TransitionToProgram"](#transitiontoprogram) - - ["EnableStudioMode"](#enablestudiomode) - - ["DisableStudioMode"](#disablestudiomode) - - ["ToggleStudioMode"](#togglestudiomode) - - ["StartStopStreaming"](#startstopstreaming) - - ["StartStopRecording"](#startstoprecording) - - ["StartStreaming"](#startstreaming) - - ["StopStreaming"](#stopstreaming) - - ["StartRecording"](#startrecording) - - ["StopRecording"](#stoprecording) - - ["GetStreamingStatus"](#getstreamingstatus) - - ["GetTransitionList"](#gettransitionlist) - - ["GetCurrentTransition"](#getcurrenttransition) - - ["SetCurrentTransition"](#setcurrenttransition) - - ["SetTransitionDuration"](#settransitionduration) - - ["GetTransitionDuration"](#gettransitionduration) - - ["SetVolume"](#setvolume) - - ["GetVolume"](#getvolume) - - ["SetMute"](#setmute) - - ["ToggleMute"](#togglemute) - - ["SetSceneItemPosition"](#setsceneitemposition) - - ["SetSceneItemTransform"](#setsceneitemtransform) - - ["SetSceneItemCrop"](#setsceneitemcrop) - - ["SetCurrentSceneCollection"](#setcurrentscenecollection) - - ["GetCurrentSceneCollection"](#getcurrentscenecollection) - - ["ListSceneCollections"](#listscenecollections) - - ["SetCurrentProfile"](#setcurrentprofile) - - ["GetCurrentProfile"](#getcurrentprofile) - - ["ListProfiles"](#listprofiles) -* [Authentication](#authentication) + - **General** + - ["GetVersion"](#getversion) + - ["GetAuthRequired"](#getauthrequired) + - ["Authenticate"](#authenticate) + - **Scenes** + - ["GetCurrentScene"](#getcurrentscene) + - ["SetCurrentScene"](#setcurrentscene) + - ["GetSceneList"](#getscenelist) + - **Studio Mode** + - ["GetStudioModeStatus"](#getstudiomodestatus) + - ["SetPreviewScene"](#setpreviewscene) + - ["TransitionToProgram"](#transitiontoprogram) + - ["EnableStudioMode"](#enablestudiomode) + - ["DisableStudioMode"](#disablestudiomode) + - ["ToggleStudioMode"](#togglestudiomode) + - **Streaming** + - ["StartStopStreaming"](#startstopstreaming) + - ["StartStreaming"](#startstreaming) + - ["StopStreaming"](#stopstreaming) + - ["GetStreamingStatus"](#getstreamingstatus) + - **Recording** + - ["StartStopRecording"](#startstoprecording) + - ["StartRecording"](#startrecording) + - ["StopRecording"](#stoprecording) + - ["GetStreamingStatus"](#getstreamingstatus) + - **Transitions** + - ["GetTransitionList"](#gettransitionlist) + - ["GetCurrentTransition"](#getcurrenttransition) + - ["SetCurrentTransition"](#setcurrenttransition) + - ["GetTransitionDuration"](#gettransitionduration) + - ["SetTransitionDuration"](#settransitionduration) + - **Sources** + - ["GetCurrentScene"](#getcurrentscene) + - ["GetSceneList"](#getscenelist) + - ["SetVolume"](#setvolume) + - ["GetVolume"](#getvolume) + - ["SetMute"](#setmute) + - ["ToggleMute"](#togglemute) + - ["SetSourceRender"](#setsourcerender) + - ["SetSceneItemPosition"](#setsceneitemposition) + - ["SetSceneItemTransform"](#setsceneitemtransform) + - ["SetSceneItemCrop"](#setsceneitemcrop) + - **Scene Collections** + - ["ListSceneCollections"](#listscenecollections) + - ["SetCurrentSceneCollection"](#setcurrentscenecollection) + - ["GetCurrentSceneCollection"](#getcurrentscenecollection) + - **Profiles** + - ["ListProfiles"](#listprofiles) + - ["SetCurrentProfile"](#setcurrentprofile) + - ["GetCurrentProfile"](#getcurrentprofile) + +## Authentication +A call to [`GetAuthRequired`](#getauthrequired) gives the client two elements : +- A challenge : a random string that will be used to generate the auth response +- A salt : applied to the password when generating the auth response + +The client knows a password and must it to authenticate itself to the server. +However, it must keep this password secret, and it is the purpose of the authentication mecanism used by obs-websocket. + +After a call to [`GetAuthRequired`](#getauthrequired), the client knows a password (kept secret), a challenge and a salt (sent by the server). +To generate the answer to the auth challenge, follow this procedure : +- Concatenate the password with the salt sent by the server (in this order : password + server salt), then generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64. +- Concatenate the base64 secret with the challenge sent by the server (in this order : base64 secret + server challenge), then generate a binary SHA256 hash of the result and encode it to base64. +- VoilĂ , this last base64 string is the auth response. You may now use it to authenticate to the server with the `Authenticate` request. + +Here's how it looks in pseudocode : +``` +password = "supersecretpassword" +challenge = "ztTBnnuqrqaKDzRM3xcVdbYm" +salt = "PZVbYpvAnZut2SS6JNJytDm9" + +secret_string = password + salt +secret_hash = binary_sha256(secret_string) +secret = base64_encode(secret_hash) + +auth_response_string = secret + challenge +auth_response_hash = binary_sha256(auth_response_string) +auth_response = base64_encode(auth_response_hash) +``` + +A client can then authenticate to the server by calling [`Authenticate`](#authenticate) with the computed challenge response. ## Events ### Description @@ -701,32 +753,3 @@ __Response__ : OK with the additional fields : - **"profiles"** (array of objects) : names of available profiles --- - -### Authentication -A call to `GetAuthRequired` gives the client two elements : -- A challenge : a random string that will be used to generate the auth response -- A salt : applied to the password when generating the auth response - -The client knows a password and must it to authenticate itself to the server. -However, it must keep this password secret, and it is the purpose of the authentication mecanism used by obs-websocket. - -After a call to `GetAuthRequired`, the client knows a password (kept secret), a challenge and a salt (sent by the server). -To generate the answer to the auth challenge, follow this procedure : -- Concatenate the password with the salt sent by the server (in this order : password + server salt), then generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64. -- Concatenate the base64 secret with the challenge sent by the server (in this order : base64 secret + server challenge), then generate a binary SHA256 hash of the result and encode it to base64. -- VoilĂ , this last base64 string is the auth response. You may now use it to authenticate to the server with the `Authenticate` request. - -Here's how it looks in pseudocode : -``` -password = "supersecretpassword" -challenge = "ztTBnnuqrqaKDzRM3xcVdbYm" -salt = "PZVbYpvAnZut2SS6JNJytDm9" - -secret_string = password + salt -secret_hash = binary_sha256(secret_string) -secret = base64_encode(secret_hash) - -auth_response_string = secret + challenge -auth_response_hash = binary_sha256(auth_response_string) -auth_response = base64_encode(auth_response_hash) -```