Add a few more exchanges and improve secret handling

This commit is contained in:
Christopher Schnick 2023-01-07 04:12:17 +01:00
parent 70568f7a9b
commit a82717154d
13 changed files with 222 additions and 21 deletions

View file

@ -337,6 +337,19 @@ public class BeaconClient implements AutoCloseable {
} }
} }
@JsonTypeName("daemon")
@Value
@Builder
@Jacksonized
@EqualsAndHashCode(callSuper = false)
public static class DaemonInformation extends ClientInformation {
@Override
public String toDisplayString() {
return "Daemon";
}
}
@JsonTypeName("gateway") @JsonTypeName("gateway")
@Value @Value
@Builder @Builder

View file

@ -15,7 +15,7 @@ public class BeaconConfig {
private static final String ATTACH_DEBUGGER_PROP = "io.xpipe.beacon.attachDebuggerToDaemon"; private static final String ATTACH_DEBUGGER_PROP = "io.xpipe.beacon.attachDebuggerToDaemon";
private static final String EXEC_DEBUG_PROP = "io.xpipe.beacon.printDaemonOutput"; private static final String EXEC_DEBUG_PROP = "io.xpipe.beacon.printDaemonOutput";
private static final String EXEC_PROCESS_PROP = "io.xpipe.beacon.customDaemonCommand"; private static final String EXEC_PROCESS_PROP = "io.xpipe.beacon.customDaemonCommand";
private static final String DAEMON_ARGUMENTS_PROP = "io.xpipe.beacon.daemonArgs"; public static final String DAEMON_ARGUMENTS_PROP = "io.xpipe.beacon.daemonArgs";
private static final String LOCAL_PROXY_PROP = "io.xpipe.beacon.localProxy"; private static final String LOCAL_PROXY_PROP = "io.xpipe.beacon.localProxy";
public static boolean localProxy() { public static boolean localProxy() {

View file

@ -10,6 +10,7 @@ public class BeaconJacksonModule extends SimpleModule {
context.registerSubtypes( context.registerSubtypes(
new NamedType(BeaconClient.ApiClientInformation.class), new NamedType(BeaconClient.ApiClientInformation.class),
new NamedType(BeaconClient.CliClientInformation.class), new NamedType(BeaconClient.CliClientInformation.class),
new NamedType(BeaconClient.DaemonInformation.class),
new NamedType(BeaconClient.ReachableCheckInformation.class) new NamedType(BeaconClient.ReachableCheckInformation.class)
); );
} }

View file

@ -0,0 +1,73 @@
package io.xpipe.beacon;
import io.xpipe.core.util.SecretProvider;
import lombok.SneakyThrows;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Random;
public class SecretProviderImpl extends SecretProvider {
private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";
private static final int TAG_LENGTH_BIT = 128;
private static final int IV_LENGTH_BYTE = 12;
private static final int AES_KEY_BIT = 128;
private static final byte[] IV = getFixedNonce(IV_LENGTH_BYTE);
private static byte[] getFixedNonce(int numBytes) {
byte[] nonce = new byte[numBytes];
new SecureRandom(new byte[] {1, -28, 123}).nextBytes(nonce);
return nonce;
}
private static SecretKey getAESKey(int keysize) throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
var salt = new byte[16];
new Random(keysize).nextBytes(salt);
KeySpec spec = new PBEKeySpec(new char[] {'X', 'P', 'E' << 1}, salt, 65536, keysize);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return secret;
}
@Override
@SneakyThrows
public byte[] encrypt(byte[] c) {
SecretKey secretKey = getAESKey(AES_KEY_BIT);
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH_BIT, IV));
var bytes = cipher.doFinal(c);
bytes = ByteBuffer.allocate(IV.length + bytes.length)
.order(ByteOrder.LITTLE_ENDIAN)
.put(IV)
.put(bytes)
.array();
return bytes;
}
@Override
@SneakyThrows
public byte[] decrypt(byte[] c) {
ByteBuffer bb = ByteBuffer.wrap(c).order(ByteOrder.LITTLE_ENDIAN);
byte[] iv = new byte[IV_LENGTH_BYTE];
bb.get(iv);
byte[] cipherText = new byte[bb.remaining()];
bb.get(cipherText);
SecretKey secretKey = getAESKey(AES_KEY_BIT);
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
return cipher.doFinal(cipherText);
}
}

View file

@ -0,0 +1,27 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class FocusExchange implements MessageExchange {
@Override
public String getId() {
return "focus";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
}
}

View file

@ -0,0 +1,31 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
public class OpenExchange implements MessageExchange {
@Override
public String getId() {
return "open";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull List<String> arguments;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
}
}

View file

@ -1,6 +1,7 @@
import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.Module;
import io.xpipe.beacon.BeaconJacksonModule; import io.xpipe.beacon.BeaconJacksonModule;
import io.xpipe.beacon.BeaconProxyImpl; import io.xpipe.beacon.BeaconProxyImpl;
import io.xpipe.beacon.SecretProviderImpl;
import io.xpipe.beacon.exchange.*; import io.xpipe.beacon.exchange.*;
import io.xpipe.beacon.exchange.api.QueryRawDataExchange; import io.xpipe.beacon.exchange.api.QueryRawDataExchange;
import io.xpipe.beacon.exchange.api.QueryTableDataExchange; import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
@ -8,6 +9,7 @@ import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
import io.xpipe.beacon.exchange.cli.*; import io.xpipe.beacon.exchange.cli.*;
import io.xpipe.core.util.ProxyFunction; import io.xpipe.core.util.ProxyFunction;
import io.xpipe.core.util.ProxyProvider; import io.xpipe.core.util.ProxyProvider;
import io.xpipe.core.util.SecretProvider;
module io.xpipe.beacon { module io.xpipe.beacon {
exports io.xpipe.beacon; exports io.xpipe.beacon;
@ -33,6 +35,7 @@ module io.xpipe.beacon {
uses ProxyFunction; uses ProxyFunction;
provides ProxyProvider with BeaconProxyImpl; provides ProxyProvider with BeaconProxyImpl;
provides SecretProvider with SecretProviderImpl;
provides Module with BeaconJacksonModule; provides Module with BeaconJacksonModule;
provides io.xpipe.beacon.exchange.MessageExchange with provides io.xpipe.beacon.exchange.MessageExchange with
ForwardExchange, ForwardExchange,
@ -49,6 +52,8 @@ module io.xpipe.beacon {
ProxyFunctionExchange, ProxyFunctionExchange,
QueryStoreExchange, QueryStoreExchange,
StatusExchange, StatusExchange,
FocusExchange,
OpenExchange,
StopExchange, StopExchange,
RenameStoreExchange, RenameStoreExchange,
RemoveStoreExchange, RemoveStoreExchange,

View file

@ -1,6 +1,7 @@
package io.xpipe.core.util; package io.xpipe.core.util;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@ -18,6 +19,12 @@ public class JacksonMapper {
private static ObjectMapper INSTANCE = new ObjectMapper(); private static ObjectMapper INSTANCE = new ObjectMapper();
private static boolean init = false; private static boolean init = false;
public static <T> T parse(String s, Class<T> c) throws JsonProcessingException {
var mapper = getDefault();
var tree = mapper.readTree(s);
return mapper.treeToValue(tree, c);
}
static { static {
ObjectMapper objectMapper = BASE; ObjectMapper objectMapper = BASE;
objectMapper.enable(SerializationFeature.INDENT_OUTPUT); objectMapper.enable(SerializationFeature.INDENT_OUTPUT);

View file

@ -0,0 +1,16 @@
package io.xpipe.core.util;
import java.util.ServiceLoader;
public abstract class SecretProvider {
private static final SecretProvider INSTANCE = ServiceLoader.load(ModuleLayer.boot(), SecretProvider.class).findFirst().orElseThrow();
public abstract byte[] encrypt(byte[] c);
public abstract byte[] decrypt(byte[] c);
public static SecretProvider get() {
return INSTANCE;
}
}

View file

@ -5,6 +5,7 @@ import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
@ -16,24 +17,32 @@ public class SecretValue {
String value; String value;
public static SecretValue create(String s) { public static SecretValue encrypt(char[] c) {
if (c == null) {
return null;
}
var utf8 = StandardCharsets.UTF_8.encode(CharBuffer.wrap(c));
var bytes = new byte[utf8.limit()];
utf8.get(bytes);
Arrays.fill(c, (char) 0);
bytes = SecretProvider.get().encrypt(bytes);
var base64 = Base64.getEncoder().encodeToString(bytes);
return new SecretValue(base64.replace("/", "-"));
}
public static SecretValue encrypt(String s) {
if (s == null) { if (s == null) {
return null; return null;
} }
if (s.length() < 2) { return encrypt(s.toCharArray());
return new SecretValue(s);
} }
return new SecretValue(Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8))); public void withSecretValue(Consumer<char[]> con) {
} var chars = decryptChars();
con.accept(chars);
public void withSecretValue(Consumer<char[]> chars) { Arrays.fill(chars, (char) 0);
var bytes = Base64.getDecoder().decode(value);
var buffer = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(bytes));
var array = buffer.array();
chars.accept(array);
Arrays.fill(array, (char) 0);
} }
@Override @Override
@ -45,14 +54,28 @@ public class SecretValue {
return value; return value;
} }
public String getSecretValue() { public char[] decryptChars() {
if (value.length() < 2) {
return value;
}
try { try {
return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8); var bytes = Base64.getDecoder().decode(value.replace("-", "/"));
} catch (Exception exception) { bytes = SecretProvider.get().decrypt(bytes);
return ""; var charBuffer = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(bytes));
var chars = new char[charBuffer.limit()];
charBuffer.get(chars);
return chars;
} catch (Exception ex) {
return new char[0];
} }
} }
public String decrypt() {
return new String(decryptChars());
}
public static SecretValue ofSecret(String s) {
return new SecretValue(s);
}
public String getSecretValue() {
return decrypt();
}
} }

View file

@ -28,6 +28,7 @@ open module io.xpipe.core {
uses io.xpipe.core.util.ProxyProvider; uses io.xpipe.core.util.ProxyProvider;
uses io.xpipe.core.util.ProxyManagerProvider; uses io.xpipe.core.util.ProxyManagerProvider;
uses io.xpipe.core.util.DataStateProvider; uses io.xpipe.core.util.DataStateProvider;
uses io.xpipe.core.util.SecretProvider;
provides WriteMode with WriteMode.Replace, WriteMode.Append, WriteMode.Prepend; provides WriteMode with WriteMode.Replace, WriteMode.Append, WriteMode.Prepend;
provides com.fasterxml.jackson.databind.Module with provides com.fasterxml.jackson.databind.Module with

View file

@ -50,6 +50,10 @@ public interface DataStoreProvider {
default void storageInit() throws Exception { default void storageInit() throws Exception {
} }
default boolean isShareable() {
return false;
}
String queryInformationString(DataStore store, int length) throws Exception; String queryInformationString(DataStore store, int length) throws Exception;
public String toSummaryString(DataStore store, int length); public String toSummaryString(DataStore store, int length);

View file

@ -22,7 +22,7 @@ public class SecretFieldComp extends Comp<CompStructure<TextField>> {
var text = new PasswordField(); var text = new PasswordField();
text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null); text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
text.textProperty().addListener((c, o, n) -> { text.textProperty().addListener((c, o, n) -> {
value.setValue(n != null && n.length() > 0 ? SecretValue.create(n) : null); value.setValue(n != null && n.length() > 0 ? SecretValue.encrypt(n) : null);
}); });
value.addListener((c, o, n) -> { value.addListener((c, o, n) -> {
PlatformThread.runLaterIfNeeded(() -> { PlatformThread.runLaterIfNeeded(() -> {