From 3fca3847dc2891e5179faf7e19aeee0764e2ca3c Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sun, 30 Jun 2024 16:04:48 +0800 Subject: [PATCH] feat: init version v1.0 --- build.gradle | 100 ++++++++++++++++ build.json | 24 ++++ .../tools/pinentry/PinEntryException.java | 7 ++ .../hatter/tools/pinentry/PinEntryTool.java | 110 ++++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 build.gradle create mode 100644 build.json create mode 100644 src/main/java/me/hatter/tools/pinentry/PinEntryException.java create mode 100644 src/main/java/me/hatter/tools/pinentry/PinEntryTool.java diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..41d4796 --- /dev/null +++ b/build.gradle @@ -0,0 +1,100 @@ +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 'packjar' + +task packjarsrc << { + ant.jar(destfile: "${baseProjectName}-sources.jar") { + fileset(dir: 'src/main/java', includes: '**/*.java') + } +} +packjarsrc.dependsOn build + +task packjar << { + def packtempclasses = "packtempclasses" + def libs = ant.path { + fileset(dir: 'build/libs', includes: '*.jar') + } + libs.list().each { + ant.unzip(dest: packtempclasses, src: it) + } + new File(packtempclasses + "/jar-version-build.txt").write(new Date().format("yyyyMMdd"), "UTF-8") + ant.jar(destfile: "${baseProjectName}.jar") { + fileset(dir: packtempclasses, includes: '**/*.*') + } + ant.delete(dir: packtempclasses) +} +packjar.dependsOn packjarsrc + +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/=utf-8') +} + diff --git a/build.json b/build.json new file mode 100644 index 0000000..e327881 --- /dev/null +++ b/build.json @@ -0,0 +1,24 @@ +{ + "project": { + "name": "pinentry-cli-java", + "archiveName": "pinentry-cli-java" + }, + "application": false, + "java": "1.8", + "builder": { + "name": "gradle", + "version": "3.1" + }, + "repo": { + "gid": "me.hatter", + "aid": "pinentry-cli-java", + "jar": "pinentry-cli-java.jar", + "src": "pinentry-cli-java-sources.jar", + "dependencies": [ + "me.hatter:commons:3.71" + ], + "testDependencies": [ + "junit:junit:4.12" + ] + } +} diff --git a/src/main/java/me/hatter/tools/pinentry/PinEntryException.java b/src/main/java/me/hatter/tools/pinentry/PinEntryException.java new file mode 100644 index 0000000..5cab325 --- /dev/null +++ b/src/main/java/me/hatter/tools/pinentry/PinEntryException.java @@ -0,0 +1,7 @@ +package me.hatter.tools.pinentry; + +public class PinEntryException extends Exception { + public PinEntryException(String message) { + super(message); + } +} diff --git a/src/main/java/me/hatter/tools/pinentry/PinEntryTool.java b/src/main/java/me/hatter/tools/pinentry/PinEntryTool.java new file mode 100644 index 0000000..f133ad9 --- /dev/null +++ b/src/main/java/me/hatter/tools/pinentry/PinEntryTool.java @@ -0,0 +1,110 @@ +package me.hatter.tools.pinentry; + +import com.alibaba.fastjson.JSONObject; +import me.hatter.tools.commons.bytes.Bytes; +import me.hatter.tools.commons.process.ShellCommandTool; +import me.hatter.tools.commons.security.crypt.AESCryptTool; +import me.hatter.tools.commons.security.random.RandomTool; +import me.hatter.tools.commons.string.StringUtil; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +public class PinEntryTool { + private String pinEntryCli = null; + private String pinEntry = null; + private boolean disableFallbackCli = false; + + public static void main(String[] args) throws PinEntryException { + System.out.println(PinEntryTool.instance().getPin()); + } + + public static PinEntryTool instance() { + return new PinEntryTool(); + } + + public PinEntryTool pinEntryCli(String pinEntryCli) { + this.pinEntryCli = pinEntryCli; + return this; + } + + public PinEntryTool pinEntry(String pinEntry) { + this.pinEntry = pinEntry; + return this; + } + + public PinEntryTool disableFallbackCli() { + return disableFallbackCli(true); + } + + public PinEntryTool disableFallbackCli(boolean disableFallbackCli) { + this.disableFallbackCli = disableFallbackCli; + return this; + } + + public String getPin() throws PinEntryException { + return getPin(null, null); + } + + public String getPin(String description, String prompt) throws PinEntryException { + final byte[] key = RandomTool.random().nextbytes(32); + final String[] commands = buildCommands(key, description, prompt); + final StringBuilder output = new StringBuilder(); + final int exitCode = ShellCommandTool.instance() + .commands(commands) + .env(System.getenv()) + .noStdPrint() + .run((out, err) -> { + if (out != null) { + output.append(out.string()); + } + }); + try { + final JSONObject jo = JSONObject.parseObject(output.toString()); + final boolean success = jo.getBoolean("success"); + final String error = jo.getString("error"); + final String pin = jo.getString("pin"); + if (!success) { + throw new PinEntryException("Get pin not succeed, exit code: " + exitCode + ", error: " + error); + } + final String[] nonceAndCiphertext = pin.split("\\."); + if (nonceAndCiphertext.length != 2) { + throw new PinEntryException("Get pin not succeed, pin: " + pin); + } + final byte[] nonce = Bytes.fromHex(nonceAndCiphertext[0]).bytes(); + final Bytes ciphertextWithTag = Bytes.fromHex(nonceAndCiphertext[1]); + final byte[] pinBytes = AESCryptTool.gcmDecrypt(key, nonce).from(ciphertextWithTag).toBytes().bytes(); + return new String(pinBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + if (exitCode != 0) { + throw new PinEntryException("Get pin not succeed, exit code: " + exitCode); + } else { + throw new PinEntryException("Get pin not succeed, exit code: " + exitCode + ", additional error: " + e.getMessage()); + } + } + } + + private String[] buildCommands(byte[] key, String description, String prompt) { + final List commands = new ArrayList<>(); + commands.add(StringUtil.def(pinEntryCli, "pinentry-cli")); + commands.add("--encryption-key"); + commands.add(Bytes.from(key).asHex()); + if (StringUtil.isNotEmpty(pinEntry)) { + commands.add("--pin-entry"); + commands.add(pinEntry); + } + if (StringUtil.isNotEmpty(description)) { + commands.add("--description"); + commands.add(description); + } + if (StringUtil.isNotEmpty(prompt)) { + commands.add("--prompt"); + commands.add(prompt); + } + if (disableFallbackCli) { + commands.add("--disable-fallback-cli"); + } + return commands.toArray(new String[0]); + } +}