feat: secure editor
This commit is contained in:
62
.gitignore
vendored
62
.gitignore
vendored
@@ -1,54 +1,10 @@
|
|||||||
# ---> Java
|
build
|
||||||
# Compiled class file
|
classes
|
||||||
*.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
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
.gradle
|
||||||
.LSOverride
|
.classpath
|
||||||
|
.project
|
||||||
# Icon must end with two \r
|
.settings
|
||||||
Icon
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
# 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
|
|
||||||
92
build.gradle
Normal file
92
build.gradle
Normal 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
21
build.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/main/java/me/hatter/tools/secureeditor/FutureResult.java
Normal file
23
src/main/java/me/hatter/tools/secureeditor/FutureResult.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/main/java/me/hatter/tools/secureeditor/Main.java
Normal file
114
src/main/java/me/hatter/tools/secureeditor/Main.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user