@@ -240,6 +240,8 @@ public void run() {
240240 debug ("Reading V1 vote block (256 bytes expected) at " + startTime + " ms" );
241241
242242 if (in .available () < 256 ) {
243+ logWarning ("Invalid vote format: Insufficient data for V1 vote block from " + address
244+ + " (expected 256 bytes)" );
243245 debug ("Insufficient data available for V1 vote block; closing connection from " + address );
244246 writer .close ();
245247 in .close ();
@@ -263,20 +265,26 @@ public void run() {
263265 try {
264266 decrypted = RSA .decrypt (block , getKeyPair ().getPrivate ());
265267 } catch (BadPaddingException e ) {
268+ // Log only the first 32 bytes as hex to avoid exposing full vote block
266269 StringBuilder blockHex = new StringBuilder ();
267- for (byte b : block ) {
268- blockHex .append (String .format ("%02X " , b ));
270+ int bytesToLog = Math .min (32 , block .length );
271+ for (int i = 0 ; i < bytesToLog ; i ++) {
272+ blockHex .append (String .format ("%02X " , block [i ]));
273+ }
274+ if (block .length > bytesToLog ) {
275+ blockHex .append ("... (truncated)" );
269276 }
270277 logWarning (
271- "Decryption failed. Either the vote block is invalid or the public key does not match the server list from "
278+ "Decryption failed: Invalid V1 vote block or public key mismatch from "
272279 + address );
280+ debug ("Vote block preview (first 32 bytes): " + blockHex .toString ().trim ());
273281 throw e ;
274282 }
275283 int position = 0 ;
276284 String opcode = readString (decrypted , position );
277285 position += opcode .length () + 1 ;
278286 if (!opcode .equals ("VOTE" )) {
279- throw new Exception ("Unable to decode RSA: invalid opcode " + opcode );
287+ throw new Exception ("Invalid vote format: Expected opcode 'VOTE' but got ' " + opcode + "' from " + address );
280288 }
281289 String serviceName = readString (decrypted , position );
282290 position += serviceName .length () + 1 ;
@@ -290,6 +298,8 @@ public void run() {
290298 + "\n " ;
291299 debug ("Processed V1 vote block." );
292300 } else {
301+ logWarning ("Invalid vote format: Failed to read complete V1 vote block from " + address
302+ + " (expected 256 bytes, got " + totalRead + ")" );
293303 debug ("Failed to read V1 vote, random ping? expected 256 bytes, got " + totalRead );
294304 continue ;
295305 // throw new Exception("Failed to read V1 vote block: expected 256 bytes, got "
@@ -322,7 +332,7 @@ public void run() {
322332 int jsonEnd = voteData .lastIndexOf ("}" );
323333 if (jsonStart == -1 || jsonEnd == -1 || jsonStart > jsonEnd ) {
324334 throw new Exception (
325- "Expected JSON-formatted vote payload, got: " + voteData + " from " + address );
335+ "Invalid vote format: Expected JSON-formatted vote payload, got: " + voteData + " from " + address );
326336 }
327337 String jsonPayloadRaw = voteData .substring (jsonStart , jsonEnd + 1 ).trim ();
328338 debug ("Extracted raw JSON payload: [" + jsonPayloadRaw + "]" );
@@ -332,20 +342,53 @@ public void run() {
332342 if (jsonPayloadRaw .startsWith ("[" )) {
333343 JsonArray jsonArray = gson .fromJson (jsonPayloadRaw , JsonArray .class );
334344 if (jsonArray .size () == 0 ) {
335- throw new Exception ("Empty JSON array in vote payload from " + address );
345+ throw new Exception ("Invalid vote format: Empty JSON array in vote payload from " + address );
336346 }
337347 voteMessage = jsonArray .get (0 ).getAsJsonObject ();
338348 } else {
339349 voteMessage = gson .fromJson (jsonPayloadRaw , JsonObject .class );
340350 }
341351
342- // Extract the inner payload and signature.
352+ // Validate and extract the outer payload and signature fields.
353+ if (!voteMessage .has ("payload" )) {
354+ throw new Exception ("Invalid vote format: Missing required 'payload' field in outer JSON from " + address );
355+ }
356+ if (!voteMessage .has ("signature" )) {
357+ throw new Exception ("Invalid vote format: Missing required 'signature' field in outer JSON from " + address );
358+ }
343359 String payload = voteMessage .get ("payload" ).getAsString ();
344360 String sigHash = voteMessage .get ("signature" ).getAsString ();
345- byte [] sigBytes = Base64 .getDecoder ().decode (sigHash );
361+ byte [] sigBytes ;
362+ try {
363+ sigBytes = Base64 .getDecoder ().decode (sigHash );
364+ } catch (IllegalArgumentException e ) {
365+ throw new Exception ("Invalid vote format: Signature is not valid Base64 from " + address + ": " + e .getMessage ());
366+ }
346367
347368 // Parse the inner payload JSON.
348- JsonObject votePayload = gson .fromJson (payload , JsonObject .class );
369+ JsonObject votePayload ;
370+ try {
371+ votePayload = gson .fromJson (payload , JsonObject .class );
372+ } catch (Exception e ) {
373+ throw new Exception ("Invalid vote format: Inner payload is not valid JSON from " + address + ": " + e .getMessage ());
374+ }
375+
376+ // Validate required fields in inner payload.
377+ if (!votePayload .has ("serviceName" )) {
378+ throw new Exception ("Invalid vote format: Missing required 'serviceName' field in vote payload from " + address );
379+ }
380+ if (!votePayload .has ("username" )) {
381+ throw new Exception ("Invalid vote format: Missing required 'username' field in vote payload from " + address );
382+ }
383+ if (!votePayload .has ("address" )) {
384+ throw new Exception ("Invalid vote format: Missing required 'address' field in vote payload from " + address );
385+ }
386+ if (!votePayload .has ("timestamp" )) {
387+ throw new Exception ("Invalid vote format: Missing required 'timestamp' field in vote payload from " + address );
388+ }
389+ if (!votePayload .has ("challenge" )) {
390+ throw new Exception ("Invalid vote format: Missing required 'challenge' field in vote payload from " + address );
391+ }
349392
350393 // Retrieve serviceName from the inner JSON.
351394 String serviceNameFromPayload = votePayload .get ("serviceName" ).getAsString ();
@@ -355,16 +398,19 @@ public void run() {
355398 if (key == null ) {
356399 key = getTokens ().get ("default" );
357400 if (key == null ) {
358- throw new Exception ("Unknown token for service '" + serviceNameFromPayload + "'" );
401+ throw new Exception ("Authentication failed: Unknown token for service '" + serviceNameFromPayload + "' from " + address );
359402 }
403+ debug ("Using default token for service: " + serviceNameFromPayload );
404+ } else {
405+ debug ("Using service-specific token for: " + serviceNameFromPayload );
360406 }
361407
362408 // Debug: log the payload string and its computed HMAC for comparison.
363409 debug ("Inner payload string: [" + payload + "]" );
364410
365411 // Verify HMAC signature using the payload bytes.
366412 if (!hmacEqual (sigBytes , payload .getBytes (StandardCharsets .UTF_8 ), key )) {
367- throw new Exception ("Signature is not valid (invalid token?) from " + address );
413+ throw new Exception ("Authentication failed: Signature verification failed (invalid token?) for service '" + serviceNameFromPayload + "' from " + address );
368414 }
369415
370416 // Extract vote fields from the inner payload.
@@ -373,14 +419,11 @@ public void run() {
373419 address1 = votePayload .get ("address" ).getAsString ();
374420 timeStamp = votePayload .get ("timestamp" ).getAsString ();
375421
376- // Check the challenge.
377- if (!votePayload .has ("challenge" )) {
378- throw new Exception ("Vote payload missing challenge field from " + address );
379- }
422+ // Verify the challenge.
380423 String receivedChallenge = votePayload .get ("challenge" ).getAsString ().trim ();
381424 if (!receivedChallenge .equals (challenge .trim ())) {
382425 throw new Exception (
383- "Invalid challenge: expected " + challenge + " but got " + receivedChallenge );
426+ "Authentication failed: Invalid challenge ( expected ' " + challenge + "' but got ' " + receivedChallenge + "') from " + address );
384427 }
385428 } else {
386429 String [] fields = voteData .split ("\n " );
@@ -428,25 +471,32 @@ public void run() {
428471 in .close ();
429472 socket .close ();
430473 } catch (MalformedJsonException ex ) {
431- logWarning ("Malformed JSON payload received from: " + address + " - " + ex .getMessage ());
474+ logWarning ("Invalid vote format: Malformed JSON payload received from " + address + " - " + ex .getMessage ());
432475 debug (ex );
433476 } catch (SocketException ex ) {
434477 if (running ) {
435- logWarning ("Protocol error from: " + address + " - " + ex .getLocalizedMessage ());
478+ logWarning ("Connection error: Protocol error from " + address + " - " + ex .getLocalizedMessage ());
436479 debug (ex );
437480 } else {
438481 logWarning ("Votifier socket closed." );
439482 }
440483 } catch (BadPaddingException ex ) {
441- logWarning ("Unable to decrypt vote record from: " + address
484+ logWarning ("Authentication failed: Unable to decrypt V1 vote record from " + address
442485 + ". Make sure that your public key matches the one you gave the server list." );
443486 debug (ex );
444487 } catch (SocketTimeoutException ex ) {
445- logWarning ("Socket timeout while waiting for vote payload from: " + address + " - " + ex .getMessage ());
488+ logWarning ("Connection timeout: Socket timeout while waiting for vote payload from " + address + " - " + ex .getMessage ());
446489 debug (ex );
447490 } catch (Exception ex ) {
448- logWarning ("Exception caught while receiving a vote notification from: " + address + " - "
449- + ex .getLocalizedMessage ());
491+ String errorMsg = ex .getMessage ();
492+ if (errorMsg != null && (errorMsg .startsWith ("Invalid vote format:" ) ||
493+ errorMsg .startsWith ("Authentication failed:" ))) {
494+ // These are validation errors with detailed messages, log them as-is
495+ logWarning (errorMsg );
496+ } else {
497+ // Generic exception with less context
498+ logWarning ("Error processing vote from " + address + ": " + ex .getLocalizedMessage ());
499+ }
450500 debug (ex );
451501 }
452502 }
0 commit comments