diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/SecretFieldComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/SecretFieldComp.java index a76f8bb5..6f631852 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/SecretFieldComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/SecretFieldComp.java @@ -18,12 +18,16 @@ public class SecretFieldComp extends Comp> { this.value = value; } + protected SecretValue encrypt(char[] c) { + return SecretHelper.encrypt(c); + } + @Override public CompStructure createBase() { 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 ? SecretHelper.encrypt(n) : null); + value.setValue(n != null && n.length() > 0 ? encrypt(n.toCharArray()) : null); }); value.addListener((c, o, n) -> { PlatformThread.runLaterIfNeeded(() -> { diff --git a/app/src/main/java/io/xpipe/app/issue/ExceptionConverter.java b/app/src/main/java/io/xpipe/app/issue/ExceptionConverter.java index 377c13d2..2b656456 100644 --- a/app/src/main/java/io/xpipe/app/issue/ExceptionConverter.java +++ b/app/src/main/java/io/xpipe/app/issue/ExceptionConverter.java @@ -15,7 +15,13 @@ public class ExceptionConverter { } return switch (ex) { - case ProcessOutputException e -> e.getOutput(); + case ProcessOutputException e -> { + if (e.getOutput().isBlank()) { + yield e.getMessage(); + } else { + yield e.getOutput(); + } + } case StackOverflowError e -> AppI18n.get("app.stackOverflow"); case OutOfMemoryError e -> AppI18n.get("app.outOfMemory"); case FileNotFoundException e -> AppI18n.get("app.fileNotFound", msg); diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 3a9efd37..07973cc3 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -270,11 +270,15 @@ public class AppPrefs { lockCrypt.setValue(new LockedSecretValue("xpipe".toCharArray()).getEncryptedValue()); } - public boolean checkLock(SecretValue lockPw) { + public boolean unlock(SecretValue lockPw) { lockPassword.setValue(lockPw); var check = new LockedSecretValue("xpipe".toCharArray()).getEncryptedValue(); - lockPassword.setValue(null); - return check.equals(lockCrypt.get()); + if (!check.equals(lockCrypt.get())) { + lockPassword.setValue(null); + return false; + } else { + return true; + } } public StringProperty getLockCrypt() { diff --git a/app/src/main/java/io/xpipe/app/util/DefaultSecretValue.java b/app/src/main/java/io/xpipe/app/util/DefaultSecretValue.java index 87f64bc3..ec1e4550 100644 --- a/app/src/main/java/io/xpipe/app/util/DefaultSecretValue.java +++ b/app/src/main/java/io/xpipe/app/util/DefaultSecretValue.java @@ -2,6 +2,7 @@ package io.xpipe.app.util; import com.fasterxml.jackson.annotation.JsonTypeName; import io.xpipe.core.util.AesSecretValue; +import lombok.EqualsAndHashCode; import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; @@ -18,6 +19,7 @@ import java.util.Random; @JsonTypeName("default") @SuperBuilder @Jacksonized +@EqualsAndHashCode(callSuper = true) public class DefaultSecretValue extends AesSecretValue { public DefaultSecretValue(char[] secret) { diff --git a/app/src/main/java/io/xpipe/app/util/LockChangeAlert.java b/app/src/main/java/io/xpipe/app/util/LockChangeAlert.java index 9cd1797f..29bba45b 100644 --- a/app/src/main/java/io/xpipe/app/util/LockChangeAlert.java +++ b/app/src/main/java/io/xpipe/app/util/LockChangeAlert.java @@ -22,18 +22,28 @@ public class LockChangeAlert { alert.setAlertType(Alert.AlertType.CONFIRMATION); var label1 = new LabelComp(AppI18n.observable("password")).createRegion(); - var p1 = new SecretFieldComp(prop1).createRegion(); + var p1 = new SecretFieldComp(prop1) { + @Override + protected SecretValue encrypt(char[] c) { + return SecretHelper.encryptInPlace(c); + } + }.createRegion(); p1.setStyle("-fx-border-width: 1px"); var label2 = new LabelComp(AppI18n.observable("repeatPassword")).createRegion(); - var p2 = new SecretFieldComp(prop2).createRegion(); + var p2 = new SecretFieldComp(prop2) { + @Override + protected SecretValue encrypt(char[] c) { + return SecretHelper.encryptInPlace(c); + } + }.createRegion(); p1.setStyle("-fx-border-width: 1px"); var content = new VBox(label1, p1, new Spacer(15), label2, p2); content.setSpacing(5); alert.getDialogPane().setContent(content); }) - .filter(b -> b.getButtonData().isDefaultButton() && (prop1.getValue() != null && prop2.getValue() != null && prop1.getValue().equals(prop2.getValue())) || (prop1.getValue() == null && prop2.getValue() == null)) + .filter(b -> b.getButtonData().isDefaultButton() && ((prop1.getValue() != null && prop2.getValue() != null && prop1.getValue().equals(prop2.getValue())) || (prop1.getValue() == null && prop2.getValue() == null))) .ifPresent(t -> { AppPrefs.get().changeLock(prop1.getValue()); }); diff --git a/app/src/main/java/io/xpipe/app/util/LockedSecretValue.java b/app/src/main/java/io/xpipe/app/util/LockedSecretValue.java index f1381a34..9f0b74ee 100644 --- a/app/src/main/java/io/xpipe/app/util/LockedSecretValue.java +++ b/app/src/main/java/io/xpipe/app/util/LockedSecretValue.java @@ -3,6 +3,7 @@ package io.xpipe.app.util; import com.fasterxml.jackson.annotation.JsonTypeName; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.core.util.AesSecretValue; +import lombok.EqualsAndHashCode; import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; @@ -18,6 +19,7 @@ import java.util.Random; @JsonTypeName("locked") @SuperBuilder @Jacksonized +@EqualsAndHashCode(callSuper = true) public class LockedSecretValue extends AesSecretValue { public LockedSecretValue(char[] secret) { diff --git a/app/src/main/java/io/xpipe/app/util/UnlockAlert.java b/app/src/main/java/io/xpipe/app/util/UnlockAlert.java index c513c33d..5e9a4525 100644 --- a/app/src/main/java/io/xpipe/app/util/UnlockAlert.java +++ b/app/src/main/java/io/xpipe/app/util/UnlockAlert.java @@ -42,7 +42,7 @@ public class UnlockAlert { return; } - if (AppPrefs.get().checkLock(pw.get())) { + if (AppPrefs.get().unlock(pw.get())) { return; } } diff --git a/core/src/main/java/io/xpipe/core/util/AesSecretValue.java b/core/src/main/java/io/xpipe/core/util/AesSecretValue.java index f171c7a8..5f4141f5 100644 --- a/core/src/main/java/io/xpipe/core/util/AesSecretValue.java +++ b/core/src/main/java/io/xpipe/core/util/AesSecretValue.java @@ -1,5 +1,6 @@ package io.xpipe.core.util; +import lombok.EqualsAndHashCode; import lombok.SneakyThrows; import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; @@ -15,6 +16,7 @@ import java.security.spec.InvalidKeySpecException; @SuperBuilder @Jacksonized +@EqualsAndHashCode(callSuper = true) public class AesSecretValue extends EncryptedSecretValue { private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding"; diff --git a/core/src/main/java/io/xpipe/core/util/EncryptedSecretValue.java b/core/src/main/java/io/xpipe/core/util/EncryptedSecretValue.java index 1129a17d..c7d2afbb 100644 --- a/core/src/main/java/io/xpipe/core/util/EncryptedSecretValue.java +++ b/core/src/main/java/io/xpipe/core/util/EncryptedSecretValue.java @@ -1,5 +1,6 @@ package io.xpipe.core.util; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; @@ -11,6 +12,7 @@ import java.util.Base64; @SuperBuilder @Jacksonized +@EqualsAndHashCode public class EncryptedSecretValue implements SecretValue { @Getter @@ -38,7 +40,7 @@ public class EncryptedSecretValue implements SecretValue { charBuffer.get(chars); return chars; } catch (Exception ex) { - return new char[0]; + throw new IllegalStateException("Unable to decrypt secret"); } } diff --git a/dist/changelogs/0.5.36.md b/dist/changelogs/0.5.36.md index 278988e4..a9718902 100644 --- a/dist/changelogs/0.5.36.md +++ b/dist/changelogs/0.5.36.md @@ -1,7 +1,12 @@ ## Changes in 0.5.36 -- Add support for PowerShell Remote connections +- Add support to lock your workspace with a custom password similar to password managers. + This will encrypt all stored sensitive information using the password as the key. + The general password encryption has also been reworked, which results in all existing passwords becoming invalid. + So make sure to reenter your passwords for any connection. +- Improve file browser performance +- Add experimental support for PowerShell Remote connections - Add support for PowerShell core shell environments -- Implement initial support for package manager builds +- Implement initial support for future package manager builds - Rework updating system to accommodate package managers - Improve error handling - Fix many bugs