diff --git a/README.md b/README.md index df136ed..7c6dd1a 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ list : list all key documents. peek : view details of a key document. `peek <# from list>` +sign : signs a string of text, and produces signature. `sign ` + use : designate currently used key document. `use <# from list>` ```` diff --git a/pom.xml b/pom.xml index 653eb2c..e83ed70 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.white5moke handeroffer - 0.0.1 + 0.0.2 17 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java deleted file mode 100644 index 4f326f4..0000000 --- a/src/main/java/module-info.java +++ /dev/null @@ -1,6 +0,0 @@ -module org.white5moke.handoff { - requires org.json; - requires org.apache.commons.lang3; - requires org.apache.commons.codec; - requires commons.cli; -} \ No newline at end of file diff --git a/src/main/java/org/white5moke/handoff/App.java b/src/main/java/org/white5moke/handoff/App.java index 6653ad0..002f971 100644 --- a/src/main/java/org/white5moke/handoff/App.java +++ b/src/main/java/org/white5moke/handoff/App.java @@ -2,6 +2,7 @@ import org.apache.commons.codec.digest.DigestUtils; import org.json.JSONObject; +import org.white5moke.handoff.client.Handoff; import org.white5moke.handoff.client.HandoffClient; import javax.crypto.*; @@ -38,7 +39,8 @@ public App() throws Exception { * @throws IllegalBlockSizeException * @throws BadPaddingException */ - public static void a1() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException, InvalidKeySpecException, InvalidKeyException, SignatureException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { + public static void a1() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException, + InvalidKeySpecException, InvalidKeyException, SignatureException, NoSuchPaddingException { final String SECP = "secp256r1"; KeyPairGenerator gen = KeyPairGenerator.getInstance("EC"); diff --git a/src/main/java/org/white5moke/handoff/client/Handoff.java b/src/main/java/org/white5moke/handoff/client/Handoff.java new file mode 100644 index 0000000..f0d2eb6 --- /dev/null +++ b/src/main/java/org/white5moke/handoff/client/Handoff.java @@ -0,0 +1,98 @@ +package org.white5moke.handoff.client; + +import org.apache.commons.lang3.StringUtils; + +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.time.Instant; +import java.util.Base64; + +public class Handoff { + + private PrivateKey privateKey; + + public Handoff() throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException { + //signingKeys(); + loop(); + } + + private void loop() throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException { + int i = 0; + while(i < 5) { + gen(); + i++; + } + } + + private void gen() throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + + //PrivateKey priv = privateKeyFromString(); + Signature sig = Signature.getInstance("SHA256withECDSA"); + //sig.initSign(priv, random); + + + String timestampStr = StringUtils.rightPad(String.valueOf(Instant.now().toEpochMilli()), 16, StringUtils.SPACE); + String randomStr = StringUtils.rightPad(Base64.getEncoder().encodeToString(random.generateSeed(16)), 32, StringUtils.SPACE); + + String msg = String.format("%s%s", + timestampStr, + randomStr); + + sig.update(msg.getBytes(StandardCharsets.UTF_8)); + byte[] signed = sig.sign(); + + msg += StringUtils.rightPad(Base64.getEncoder().encodeToString(signed), 97, StringUtils.SPACE); + + + + System.out.println(msg); + } + + private PrivateKey privateKeyFromString(String privateKey) throws NoSuchAlgorithmException, + InvalidKeySpecException { + byte[] pk = Base64.getDecoder().decode(privateKey); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pk); + + KeyFactory factory = KeyFactory.getInstance("EC"); + + return factory.generatePrivate(spec); + } + + private PublicKey publicKeyFromString(String publicKey) throws NoSuchAlgorithmException, + InvalidKeySpecException { + byte[] pk = Base64.getDecoder().decode(publicKey); + + EncodedKeySpec spec = new X509EncodedKeySpec(pk); + + KeyFactory factory = KeyFactory.getInstance("EC"); + PublicKey pubKey = factory.generatePublic(spec); + + return pubKey; + } + + private KeyPair signingKeys() throws NoSuchAlgorithmException { + KeyPairGenerator sGen = KeyPairGenerator.getInstance("EC"); + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + + sGen.initialize(256, random); + + KeyPair sPair = sGen.generateKeyPair(); + + System.out.println("pub: " + Base64.getEncoder().encodeToString(sPair.getPublic().getEncoded())); + System.out.println("priv: " + Base64.getEncoder().encodeToString(sPair.getPrivate().getEncoded())); + return sPair; + } + + public PrivateKey getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(PrivateKey privateKey) { + this.privateKey = privateKey; + } +} diff --git a/src/main/java/org/white5moke/handoff/client/HandoffClient.java b/src/main/java/org/white5moke/handoff/client/HandoffClient.java index c4772f7..ec9e1a0 100644 --- a/src/main/java/org/white5moke/handoff/client/HandoffClient.java +++ b/src/main/java/org/white5moke/handoff/client/HandoffClient.java @@ -1,3 +1,8 @@ +/** + * TODO: + * - encrypt, encrpyt, encrypt the key documents after `gen`! + */ + package org.white5moke.handoff.client; import org.apache.commons.codec.digest.DigestUtils; @@ -5,33 +10,61 @@ import org.json.JSONObject; import org.white5moke.handoff.know.PoW; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.time.ZoneId; -import java.util.Arrays; -import java.util.Base64; -import java.util.List; -import java.util.Scanner; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; public class HandoffClient { private Path home = Path.of(System.getProperty("user.home"), ".handoff"); private Scanner scan = new Scanner(System.in); - private List hashList; + private List hashList = new ArrayList<>(); private String currentDocumentHash = StringUtils.EMPTY; public HandoffClient() throws IOException, NoSuchAlgorithmException, SignatureException, - InvalidKeyException { + InvalidKeyException, InvalidKeySpecException, NoSuchPaddingException, IllegalBlockSizeException, + BadPaddingException { runLoop(); } + public String getCurrentDocumentHash() { + return currentDocumentHash; + } + + public void setCurrentDocumentHash(String currentDocumentHash) { + this.currentDocumentHash = currentDocumentHash; + } + + public List getHashList() { + return hashList; + } + + public void setHashList(List hl) { + this.hashList = hl; + } + private void runLoop() throws IOException, NoSuchAlgorithmException, SignatureException, - InvalidKeyException { + InvalidKeyException, InvalidKeySpecException { + + // set a default key-doc to use (let user change if needed) + String filename = Files.list(getHome()).map(x -> x.getFileName().toString()).findFirst().orElse(null); + setCurrentDocumentHash(filename.trim()); + System.out.println(String.format("current key document: `%s`", + getCurrentDocumentHash() + )); + while (true) { System.out.print("> "); @@ -74,6 +107,9 @@ private void runLoop() throws IOException, NoSuchAlgorithmException, SignatureEx case "use" -> { useKey(theMsg); } + case "sign" -> { + sign(theMsg); + } default -> {} } } @@ -96,11 +132,52 @@ private void help() { System.out.println("peek : view the details of a key document `peek <# from `list`>`"); + System.out.println("sign : signs a string of text, and produces signature. `sign `"); + System.out.println("use : designate currently used key document. `use <# from `list`>`"); } + private void sign(String msg) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, + InvalidKeyException, SignatureException { + if(getCurrentDocumentHash().isEmpty()) { + System.out.println("no key document is active. try `use #` command."); + } else { + // do it + JSONObject doc = acquireJson(getCurrentDocumentHash()); + + String privBase64ed = doc.getJSONObject("signing").getString("priv"); + byte[] privDecoded = Base64.getDecoder().decode(privBase64ed); + + KeyFactory factory = KeyFactory.getInstance("EC"); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privDecoded); + PrivateKey privKey = factory.generatePrivate(spec); + + Signature sign = Signature.getInstance("SHA256withECDSA"); + sign.initSign(privKey); + + byte[] msgBytes = msg.trim().getBytes(StandardCharsets.UTF_8); + sign.update(msgBytes); + + byte[] signature = sign.sign(); + + System.out.println(String.format("signature for the message, `%s`: `%s` \nusing key document `%s`", + msg, + Base64.getEncoder().encodeToString(signature), + getCurrentDocumentHash() + )); + } + } + + private JSONObject acquireJson(String hash) throws IOException { + Path p = Path.of(getHome().toString(), getCurrentDocumentHash()); + String keyDoc = Files.readString(p); + JSONObject doc = new JSONObject(keyDoc); + + return doc; + } + private void delete() throws IOException { - Files.list(home).forEach(f -> { + Files.list(getHome()).forEach(f -> { try { Files.delete(f); System.out.println(String.format("removed `%s`", f.getFileName().toString())); @@ -111,18 +188,16 @@ private void delete() throws IOException { System.out.println("complete."); - currentDocumentHash = StringUtils.EMPTY; + setCurrentDocumentHash(StringUtils.EMPTY); } private void current() throws IOException { - if(currentDocumentHash.isEmpty()) { + if(getCurrentDocumentHash().isEmpty()) { System.out.println("no key document is active. try `use #` command."); } else { - System.out.println(String.format("your active key document is `%s`", currentDocumentHash)); + System.out.println(String.format("your active key document is `%s`", getCurrentDocumentHash())); - Path p = Path.of(home.toString(), currentDocumentHash); - String keyDoc = Files.readString(p); - JSONObject doc = new JSONObject(keyDoc); + JSONObject doc = acquireJson(getCurrentDocumentHash()); } } @@ -142,31 +217,33 @@ private void peek(String msg) throws IOException { int selection = Integer.parseInt(arr[0].strip()); - hashList = Files - .list(home) + setHashList( + Files.list(getHome()) .sorted((f1, f2) -> Long.valueOf(f2.toFile().lastModified()) - .compareTo(f1.toFile().lastModified())) - .toList(); + .compareTo(f1.toFile().lastModified())).toList() + ); - if(selection > hashList.size()-1 || selection < 0) { + List hl = getHashList(); + + if(selection > hl.size()-1 || selection < 0) { System.out.println("not a valid selection =("); return; } - Path g = hashList.get(selection); - currentDocumentHash = g.getFileName().toString(); + Path g = hl.get(selection); + setCurrentDocumentHash(g.getFileName().toString()); - System.out.println(String.format("peeking at key document `%s`", currentDocumentHash)); + System.out.println(String.format("peeking at key document `%s`", getCurrentDocumentHash())); - String content = Files.readString(Path.of(home.toString(), currentDocumentHash)); + String content = Files.readString(Path.of(getHome().toString(), getCurrentDocumentHash())); JSONObject doc = new JSONObject(content); - System.out.println(String.format("message: `%s`", doc.getString("msg"))); - System.out.println(String.format("timestamp: %s", Instant.ofEpochMilli(doc.getLong("time")) + System.out.println(String.format("message : `%s`", doc.getString("msg"))); + System.out.println(String.format("timestamp : %s", Instant.ofEpochMilli(doc.getLong("time")) .atZone(ZoneId.of("UTC")).toLocalDateTime().toString())); - System.out.println(String.format("work factor: %d", doc.getJSONObject("pow").getLong("work"))); - System.out.println(String.format("PoW hash: `%s`", doc.getJSONObject("pow").getString("hash"))); + System.out.println(String.format("work factor : %d", doc.getJSONObject("pow").getLong("work"))); + System.out.println(String.format("PoW hash : `%s`", doc.getJSONObject("pow").getString("hash"))); } private void useKey(String msg) throws IOException, ArrayIndexOutOfBoundsException { @@ -195,10 +272,10 @@ private void useKey(String msg) throws IOException, ArrayIndexOutOfBoundsExcepti int selection = Integer.parseInt(arr[0].strip()); - hashList = Files.list(home) + setHashList(Files.list(home) .sorted((f1, f2) -> Long.valueOf(f2.toFile().lastModified()) .compareTo(f1.toFile().lastModified())) - .toList(); + .toList()); if(selection > hashList.size()-1 || selection < 0) { System.out.println("not a valid selection =("); @@ -243,6 +320,14 @@ private void list() throws IOException { } } + public Path getHome() { + return home; + } + + public void setHome(Path home) { + this.home = home; + } + /** * @param msg Generic string * @throws NoSuchAlgorithmException @@ -254,6 +339,7 @@ private void generateKey(String msg) throws NoSuchAlgorithmException, InvalidKey SignatureException, IOException { KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); + // TODO : also make `keysize` adjustable within bit range of algo. gen.initialize(2048); KeyPair pair = gen.generateKeyPair(); @@ -292,7 +378,7 @@ private void generateKey(String msg) throws NoSuchAlgorithmException, InvalidKey Signature signing = Signature.getInstance("SHA256withECDSA"); signing.initSign(sPrivKey); - String wholeJson = keysJson.toString(4); + String wholeJson = keysJson.toString(); signing.update(wholeJson.getBytes(StandardCharsets.UTF_8)); byte[] signature = signing.sign(); keysJson.put("signature", Base64.getEncoder().encodeToString(signature)); @@ -301,7 +387,8 @@ private void generateKey(String msg) throws NoSuchAlgorithmException, InvalidKey String hashStr = DigestUtils.sha256Hex(wholeJson.getBytes(StandardCharsets.UTF_8)); keysJson.put("hash", hashStr); - // TODO : PoW for value creation + // PoW for value creation (sufficient knowledge was made) + // TODO : let command allow for num of bits setting. PoW pow = new PoW(keysJson.toString().getBytes(StandardCharsets.UTF_8), 1); System.out.println("doing the work..."); @@ -318,7 +405,6 @@ private void generateKey(String msg) throws NoSuchAlgorithmException, InvalidKey Files.createDirectories(storePath.getParent()); Files.createFile(storePath); - //Files.write(storePath, contents, StandardOpenOption.CREATE); Files.writeString(storePath, keysJson.toString(), StandardOpenOption.CREATE); System.out.println(String.format("key saved to document `%s`", storePath.toString()));