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 16f165a92c
commit 18a8bc8dc5
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")
@Value
@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 EXEC_DEBUG_PROP = "io.xpipe.beacon.printDaemonOutput";
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";
public static boolean localProxy() {

View file

@ -10,6 +10,7 @@ public class BeaconJacksonModule extends SimpleModule {
context.registerSubtypes(
new NamedType(BeaconClient.ApiClientInformation.class),
new NamedType(BeaconClient.CliClientInformation.class),
new NamedType(BeaconClient.DaemonInformation.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 io.xpipe.beacon.BeaconJacksonModule;
import io.xpipe.beacon.BeaconProxyImpl;
import io.xpipe.beacon.SecretProviderImpl;
import io.xpipe.beacon.exchange.*;
import io.xpipe.beacon.exchange.api.QueryRawDataExchange;
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.core.util.ProxyFunction;
import io.xpipe.core.util.ProxyProvider;
import io.xpipe.core.util.SecretProvider;
module io.xpipe.beacon {
exports io.xpipe.beacon;
@ -33,6 +35,7 @@ module io.xpipe.beacon {
uses ProxyFunction;
provides ProxyProvider with BeaconProxyImpl;
provides SecretProvider with SecretProviderImpl;
provides Module with BeaconJacksonModule;
provides io.xpipe.beacon.exchange.MessageExchange with
ForwardExchange,
@ -49,6 +52,8 @@ module io.xpipe.beacon {
ProxyFunctionExchange,
QueryStoreExchange,
StatusExchange,
FocusExchange,
OpenExchange,
StopExchange,
RenameStoreExchange,
RemoveStoreExchange,

View file

@ -1,6 +1,7 @@
package io.xpipe.core.util;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -18,6 +19,12 @@ public class JacksonMapper {
private static ObjectMapper INSTANCE = new ObjectMapper();
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 {
ObjectMapper objectMapper = BASE;
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 java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
@ -16,24 +17,32 @@ public class SecretValue {
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) {
return null;
}
if (s.length() < 2) {
return new SecretValue(s);
}
return new SecretValue(Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8)));
return encrypt(s.toCharArray());
}
public void withSecretValue(Consumer<char[]> chars) {
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);
public void withSecretValue(Consumer<char[]> con) {
var chars = decryptChars();
con.accept(chars);
Arrays.fill(chars, (char) 0);
}
@Override
@ -45,14 +54,28 @@ public class SecretValue {
return value;
}
public String getSecretValue() {
if (value.length() < 2) {
return value;
}
public char[] decryptChars() {
try {
return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8);
} catch (Exception exception) {
return "";
var bytes = Base64.getDecoder().decode(value.replace("-", "/"));
bytes = SecretProvider.get().decrypt(bytes);
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.ProxyManagerProvider;
uses io.xpipe.core.util.DataStateProvider;
uses io.xpipe.core.util.SecretProvider;
provides WriteMode with WriteMode.Replace, WriteMode.Append, WriteMode.Prepend;
provides com.fasterxml.jackson.databind.Module with

View file

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

View file

@ -22,7 +22,7 @@ public class SecretFieldComp extends Comp<CompStructure<TextField>> {
var text = new PasswordField();
text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
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) -> {
PlatformThread.runLaterIfNeeded(() -> {