9
9
import net .twasi .obsremotejava .events .responses .SwitchScenesResponse ;
10
10
import net .twasi .obsremotejava .events .responses .TransitionBeginResponse ;
11
11
import net .twasi .obsremotejava .events .responses .TransitionEndResponse ;
12
+ import net .twasi .obsremotejava .requests .Authenticate .AuthenticateRequest ;
13
+ import net .twasi .obsremotejava .requests .Authenticate .AuthenticateResponse ;
12
14
import net .twasi .obsremotejava .requests .GetAuthRequired .GetAuthRequiredRequest ;
13
15
import net .twasi .obsremotejava .requests .GetAuthRequired .GetAuthRequiredResponse ;
14
16
import net .twasi .obsremotejava .requests .GetCurrentProfile .GetCurrentProfileRequest ;
75
77
import org .eclipse .jetty .websocket .api .annotations .OnWebSocketMessage ;
76
78
import org .eclipse .jetty .websocket .api .annotations .WebSocket ;
77
79
80
+ import java .nio .charset .StandardCharsets ;
81
+ import java .security .MessageDigest ;
82
+ import java .security .NoSuchAlgorithmException ;
83
+ import java .util .Base64 ;
78
84
import java .util .HashMap ;
79
85
import java .util .Map ;
80
86
import java .util .concurrent .CountDownLatch ;
84
90
@ WebSocket (maxTextMessageSize = 64 * 1024 , maxIdleTime = 360000000 )
85
91
public class OBSCommunicator {
86
92
private boolean debug ;
93
+ private final String password ;
87
94
private final CountDownLatch closeLatch ;
88
95
public final Map <String , Class > messageTypes = new HashMap <>();
89
96
@@ -93,8 +100,9 @@ public class OBSCommunicator {
93
100
94
101
private Callback onConnect ;
95
102
private Callback onDisconnect ;
103
+ private StringCallback onConnectionFailed ;
96
104
97
- // Optinal callbacks
105
+ // Optional callbacks
98
106
private Callback onReplayStarted ;
99
107
private Callback onReplayStarting ;
100
108
private Callback onReplayStopped ;
@@ -106,9 +114,14 @@ public class OBSCommunicator {
106
114
107
115
private GetVersionResponse versionInfo ;
108
116
109
- public OBSCommunicator (boolean debug ) {
117
+ public OBSCommunicator (boolean debug , String password ) {
110
118
this .closeLatch = new CountDownLatch (1 );
111
119
this .debug = debug ;
120
+ this .password = password ;
121
+ }
122
+
123
+ public OBSCommunicator (boolean debug ) {
124
+ this (debug , null );
112
125
}
113
126
114
127
public boolean awaitClose (int duration , TimeUnit unit ) throws InterruptedException {
@@ -166,12 +179,23 @@ public void onMessage(String msg) {
166
179
GetAuthRequiredResponse authRequiredResponse = (GetAuthRequiredResponse ) responseBase ;
167
180
if (authRequiredResponse .isAuthRequired ()) {
168
181
System .out .println ("Authentication is required." );
169
- // TODO support authentication
182
+ authenticateWithServer ( authRequiredResponse . getChallenge (), authRequiredResponse . getSalt ());
170
183
} else {
171
184
System .out .println ("Authentication is not required. You're ready to go!" );
172
185
this .onConnect .run (versionInfo );
173
186
}
174
187
break ;
188
+
189
+ case "AuthenticateResponse" :
190
+ AuthenticateResponse authenticateResponse = (AuthenticateResponse ) responseBase ;
191
+
192
+ if ("ok" .equals (authenticateResponse .getStatus ())) {
193
+ this .onConnect .run (versionInfo );
194
+ } else {
195
+ this .onConnectionFailed .run ("Failed to authenticate with password. Error: " + authenticateResponse .getError ());
196
+ }
197
+
198
+ break ;
175
199
default :
176
200
if (callbacks .containsKey (type )) {
177
201
callbacks .get (type ).run (responseBase );
@@ -233,6 +257,37 @@ public void onMessage(String msg) {
233
257
}
234
258
}
235
259
260
+ private void authenticateWithServer (String challenge , String salt ) {
261
+ if (password == null ) {
262
+ System .err .println ("Authentication required by server but no password set for client" );
263
+ this .onConnectionFailed .run ("Authentication required by server but no password set for client" );
264
+ return ;
265
+ }
266
+
267
+ MessageDigest digest ;
268
+ try {
269
+ digest = MessageDigest .getInstance ("SHA-256" );
270
+ } catch (NoSuchAlgorithmException e ) {
271
+ System .err .println ("Failed to perform password authentication with server" );
272
+ e .printStackTrace ();
273
+ this .onConnectionFailed .run ("Failed to perform password authentication with server" );
274
+ return ;
275
+ }
276
+
277
+ // Generate authentication response secret
278
+ String secretString = password + salt ;
279
+ byte [] secretHash = digest .digest (secretString .getBytes (StandardCharsets .UTF_8 ));
280
+ String encodedSecret = Base64 .getEncoder ().encodeToString (secretHash );
281
+
282
+ String authResponseString = encodedSecret + challenge ;
283
+ byte [] authResponseHash = digest .digest (authResponseString .getBytes (StandardCharsets .UTF_8 ));
284
+ String authResponse = Base64 .getEncoder ().encodeToString (authResponseHash );
285
+
286
+ // Send authentication response secret to server
287
+ session .getRemote ()
288
+ .sendStringByFuture (new Gson ().toJson (new AuthenticateRequest (this , authResponse )));
289
+ }
290
+
236
291
public void registerOnConnect (Callback onConnect ) {
237
292
this .onConnect = onConnect ;
238
293
}
@@ -241,6 +296,10 @@ public void registerOnDisconnect(Callback onDisconnect) {
241
296
this .onDisconnect = onDisconnect ;
242
297
}
243
298
299
+ public void registerOnConnectionFailed (StringCallback onConnectionFailed ) {
300
+ this .onConnectionFailed = onConnectionFailed ;
301
+ }
302
+
244
303
public void registerOnReplayStarted (Callback onReplayStarted ) {
245
304
this .onReplayStarted = onReplayStarted ;
246
305
}
0 commit comments