mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-30 00:56:56 +13:00
Add a few more exchanges and improve secret handling
This commit is contained in:
parent
16f165a92c
commit
18a8bc8dc5
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")
|
||||
@Value
|
||||
@Builder
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
|
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 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,
|
||||
|
|
|
@ -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);
|
||||
|
|
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 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(() -> {
|
||||
|
|
Loading…
Reference in a new issue