diff --git a/bundleserver/pom.xml b/bundleserver/pom.xml
index ec5741c216..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
@@ -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/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 adae5a2cc5..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";
@@ -65,7 +67,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;
@@ -126,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);
@@ -145,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");
@@ -155,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 5ae5a52e1a..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,9 +17,19 @@
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;
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;
@@ -33,7 +43,8 @@
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.BundleServerApplication;
import com.ddd.server.bundlesecurity.SecurityExceptions.AESAlgorithmException;
import com.ddd.server.bundlesecurity.SecurityExceptions.BundleIDCryptographyException;
import com.ddd.server.bundlesecurity.SecurityExceptions.EncodingException;
@@ -41,6 +52,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 {
@@ -64,46 +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
@@ -114,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 + "serverIdentity.pvt");
+ 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 + "serverSignedPreKey.pvt");
+ 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);
@@ -127,7 +150,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.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);
@@ -139,15 +162,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());
}
}
@@ -326,7 +349,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;
@@ -341,39 +364,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..f6a9971608
--- /dev/null
+++ b/bundleserver/src/main/java/com/ddd/server/keygenerator/CLRunner.java
@@ -0,0 +1,39 @@
+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.DecodePublicKey;
+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;
+
+ @Autowired
+ DecodePublicKey decodePublicKey;
+
+ @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);
+ } 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
new file mode 100644
index 0000000000..6226c443aa
--- /dev/null
+++ b/bundleserver/src/main/java/com/ddd/server/keygenerator/commands/GenerateKeys.java
@@ -0,0 +1,140 @@
+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 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 = "Public key file name")
+ private String publicKeyOutputFileName;
+
+ 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) {
+ 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);
+ }
+
+ 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");
+ }
+ }
+
+ @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 null;
+ }
+
+}
\ 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"