feat: secure editor

This commit is contained in:
2023-12-02 12:15:05 +08:00
parent 0ca79fa6d5
commit 41627c2779
5 changed files with 259 additions and 53 deletions

62
.gitignore vendored
View File

@@ -1,54 +1,10 @@
# ---> Java
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# ---> macOS
# General
build
classes
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
.gradle
.classpath
.project
.settings
*.iml
*.ipr
*.iws

92
build.gradle Normal file
View File

@@ -0,0 +1,92 @@
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
def JsonSlurper = Class.forName('groovy.json.JsonSlurper');
def buildJSON = JsonSlurper.newInstance().parseText(new File("build.json").text)
if (buildJSON.application) { apply plugin: 'application' }
def baseProjectName = buildJSON?.project?.name ?: '__project_name__';
def shellCommandName = baseProjectName
def eclipseProjectName = baseProjectName
def eclipseProjectComment = buildJSON?.project?.comment ?: '__project_name_comment__'
def jarManifestMainClass = buildJSON?.project?.main ?: 'SampleMain'
if (buildJSON.application) { mainClassName = jarManifestMainClass }
archivesBaseName = buildJSON?.project?.archiveName ?: baseProjectName
sourceCompatibility = 1.8
targetCompatibility = 1.8
def addRepo = new File(System.getProperty("user.home"), ".build_add.repo")
repositories {
mavenLocal()
// mavenCentral()
maven() { url 'https://maven.aliyun.com/repository/central' }
if (addRepo.exists()) { maven() { url addRepo.text.trim() } }
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
// '-x test' skip unit test
defaultTasks 'build'
buildscript {
repositories {
mavenLocal()
maven() { url 'https://maven.aliyun.com/repository/central' }
mavenCentral()
jcenter()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.11.RELEASE")
}
}
apply plugin: 'org.springframework.boot'
springBoot {
mainClass = jarManifestMainClass
}
dependencies {
compile files(fileTree(dir: 'lib', includes: ['*.jar'], excludes: ['*-sources.jar', '*-javadoc.jar']))
if (buildJSON.repo != null && buildJSON.repo.dependencies != null) {
buildJSON.repo.dependencies.each {
compile("${it}")
}
}
if (buildJSON.repo != null && buildJSON.repo.testDependencies != null) {
buildJSON.repo.testDependencies.each {
testCompile("${it}")
}
}
}
eclipse {
project {
name = eclipseProjectName
comment = eclipseProjectComment
}
classpath {
defaultOutputDir = file('classes')
downloadSources = true
file {
whenMerged { classpath ->
classpath.entries.findAll { it.kind=='lib' }.each {
if ((it.path != null) && (it.sourcePath == null) && file(it.path.replace(".jar", "-sources.jar")).exists()) {
it.sourcePath = getFileReferenceFactory().fromPath(it.path.replace(".jar", "-sources.jar"))
}
}
}
}
}
}
eclipseJdt << {
File f = file('.settings/org.eclipse.core.resources.prefs')
f.write('eclipse.preferences.version=1\n')
f.append('encoding/<project>=utf-8')
}

21
build.json Normal file
View File

@@ -0,0 +1,21 @@
{
"project": {
"name": "secure-editor-java",
"main": "me.hatter.tools.secureeditor.Main",
"archiveName": "secure-editor"
},
"application": false,
"java": "1.8",
"builder": {
"name": "gradle",
"version": "3.1"
},
"repo": {
"dependencies": [
"me.hatter:commons:3.70"
],
"testDependencies": [
"junit:junit:4.12"
]
}
}

View File

@@ -0,0 +1,23 @@
package me.hatter.tools.secureeditor;
import java.util.concurrent.CountDownLatch;
public class FutureResult {
private volatile String result;
private final CountDownLatch countDown;
public FutureResult() {
this.result = null;
this.countDown = new CountDownLatch(1);
}
public void setResult(String result) {
this.result = result;
countDown.countDown();
}
public String getResult() throws InterruptedException {
countDown.await();
return result;
}
}

View File

@@ -0,0 +1,114 @@
package me.hatter.tools.secureeditor;
import me.hatter.tools.commons.bytes.Bytes;
import me.hatter.tools.commons.security.crypt.AESCryptTool;
import me.hatter.tools.commons.string.StringUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowListener;
import java.io.File;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) throws InterruptedException {
if (args.length != 4) {
System.err.println("[ERROR] Arguments is not 4, actual is: " + args.length);
System.exit(-1);
}
final String f = args[0];
final String algorithm = args[1];
final String key = args[2];
final String nonce = args[3];
final File file = new File(f);
if ((!file.exists()) || (!file.isFile())) {
System.err.println("[ERROR] File is not exist or file: " + f);
System.exit(-1);
}
if (!StringUtil.equals("aes-256-gcm", algorithm)) {
System.err.println("[ERROR] Algorithm is not aes-256-gcm.");
System.exit(-1);
}
final byte[] keyBytes = Bytes.fromHex(key).bytes();
final byte[] nonceBytes = Bytes.fromHex(nonce).bytes();
final Bytes decrypted = AESCryptTool.gcmDecrypt(keyBytes, nonceBytes).from(file).toBytes();
final boolean readonly = StringUtil.isOn(System.getProperty("readonly"));
final FutureResult futureResult = showWindow("Secure edit file",
decrypted.string(),
"File is encrypted in temp file",
!readonly);
final String result = futureResult.getResult();
if (result != null) {
AESCryptTool.gcmEncrypt(keyBytes, nonceBytes).from(Bytes.from(result)).to(file);
}
}
public static FutureResult showWindow(String title, String text, String message, boolean editable) {
final FutureResult futureResult = new FutureResult();
final JFrame frame = new JFrame(StringUtil.def(title, "Not titled"));
frame.addWindowListener((WindowListener) Proxy.newProxyInstance(
Proxy.class.getClassLoader(),
new Class[]{WindowListener.class},
(proxy, method, args) -> {
if ((method != null) && StringUtil.equals("windowClosing", method.getName())) {
frame.setVisible(false);
frame.dispose();
futureResult.setResult(null);
}
return null;
}));
final int rows = Integer.parseInt(StringUtil.def(System.getenv("ROWS"), "30"));
final int columns = Integer.parseInt(StringUtil.def(System.getenv("COLUMNS"), "80"));
final JTextArea textArea = new JTextArea(StringUtil.def(text, ""), rows, columns);
textArea.setWrapStyleWord(true);
textArea.setLineWrap(true);
textArea.setEditable(editable);
final JScrollPane textScrollPane = new JScrollPane(textArea);
final JLabel label = new JLabel(StringUtil.def(message, "This is default message!"));
JButton btnOK = null;
if (editable) {
btnOK = new JButton("OK!");
btnOK.addActionListener(e -> {
final String t = textArea.getText();
frame.setVisible(false);
frame.dispose();
futureResult.setResult(t);
});
}
final JButton btnCancel = new JButton("Cancel");
btnCancel.addActionListener(e -> {
frame.setVisible(false);
frame.dispose();
futureResult.setResult(null);
});
final JPanel pane = new JPanel();
if (btnOK != null) {
pane.add(btnOK);
}
pane.add(btnCancel);
frame.getContentPane().add(label, BorderLayout.NORTH);
frame.getContentPane().add(pane, BorderLayout.SOUTH);
frame.getContentPane().add(textScrollPane, BorderLayout.CENTER);
frame.pack();
final Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
frame.setLocation((dim.width / 2) - (frame.getSize().width / 2), (dim.height / 2) - (frame.getSize().height / 2));
frame.setVisible(true);
return futureResult;
}
}