goauth

Fabric plugin for enhanced whitelists
git clone git://git.bain.cz/goauth.git
Log | Files | Refs | README | LICENSE

AuthUpdateRunner.java (5294B)


      1 package cz.bain.plugins.goauth;
      2 
      3 import com.google.gson.*;
      4 
      5 import javax.crypto.Cipher;
      6 import javax.crypto.spec.IvParameterSpec;
      7 import javax.crypto.spec.SecretKeySpec;
      8 import java.io.IOException;
      9 import java.io.InputStream;
     10 import java.io.OutputStream;
     11 import java.net.InetSocketAddress;
     12 import java.net.ServerSocket;
     13 import java.net.Socket;
     14 import java.net.SocketTimeoutException;
     15 import java.nio.charset.StandardCharsets;
     16 import java.security.GeneralSecurityException;
     17 
     18 public class AuthUpdateRunner implements Runnable {
     19 
     20     private final SecretKeySpec key;
     21     private final Goauth plugin;
     22     private final int port;
     23 
     24     public AuthUpdateRunner(byte[] key, Goauth plugin, int port) {
     25         this.key = new SecretKeySpec(key, "ChaCha20");
     26         this.plugin = plugin;
     27         this.port = port;
     28     }
     29 
     30     @Override
     31     public void run() {
     32         ServerSocket serverSocket;
     33         try {
     34             serverSocket = new ServerSocket();
     35             serverSocket.bind(new InetSocketAddress(port), 2);
     36             serverSocket.setSoTimeout(5);
     37 
     38         } catch (IOException e) {
     39             plugin.logger.error("Failed to create socket for updates!");
     40             return;
     41         }
     42         try (serverSocket) {
     43             while (this.plugin.running) {
     44                 try (
     45                         Socket socket = serverSocket.accept();
     46                         InputStream inputStream = socket.getInputStream();
     47                         OutputStream outputStream = socket.getOutputStream()
     48                 ) {
     49                     // protecting ourselves from being bombarded with large amounts of data
     50                     byte[] sizeBytes = inputStream.readNBytes(4);
     51                     if (sizeBytes.length != 4) continue;
     52                     int size = fromByteArray(sizeBytes);
     53                     if (size > 2048 || size <= 0) {
     54                         outputStream.write(0x01);
     55                         outputStream.flush();
     56                         plugin.logger.warn("Whitelist update rejected: payload size too big ({})", size);
     57                         continue;
     58                     } else {
     59                         outputStream.write(0x00);
     60                         outputStream.flush();
     61                     }
     62                     byte[] iv = inputStream.readNBytes(12);
     63                     if (iv.length != 12) continue;
     64                     byte[] cipherText = inputStream.readNBytes(size);
     65                     if (cipherText.length != size) continue;
     66                     byte[] plainText;
     67                     try {
     68                         plainText = decryptMessage(cipherText, iv);
     69                     } catch (GeneralSecurityException e) {
     70                         plugin.logger.warn("Whitelist update rejected: failed to decrypt payload");
     71                         outputStream.write(0x01);
     72                         outputStream.flush();
     73                         continue;
     74                     }
     75                     JsonArray el = JsonParser.parseString(new String(plainText, StandardCharsets.UTF_8)).getAsJsonArray();
     76                     plugin.logger.info("Got " + el.size() + " new whitelist updates");
     77                     Gson gson = new Gson();
     78                     synchronized (this.plugin.completed) {
     79                         for (JsonElement user : el) {
     80                             AuthUpdate update;
     81                             try {
     82                                 update = gson.fromJson(user, AuthUpdate.class);
     83                             } catch (JsonSyntaxException ignored) {
     84                                 plugin.logger.warn("Whitelist update rejected: bad update data");
     85                                 outputStream.write(0x01);
     86                                 outputStream.flush();
     87                                 break;
     88                             }
     89                             this.plugin.completed.add(update);
     90                             outputStream.write(0x00);
     91                         }
     92                     }
     93                 } catch (SocketTimeoutException ignored) {
     94                 } catch (IOException | IllegalStateException e) {
     95                     plugin.logger.warn("Error while processing OAuth updates, dropping connection");
     96                     e.printStackTrace();
     97                 }
     98             }
     99         } catch (IOException e) {
    100             throw new RuntimeException(e);
    101         }
    102     }
    103 
    104     /**
    105      * Decrypts a message with the ChaCha20-Poly1305 cipher.
    106      *
    107      * @param cipherText cipher text with appended digest to the end
    108      * @param iv         unique nonce
    109      * @return plain text
    110      * @throws GeneralSecurityException when the decryption goes wrong
    111      */
    112     private byte[] decryptMessage(byte[] cipherText, byte[] iv) throws GeneralSecurityException {
    113         Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305");
    114         IvParameterSpec ivSpec = new IvParameterSpec(iv);
    115         cipher.init(Cipher.DECRYPT_MODE, this.key, ivSpec);
    116 
    117         return cipher.doFinal(cipherText);
    118     }
    119 
    120     /**
    121      * Little endian int from byte array
    122      *
    123      * @param bytes byte array
    124      * @return int
    125      */
    126     private int fromByteArray(byte[] bytes) {
    127         return ((bytes[0] & 0xFF) << 24) |
    128                 ((bytes[1] & 0xFF) << 16) |
    129                 ((bytes[2] & 0xFF) << 8) |
    130                 (bytes[3] & 0xFF);
    131     }
    132 }