totpgenerator

Generate TOTP verification codes based on encrypted GPG files.
git clone git://git.wimdupont.com/totpgenerator.git
Log | Files | Refs | README | LICENSE

commit 6b6ef474a4a9fd17a1f6858e2b102c5150ccbac1
parent 35a6adb000319694245fdaadd340a929213ef2e9
Author: Wim Dupont <wim@wimdupont.com>
Date:   Tue, 28 Feb 2023 21:42:06 +0100

version bump and minor updates

Diffstat:
M.gitignore | 2++
Mpom.xml | 6+++---
Dsrc/main/java/Main.java | 12------------
Asrc/main/java/com/wimdupont/Main.java | 12++++++++++++
Asrc/main/java/com/wimdupont/service/GpgUtil.java | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/main/java/service/GpgUtil.java | 145-------------------------------------------------------------------------------
6 files changed, 166 insertions(+), 160 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -33,3 +33,5 @@ build/ .vscode/ /src/main/resources/application.properties +/src/main/resources/secret.asc +/src/main/resources/totpfiles/ diff --git a/pom.xml b/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> - <groupId>com.dupont</groupId> + <groupId>com.wimdupont</groupId> <artifactId>TotpGenerator</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> @@ -23,7 +23,7 @@ <dependency> <groupId>com.github.bastiaanjansen</groupId> <artifactId>otp-java</artifactId> - <version>1.2.3</version> + <version>2.0.1</version> </dependency> </dependencies> @@ -62,7 +62,7 @@ <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass> - Main + com.wimdupont.Main </mainClass> </manifest> </archive> diff --git a/src/main/java/Main.java b/src/main/java/Main.java @@ -1,12 +0,0 @@ -import com.bastiaanjansen.otp.TOTPGenerator.Builder; -import service.GpgUtil; - -import java.util.Date; - -public class Main { - public static void main(String[] args) { - String fileArgument = args.length > 0 ? args[0] : null; - System.out.println(Builder.withDefaultValues(new GpgUtil().decrypt(fileArgument).orElseThrow().getBytes()).generate(new Date())); - System.exit(0); - } -} diff --git a/src/main/java/com/wimdupont/Main.java b/src/main/java/com/wimdupont/Main.java @@ -0,0 +1,12 @@ +package com.wimdupont; + +import com.bastiaanjansen.otp.TOTPGenerator; +import com.wimdupont.service.GpgUtil; + +public class Main { + public static void main(String[] args) { + String fileArgument = args.length > 0 ? args[0] : null; + System.out.println(TOTPGenerator.withDefaultValues(new GpgUtil().decrypt(fileArgument).orElseThrow().getBytes()).now()); + System.exit(0); + } +} diff --git a/src/main/java/com/wimdupont/service/GpgUtil.java b/src/main/java/com/wimdupont/service/GpgUtil.java @@ -0,0 +1,149 @@ +package com.wimdupont.service; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPCompressedData; +import org.bouncycastle.openpgp.PGPEncryptedData; +import org.bouncycastle.openpgp.PGPEncryptedDataList; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; + +import java.io.ByteArrayOutputStream; +import java.io.Console; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.Security; +import java.util.Arrays; +import java.util.Iterator; +import java.util.MissingResourceException; +import java.util.Optional; +import java.util.Properties; +import java.util.Scanner; +import java.util.stream.Collectors; + +public class GpgUtil { + + private static String dirPath; + private static String secretFile; + private static final String GPG_EXTENSION = ".gpg"; + + public GpgUtil() { + Properties properties = loadProperties(); + dirPath = properties.getProperty("dir.path"); + secretFile = properties.getProperty("secret.file"); + if (dirPath == null | secretFile == null) + throw new RuntimeException("Properties missing."); + } + + public Optional<String> decrypt(String fileArgument) { + final Scanner scanner = new Scanner(System.in); + File file; + if (fileArgument != null) { + file = new File(dirPath + fileArgument + GPG_EXTENSION).getAbsoluteFile(); + if (!file.exists()) throw new RuntimeException(String.format("File \"%s\" not found", file.getName())); + } else { + File[] files = new File(dirPath).listFiles(); + if (files == null || files.length < 1) throw new RuntimeException(dirPath + " contains no files"); + System.out.println("Which TOTP?"); + String fileNames = Arrays.stream(files) + .filter(f -> f.getName().contains(GPG_EXTENSION)) + .map(f -> f.getName().replace(GPG_EXTENSION, "")) + .collect(Collectors.joining(", ")); + System.out.println(fileNames); + String fileName = scanner.nextLine(); + file = Arrays.stream(files).filter(f -> f.getName().replace(GPG_EXTENSION, "").equals(fileName)).findAny() + .orElseThrow(() -> new RuntimeException("File not found")); + } + + System.out.println("Type in GPG password."); + Console cons = System.console(); + String pwd; + if (cons != null) { + pwd = new String(cons.readPassword()); + } else { + System.out.println("Careful, input is not hidden"); + pwd = scanner.nextLine(); + } + try { + return Optional.ofNullable(decryptFile(new FileInputStream(file.getAbsolutePath()), pwd.toCharArray())); + } catch (IOException | PGPException e) { + e.printStackTrace(); + return Optional.empty(); + } + } + + private Properties loadProperties() { + final Properties properties = new Properties(); + InputStream is = getClass().getResourceAsStream("/application.properties"); + try { + if (is == null) { + throw new IOException(); + } + properties.load(is); + } catch (IOException e) { + throw new MissingResourceException("Missing application properties", Properties.class.getSimpleName(), "application.properties"); + } + return properties; + } + + private PGPSecretKey readSecretKeyFromCol(InputStream in, long keyId) throws IOException, PGPException { + in = PGPUtil.getDecoderStream(in); + PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(in, new BcKeyFingerprintCalculator()); + PGPSecretKey key = pgpSec.getSecretKey(keyId); + if (key == null) { + throw new IllegalArgumentException("Can't find encryption key in key ring."); + } + return key; + } + + private String decryptFile(InputStream in, char[] pass) throws IOException, PGPException { + Security.addProvider(new BouncyCastleProvider()); + PGPSecretKey secKey; + in = PGPUtil.getDecoderStream(in); + JcaPGPObjectFactory pgpFact; + + PGPObjectFactory pgpF = new PGPObjectFactory(in, new BcKeyFingerprintCalculator()); + Object o = pgpF.nextObject(); + PGPEncryptedDataList encList; + if (o instanceof PGPEncryptedDataList) { + encList = (PGPEncryptedDataList) o; + } else { + encList = (PGPEncryptedDataList) pgpF.nextObject(); + } + Iterator<PGPEncryptedData> itt = encList.getEncryptedDataObjects(); + PGPPrivateKey sKey = null; + PGPPublicKeyEncryptedData encP = null; + while (sKey == null && itt.hasNext()) { + encP = (PGPPublicKeyEncryptedData) itt.next(); + secKey = readSecretKeyFromCol(new FileInputStream(secretFile), encP.getKeyID()); + sKey = secKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass)); + } + if (sKey == null) { + throw new IllegalArgumentException("Secret key for message not found."); + } + InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey)); + pgpFact = new JcaPGPObjectFactory(clear); + PGPCompressedData c1 = (PGPCompressedData) pgpFact.nextObject(); + pgpFact = new JcaPGPObjectFactory(c1.getDataStream()); + PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject(); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + InputStream inLd = ld.getDataStream(); + int ch; + while ((ch = inLd.read()) >= 0) { + bOut.write(ch); + } + return bOut.toString(); + } +} diff --git a/src/main/java/service/GpgUtil.java b/src/main/java/service/GpgUtil.java @@ -1,145 +0,0 @@ -package service; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openpgp.PGPCompressedData; -import org.bouncycastle.openpgp.PGPEncryptedData; -import org.bouncycastle.openpgp.PGPEncryptedDataList; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPLiteralData; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.bouncycastle.openpgp.PGPUtil; -import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; -import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; - -import java.io.ByteArrayOutputStream; -import java.io.Console; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.Security; -import java.util.Arrays; -import java.util.Iterator; -import java.util.Optional; -import java.util.Properties; -import java.util.Scanner; -import java.util.stream.Collectors; - -public class GpgUtil { - - private static String dirPath; - private static String secretFile; - private static final String GPG_EXTENSION = ".gpg"; - - public GpgUtil() { - try { - Properties properties = loadProperties(); - dirPath = properties.getProperty("dir.path"); - secretFile = properties.getProperty("secret.file"); - if (dirPath == null | secretFile == null) - throw new RuntimeException("Properties missing."); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public Optional<String> decrypt(String fileArgument) { - final Scanner scanner = new Scanner(System.in); - File file; - if (fileArgument != null) { - file = new File(dirPath + fileArgument + GPG_EXTENSION).getAbsoluteFile(); - if (!file.exists()) throw new RuntimeException(String.format("File \"%s\" not found", file.getName())); - } else { - File[] files = new File(dirPath).listFiles(); - if (files == null || files.length < 1) throw new RuntimeException(dirPath + " contains no files"); - System.out.println("Which TOTP?"); - String fileNames = Arrays.stream(files) - .filter(f -> f.getName().contains(GPG_EXTENSION)) - .map(f -> f.getName().replace(GPG_EXTENSION, "")) - .collect(Collectors.joining(", ")); - System.out.println(fileNames); - String fileName = scanner.nextLine(); - file = Arrays.stream(files).filter(f -> f.getName().replace(GPG_EXTENSION, "").equals(fileName)).findAny() - .orElseThrow(() -> new RuntimeException("File not found")); - } - - System.out.println("Type in GPG password."); - Console cons = System.console(); - String pwd; - if (cons != null) { - pwd = new String(cons.readPassword()); - } else { - System.out.println("Careful, input is not hidden"); - pwd = scanner.nextLine(); - } - try { - return Optional.ofNullable(decryptFile(new FileInputStream(file.getAbsolutePath()), pwd.toCharArray())); - } catch (IOException | PGPException e) { - e.printStackTrace(); - return Optional.empty(); - } - } - - private Properties loadProperties() throws IOException { - final Properties properties = new Properties(); - InputStream is = getClass().getResourceAsStream("/application.properties"); - properties.load(is); - return properties; - } - - private PGPSecretKey readSecretKeyFromCol(InputStream in, long keyId) throws IOException, PGPException { - in = PGPUtil.getDecoderStream(in); - PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(in, new BcKeyFingerprintCalculator()); - PGPSecretKey key = pgpSec.getSecretKey(keyId); - if (key == null) { - throw new IllegalArgumentException("Can't find encryption key in key ring."); - } - return key; - } - - private String decryptFile(InputStream in, char[] pass) throws IOException, PGPException { - Security.addProvider(new BouncyCastleProvider()); - PGPSecretKey secKey; - in = PGPUtil.getDecoderStream(in); - JcaPGPObjectFactory pgpFact; - - PGPObjectFactory pgpF = new PGPObjectFactory(in, new BcKeyFingerprintCalculator()); - Object o = pgpF.nextObject(); - PGPEncryptedDataList encList; - if (o instanceof PGPEncryptedDataList) { - encList = (PGPEncryptedDataList) o; - } else { - encList = (PGPEncryptedDataList) pgpF.nextObject(); - } - Iterator<PGPEncryptedData> itt = encList.getEncryptedDataObjects(); - PGPPrivateKey sKey = null; - PGPPublicKeyEncryptedData encP = null; - while (sKey == null && itt.hasNext()) { - encP = (PGPPublicKeyEncryptedData) itt.next(); - secKey = readSecretKeyFromCol(new FileInputStream(secretFile), encP.getKeyID()); - sKey = secKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass)); - } - if (sKey == null) { - throw new IllegalArgumentException("Secret key for message not found."); - } - InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey)); - pgpFact = new JcaPGPObjectFactory(clear); - PGPCompressedData c1 = (PGPCompressedData) pgpFact.nextObject(); - pgpFact = new JcaPGPObjectFactory(c1.getDataStream()); - PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject(); - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - InputStream inLd = ld.getDataStream(); - int ch; - while ((ch = inLd.read()) >= 0) { - bOut.write(ch); - } - return bOut.toString(); - } -}