mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-03 03:01:13 +12:00
Add a few more exchanges and improve secret handling
This commit is contained in:
parent
70568f7a9b
commit
a82717154d
13 changed files with 222 additions and 21 deletions
|
@ -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
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
73
beacon/src/main/java/io/xpipe/beacon/SecretProviderImpl.java
Normal file
73
beacon/src/main/java/io/xpipe/beacon/SecretProviderImpl.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
16
core/src/main/java/io/xpipe/core/util/SecretProvider.java
Normal file
16
core/src/main/java/io/xpipe/core/util/SecretProvider.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(() -> {
|
||||||
|
|
Loading…
Reference in a new issue