From 456281cf7ab15c6fba2c4439f30faef8fa442002 Mon Sep 17 00:00:00 2001 From: ManasC478 Date: Wed, 31 Jan 2024 18:48:39 -0800 Subject: [PATCH 1/2] created cli key generator tool and error handling bug fixes --- bundleserver/pom.xml | 6 + .../server/bundlesecurity/BundleSecurity.java | 4 +- .../server/bundlesecurity/SecurityUtils.java | 5 +- .../server/bundlesecurity/ServerSecurity.java | 80 ++++++------ .../BundleTransmission.java | 10 +- .../com/ddd/server/keygenerator/CLRunner.java | 33 +++++ .../keygenerator/commands/GenerateKeys.java | 118 ++++++++++++++++++ .../src/main/resources/application.yml | 3 +- 8 files changed, 210 insertions(+), 49 deletions(-) create mode 100644 bundleserver/src/main/java/com/ddd/server/keygenerator/CLRunner.java create mode 100644 bundleserver/src/main/java/com/ddd/server/keygenerator/commands/GenerateKeys.java diff --git a/bundleserver/pom.xml b/bundleserver/pom.xml index ec5741c216..a36d1cde14 100644 --- a/bundleserver/pom.xml +++ b/bundleserver/pom.xml @@ -31,6 +31,7 @@ org.projectlombok lombok + 1.18.30 true @@ -125,6 +126,11 @@ mysql-connector-j 8.3.0 + + info.picocli + picocli-spring-boot-starter + 4.7.5 + diff --git a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/BundleSecurity.java b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/BundleSecurity.java index b153114b22..a4d91edac4 100644 --- a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/BundleSecurity.java +++ b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/BundleSecurity.java @@ -156,7 +156,9 @@ public Payload decryptPayload(UncompressedBundle uncompressedBundle) { uncompressedBundle.getSource().getAbsolutePath()); } catch (Exception e) { // TODO - e.printStackTrace(); + System.out.println("[BS] Failed to decrypt payload"); + // e.printStackTrace(); + return null; } try { diff --git a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityUtils.java b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityUtils.java index adae5a2cc5..bb72038aa3 100644 --- a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityUtils.java +++ b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityUtils.java @@ -65,7 +65,10 @@ public class SecurityUtils { public static final String SERVER_IDENTITY_KEY = "server_identity.pub"; public static final String SERVER_SIGNEDPRE_KEY = "server_signed_pre.pub"; - public static final String SERVER_RATCHET_KEY = "server_ratchet.pub"; + public static final String SERVER_RATCHET_KEY = "server_ratchet.pub"; + public static final String SERVER_IDENTITY_PRIVATE_KEY = "serverIdentity.pvt"; + public static final String SERVER_SIGNEDPRE_PRIVATE_KEY = "serverSignedPreKey.pvt"; + public static final String SERVER_RATCHET_PRIVATE_KEY = "serverRatchetKey.pvt"; public static final int CHUNKSIZE = 1024 * 1024; /* 1MB */ public static final int ITERATIONS = 65536; diff --git a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/ServerSecurity.java b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/ServerSecurity.java index 76e9d9ddec..a8d996dd24 100644 --- a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/ServerSecurity.java +++ b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/ServerSecurity.java @@ -15,13 +15,14 @@ import java.util.Base64; import java.util.HashMap; import java.util.List; -import java.util.stream.Stream; - -import javax.websocket.EncodeException; +import org.whispersystems.libsignal.DuplicateMessageException; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.InvalidMessageException; +import org.whispersystems.libsignal.LegacyMessageException; +import org.whispersystems.libsignal.NoSessionException; import org.whispersystems.libsignal.SessionCipher; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.ecc.Curve; @@ -35,7 +36,6 @@ import org.whispersystems.libsignal.state.SessionRecord; import org.whispersystems.libsignal.state.SessionState; import org.whispersystems.libsignal.state.SignalProtocolStore; -import org.whispersystems.libsignal.util.guava.Optional; import com.ddd.server.bundlesecurity.SecurityExceptions.AESAlgorithmException; import com.ddd.server.bundlesecurity.SecurityExceptions.BundleIDCryptographyException; import com.ddd.server.bundlesecurity.SecurityExceptions.EncodingException; @@ -43,6 +43,7 @@ import com.ddd.server.bundlesecurity.SecurityExceptions.InvalidClientIDException; import com.ddd.server.bundlesecurity.SecurityExceptions.InvalidClientSessionException; import com.ddd.server.bundlesecurity.SecurityExceptions.ServerIntializationException; +import com.ddd.server.bundlesecurity.SecurityExceptions.SignatureVerificationException; import com.ddd.server.bundlesecurity.SecurityUtils.ClientSession; public class ServerSecurity { @@ -69,7 +70,6 @@ public class ServerSecurity { private ServerSecurity(String serverRootPath) throws ServerIntializationException { clientMap = new HashMap<>(); - String serverKeyPath = serverRootPath + File.separator + SecurityUtils.SERVER_KEY_PATH; try { @@ -118,10 +118,10 @@ private ServerSecurity(String serverRootPath) throws ServerIntializationExceptio */ private void loadKeysfromFiles(String serverKeyPath) throws FileNotFoundException, IOException, InvalidKeyException, EncodingException { - byte[] identityKey = SecurityUtils.readFromFile(serverKeyPath + File.separator + "serverIdentity.pvt"); + byte[] identityKey = SecurityUtils.readFromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_IDENTITY_PRIVATE_KEY); ourIdentityKeyPair = new IdentityKeyPair(identityKey); - byte[] signedPreKeyPvt = SecurityUtils.readFromFile(serverKeyPath + File.separator + "serverSignedPreKey.pvt"); + byte[] signedPreKeyPvt = SecurityUtils.readFromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_SIGNEDPRE_PRIVATE_KEY); byte[] signedPreKeyPub = SecurityUtils.decodePublicKeyfromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_SIGNEDPRE_KEY); ECPublicKey signedPreKeyPublicKey = Curve.decodePoint(signedPreKeyPub, 0); @@ -129,7 +129,7 @@ private void loadKeysfromFiles(String serverKeyPath) throws FileNotFoundExceptio ourSignedPreKey = new ECKeyPair(signedPreKeyPublicKey, signedPreKeyPrivateKey); - byte[] ratchetKeyPvt = SecurityUtils.readFromFile(serverKeyPath + File.separator + "serverRatchetKey.pvt"); + byte[] ratchetKeyPvt = SecurityUtils.readFromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_RATCHET_PRIVATE_KEY); byte[] ratchetKeyPub = SecurityUtils.decodePublicKeyfromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_RATCHET_KEY); ECPublicKey ratchetKeyPublicKey = Curve.decodePoint(ratchetKeyPub, 0); @@ -141,15 +141,15 @@ private void loadKeysfromFiles(String serverKeyPath) throws FileNotFoundExceptio /* TODO: Change to keystore */ private void writePrivateKeys(String path) throws FileNotFoundException, IOException { - try (FileOutputStream stream = new FileOutputStream(path + File.separator + "serverIdentity.pvt")) { + try (FileOutputStream stream = new FileOutputStream(path + File.separator + SecurityUtils.SERVER_IDENTITY_PRIVATE_KEY)) { stream.write(ourIdentityKeyPair.serialize()); } - try (FileOutputStream stream = new FileOutputStream(path + File.separator + "serverSignedPreKey.pvt")) { + try (FileOutputStream stream = new FileOutputStream(path + File.separator + SecurityUtils.SERVER_SIGNEDPRE_PRIVATE_KEY)) { stream.write(ourSignedPreKey.getPrivateKey().serialize()); } - try (FileOutputStream stream = new FileOutputStream(path + File.separator + "serverRatchetKey.pvt")) { + try (FileOutputStream stream = new FileOutputStream(path + File.separator + SecurityUtils.SERVER_RATCHET_PRIVATE_KEY)) { stream.write(ourRatchetKey.getPrivateKey().serialize()); } } @@ -328,7 +328,7 @@ public static synchronized ServerSecurity getInstance(String serverRootPath) thr return singleServerInstance; } - public void decrypt(String bundlePath, String decryptedPath) throws IOException, InvalidClientSessionException + public void decrypt(String bundlePath, String decryptedPath) throws IOException, InvalidClientSessionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, NoSessionException, SignatureVerificationException { ClientSession client = getClientSessionFromFile(bundlePath); String payloadPath = bundlePath + File.separator + SecurityUtils.PAYLOAD_DIR; @@ -343,39 +343,33 @@ public void decrypt(String bundlePath, String decryptedPath) throws IOException, System.out.println(decryptedFile); int fileCount = new File(payloadPath).list().length; - try { - for (int i = 1; i <= fileCount; ++i) { - String payloadName = SecurityUtils.PAYLOAD_FILENAME + String.valueOf(i); - String signatureFile = signPath + File.separator + payloadName + SecurityUtils.SIGNATURE_FILENAME; - - byte[] encryptedData = SecurityUtils.readFromFile(payloadPath + File.separator + payloadName); - byte[] serverDecryptedMessage = client.cipherSession.decrypt(new SignalMessage (encryptedData)); - updateSessionRecord(client); - try (FileOutputStream stream = new FileOutputStream(decryptedFile, true)) { - stream.write(serverDecryptedMessage); - } - System.out.printf("[SEC]:Decrypted Size = %d\n", serverDecryptedMessage.length); - - if (SecurityUtils.verifySignature(serverDecryptedMessage, client.IdentityKey.getPublicKey(), signatureFile)) { - System.out.println("[SEC]:Verified Signature!"); - } else { - // Failed to verify sign, delete bundle and return - System.out.println("[SEC]:Invalid Signature ["+ payloadName +"], Aborting bundle "+ bundleID); - - try { - Files.deleteIfExists(Paths.get(decryptedFile)); - } - catch (Exception e) { - System.out.printf("[SEC] Error: Failed to delete decrypted file [%s]", decryptedFile); - System.out.println(e); - } - } + for (int i = 1; i <= fileCount; ++i) { + String payloadName = SecurityUtils.PAYLOAD_FILENAME + String.valueOf(i); + String signatureFile = signPath + File.separator + payloadName + SecurityUtils.SIGNATURE_FILENAME; + + byte[] encryptedData = SecurityUtils.readFromFile(payloadPath + File.separator + payloadName); + byte[] serverDecryptedMessage = client.cipherSession.decrypt(new SignalMessage (encryptedData)); + updateSessionRecord(client); + try (FileOutputStream stream = new FileOutputStream(decryptedFile, true)) { + stream.write(serverDecryptedMessage); + } + System.out.printf("[SEC]:Decrypted Size = %d\n", serverDecryptedMessage.length); + + if (SecurityUtils.verifySignature(serverDecryptedMessage, client.IdentityKey.getPublicKey(), signatureFile)) { + System.out.println("[SEC]:Verified Signature!"); + } else { + // Failed to verify sign, delete bundle and return + System.out.println("[SEC]:Invalid Signature ["+ payloadName +"], Aborting bundle "+ bundleID); + + try { + Files.deleteIfExists(Paths.get(decryptedFile)); } - } catch (Exception e) { - System.out.println("[SEC]: Failed to Decrypt Client's Message\n" + e); - e.printStackTrace(); + catch (Exception e) { + System.out.printf("[SEC] Error: Failed to delete decrypted file [%s]", decryptedFile); + System.out.println(e); + } + } } - return; } public String[] encrypt(String toBeEncPath, String encPath, String bundleID, String clientID) throws InvalidClientSessionException, BundleIDCryptographyException, IOException, InvalidKeyException, EncodingException diff --git a/bundleserver/src/main/java/com/ddd/server/bundletransmission/BundleTransmission.java b/bundleserver/src/main/java/com/ddd/server/bundletransmission/BundleTransmission.java index c78259e902..4ca7ced62c 100644 --- a/bundleserver/src/main/java/com/ddd/server/bundletransmission/BundleTransmission.java +++ b/bundleserver/src/main/java/com/ddd/server/bundletransmission/BundleTransmission.java @@ -73,7 +73,7 @@ public BundleTransmission( } @Transactional(rollbackFor = Exception.class) - public void processReceivedBundle(String transportId, Bundle bundle) { + public void processReceivedBundle(String transportId, Bundle bundle) throws Exception { File bundleRecvProcDir = new File( this.config.getBundleTransmission().getReceivedProcessingDirectory() @@ -101,6 +101,9 @@ public void processReceivedBundle(String transportId, Bundle bundle) { } Payload payload = this.bundleSecurity.decryptPayload(uncompressedBundle); + if (payload == null) { + throw new Exception("Payload is null"); + } UncompressedPayload uncompressedPayload = this.bundleGenServ.extractPayload( @@ -182,11 +185,12 @@ public void processReceivedBundles(String transportId) { // this.applicationDataManager.collectDataForClients(clientId); // } for (final File bundleFile : transportDir.listFiles()) { + Bundle bundle = new Bundle(bundleFile); try { - Bundle bundle = new Bundle(bundleFile); this.processReceivedBundle(transportId, bundle); } catch (Exception e) { - e.printStackTrace(); + System.out.println( + "[BT] Failed to process received bundle from transportId: " + transportId + ", error: " + e.getMessage()); } finally { try { FileUtils.delete(bundleFile); diff --git a/bundleserver/src/main/java/com/ddd/server/keygenerator/CLRunner.java b/bundleserver/src/main/java/com/ddd/server/keygenerator/CLRunner.java new file mode 100644 index 0000000000..4633c21cfe --- /dev/null +++ b/bundleserver/src/main/java/com/ddd/server/keygenerator/CLRunner.java @@ -0,0 +1,33 @@ +package com.ddd.server.keygenerator; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.ddd.server.keygenerator.commands.GenerateKeys; + +import picocli.CommandLine; +import picocli.CommandLine.IFactory; + +@Component +public class CLRunner implements CommandLineRunner { + @Autowired + IFactory factory; + + @Autowired + GenerateKeys generateKeys; + + @Override + public void run(String... args) throws Exception { + // run picocli impl + String command = args.length > 0 ? args[0] : null; + + if (command == null) { + return; + } + + if (command.equals("generate-keys")) { + new CommandLine(generateKeys, factory).execute(args); + } + } +} \ No newline at end of file diff --git a/bundleserver/src/main/java/com/ddd/server/keygenerator/commands/GenerateKeys.java b/bundleserver/src/main/java/com/ddd/server/keygenerator/commands/GenerateKeys.java new file mode 100644 index 0000000000..0466284f9f --- /dev/null +++ b/bundleserver/src/main/java/com/ddd/server/keygenerator/commands/GenerateKeys.java @@ -0,0 +1,118 @@ +package com.ddd.server.keygenerator.commands; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Base64; +import java.util.concurrent.Callable; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.libsignal.IdentityKeyPair; +import org.whispersystems.libsignal.ecc.Curve; +import org.whispersystems.libsignal.ecc.ECKeyPair; + +import picocli.CommandLine; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +@Component +@CommandLine.Command(name = "generate-keys", description = "Generate pair of EC keys") +public class GenerateKeys implements Callable { + private final String PUB_KEY_HEADER = "-----BEGIN EC PUBLIC KEY-----"; + private final String PUB_KEY_FOOTER = "-----END EC PUBLIC KEY-----"; + private final String PVT_KEY_HEADER = "-----BEGIN EC PRIVATE KEY-----"; + private final String PVT_KEY_FOOTER = "-----END EC PRIVATE KEY-----"; + + @Value("${bundle-server.bundle-security.server-serverkeys-path}") + private String storePath; + + private IdentityKeyPair keyPair; + private String base64PrivateKey; + private String base64PublicKey; + + @Parameters(arity = "1", index = "0") + String command; + + @Option(names = "-pvtout", description = "Private key file name") + private String privateKeyOutputFileName; + + @Option(names = "-pubout", description = "Private key file name") + private String publicKeyOutputFileName; + + private void createIdentityKeyPair() { + System.out.println("Generating keys..."); + ECKeyPair identityKeyPair = Curve.generateKeyPair(); + keyPair = new IdentityKeyPair(new IdentityKey(identityKeyPair.getPublicKey()), + identityKeyPair.getPrivateKey()); + base64PrivateKey = Base64.getUrlEncoder().encodeToString(keyPair.serialize()); + base64PublicKey = Base64.getUrlEncoder().encodeToString(keyPair.getPublicKey().getPublicKey().serialize()); + } + + private void writeToFile(boolean isPrivate, File file) { + String encodedKey = (isPrivate ? PVT_KEY_HEADER : PUB_KEY_HEADER) + "\n"; + try (FileOutputStream stream = new FileOutputStream(file, false)) { + encodedKey += isPrivate ? base64PrivateKey : base64PublicKey; + encodedKey += "\n" + (isPrivate ? PVT_KEY_FOOTER : PUB_KEY_FOOTER); + stream.write(encodedKey.getBytes()); + System.out.println("Written to file"); + } catch (Exception e) { + System.out.println("com.ddd.server.keygenerator.commands.GenerateKeys.writeToFile error: " + e.getMessage()); + } + } + + private void verifyWrite(boolean isPrivate, String filename) { + File file = new File(storePath + "/" + filename); + + if (file.exists()) { + String s = System.console().readLine("Do you want to overwrite " + file.getPath() + "? [y/n]: "); + + if ("y".equalsIgnoreCase(s)) { + System.out.println("Overwriting " + file.getPath() + "..."); + writeToFile(isPrivate, file); + } else { + if (isPrivate) { + printPrivateKey(); + } else { + printPublicKey(); + } + } + } else { + try { + file.createNewFile(); + } catch (IOException e) { + System.out.println("com.ddd.server.keygenerator.commands.GenerateKeys.verifyWrite error: " + e.getMessage()); + } + System.out.println("Writing to " + file.getPath() + "..."); + writeToFile(isPrivate, file); + } + } + + private void printPrivateKey() { + System.out.println("Private key: " + base64PrivateKey); + } + + private void printPublicKey() { + System.out.println("Public key: " + base64PublicKey); + } + + @Override + public Integer call() { + createIdentityKeyPair(); + if (privateKeyOutputFileName == null) { + printPrivateKey(); + } else { + verifyWrite(true, privateKeyOutputFileName); + } + + if (publicKeyOutputFileName == null) { + printPublicKey(); + } else { + verifyWrite(false, publicKeyOutputFileName); + } + + return 0; + } + +} \ No newline at end of file diff --git a/bundleserver/src/main/resources/application.yml b/bundleserver/src/main/resources/application.yml index 9fdef50581..10abc56198 100644 --- a/bundleserver/src/main/resources/application.yml +++ b/bundleserver/src/main/resources/application.yml @@ -44,4 +44,5 @@ bundle-server: data-store-adaptor: app-data-root: "${bundle-server.bundle-store-root}" bundle-security: - server-key-path: "${bundle-server.bundle-store-root}BundleSecurity/Keys/Server/Server_Keys" + server-key-path: "${bundle-server.bundle-store-root}BundleSecurity/Keys/Server" + server-serverkeys-path: "${bundle-server.bundle-store-root}BundleSecurity/Keys/Server/Server_Keys" From 036be6522ddcefdea57b09df44258556fd649303 Mon Sep 17 00:00:00 2001 From: ManasC478 Date: Sat, 3 Feb 2024 22:21:07 -0800 Subject: [PATCH 2/2] added new decode private key command, removed automatic key pair generation, and other bug fixes --- bundleserver/pom.xml | 4 +- .../bundlesecurity/SecurityExceptions.java | 3 +- .../server/bundlesecurity/SecurityUtils.java | 36 +++++-- .../server/bundlesecurity/ServerSecurity.java | 92 +++++++++------- .../com/ddd/server/keygenerator/CLRunner.java | 6 ++ .../commands/DecodePublicKey.java | 100 ++++++++++++++++++ .../keygenerator/commands/GenerateKeys.java | 68 ++++++++---- 7 files changed, 239 insertions(+), 70 deletions(-) create mode 100644 bundleserver/src/main/java/com/ddd/server/keygenerator/commands/DecodePublicKey.java diff --git a/bundleserver/pom.xml b/bundleserver/pom.xml index a36d1cde14..065b8a64b8 100644 --- a/bundleserver/pom.xml +++ b/bundleserver/pom.xml @@ -15,8 +15,8 @@ Bundle Server 17 - 11 - 11 + 17 + 17 UTF-8 3.5.1 1.50.2 diff --git a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityExceptions.java b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityExceptions.java index 0d936ea0ce..134f65a0ae 100644 --- a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityExceptions.java +++ b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityExceptions.java @@ -58,8 +58,7 @@ public BundleIDCryptographyException(String errorMessage) } public static class BundleDecryptionException extends Exception { - public BundleDecryptionException(String errorMessage) - { + public BundleDecryptionException(String errorMessage) { super(errorMessage); } } diff --git a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityUtils.java b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityUtils.java index bb72038aa3..ffc6fa14dd 100644 --- a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityUtils.java +++ b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/SecurityUtils.java @@ -53,8 +53,10 @@ public class SecurityUtils { public static final String BUNDLEID_FILENAME = "bundle.id"; public static final String DECRYPTED_FILE_EXT = ".decrypted"; - public static final String PUBLICKEY_HEADER = "-----BEGIN EC PUBLIC KEY-----"; - public static final String PUBLICKEY_FOOTER = "-----END EC PUBLIC KEY-----"; + public static final String PUB_KEY_HEADER = "-----BEGIN EC PUBLIC KEY-----"; + public static final String PUB_KEY_FOOTER = "-----END EC PUBLIC KEY-----"; + public static final String PVT_KEY_HEADER = "-----BEGIN EC PRIVATE KEY-----"; + public static final String PVT_KEY_FOOTER = "-----END EC PRIVATE KEY-----"; public static final String CLIENT_KEY_PATH = "Client_Keys"; public static final String SERVER_KEY_PATH = "Server_Keys"; @@ -129,10 +131,10 @@ public static String generateID(byte[] publicKey) throws IDGenerationException public static void createEncodedPublicKeyFile(ECPublicKey publicKey, String path) throws EncodingException { - String encodedKey = PUBLICKEY_HEADER+"\n"; - try (FileOutputStream stream = new FileOutputStream(path)) { + String encodedKey = PUB_KEY_HEADER+"\n"; + try (FileOutputStream stream = new FileOutputStream(path, false)) { encodedKey += Base64.getUrlEncoder().encodeToString(publicKey.serialize()); - encodedKey += "\n" + PUBLICKEY_FOOTER; + encodedKey += "\n" + PUB_KEY_FOOTER; stream.write(encodedKey.getBytes()); } catch (IOException e) { throw new EncodingException("[BS]: Failed to Encode Public Key to file:"+e); @@ -148,8 +150,8 @@ public static byte[] decodePublicKeyfromFile(String path) throws EncodingExcepti throw new InvalidKeyException("Error: Invalid Public Key Length"); } - if ((true == encodedKeyList.get(0).equals(PUBLICKEY_HEADER)) && - (true == encodedKeyList.get(2).equals(PUBLICKEY_FOOTER))) { + if (encodedKeyList.get(0).equals(PUB_KEY_HEADER) && + encodedKeyList.get(2).equals(PUB_KEY_FOOTER)) { return Base64.getUrlDecoder().decode(encodedKeyList.get(1)); } else { throw new InvalidKeyException("Error: Invalid Public Key Format"); @@ -158,6 +160,26 @@ public static byte[] decodePublicKeyfromFile(String path) throws EncodingExcepti throw new EncodingException("Error: Invalid Public Key Format"); } } + + public static byte[] decodePrivateKeyFromFile(String path) throws EncodingException { + try { + List encodedKeyList = Files.readAllLines(Paths.get(path.trim())); + + if (encodedKeyList.size() != 3) { + throw new InvalidKeyException("Error: Invalid Public Key Length"); + } + + if (encodedKeyList.get(0).equals(PVT_KEY_HEADER) && + encodedKeyList.get(2).equals(PVT_KEY_FOOTER)) { + return Base64.getUrlDecoder().decode(encodedKeyList.get(1)); + } else { + throw new InvalidKeyException("Error: Invalid Public Key Format"); + } + } catch (InvalidKeyException | IOException e) { + throw new EncodingException("Error: Invalid Public Key Format"); + } + + } public static InMemorySignalProtocolStore createInMemorySignalProtocolStore() { diff --git a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/ServerSecurity.java b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/ServerSecurity.java index c80eeb40a1..5a266ca034 100644 --- a/bundleserver/src/main/java/com/ddd/server/bundlesecurity/ServerSecurity.java +++ b/bundleserver/src/main/java/com/ddd/server/bundlesecurity/ServerSecurity.java @@ -17,6 +17,12 @@ import java.util.List; import java.util.stream.Stream; +import org.springframework.boot.ExitCodeGenerator; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.data.domain.Example; import org.whispersystems.libsignal.DuplicateMessageException; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKeyPair; @@ -37,6 +43,8 @@ import org.whispersystems.libsignal.state.SessionRecord; import org.whispersystems.libsignal.state.SessionState; import org.whispersystems.libsignal.state.SignalProtocolStore; + +import com.ddd.server.BundleServerApplication; import com.ddd.server.bundlesecurity.SecurityExceptions.AESAlgorithmException; import com.ddd.server.bundlesecurity.SecurityExceptions.BundleIDCryptographyException; import com.ddd.server.bundlesecurity.SecurityExceptions.EncodingException; @@ -68,45 +76,58 @@ public class ServerSecurity { * Exceptions: * IOException: Thrown if keys cannot be written to provided path */ - private ServerSecurity(String serverRootPath) throws ServerIntializationException + private ServerSecurity(String serverRootPath) throws ServerIntializationException { clientMap = new HashMap<>(); String serverKeyPath = serverRootPath + File.separator + SecurityUtils.SERVER_KEY_PATH; + try { + loadKeysfromFiles(serverKeyPath); + this.serverRootPath = serverRootPath; + serverProtocolStore = SecurityUtils.createInMemorySignalProtocolStore(); + + String name = DEFAULT_SERVER_NAME; try { - // TODO: Load protocol store from files(serverProtocolStore) - loadKeysfromFiles(serverKeyPath); - System.out.println("[Sec]: Using Existing Keys"); - } catch (InvalidKeyException | IOException | EncodingException e) { - System.out.println("[Sec]: Error Loading Keys from files, generating new keys instead"); - - ECKeyPair identityKeyPair = Curve.generateKeyPair(); - ourIdentityKeyPair = new IdentityKeyPair(new IdentityKey(identityKeyPair.getPublicKey()), - identityKeyPair.getPrivateKey()); - ourSignedPreKey = Curve.generateKeyPair(); - ourRatchetKey = ourSignedPreKey; - - try { - writeKeysToFiles(serverKeyPath, true); - } catch (IOException | EncodingException exception) { - throw new ServerIntializationException("Failed to write keys to Files:"+exception); + name = SecurityUtils.generateID(ourIdentityKeyPair.getPublicKey().serialize()); + } catch (IDGenerationException e) { + System.out.println("[SEC]:Failed to generate ID, using default value:"+name); } + ourAddress = new SignalProtocolAddress(name, ServerDeviceID); + ourOneTimePreKey = Optional.absent(); + + clientRootPath = serverRootPath+File.separator+"Clients"; + SecurityUtils.createDirectory(clientRootPath); + } catch (Exception e) { + System.out.println(e.getMessage()); + System.out.println("Error loading server keys. Ensure the following key files exist in your application.yml's {bundle-server.bundle-security.server-serverkeys-path} path:\n"+ + "server_identity.pub\n"+ + "serverIdentity.pvt\n"+ + "server_signed_pre.pub\n"+ + "serverSignedPreKey.pvt\n"+ + "server_ratchet.pub\n"+ + "serverRatchetKey.pvt"); + // BundleServerApplication.exit(); } + // try { + // // TODO: Load protocol store from files(serverProtocolStore) + // loadKeysfromFiles(serverKeyPath); + // System.out.println("[Sec]: Using Existing Keys"); + // } catch (InvalidKeyException | IOException | EncodingException e) { + // System.out.println("[Sec]: Error Loading Keys from files, generating new keys instead"); + + // ECKeyPair identityKeyPair = Curve.generateKeyPair(); + // ourIdentityKeyPair = new IdentityKeyPair(new IdentityKey(identityKeyPair.getPublicKey()), + // identityKeyPair.getPrivateKey()); + // ourSignedPreKey = Curve.generateKeyPair(); + // ourRatchetKey = ourSignedPreKey; + + // try { + // writeKeysToFiles(serverKeyPath, true); + // } catch (IOException | EncodingException exception) { + // throw new ServerIntializationException("Failed to write keys to Files:"+exception); + // } + // } - this.serverRootPath = serverRootPath; - serverProtocolStore = SecurityUtils.createInMemorySignalProtocolStore(); - - String name = DEFAULT_SERVER_NAME; - try { - name = SecurityUtils.generateID(ourIdentityKeyPair.getPublicKey().serialize()); - } catch (IDGenerationException e) { - System.out.println("[SEC]:Failed to generate ID, using default value:"+name); - } - ourAddress = new SignalProtocolAddress(name, ServerDeviceID); - ourOneTimePreKey = Optional.absent(); - - clientRootPath = serverRootPath+File.separator+"Clients"; - SecurityUtils.createDirectory(clientRootPath); } /* load the previously used keys from the provided path @@ -117,12 +138,11 @@ private ServerSecurity(String serverRootPath) throws ServerIntializationExceptio * IOException: Thrown if keys cannot be written to provided path * InvalidKeyException: Thrown if the file has an invalid key */ - private void loadKeysfromFiles(String serverKeyPath) throws FileNotFoundException, IOException, InvalidKeyException, EncodingException - { - byte[] identityKey = SecurityUtils.readFromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_IDENTITY_PRIVATE_KEY); + private void loadKeysfromFiles(String serverKeyPath) throws EncodingException, InvalidKeyException { + byte[] identityKey = SecurityUtils.decodePrivateKeyFromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_IDENTITY_PRIVATE_KEY); ourIdentityKeyPair = new IdentityKeyPair(identityKey); - byte[] signedPreKeyPvt = SecurityUtils.readFromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_SIGNEDPRE_PRIVATE_KEY); + byte[] signedPreKeyPvt = SecurityUtils.decodePrivateKeyFromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_SIGNEDPRE_PRIVATE_KEY); byte[] signedPreKeyPub = SecurityUtils.decodePublicKeyfromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_SIGNEDPRE_KEY); ECPublicKey signedPreKeyPublicKey = Curve.decodePoint(signedPreKeyPub, 0); @@ -130,7 +150,7 @@ private void loadKeysfromFiles(String serverKeyPath) throws FileNotFoundExceptio ourSignedPreKey = new ECKeyPair(signedPreKeyPublicKey, signedPreKeyPrivateKey); - byte[] ratchetKeyPvt = SecurityUtils.readFromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_RATCHET_PRIVATE_KEY); + byte[] ratchetKeyPvt = SecurityUtils.decodePrivateKeyFromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_RATCHET_PRIVATE_KEY); byte[] ratchetKeyPub = SecurityUtils.decodePublicKeyfromFile(serverKeyPath + File.separator + SecurityUtils.SERVER_RATCHET_KEY); ECPublicKey ratchetKeyPublicKey = Curve.decodePoint(ratchetKeyPub, 0); diff --git a/bundleserver/src/main/java/com/ddd/server/keygenerator/CLRunner.java b/bundleserver/src/main/java/com/ddd/server/keygenerator/CLRunner.java index 4633c21cfe..f6a9971608 100644 --- a/bundleserver/src/main/java/com/ddd/server/keygenerator/CLRunner.java +++ b/bundleserver/src/main/java/com/ddd/server/keygenerator/CLRunner.java @@ -4,6 +4,7 @@ import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; +import com.ddd.server.keygenerator.commands.DecodePublicKey; import com.ddd.server.keygenerator.commands.GenerateKeys; import picocli.CommandLine; @@ -17,6 +18,9 @@ public class CLRunner implements CommandLineRunner { @Autowired GenerateKeys generateKeys; + @Autowired + DecodePublicKey decodePublicKey; + @Override public void run(String... args) throws Exception { // run picocli impl @@ -28,6 +32,8 @@ public void run(String... args) throws Exception { if (command.equals("generate-keys")) { new CommandLine(generateKeys, factory).execute(args); + } else if (command.equals("decode-pub-key")) { + new CommandLine(decodePublicKey, factory).execute(args); } } } \ No newline at end of file diff --git a/bundleserver/src/main/java/com/ddd/server/keygenerator/commands/DecodePublicKey.java b/bundleserver/src/main/java/com/ddd/server/keygenerator/commands/DecodePublicKey.java new file mode 100644 index 0000000000..e131221efc --- /dev/null +++ b/bundleserver/src/main/java/com/ddd/server/keygenerator/commands/DecodePublicKey.java @@ -0,0 +1,100 @@ +package com.ddd.server.keygenerator.commands; + +import java.io.File; +import java.util.Base64; +import java.util.concurrent.Callable; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import org.whispersystems.libsignal.IdentityKeyPair; +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.ecc.ECPublicKey; + +import com.ddd.server.bundlesecurity.SecurityUtils; +import com.ddd.server.bundlesecurity.SecurityExceptions.EncodingException; + +import picocli.CommandLine; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +@Component +@CommandLine.Command(name = "decode-pub-key", description = "Decode public key given private key") +public class DecodePublicKey implements Callable { + @Value("${bundle-server.bundle-security.server-serverkeys-path}") + private String storePath; + + @Parameters(arity = "1", index = "0") + String command; + + @Option(names = "-pvtin-file", description = "Private key file name") + private String pvtFilename; + + @Option(names = "-pvtin-base64", description = "Private key base64") + private String pvtbase64; + + @Option(names = "-pvtin-raw", description = "Private key raw") + private String pvtraw; + + @Option(names = "-pubout", description = "Public key file name") + private String pubFilename; + + private ECPublicKey extractPublicKey(byte[] privateKey) throws InvalidKeyException { + IdentityKeyPair identityKeyPair = new IdentityKeyPair(privateKey); + return identityKeyPair.getPublicKey().getPublicKey(); + } + + private byte[] decodeFile() throws EncodingException { + return SecurityUtils.decodePrivateKeyFromFile(storePath + File.separator + pvtFilename); + } + + private byte[] decodeBase64() { + return Base64.getUrlDecoder().decode(pvtbase64); + } + + private byte[] decodeRaw() { + return pvtraw.getBytes(); + } + + private void writeToFile(ECPublicKey pubKey) throws EncodingException { + SecurityUtils.createEncodedPublicKeyFile(pubKey, storePath + File.separator + pubFilename); + System.out.println("Written to file"); + } + + private void print(ECPublicKey pubKey) { + System.out.println("Extracted public key: "+Base64.getUrlEncoder().encodeToString(pubKey.serialize())); + } + + @Override + public Void call() { + byte[] serializedPrivateKey = null; + if (pvtFilename != null) { + try { + serializedPrivateKey = decodeFile(); + } catch (EncodingException e) { + System.out.println("Couldn't decode file, check file name."); + } + } else if (pvtbase64 != null) { + serializedPrivateKey = decodeBase64(); + } else if (pvtraw != null) { + serializedPrivateKey = decodeRaw(); + } + + if (serializedPrivateKey != null) { + ECPublicKey pubKey; + try { + pubKey = extractPublicKey(serializedPrivateKey); + if (pubFilename != null) { + writeToFile(pubKey); + } else { + print(pubKey); + } + } catch (Exception e) { + System.out.println(e.getMessage()); + System.out.println("Couldn't complete extraction."); + } + } + + return null; + } +} diff --git a/bundleserver/src/main/java/com/ddd/server/keygenerator/commands/GenerateKeys.java b/bundleserver/src/main/java/com/ddd/server/keygenerator/commands/GenerateKeys.java index 0466284f9f..6226c443aa 100644 --- a/bundleserver/src/main/java/com/ddd/server/keygenerator/commands/GenerateKeys.java +++ b/bundleserver/src/main/java/com/ddd/server/keygenerator/commands/GenerateKeys.java @@ -19,7 +19,7 @@ @Component @CommandLine.Command(name = "generate-keys", description = "Generate pair of EC keys") -public class GenerateKeys implements Callable { +public class GenerateKeys implements Callable { private final String PUB_KEY_HEADER = "-----BEGIN EC PUBLIC KEY-----"; private final String PUB_KEY_FOOTER = "-----END EC PUBLIC KEY-----"; private final String PVT_KEY_HEADER = "-----BEGIN EC PRIVATE KEY-----"; @@ -28,26 +28,36 @@ public class GenerateKeys implements Callable { @Value("${bundle-server.bundle-security.server-serverkeys-path}") private String storePath; - private IdentityKeyPair keyPair; private String base64PrivateKey; private String base64PublicKey; @Parameters(arity = "1", index = "0") String command; + @Option(names = "-type", defaultValue="identity", description = "Type of key pair: identity, ratchet, signedpre") + private String type; + @Option(names = "-pvtout", description = "Private key file name") private String privateKeyOutputFileName; - @Option(names = "-pubout", description = "Private key file name") + @Option(names = "-pubout", description = "Public key file name") private String publicKeyOutputFileName; - private void createIdentityKeyPair() { - System.out.println("Generating keys..."); - ECKeyPair identityKeyPair = Curve.generateKeyPair(); - keyPair = new IdentityKeyPair(new IdentityKey(identityKeyPair.getPublicKey()), - identityKeyPair.getPrivateKey()); - base64PrivateKey = Base64.getUrlEncoder().encodeToString(keyPair.serialize()); - base64PublicKey = Base64.getUrlEncoder().encodeToString(keyPair.getPublicKey().getPublicKey().serialize()); + private void createKeyPair() { + System.out.println("Generating "+type+" keys..."); + ECKeyPair keyPair = Curve.generateKeyPair(); + + + if (type.toLowerCase().equals("identity")) { + IdentityKeyPair identityKeyPair = new IdentityKeyPair(new IdentityKey(keyPair.getPublicKey()), + keyPair.getPrivateKey()); + + base64PrivateKey = Base64.getUrlEncoder().encodeToString(identityKeyPair.serialize()); + base64PublicKey = Base64.getUrlEncoder().encodeToString(identityKeyPair.getPublicKey().getPublicKey().serialize()); + } else { + base64PrivateKey = Base64.getUrlEncoder().encodeToString(keyPair.getPrivateKey().serialize()); + base64PublicKey = Base64.getUrlEncoder().encodeToString(keyPair.getPublicKey().serialize()); + } } private void writeToFile(boolean isPrivate, File file) { @@ -97,22 +107,34 @@ private void printPublicKey() { System.out.println("Public key: " + base64PublicKey); } - @Override - public Integer call() { - createIdentityKeyPair(); - if (privateKeyOutputFileName == null) { - printPrivateKey(); - } else { - verifyWrite(true, privateKeyOutputFileName); + private void validInputs() throws IllegalArgumentException { + if (!type.toLowerCase().equals("identity") && !type.toLowerCase().equals("ratchet") + && !type.toLowerCase().equals("signedpre")) { + throw new IllegalArgumentException("Type must be one of the following: identity, ratchet, signedpre"); } - - if (publicKeyOutputFileName == null) { - printPublicKey(); - } else { - verifyWrite(false, publicKeyOutputFileName); + } + + @Override + public Void call() { + try { + validInputs(); + createKeyPair(); + if (privateKeyOutputFileName == null) { + printPrivateKey(); + } else { + verifyWrite(true, privateKeyOutputFileName); + } + + if (publicKeyOutputFileName == null) { + printPublicKey(); + } else { + verifyWrite(false, publicKeyOutputFileName); + } + } catch (Exception e) { + System.out.println(e.getMessage()); } - return 0; + return null; } } \ No newline at end of file