diff --git a/Dorkbox-Util/.classpath b/Dorkbox-Util/.classpath
new file mode 100644
index 0000000..0e81c85
--- /dev/null
+++ b/Dorkbox-Util/.classpath
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Dorkbox-Util/.gitignore b/Dorkbox-Util/.gitignore
new file mode 100644
index 0000000..840e7d3
--- /dev/null
+++ b/Dorkbox-Util/.gitignore
@@ -0,0 +1 @@
+/classes/
diff --git a/Dorkbox-Util/.project b/Dorkbox-Util/.project
new file mode 100644
index 0000000..0e6cf73
--- /dev/null
+++ b/Dorkbox-Util/.project
@@ -0,0 +1,17 @@
+
+
+ Dorkbox-Util
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/Dorkbox-Util/Test - Dorkbox Util.launch b/Dorkbox-Util/Test - Dorkbox Util.launch
new file mode 100644
index 0000000..f895eb0
--- /dev/null
+++ b/Dorkbox-Util/Test - Dorkbox Util.launch
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Dorkbox-Util/scripts/certutil_x64 b/Dorkbox-Util/scripts/certutil_x64
new file mode 100644
index 0000000..451f773
Binary files /dev/null and b/Dorkbox-Util/scripts/certutil_x64 differ
diff --git a/Dorkbox-Util/scripts/certutil_x86 b/Dorkbox-Util/scripts/certutil_x86
new file mode 100644
index 0000000..764cae2
Binary files /dev/null and b/Dorkbox-Util/scripts/certutil_x86 differ
diff --git a/Dorkbox-Util/scripts/incert.bat b/Dorkbox-Util/scripts/incert.bat
new file mode 100644
index 0000000..a2744d6
--- /dev/null
+++ b/Dorkbox-Util/scripts/incert.bat
@@ -0,0 +1,9 @@
+// WINDOWS XP
+// from: http://msdn.microsoft.com/en-us/library/e78byta0.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-2
+// certmgr /c /add TrustedCert.cer /s root
+//
+// more info from: http://tomfloor.wordpress.com/2012/11/09/command-line-certificate-installation-on-xp/
+// tool to download is from:
+// https://www.sslcertificaten.nl/download/Code_Signing_Tools/Authenticode_for_Internet_Explorer_toolkit
+
+// install cert in windows: http://msdn.microsoft.com/en-us/library/e78byta0(v=vs.71).aspx (use certmgr.exe)
\ No newline at end of file
diff --git a/Dorkbox-Util/scripts/incert.sh b/Dorkbox-Util/scripts/incert.sh
new file mode 100644
index 0000000..b0a01bc
--- /dev/null
+++ b/Dorkbox-Util/scripts/incert.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+# This installs our cert into firefox, thunderbird, and chrome for linux
+# usage: incert.sh cert_name
+#
+
+
+# install for global OS purposes:
+# Given a CA ceritificate file 'foo.crt', follow these steps to install it on Ubuntu:
+#
+# Create a directory for extra CA certificates in /usr/share/ca-certificates
+#
+# sudo mkdir /usr/share/ca-certificates/extra
+# Copy the '.crt' file to the directory
+#
+# sudo cp foo.crt /usr/share/ca-certificates/extra/foo.crt
+# Add the '.crt' file's path relative to /usr/share/ca-certificates to /etc/ca-certificates.conf
+#
+# sudo dpkg-reconfigure ca-certificates
+# Update the installed CA's
+#
+# sudo update-ca-certificates
+
+
+
+CERT_TOOL=""
+if [ "i686" = "$BUILDARCH" ]; then
+ CERT_TOOL="./certutil_x86"
+else
+ CERT_TOOL="./certutil_x64"
+fi
+
+
+CERT_FILE="certificate.crt"
+if [ -e "$1" ]; then
+ CERT_FILE=$1
+fi
+
+CERT_NAME="Dorkbox LLC CA"
+
+# This installs for firefox
+if [ -e ~/.mozilla* ]; then
+ for CERT_DB in $(find ~/.mozilla* -name "cert8.db")
+ do
+ CERT_DIR=$(dirname ${CERT_DB});
+ #log "mozilla certificate" "install '${certificateName}' in ${CERT_DIR}"
+ "${CERT_TOOL}" -d ${CERT_DIR} -A -n "${CERT_NAME}" -t "TCu,Cu,Cuw,Tuw" -i ${CERT_FILE}
+ done
+fi
+
+# This installs for thunderbird
+if [ -e ~/.thunderbird* ]; then
+ for CERT_DB in $(find ~/.thunderbird* -name "cert8.db")
+ do
+ CERT_DIR=$(dirname ${CERT_DB});
+ #log "mozilla certificate" "install '${certificateName}' in ${CERT_DIR}"
+ "${CERT_TOOL}" -d ${CERT_DIR} -A -n "${CERT_NAME}" -t "TCu,Cu,Cuw,Tuw" -i ${CERT_FILE}
+ done
+fi
+
+
+# This installs for chrome
+#NSS_DEFAULT_DB_TYPE="sql"
+if [ ! -e ~/.pki/nssdb ]; then
+ echo "==========================="
+ echo "No Database found. Creating one..."
+ echo "==========================="
+ "${CERT_TOOL}" -N -d sql:$HOME/.pki/nssdb
+fi
+"${CERT_TOOL}" -d sql:$HOME/.pki/nssdb -A -n "${CERT_NAME}" -t "TCu,Cu,Cuw,Tuw" -i ${CERT_FILE}
+"${CERT_TOOL}" -d ~/.pki/nssdb -L
+
diff --git a/Dorkbox-Util/scripts/win_xp_codesigningx86.exe b/Dorkbox-Util/scripts/win_xp_codesigningx86.exe
new file mode 100644
index 0000000..ce3e5ba
Binary files /dev/null and b/Dorkbox-Util/scripts/win_xp_codesigningx86.exe differ
diff --git a/Dorkbox-Util/src/dorkbox/util/ClassHelper.java b/Dorkbox-Util/src/dorkbox/util/ClassHelper.java
new file mode 100644
index 0000000..3b6582c
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/ClassHelper.java
@@ -0,0 +1,154 @@
+package dorkbox.util;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+public class ClassHelper {
+
+ /**
+ * Retrieves the generic type parameter for the PARENT (super) class of the specified class. This ONLY works
+ * on parent classes because of how type erasure works in java!
+ *
+ * @param clazz class to get the parameter from
+ * @param genericParameterToGet 0-based index of parameter as class to get
+ */
+ public final static Class> getGenericParameterAsClassForSuperClass(Class> clazz, int genericParameterToGet) {
+ Class> classToCheck = clazz;
+
+ // case of multiple inheritance, we are trying to get the first available generic info
+ // don't check for Object.class (this is where superclass is null)
+ while (classToCheck != Object.class) {
+ // check to see if we have what we are looking for on our CURRENT class
+ Type superClassGeneric = classToCheck.getGenericSuperclass();
+ if (superClassGeneric instanceof ParameterizedType) {
+ Type[] actualTypeArguments = ((ParameterizedType) superClassGeneric).getActualTypeArguments();
+ // is it possible?
+ if (actualTypeArguments.length > genericParameterToGet) {
+ Class> rawTypeAsClass = ClassHelper.getRawTypeAsClass(actualTypeArguments[genericParameterToGet]);
+ return rawTypeAsClass;
+ } else {
+ // record the parameters.
+
+ }
+ }
+
+ // NO MATCH, so walk up.
+ classToCheck = classToCheck.getSuperclass();
+ }
+
+ classToCheck = clazz;
+
+ // NOTHING! now check interfaces!
+ classToCheck = clazz;
+ while (classToCheck != Object.class) {
+ // check to see if we have what we are looking for on our CURRENT class interfaces
+ Type[] genericInterfaces = classToCheck.getGenericInterfaces();
+ for (Type genericInterface : genericInterfaces) {
+ if (genericInterface instanceof ParameterizedType) {
+ Type[] actualTypeArguments = ((ParameterizedType) genericInterface).getActualTypeArguments();
+ // is it possible?
+ if (actualTypeArguments.length > genericParameterToGet) {
+ Class> rawTypeAsClass = ClassHelper.getRawTypeAsClass(actualTypeArguments[genericParameterToGet]);
+ return rawTypeAsClass;
+ } else {
+ // record the parameters.
+
+ }
+ }
+ }
+
+
+ // NO MATCH, so walk up.
+ classToCheck = classToCheck.getSuperclass();
+ }
+
+
+ // couldn't find it.
+ return null;
+ }
+
+ /**
+ * Return the class that is this type.
+ */
+ public final static Class> getRawTypeAsClass(Type type) {
+ if (type instanceof Class) {
+ Class> class1 = (Class>)type;
+
+// if (class1.isArray()) {
+// System.err.println("CLASS IS ARRAY TYPE: SHOULD WE DO ANYTHING WITH IT? " + class1.getSimpleName());
+// return class1.getComponentType();
+// } else {
+ return class1;
+// }
+ } else if (type instanceof GenericArrayType) {
+ // note: cannot have primitive types here, only objects that are arrays (byte[], Integer[], etc)
+ Type type2 = ((GenericArrayType) type).getGenericComponentType();
+ Class> rawType = getRawTypeAsClass(type2);
+
+ return Array.newInstance(rawType, 0).getClass();
+ } else if (type instanceof ParameterizedType) {
+ // we cannot use parameterized types, because java can't go between classes and ptypes - and this
+ // going "in-between" is the magic -- and value -- of this entire infrastructure.
+ // return the type.
+
+ return (Class>) ((ParameterizedType) type).getRawType();
+
+// Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
+// return (Class>) actualTypeArguments[0];
+ } else if (type instanceof TypeVariable) {
+ // we have a COMPLEX type parameter
+ Type[] bounds = ((TypeVariable>)type).getBounds();
+ if (bounds.length > 0) {
+ return getRawTypeAsClass(bounds[0]);
+ }
+ }
+
+ throw new RuntimeException("Unknown/messed up type parameter . Can't figure it out... Quit being complex!");
+ }
+
+ /**
+ * Check to see if clazz or interface directly has one of the interfaces defined by clazzItMustHave
+ *
+ * If the class DOES NOT directly have the interface it will fail. the PARENT class is not checked.
+ */
+ public final static boolean hasInterface(Class> clazzItMustHave, Class> clazz) {
+ if (clazzItMustHave == clazz) {
+ return true;
+ }
+
+ Class>[] interfaces = clazz.getInterfaces();
+ for (Class> iface : interfaces) {
+ if (iface == clazzItMustHave) {
+ return true;
+ }
+ }
+ // now walk up to see if we can find it.
+ for (Class> iface : interfaces) {
+ return hasInterface(clazzItMustHave, iface);
+ }
+
+ // if we don't find it.
+ return false;
+ }
+
+ /**
+ * Checks to see if the clazz is a subclass of a parent class.
+ * @param baseClass
+ * @param genericClass
+ */
+ public static boolean hasParentClass(Class> parentClazz, Class> clazz) {
+ Class> superClass = clazz.getSuperclass();
+ if (parentClazz == superClass) {
+ return true;
+ }
+
+ if (superClass != null && superClass != Object.class) {
+ return hasParentClass(parentClazz, superClass);
+ }
+
+ return false;
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/CountingLatch.java b/Dorkbox-Util/src/dorkbox/util/CountingLatch.java
new file mode 100644
index 0000000..a1f7b9f
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/CountingLatch.java
@@ -0,0 +1,74 @@
+package dorkbox.util;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.AbstractQueuedSynchronizer;
+
+public class CountingLatch {
+ /**
+ * Synchronization control for CountingLatch. Uses AQS state to represent
+ * count.
+ */
+ private static final class Sync extends AbstractQueuedSynchronizer {
+ private static final long serialVersionUID = -2911206339865903403L;
+
+ private Sync() {}
+
+ private Sync(final int initialState) {
+ setState(initialState);
+ }
+
+ int getCount() {
+ return getState();
+ }
+
+ @Override
+ protected int tryAcquireShared(final int acquires) {
+ return getState() == 0 ? 1 : -1;
+ }
+
+ @Override
+ protected boolean tryReleaseShared(final int delta) {
+ // Decrement count; signal when transition to zero
+ for (;;) {
+ final int c = getState();
+ final int nextc = c + delta;
+ if (nextc < 0) {
+ return false;
+ }
+ if (compareAndSetState(c, nextc)) {
+ return nextc == 0;
+ }
+ }
+ }
+ }
+
+ private final Sync sync;
+
+ public CountingLatch() {
+ this.sync = new Sync();
+ }
+
+ public CountingLatch(final int initialCount) {
+ this.sync = new Sync(initialCount);
+ }
+
+ public void increment() {
+ this.sync.releaseShared(1);
+ }
+
+ public int getCount() {
+ return this.sync.getCount();
+ }
+
+ public void decrement() {
+ this.sync.releaseShared(-1);
+ }
+
+ public void await() throws InterruptedException {
+ this.sync.acquireSharedInterruptibly(1);
+ }
+
+ public boolean await(final long timeout) throws InterruptedException {
+ return this.sync.tryAcquireSharedNanos(1, TimeUnit.MILLISECONDS.toNanos(timeout));
+ }
+}
\ No newline at end of file
diff --git a/Dorkbox-Util/src/dorkbox/util/DelayTimer.java b/Dorkbox-Util/src/dorkbox/util/DelayTimer.java
new file mode 100644
index 0000000..5e5b2c5
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/DelayTimer.java
@@ -0,0 +1,72 @@
+package dorkbox.util;
+
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+
+public class DelayTimer {
+ public interface Callback {
+ public void execute();
+ }
+
+ private final String name;
+ private final Callback listener;
+
+ private Timer timer;
+
+ public DelayTimer(Callback listener) {
+ this(null, listener);
+ }
+
+ public DelayTimer(String name, Callback listener) {
+ this.name = name;
+ this.listener = listener;
+ }
+
+ /**
+ * @return true if this timer is still waiting to run.
+ */
+ public synchronized boolean isWaiting() {
+ return this.timer != null;
+ }
+
+ /**
+ * Cancel the delay timer!
+ */
+ public synchronized void cancel() {
+ if (this.timer != null) {
+ this.timer.cancel();
+ this.timer.purge();
+ this.timer = null;
+ }
+ }
+
+ /**
+ * @param delay milliseconds to wait
+ */
+ public synchronized void delay(long delay) {
+ cancel();
+
+ if (delay > 0) {
+ if (this.name != null) {
+ this.timer = new Timer(this.name, true);
+ } else {
+ this.timer = new Timer(true);
+ }
+
+
+ TimerTask t = new TimerTask() {
+ @Override
+ public void run() {
+ DelayTimer.this.listener.execute();
+ cancel();
+ }
+ };
+ this.timer.schedule(t, delay);
+ } else {
+ this.listener.execute();
+ this.timer = null;
+ }
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/FileUtil.java b/Dorkbox-Util/src/dorkbox/util/FileUtil.java
new file mode 100644
index 0000000..53e15f4
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/FileUtil.java
@@ -0,0 +1,688 @@
+package dorkbox.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.io.Reader;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * File related utilities.
+ */
+public class FileUtil {
+ private static final Logger logger = LoggerFactory.getLogger(FileUtil.class);
+
+ public static byte[] ZIP_HEADER = { 'P', 'K', 0x3, 0x4 };
+
+ /**
+ * Renames a file. Windows has all sorts of problems which are worked around.
+ *
+ * @return true if successful, false otherwise
+ */
+ public static boolean renameTo(File source, File dest) {
+ // if we're on a civilized operating system we may be able to simple
+ // rename it
+ if (source.renameTo(dest)) {
+ return true;
+ }
+
+ // fall back to trying to rename the old file out of the way, rename the
+ // new file into
+ // place and then delete the old file
+ if (dest.exists()) {
+ File temp = new File(dest.getPath() + "_old");
+ if (temp.exists()) {
+ if (!temp.delete()) {
+ logger.warn("Failed to delete old intermediate file {}.", temp);
+ // the subsequent code will probably fail
+ }
+ }
+ if (dest.renameTo(temp)) {
+ if (source.renameTo(dest)) {
+ if (temp.delete()) {
+ logger.warn("Failed to delete intermediate file {}.", temp);
+ }
+ return true;
+ }
+ }
+ }
+
+ // as a last resort, try copying the old data over the new
+ FileInputStream fin = null;
+ FileOutputStream fout = null;
+ try {
+ fin = new FileInputStream(source);
+ fout = new FileOutputStream(dest);
+ Sys.copyStream(fin, fout);
+ if (!source.delete()) {
+ logger.warn("Failed to delete {} after brute force copy to {}.", source, dest);
+ }
+ return true;
+
+ } catch (IOException ioe) {
+ logger.warn("Failed to copy {} to {}.", source, dest, ioe);
+ return false;
+
+ } finally {
+ Sys.close(fin);
+ Sys.close(fout);
+ }
+ }
+
+ /**
+ * Reads the contents of the supplied input stream into a list of lines.
+ * Closes the reader on successful or failed completion.
+ */
+ public static List readLines(Reader in) throws IOException {
+ List lines = new ArrayList();
+ try {
+ BufferedReader bin = new BufferedReader(in);
+ for (String line = null; (line = bin.readLine()) != null; lines.add(line)) {}
+ } finally {
+ Sys.close(in);
+ }
+ return lines;
+ }
+
+ /**
+ * Copies a files from one location to another. Overwriting any existing file at the destination.
+ */
+ public static File copyFile(String in, String out) throws IOException {
+ return copyFile(new File(in), new File(out));
+ }
+
+ /**
+ * Copies a files from one location to another. Overwriting any existing file at the destination.
+ */
+ public static File copyFile(File in, File out) throws IOException {
+ if (in == null) {
+ throw new IllegalArgumentException("in cannot be null.");
+ }
+ if (out == null) {
+ throw new IllegalArgumentException("out cannot be null.");
+ }
+
+
+ String normalizedIn = in.getCanonicalFile().getAbsolutePath();
+ String normalizedout = out.getCanonicalFile().getAbsolutePath();
+
+ // if out doesn't exist, then create it.
+ File parentOut = out.getParentFile();
+ if (!parentOut.canWrite()) {
+ parentOut.mkdirs();
+ }
+
+ logger.trace("Copying file: {} --> {}", in, out);
+
+ FileChannel sourceChannel = null;
+ FileChannel destinationChannel = null;
+ try {
+ sourceChannel = new FileInputStream(normalizedIn).getChannel();
+ destinationChannel = new FileOutputStream(normalizedout).getChannel();
+ sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel);
+ } finally {
+ try {
+ if (sourceChannel != null) {
+ sourceChannel.close();
+ }
+ } catch (Exception ignored) {
+ }
+ try {
+ if (destinationChannel != null) {
+ destinationChannel.close();
+ }
+ } catch (Exception ignored) {
+ }
+ }
+
+ out.setLastModified(in.lastModified());
+
+ return out;
+ }
+
+
+ /**
+ * Moves a file, overwriting any existing file at the destination.
+ */
+ public static File moveFile(String in, String out) throws IOException {
+ if (in == null || in.isEmpty()) {
+ throw new IllegalArgumentException("in cannot be null.");
+ }
+ if (out == null || out.isEmpty()) {
+ throw new IllegalArgumentException("out cannot be null.");
+ }
+
+ return moveFile(new File(in), new File(out));
+ }
+
+ /**
+ * Moves a file, overwriting any existing file at the destination.
+ */
+ public static File moveFile(File in, File out) throws IOException {
+ if (in == null) {
+ throw new IllegalArgumentException("in cannot be null.");
+ }
+ if (out == null) {
+ throw new IllegalArgumentException("out cannot be null.");
+ }
+
+ System.err.println("\t\t: Moving file");
+ System.err.println("\t\t: " + in.getAbsolutePath());
+ System.err.println("\t\t: " + out.getAbsolutePath());
+
+ if (out.canRead()) {
+ out.delete();
+ }
+
+ boolean renameSuccess = renameTo(in, out);
+ if (!renameSuccess) {
+ throw new RuntimeException("Unable to move file: '" + in.getAbsolutePath() + "' -> '" + out.getAbsolutePath() + "'");
+ }
+ return out;
+ }
+
+ /**
+ * Copies a directory from one location to another
+ */
+ public static void copyDirectory(String src, String dest, String... dirNamesToIgnore) throws IOException {
+ copyDirectory(new File(src), new File(dest), dirNamesToIgnore);
+ }
+
+
+ /**
+ * Copies a directory from one location to another
+ */
+ public static void copyDirectory(File src, File dest, String... dirNamesToIgnore) throws IOException {
+ if (dirNamesToIgnore.length > 0) {
+ String name = src.getName();
+ for (String ignore : dirNamesToIgnore) {
+ if (name.equals(ignore)) {
+ return;
+ }
+ }
+ }
+
+
+ if (src.isDirectory()) {
+ // if directory not exists, create it
+ if (!dest.exists()) {
+ dest.mkdir();
+ logger.trace("Directory copied from {} --> {}", src, dest);
+ }
+
+ // list all the directory contents
+ String files[] = src.list();
+
+ for (String file : files) {
+ // construct the src and dest file structure
+ File srcFile = new File(src, file);
+ File destFile = new File(dest, file);
+
+ // recursive copy
+ copyDirectory(srcFile, destFile, dirNamesToIgnore);
+ }
+ } else {
+ // if file, then copy it
+ copyFile(src, dest);
+ }
+ }
+
+ /**
+ * Safely moves a directory from one location to another (by copying it first, then deleting the original).
+ */
+ public static void moveDirectory(String src, String dest, String... dirNamesToIgnore) throws IOException {
+ moveDirectory(new File(src), new File(dest), dirNamesToIgnore);
+ }
+
+ /**
+ * Safely moves a directory from one location to another (by copying it first, then deleting the original).
+ */
+ public static void moveDirectory(File src, File dest, String... dirNamesToIgnore) throws IOException {
+ if (dirNamesToIgnore.length > 0) {
+ String name = src.getName();
+ for (String ignore : dirNamesToIgnore) {
+ if (name.equals(ignore)) {
+ return;
+ }
+ }
+ }
+
+
+ if (src.isDirectory()) {
+ // if directory not exists, create it
+ if (!dest.exists()) {
+ dest.mkdir();
+ logger.trace("Directory copied from {} --> {}", src, dest);
+ }
+
+ // list all the directory contents
+ String files[] = src.list();
+
+ for (String file : files) {
+ // construct the src and dest file structure
+ File srcFile = new File(src, file);
+ File destFile = new File(dest, file);
+
+ // recursive copy
+ moveDirectory(srcFile, destFile, dirNamesToIgnore);
+ }
+ } else {
+ // if file, then copy it
+ moveFile(src, dest);
+ }
+ }
+
+ /**
+ * Deletes a file or directory and all files and sub-directories under it.
+ */
+ public static boolean delete(String fileName) {
+ if (fileName == null) {
+ throw new IllegalArgumentException("fileName cannot be null.");
+ }
+
+ return delete(new File(fileName));
+ }
+
+ /**
+ * Deletes a file or directory and all files and sub-directories under it.
+ */
+ public static boolean delete(File file) {
+ if (file.exists() && file.isDirectory()) {
+ File[] files = file.listFiles();
+ for (int i = 0, n = files.length; i < n; i++) {
+ if (files[i].isDirectory()) {
+ delete(files[i].getAbsolutePath());
+ } else {
+ logger.trace("Deleting file: {}", files[i]);
+ files[i].delete();
+ }
+ }
+ }
+ logger.trace("Deleting file: {}", file);
+
+ return file.delete();
+ }
+
+
+ /**
+ * Creates the directories in the specified location.
+ */
+ public static String mkdir(File location) {
+ if (location == null) {
+ throw new IllegalArgumentException("fileDir cannot be null.");
+ }
+
+ String path = location.getAbsolutePath();
+ if (location.mkdirs()) {
+ logger.trace("Created directory: {}", path);
+ }
+
+ return path;
+ }
+
+ /**
+ * Creates the directories in the specified location.
+ */
+ public static String mkdir(String location) {
+ if (location == null) {
+ throw new IllegalArgumentException("path cannot be null.");
+ }
+
+ return mkdir(new File(location));
+ }
+
+
+ /**
+ * Creates a temp file
+ */
+ public static File tempFile(String fileName) throws IOException {
+ if (fileName == null) {
+ throw new IllegalArgumentException("fileName cannot be null");
+ }
+
+ return File.createTempFile(fileName, null).getAbsoluteFile();
+ }
+
+ /**
+ * Creates a temp directory
+ */
+ public static String tempDirectory(String directoryName) throws IOException {
+ if (directoryName == null) {
+ throw new IllegalArgumentException("directoryName cannot be null");
+ }
+
+ File file = File.createTempFile(directoryName, null);
+ if (!file.delete()) {
+ throw new IOException("Unable to delete temp file: " + file);
+ }
+
+ if (!file.mkdir()) {
+ throw new IOException("Unable to create temp directory: " + file);
+ }
+
+ return file.getAbsolutePath();
+ }
+
+ /**
+ * @return true if the inputStream is a zip/jar stream. DOES NOT CLOSE THE STREAM
+ */
+ public static boolean isZipStream(InputStream in) {
+ if (!in.markSupported()) {
+ in = new BufferedInputStream(in);
+ }
+ boolean isZip = true;
+ try {
+ in.mark(ZIP_HEADER.length);
+ for (int i = 0; i < ZIP_HEADER.length; i++) {
+ if (ZIP_HEADER[i] != (byte) in.read()) {
+ isZip = false;
+ break;
+ }
+ }
+ in.reset();
+ } catch (Exception e) {
+ isZip = false;
+ }
+
+ return isZip;
+ }
+
+ /**
+ * @return true if the named file is a zip/jar file
+ */
+ public static boolean isZipFile(String fileName) {
+ if (fileName == null) {
+ throw new IllegalArgumentException("fileName cannot be null");
+ }
+
+ return isZipFile(new File(fileName));
+ }
+
+ /**
+ * @return true if the file is a zip/jar file
+ */
+ public static boolean isZipFile(File file) {
+ boolean isZip = true;
+ byte[] buffer = new byte[ZIP_HEADER.length];
+
+ RandomAccessFile raf = null;
+ try {
+ raf = new RandomAccessFile(file, "r");
+ raf.readFully(buffer);
+ for (int i = 0; i < ZIP_HEADER.length; i++) {
+ if (buffer[i] != ZIP_HEADER[i]) {
+ isZip = false;
+ break;
+ }
+ }
+ } catch (Exception e) {
+ isZip = false;
+ } finally {
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return isZip;
+ }
+
+
+ /**
+ * Unzips a ZIP file
+ *
+ * @return The path to the output directory.
+ */
+ public static void unzip(String zipFile, String outputDir) throws IOException {
+ unzipJar(zipFile, outputDir, true);
+ }
+
+ /**
+ * Unzips a ZIP file
+ *
+ * @return The path to the output directory.
+ */
+ public static void unzip(File zipFile, File outputDir) throws IOException {
+ unzipJar(zipFile, outputDir, true);
+ }
+
+ /**
+ * Unzips a ZIP file
+ *
+ * @return The path to the output directory.
+ */
+ public static void unzipJar(String zipFile, String outputDir, boolean extractManifest) throws IOException {
+ if (zipFile == null) {
+ throw new IllegalArgumentException("zipFile cannot be null.");
+ }
+ if (outputDir == null) {
+ throw new IllegalArgumentException("outputDir cannot be null.");
+ }
+
+ unjarzip0(new File(zipFile), new File(outputDir), extractManifest);
+ }
+
+ /**
+ * Unzips a ZIP file
+ *
+ * @return The path to the output directory.
+ */
+ public static void unzipJar(File zipFile, File outputDir, boolean extractManifest) throws IOException {
+ if (zipFile == null) {
+ throw new IllegalArgumentException("zipFile cannot be null.");
+ }
+ if (outputDir == null) {
+ throw new IllegalArgumentException("outputDir cannot be null.");
+ }
+
+ unjarzip0(zipFile, outputDir, extractManifest);
+ }
+
+
+
+ /**
+ * Unzips a ZIP or JAR file (and handles the manifest if requested)
+ */
+ private static void unjarzip0(File zipFile, File outputDir, boolean extractManifest) throws IOException {
+ if (zipFile == null) {
+ throw new IllegalArgumentException("zipFile cannot be null.");
+ }
+ if (outputDir == null) {
+ throw new IllegalArgumentException("outputDir cannot be null.");
+ }
+
+ long fileLength = zipFile.length();
+ if (fileLength > Integer.MAX_VALUE - 1) {
+ throw new RuntimeException("Source filesize is too large!");
+ }
+
+
+ ZipInputStream inputStrem = new ZipInputStream(new FileInputStream(zipFile));
+ try {
+ while (true) {
+ ZipEntry entry = inputStrem.getNextEntry();
+ if (entry == null) {
+ break;
+ }
+
+ String name = entry.getName();
+
+ if (!extractManifest && name.startsWith("META-INF/")) {
+ continue;
+ }
+
+ File file = new File(outputDir, name);
+ if (entry.isDirectory()) {
+ mkdir(file.getPath());
+ continue;
+ }
+ mkdir(file.getParent());
+
+
+ FileOutputStream output = new FileOutputStream(file);
+ try {
+ Sys.copyStream(inputStrem, output);
+ } finally {
+ output.close();
+ }
+ }
+ } finally {
+ inputStrem.close();
+ }
+ }
+
+
+ /**
+ * Parses the specified root directory for ALL files that are in it. All of the sub-directories are searched as well.
+ *
+ * This is different, in that it returns ALL FILES, instead of ones that just match a specific extension.
+ * @return the list of all files in the root+sub-dirs.
+ */
+ public static List parseDir(File rootDirectory) {
+ return parseDir(rootDirectory, (String) null);
+ }
+
+ /**
+ * Parses the specified root directory for files that end in the extension to match. All of the sub-directories are searched as well.
+ * @return the list of all files in the root+sub-dirs that match the given extension.
+ */
+ public static List parseDir(File rootDirectory, String... extensionsToMatch) {
+ List jarList = new LinkedList();
+ LinkedList directories = new LinkedList();
+
+ if (rootDirectory.isDirectory()) {
+ directories.add(rootDirectory);
+
+ while (directories.peek() != null) {
+ File dir = directories.poll();
+ File[] listFiles = dir.listFiles();
+ for (File file : listFiles) {
+ if (file.isDirectory()) {
+ directories.add(file);
+ } else {
+ if (extensionsToMatch == null) {
+ jarList.add(file);
+ } else {
+ for (String e : extensionsToMatch) {
+ if (file.getAbsolutePath().endsWith(e)) {
+ jarList.add(file);
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ System.err.println("Cannot search dependencies, if it's a file name!");
+ }
+
+
+ return jarList;
+ }
+
+ /**
+ * Gets the relative path of a file to a specific directory in it's hierarchy.
+ *
+ * For example: getRelativeToDir("/a/b/c/d/e.bah", "c") -> "d/e.bah"
+ */
+ public static String getRelativeToDir(String fileName, String dirInHeirarchy) {
+ if (fileName == null || fileName.isEmpty()) {
+ throw new IllegalArgumentException("fileName cannot be null.");
+ }
+
+ return getRelativeToDir(new File(fileName), dirInHeirarchy);
+ }
+
+ /**
+ * Gets the relative path of a file to a specific directory in it's hierarchy.
+ *
+ * For example: getRelativeToDir("/a/b/c/d/e.bah", "c") -> "d/e.bah"
+ * @return null if it cannot be found
+ */
+ public static String getRelativeToDir(File file, String dirInHeirarchy) {
+ if (file == null) {
+ throw new IllegalArgumentException("file cannot be null.");
+ }
+
+ String absolutePath = file.getAbsolutePath();
+
+ File parent = file;
+ String parentName;
+ while ((parent = parent.getParentFile()) != null) {
+ parentName = parent.getName();
+
+ if (parentName.equals(dirInHeirarchy)) {
+ parentName = parent.getAbsolutePath();
+
+ return absolutePath.substring(parentName.length() + 1);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Extracts a file from a zip into a TEMP file, if possible. The TEMP file is deleted upon JVM exit.
+ *
+ * @throws IOException
+ * @return the location of the extracted file, or NULL if the file cannot be extracted or doesn't exist.
+ */
+ public static String extractFromZip(String zipFile, String fileToExtract) throws IOException {
+ if (zipFile == null) {
+ throw new IllegalArgumentException("file cannot be null.");
+ }
+
+ if (fileToExtract == null) {
+ throw new IllegalArgumentException("fileToExtract cannot be null.");
+ }
+
+ ZipInputStream inputStrem = new ZipInputStream(new FileInputStream(zipFile));
+ try {
+ while (true) {
+ ZipEntry entry = inputStrem.getNextEntry();
+ if (entry == null) {
+ break;
+ }
+
+ String name = entry.getName();
+ if (entry.isDirectory()) {
+ continue;
+ }
+
+ if (name.equals(fileToExtract)) {
+ File tempFile = FileUtil.tempFile(name);
+ tempFile.deleteOnExit();
+
+ FileOutputStream output = new FileOutputStream(tempFile);
+ try {
+ Sys.copyStream(inputStrem, output);
+ } finally {
+ output.close();
+ }
+
+ return tempFile.getAbsolutePath();
+ }
+ }
+ } finally {
+ inputStrem.close();
+ }
+
+ return null;
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/InputConsole.java b/Dorkbox-Util/src/dorkbox/util/InputConsole.java
new file mode 100644
index 0000000..c9c4e16
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/InputConsole.java
@@ -0,0 +1,342 @@
+package dorkbox.util;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jline.IDE_Terminal;
+import jline.Terminal;
+import jline.console.ConsoleReader;
+
+import org.fusesource.jansi.Ansi;
+import org.fusesource.jansi.AnsiConsole;
+
+public class InputConsole {
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(InputConsole.class);
+ private static final InputConsole consoleProxyReader;
+ private static final char[] emptyLine = new char[0];
+
+ static {
+ consoleProxyReader = new InputConsole();
+ // setup (if necessary) the JLINE console logger.
+ // System.setProperty("jline.internal.Log.trace", "TRUE");
+ // System.setProperty("jline.internal.Log.debug", "TRUE");
+ }
+
+ /**
+ * empty method to allow code to initialize the input console.
+ */
+ public static void init() {
+ }
+
+ public static final void destroy() {
+ consoleProxyReader.destroy0();
+ }
+
+ /** return null if no data */
+ public static final String readLine() {
+ char[] line = consoleProxyReader.readLine0();
+ return new String(line);
+ }
+
+ /** return -1 if no data */
+ public static final int read() {
+ return consoleProxyReader.read0();
+ }
+
+ /** return null if no data */
+ public static final char[] readLinePassword() {
+ return consoleProxyReader.readLinePassword0();
+ }
+
+ public static InputStream getInputStream() {
+ return new InputStream() {
+ @Override
+ public int read() throws IOException {
+ return consoleProxyReader.read0();
+ }
+
+ @Override
+ public void close() throws IOException {
+ consoleProxyReader.release0();
+ }
+ };
+ }
+
+ public static void echo(boolean enableEcho) {
+ consoleProxyReader.echo0(enableEcho);
+ }
+
+ public static boolean echo() {
+ return consoleProxyReader.echo0();
+ }
+
+
+ private ConsoleReader jlineReader;
+
+ private final Object inputLockSingle = new Object();
+ private final Object inputLockLine = new Object();
+
+ private AtomicBoolean isRunning = new AtomicBoolean(false);
+ private AtomicBoolean isInShutdown = new AtomicBoolean(false);
+ private volatile char[] readLine = null;
+ private volatile int readChar = -1;
+
+ private boolean isIDE;
+
+ // the streams are ALREADY buffered!
+ //
+ private InputConsole() {
+ try {
+ this.jlineReader = new ConsoleReader();
+
+ Terminal terminal = this.jlineReader.getTerminal();
+ terminal.setEchoEnabled(true);
+ this.isIDE = terminal instanceof IDE_Terminal;
+
+ if (this.isIDE) {
+ logger.debug("Terminal is in IDE (best guess). Unable to support single key input. Only line input available.");
+ } else {
+ logger.debug("Terminal Type: {}", terminal.getClass().getSimpleName());
+ }
+ } catch (UnsupportedEncodingException ignored) {
+ } catch (IOException ignored) {
+ }
+ }
+
+ /**
+ * make sure the input console reader thread is started.
+ */
+ private void startInputConsole() {
+ // protected by atomic!
+ if (!this.isRunning.compareAndSet(false, true) || this.isInShutdown.get()) {
+ return;
+ }
+
+ Thread consoleThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ consoleProxyReader.run();
+ }
+ });
+ consoleThread.setDaemon(true);
+ consoleThread.setName("Console Input Reader");
+
+ consoleThread.start();
+ }
+
+ private void destroy0() {
+ // Don't change this, because we don't want to enable reading, etc from this once it's destroyed.
+ // isRunning.set(false);
+
+ if (this.isInShutdown.compareAndSet(true, true)) {
+ return;
+ }
+
+ synchronized (this.inputLockSingle) {
+ this.inputLockSingle.notifyAll();
+ }
+
+ synchronized (this.inputLockLine) {
+ this.inputLockLine.notifyAll();
+ }
+
+ // we want to make sure this happens in a new thread, since this can BLOCK our main event dispatch thread
+ Thread thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ InputConsole.this.jlineReader.shutdown();
+ InputConsole.this.jlineReader = null;
+ }});
+ thread.setDaemon(true);
+ thread.setName("Console Input Shutdown");
+ thread.start();
+ }
+
+ private void echo0(boolean enableEcho) {
+ if (this.jlineReader != null) {
+ Terminal terminal = this.jlineReader.getTerminal();
+ if (terminal != null) {
+ terminal.setEchoEnabled(enableEcho);
+ }
+ }
+ }
+
+ private boolean echo0() {
+ if (this.jlineReader != null) {
+ Terminal terminal = this.jlineReader.getTerminal();
+ if (terminal != null) {
+ return terminal.isEchoEnabled();
+ }
+ }
+ return false;
+ }
+
+
+ /** return null if no data */
+ private final char[] readLine0() {
+ startInputConsole();
+
+ synchronized (this.inputLockLine) {
+ try {
+ this.inputLockLine.wait();
+ } catch (InterruptedException e) {
+ return emptyLine;
+ }
+ }
+ return this.readLine;
+ }
+
+ /** return null if no data */
+ private final char[] readLinePassword0() {
+ startInputConsole();
+
+ // don't bother in an IDE. it won't work.
+ return readLine0();
+ }
+
+ /** return -1 if no data */
+ private final int read0() {
+ startInputConsole();
+
+ synchronized (this.inputLockSingle) {
+ try {
+ this.inputLockSingle.wait();
+ } catch (InterruptedException e) {
+ return -1;
+ }
+ }
+ return this.readChar;
+ }
+
+ /**
+ * releases any thread still waiting.
+ */
+ private void release0() {
+ synchronized (this.inputLockSingle) {
+ this.inputLockSingle.notifyAll();
+ }
+
+ synchronized (this.inputLockLine) {
+ this.inputLockLine.notifyAll();
+ }
+ }
+
+ private final void run() {
+ if (this.jlineReader == null) {
+ logger.error("Unable to start Console Reader");
+ return;
+ }
+
+ // if we are eclipse, we MUST do this per line! (per character DOESN'T work.)
+ if (this.isIDE) {
+ try {
+ while ((this.readLine = this.jlineReader.readLine()) != null) {
+ // notify everyone waiting for a line of text.
+ synchronized (this.inputLockSingle) {
+ if (this.readLine.length > 0) {
+ this.readChar = this.readLine[0];
+ } else {
+ this.readChar = -1;
+ }
+ this.inputLockSingle.notifyAll();
+ }
+ synchronized (this.inputLockLine) {
+ this.inputLockLine.notifyAll();
+ }
+ }
+ } catch (Exception ignored) {
+ ignored.printStackTrace();
+ }
+ } else {
+
+ try {
+ final boolean ansiEnabled = Ansi.isEnabled();
+ Ansi ansi = Ansi.ansi();
+
+ int typedChar;
+ StringBuilder buf = new StringBuilder();
+
+ // don't type ; in a bash shell, it quits everything
+ // \n is replaced by \r in unix terminal?
+ while ((typedChar = this.jlineReader.readCharacter()) != -1) {
+ char asChar = (char) typedChar;
+
+ logger.trace("READ: {} ({})", asChar, typedChar);
+
+ // notify everyone waiting for a character.
+ synchronized (this.inputLockSingle) {
+ this.readChar = typedChar;
+ this.inputLockSingle.notifyAll();
+ }
+
+ // if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed.
+ if (typedChar == 127) {
+ int position = 0;
+
+ // clear ourself + one extra.
+ if (ansiEnabled) {
+ int amtToBackspace = 2; // ConsoleReader.getPrintableCharacters(typedChar).length();
+ int length = buf.length();
+ if (length > 1) {
+ char charAt = buf.charAt(length-1);
+ amtToBackspace += ConsoleReader.getPrintableCharacters(charAt).length();
+ buf.delete(length-1, length);
+
+ length--;
+
+ // now figure out where the cursor is at.
+ for (int i=0;i 0) {
+ this.readLine = new char[length];
+ buf.getChars(0, length, this.readLine, 0);
+ } else {
+ this.readLine = emptyLine;
+ }
+
+ this.inputLockLine.notifyAll();
+ }
+
+ // dump the characters in the backing array (slightly safer for passwords when using this method)
+ if (length > 0) {
+ buf.delete(0, buf.length());
+ }
+ } else if (asChar != '\r') {
+ // only append if we are not a new line.
+ buf.append(asChar);
+ }
+ }
+ } catch (IOException ignored) {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Dorkbox-Util/src/dorkbox/util/RegExp.java b/Dorkbox-Util/src/dorkbox/util/RegExp.java
new file mode 100644
index 0000000..3f73f90
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/RegExp.java
@@ -0,0 +1,38 @@
+package dorkbox.util;
+
+public class RegExp {
+ private static final String whitespace_chars = "" /* dummy empty string for homogeneity */
+ + "\\u0009" // CHARACTER TABULATION
+ + "\\u000A" // LINE FEED (LF)
+ + "\\u000B" // LINE TABULATION
+ + "\\u000C" // FORM FEED (FF)
+ + "\\u000D" // CARRIAGE RETURN (CR)
+ + "\\u0020" // SPACE
+ + "\\u0085" // NEXT LINE (NEL)
+ + "\\u00A0" // NO-BREAK SPACE
+ + "\\u1680" // OGHAM SPACE MARK
+ + "\\u180E" // MONGOLIAN VOWEL SEPARATOR
+ + "\\u2000" // EN QUAD
+ + "\\u2001" // EM QUAD
+ + "\\u2002" // EN SPACE
+ + "\\u2003" // EM SPACE
+ + "\\u2004" // THREE-PER-EM SPACE
+ + "\\u2005" // FOUR-PER-EM SPACE
+ + "\\u2006" // SIX-PER-EM SPACE
+ + "\\u2007" // FIGURE SPACE
+ + "\\u2008" // PUNCTUATION SPACE
+ + "\\u2009" // THIN SPACE
+ + "\\u200A" // HAIR SPACE
+ + "\\u2028" // LINE SEPARATOR
+ + "\\u2029" // PARAGRAPH SEPARATOR
+ + "\\u202F" // NARROW NO-BREAK SPACE
+ + "\\u205F" // MEDIUM MATHEMATICAL SPACE
+ + "\\u3000" // IDEOGRAPHIC SPACE
+ ;
+
+ /* A \s that actually works for Java’s native character set: Unicode */
+ public static final String whitespace_charclass = "[" + whitespace_chars + "]";
+
+ /* A \S that actually works for Java’s native character set: Unicode */
+ public static final String not_whitespace_charclass = "[^" + whitespace_chars + "]";
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/Storage.java b/Dorkbox-Util/src/dorkbox/util/Storage.java
new file mode 100644
index 0000000..e211327
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/Storage.java
@@ -0,0 +1,336 @@
+package dorkbox.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Nothing spectacular about this storage -- it allows for persistent storage of objects to disk.
+ */
+public class Storage {
+ // TODO: add snappy compression to storage objects??
+
+ private static final Logger logger = LoggerFactory.getLogger(Storage.class);
+
+ private static Map storages = new HashMap(1);
+
+ private final File file;
+
+ private long milliSeconds = 3000L;
+ private DelayTimer timer;
+ private WeakReference> objectReference;
+
+ @SuppressWarnings({"rawtypes","unchecked"})
+ public static Storage load(File file, Object loadIntoObject) {
+ if (file == null) {
+ throw new IllegalArgumentException("file cannot be null!");
+ }
+
+ if (loadIntoObject == null) {
+ throw new IllegalArgumentException("loadIntoObject cannot be null!");
+ }
+
+
+ // if we load from a NEW storage at the same location as an ALREADY EXISTING storage,
+ // without saving the existing storage first --- whoops!
+ synchronized (storages) {
+ Storage storage = storages.get(file);
+ if (storage != null) {
+ boolean waiting = storage.timer.isWaiting();
+ if (waiting) {
+ storage.saveNow();
+ }
+
+ // why load it from disk again? just copy out the values!
+ synchronized (storage) {
+ // have to load from disk!
+ Object source = load(file, loadIntoObject.getClass());
+
+ Object orig = storage.objectReference.get();
+ if (orig != null) {
+ if (orig != loadIntoObject) {
+ storage.objectReference = new WeakReference(loadIntoObject);
+ }
+
+ } else {
+ // whoopie - the old one got GC'd! (for whatever reason, it can be legit)
+ storage.objectReference = new WeakReference(loadIntoObject);
+ }
+
+ if (source != null) {
+ copyFields(source, loadIntoObject);
+ }
+ }
+ } else {
+ // this will load it from disk again, if necessary
+ storage = new Storage(file, loadIntoObject);
+ storages.put(file, storage);
+
+ // have to load from disk!
+ Object source = load(file, loadIntoObject.getClass());
+ if (source != null) {
+ copyFields(source, loadIntoObject);
+ }
+ }
+ return storage;
+ }
+ }
+
+
+ /**
+ * Also loads the saved object into the passed-in object. This is sorta slow (nothing is cached for speed!)
+ *
+ * If the saved object has more fields than the loadIntoObject, only the fields in loadIntoObject will be
+ * populated. If the loadIntoObject has more fields than the saved object, then the loadIntoObject will not
+ * have those fields changed.
+ */
+ @SuppressWarnings({"rawtypes","unchecked"})
+ private Storage(File file, Object loadIntoObject) {
+ this.file = file.getAbsoluteFile();
+ File parentFile = this.file.getParentFile();
+ if (parentFile != null) {
+ parentFile.mkdirs();
+ }
+
+ this.objectReference = new WeakReference(loadIntoObject);
+ this.timer = new DelayTimer("Storage Writer", new DelayTimer.Callback() {
+ @Override
+ public void execute() {
+ save0();
+ }
+ });
+ }
+
+ /**
+ * Loads the saved object into the passed-in object. This is sorta slow (nothing is cached for speed!)
+ *
+ * If the saved object has more fields than the loadIntoObject, only the fields in loadIntoObject will be
+ * populated. If the loadIntoObject has more fields than the saved object, then the loadIntoObject will not
+ * have those fields changed.
+ */
+ public final void load(Object loadIntoObject) {
+ if (loadIntoObject == null) {
+ throw new IllegalArgumentException("loadIntoObject cannot be null!");
+ }
+
+
+ // if we load from a NEW storage at the same location as an ALREADY EXISTING storage,
+ // without saving the existing storage first --- whoops!
+ synchronized (storages) {
+ File file2 = this.file;
+
+ Storage storage = storages.get(file2);
+ Object source = null;
+ if (storage != null) {
+ boolean waiting = storage.timer.isWaiting();
+ if (waiting) {
+ storage.saveNow();
+ }
+
+ // why load it from disk again? just copy out the values!
+ source = storage.objectReference.get();
+ if (source == null) {
+ // have to load from disk!
+ source = load(file2, loadIntoObject.getClass());
+ }
+ }
+
+ if (source != null) {
+ copyFields(source, loadIntoObject);
+ }
+ }
+ }
+
+ /**
+ * @param delay milliseconds to wait
+ */
+ public final void setSaveDelay(long milliSeconds) {
+ this.milliSeconds = milliSeconds;
+ }
+
+ /**
+ * Immediately save the storage to disk
+ */
+ public final synchronized void saveNow() {
+ this.timer.delay(0L);
+ }
+
+ /**
+ * Save the storage to disk, once xxxx milli-seconds have passed.
+ * This is to help prevent thrashing the disk, or wearing it out on multiple, rapid, changes.
+ */
+ public final synchronized void save() {
+ this.timer.delay(this.milliSeconds);
+ }
+
+ private synchronized void save0() {
+ Object object = Storage.this.objectReference.get();
+
+ if (object == null) {
+ Storage.logger.error("Object has been erased and is no longer available to save!");
+ return;
+ }
+
+ RandomAccessFile raf = null;
+ Output output = null;
+ try {
+ raf = new RandomAccessFile(this.file, "rw");
+ FileOutputStream outputStream = new FileOutputStream(raf.getFD());
+ output = new Output(outputStream, 1024); // write 1024 at a time
+
+
+ Kryo kryo = new Kryo();
+ kryo.setRegistrationRequired(false);
+ kryo.writeObject(output, object);
+ output.flush();
+ } catch (Exception e) {
+ Storage.logger.error("Error saving the data!", e);
+ } finally {
+ if (output != null) {
+ output.close();
+ }
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static T load(File file, Class extends Object> clazz) {
+ if (file.length() == 0) {
+ return null;
+ }
+
+ RandomAccessFile raf = null;
+ Input input = null;
+ try {
+ raf = new RandomAccessFile(file, "r");
+ input = new Input(new FileInputStream(raf.getFD()), 1024); // read 1024 at a time
+
+ Kryo kryo = new Kryo();
+ kryo.setRegistrationRequired(false);
+ Object readObject = kryo.readObject(input, clazz);
+ return (T) readObject;
+ } catch (Exception e) {
+ logger.error("Error reading from '{}'! Perhaps the file is corrupt?", file.getAbsolutePath());
+ return null;
+ } finally {
+ if (input != null) {
+ input.close();
+ }
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (this.file == null ? 0 : this.file.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Storage other = (Storage) obj;
+ if (this.file == null) {
+ if (other.file != null) {
+ return false;
+ }
+ } else if (!this.file.equals(other.file)) {
+ return false;
+ }
+ return true;
+ }
+
+
+ @Override
+ public String toString() {
+ return "Storage [" + this.file + "]";
+ }
+
+
+ private static void copyFields(Object source, Object dest) {
+ Class extends Object> sourceClass = source.getClass();
+ Field[] destFields = dest.getClass().getDeclaredFields();
+
+ for (Field destField : destFields) {
+ String name = destField.getName();
+ try {
+ Field sourceField = sourceClass.getDeclaredField(name);
+ destField.setAccessible(true);
+ sourceField.setAccessible(true);
+
+ Object sourceObj = sourceField.get(source);
+
+ if (sourceObj instanceof Map) {
+ Object destObj = destField.get(dest);
+ if (destObj == null) {
+ destField.set(dest, sourceObj);
+ } else if (destObj instanceof Map) {
+ @SuppressWarnings("unchecked")
+ Map sourceMap = (Map) sourceObj;
+ @SuppressWarnings("unchecked")
+ Map destMap = (Map) destObj;
+
+ destMap.clear();
+ Iterator> entries = sourceMap.entrySet().iterator();
+ while (entries.hasNext()) {
+ Map.Entry, ?> entry = (Map.Entry, ?>)entries.next();
+ Object key = entry.getKey();
+ Object value = entry.getValue();
+ destMap.put(key, value);
+ }
+
+ } else {
+ logger.error("Incompatible field type! '{}'", name);
+ }
+ } else {
+ destField.set(dest, sourceObj);
+ }
+ } catch (Exception e) {
+ logger.error("Unable to copy field: {}", name, e);
+ }
+ }
+ }
+
+ public static void shutdown() {
+ synchronized(storages) {
+ storages.clear();
+ }
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/Sys.java b/Dorkbox-Util/src/dorkbox/util/Sys.java
new file mode 100644
index 0000000..02fcf30
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/Sys.java
@@ -0,0 +1,657 @@
+package dorkbox.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.bouncycastle.crypto.digests.SHA256Digest;
+
+public class Sys {
+ public static final int KILOBYTE = 1024;
+ public static final int MEGABYTE = 1024 * KILOBYTE;
+ public static final int GIGABYTE = 1024 * MEGABYTE;
+ public static final long TERABYTE = 1024L * GIGABYTE;
+
+ public static char[] HEX_CHARS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ public static final char[] convertStringToChars(String string) {
+ char[] charArray = string.toCharArray();
+
+ eraseString(string);
+
+ return charArray;
+ }
+
+
+ public static final void eraseString(String string) {
+// You can change the value of the inner char[] using reflection.
+//
+// You must be careful to either change it with an array of the same length,
+// or to also update the count field.
+//
+// If you want to be able to use it as an entry in a set or as a value in map,
+// you will need to recalculate the hash code and set the value of the hashCode field.
+
+ try {
+ Field valueField = String.class.getDeclaredField("value");
+ valueField.setAccessible(true);
+ char[] chars = (char[]) valueField.get(string);
+ Arrays.fill(chars, '*'); // asterisk it out in case of GC not picking up the old char array.
+
+ valueField.set(string, new char[0]); // replace it.
+
+ // set count to 0
+ Field countField = String.class.getDeclaredField("count");
+ countField.setAccessible(true);
+ countField.set(string, 0);
+
+ // set hash to 0
+ Field hashField = String.class.getDeclaredField("hash");
+ hashField.setAccessible(true);
+ hashField.set(string, 0);
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static String getSizePretty(final long size) {
+ if (size > TERABYTE) {
+ return String.format("%2.2fTB", (float) size / TERABYTE);
+ }
+ if (size > GIGABYTE) {
+ return String.format("%2.2fGB", (float) size / GIGABYTE);
+ }
+ if (size > MEGABYTE) {
+ return String.format("%2.2fMB", (float) size / MEGABYTE);
+ }
+ if (size > KILOBYTE) {
+ return String.format("%2.2fKB", (float) size / KILOBYTE);
+ }
+
+ return String.valueOf(size) + "B";
+ }
+
+ /**
+ * Convenient close for a stream.
+ */
+ public static void close(InputStream inputStream) {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException ioe) {
+ System.err.println("Error closing the input stream:" + inputStream);
+ ioe.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Convenient close for a stream.
+ */
+ public static void close(OutputStream outputStream) {
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException ioe) {
+ System.err.println("Error closing the output stream:" + outputStream);
+ ioe.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Convenient close for a Reader.
+ */
+ public static void close(Reader inputReader) {
+ if (inputReader != null) {
+ try {
+ inputReader.close();
+ } catch (IOException ioe) {
+ System.err.println("Error closing input reader: " + inputReader);
+ ioe.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Convenient close for a Writer.
+ */
+ public static void close(Writer outputWriter) {
+ if (outputWriter != null) {
+ try {
+ outputWriter.close();
+ } catch (IOException ioe) {
+ System.err.println("Error closing output writer: " + outputWriter);
+ ioe.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of the input stream to the output stream.
+ *
+ * DOES NOT CLOSE THE STEAMS!
+ */
+ public static T copyStream(InputStream inputStream, T outputStream) throws IOException {
+ byte[] buffer = new byte[4096];
+ int read = 0;
+ while ((read = inputStream.read(buffer)) > 0) {
+ outputStream.write(buffer, 0, read);
+ }
+
+ return outputStream;
+ }
+
+ /**
+ * Convert the contents of the input stream to a byte array.
+ */
+ public static byte[] getBytesFromStream(InputStream inputStream) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
+
+ byte[] buffer = new byte[4096];
+ int read = 0;
+ while ((read = inputStream.read(buffer)) > 0) {
+ baos.write(buffer, 0, read);
+ }
+ baos.flush();
+ inputStream.close();
+
+ return baos.toByteArray();
+ }
+
+ public static final byte[] arrayCloneBytes(byte[] src) {
+ return arrayCloneBytes(src, 0);
+ }
+
+ public static final byte[] arrayCloneBytes(byte[] src, int position) {
+ int length = src.length - position;
+
+ byte[] b = new byte[length];
+ System.arraycopy(src, position, b, 0, length);
+ return b;
+ }
+
+ public static final byte[] concatBytes(byte[]... arrayBytes) {
+ int length = 0;
+ for (byte[] bytes : arrayBytes) {
+ length += bytes.length;
+ }
+
+ byte[] concatBytes = new byte[length];
+
+ length = 0;
+ for (byte[] bytes : arrayBytes) {
+ System.arraycopy(bytes, 0, concatBytes, length, bytes.length);
+ length += bytes.length;
+ }
+
+ return concatBytes;
+ }
+
+ /** gets the SHA256 hash + SALT of the specified username, as UTF-16 */
+ public static final byte[] getSha256WithSalt(String username, byte[] saltBytes) {
+ if (username == null) {
+ return null;
+ }
+
+ byte[] charToBytes = Sys.charToBytes(username.toCharArray());
+ byte[] userNameWithSalt = Sys.concatBytes(charToBytes, saltBytes);
+
+
+ SHA256Digest sha256 = new SHA256Digest();
+ byte[] usernameHashBytes = new byte[sha256.getDigestSize()];
+ sha256.update(userNameWithSalt, 0, userNameWithSalt.length);
+ sha256.doFinal(usernameHashBytes, 0);
+
+ return usernameHashBytes;
+ }
+
+ /** gets the SHA256 hash of the specified string, as UTF-16 */
+ public static final byte[] getSha256(String string) {
+ byte[] charToBytes = Sys.charToBytes(string.toCharArray());
+
+ SHA256Digest sha256 = new SHA256Digest();
+ byte[] usernameHashBytes = new byte[sha256.getDigestSize()];
+ sha256.update(charToBytes, 0, charToBytes.length);
+ sha256.doFinal(usernameHashBytes, 0);
+
+ return usernameHashBytes;
+ }
+
+ /** gets the SHA256 hash of the specified byte array */
+ public static final byte[] getSha256(byte[] bytes) {
+
+ SHA256Digest sha256 = new SHA256Digest();
+ byte[] hashBytes = new byte[sha256.getDigestSize()];
+ sha256.update(bytes, 0, bytes.length);
+ sha256.doFinal(hashBytes, 0);
+
+ return hashBytes;
+ }
+
+ /** this saves the char array in UTF-16 format of bytes */
+ public static final byte[] charToBytes(char[] text) {
+ // NOTE: this saves the char array in UTF-16 format of bytes.
+ byte[] bytes = new byte[text.length*2];
+ for(int i=0; i>8);
+ bytes[2*i+1] = (byte) (text[i] & 0x00FF);
+ }
+
+ return bytes;
+ }
+
+
+ public static final byte[] intsToBytes(int[] ints) {
+ int length = ints.length;
+ byte[] bytes = new byte[length];
+
+ for (int i = 0; i < length; i++) {
+ int intValue = ints[i];
+ if (intValue < 0 || intValue > 255) {
+ System.err.println("WARNING: int at index " + i + "(" + intValue + ") was not a valid byte value (0-255)");
+ return new byte[length];
+ }
+
+ bytes[i] = (byte)intValue;
+ }
+
+ return bytes;
+ }
+
+ public static final int[] bytesToInts(byte[] bytes) {
+ int length = bytes.length;
+ int[] ints = new int[length];
+
+ for (int i = 0; i < length; i++) {
+ ints[i] = bytes[i] & 0xFF;
+ }
+
+ return ints;
+ }
+
+ public static final String bytesToHex(byte[] bytes) {
+ return bytesToHex(bytes, false);
+ }
+
+ public static final String bytesToHex(byte[] bytes, boolean padding) {
+ if (padding) {
+ char[] hexString = new char[3 * bytes.length];
+ int j = 0;
+
+ for (int i = 0; i < bytes.length; i++) {
+ hexString[j++] = HEX_CHARS[(bytes[i] & 0xF0) >> 4];
+ hexString[j++] = HEX_CHARS[bytes[i] & 0x0F];
+ hexString[j++] = ' ';
+ }
+
+ return new String(hexString);
+ } else {
+ char[] hexString = new char[2 * bytes.length];
+ int j = 0;
+
+ for (int i = 0; i < bytes.length; i++) {
+ hexString[j++] = HEX_CHARS[(bytes[i] & 0xF0) >> 4];
+ hexString[j++] = HEX_CHARS[bytes[i] & 0x0F];
+ }
+
+ return new String(hexString);
+ }
+ }
+
+ /**
+ * Converts an ASCII character representing a hexadecimal
+ * value into its integer equivalent.
+ */
+ public static final int hexByteToInt(byte b) {
+ switch (b) {
+ case '0' :
+ return 0;
+ case '1' :
+ return 1;
+ case '2' :
+ return 2;
+ case '3' :
+ return 3;
+ case '4' :
+ return 4;
+ case '5' :
+ return 5;
+ case '6' :
+ return 6;
+ case '7' :
+ return 7;
+ case '8' :
+ return 8;
+ case '9' :
+ return 9;
+ case 'A' :
+ case 'a' :
+ return 10;
+ case 'B' :
+ case 'b' :
+ return 11;
+ case 'C' :
+ case 'c' :
+ return 12;
+ case 'D' :
+ case 'd' :
+ return 13;
+ case 'E' :
+ case 'e' :
+ return 14;
+ case 'F' :
+ case 'f' :
+ return 15;
+ default :
+ throw new IllegalArgumentException("Error decoding byte");
+ }
+ }
+
+ /**
+ * A 4-digit hex result.
+ */
+ public static final void hex4(char c, StringBuilder sb) {
+ sb.append(HEX_CHARS[(c & 0xF000) >> 12]);
+ sb.append(HEX_CHARS[(c & 0x0F00) >> 8]);
+ sb.append(HEX_CHARS[(c & 0x00F0) >> 4]);
+ sb.append(HEX_CHARS[c & 0x000F]);
+ }
+
+ /**
+ * Returns a string representation of the byte array as a series of
+ * hexadecimal characters.
+ *
+ * @param bytes
+ * byte array to convert
+ * @return a string representation of the byte array as a series of
+ * hexadecimal characters
+ */
+ public static final String toHexString(byte[] bytes) {
+ char[] hexString = new char[2 * bytes.length];
+ int j = 0;
+
+ for (int i = 0; i < bytes.length; i++) {
+ hexString[j++] = HEX_CHARS[(bytes[i] & 0xF0) >> 4];
+ hexString[j++] = HEX_CHARS[bytes[i] & 0x0F];
+ }
+
+ return new String(hexString);
+ }
+
+ /**
+ * XOR two byte arrays together, and save result in originalArray
+ *
+ * @param originalArray this is the base of the XOR operation.
+ * @param keyArray this is XOR'd into the original array, repeats if necessary.
+ */
+ public static void xorArrays(byte[] originalArray, byte[] keyArray) {
+ int keyIndex = 0;
+ int keyLength = keyArray.length;
+
+ for (int i=0;i array) {
+ int length = 0;
+ for (String s : array) {
+ byte[] bytes = s.getBytes();
+ if (bytes != null) {
+ length += bytes.length;
+ }
+ }
+
+ if (length == 0) {
+ return new byte[0];
+ }
+
+ byte[] bytes = new byte[length+array.size()];
+
+ length = 0;
+ for (String s : array) {
+ byte[] sBytes = s.getBytes();
+ System.arraycopy(sBytes, 0, bytes, length, sBytes.length);
+ length += sBytes.length;
+ bytes[length++] = (byte) 0x01;
+ }
+
+ return bytes;
+ }
+
+ public static final ArrayList decodeStringArray(byte[] bytes) {
+ int length = bytes.length;
+ int position = 0;
+ byte token = (byte) 0x01;
+ ArrayList list = new ArrayList(0);
+
+ int last = 0;
+ while (last+position < length) {
+ byte b = bytes[last+position++];
+ if (b == token ) {
+ byte[] xx = new byte[position-1];
+ System.arraycopy(bytes, last, xx, 0, position-1);
+ list.add(new String(xx));
+ last += position;
+ position = 0;
+ }
+
+ }
+
+ return list;
+ }
+
+ public static String printArrayRaw(byte[] bytes) {
+ return printArrayRaw(bytes, 0);
+ }
+
+ public static String printArrayRaw(byte[] bytes, int lineLength) {
+ if (lineLength > 0) {
+ int mod = lineLength;
+ int length = bytes.length;
+ int comma = length-1;
+
+ StringBuilder builder = new StringBuilder(length + length/mod);
+ for (int i = 0; i < length; i++) {
+ builder.append(bytes[i]);
+ if (i < comma) {
+ builder.append(",");
+ }
+ if (i > 0 && i%mod == 0) {
+ builder.append(OS.LINE_SEPARATOR);
+ }
+ }
+
+ return builder.toString();
+
+ } else {
+ int length = bytes.length;
+ int comma = length-1;
+
+ StringBuilder builder = new StringBuilder(length + length);
+ for (int i = 0; i < length; i++) {
+ builder.append(bytes[i]);
+ if (i < comma) {
+ builder.append(",");
+ }
+ }
+
+ return builder.toString();
+ }
+ }
+
+ public static void printArray(byte[] bytes) {
+ printArray(bytes, bytes.length, true);
+ }
+
+ public static void printArray(byte[] bytes, int length, boolean includeByteCount) {
+ if (includeByteCount) {
+ System.err.println("Bytes: " + length);
+ }
+
+ int mod = 40;
+ int comma = length-1;
+
+ StringBuilder builder = new StringBuilder(length + length/mod);
+ for (int i = 0; i < length; i++) {
+ builder.append(bytes[i]);
+ if (i < comma) {
+ builder.append(",");
+ }
+ if (i > 0 && i%mod == 0) {
+ builder.append(OS.LINE_SEPARATOR);
+ }
+ }
+
+ System.err.println(builder.toString());
+ }
+
+ /**
+ * Finds a list of classes that are annotated with the specified annotation.
+ */
+ public static final List> findAnnotatedClasses(Class extends Annotation> annotation) {
+ return findAnnotatedClasses("", annotation);
+ }
+
+ /**
+ * Finds a list of classes in the specific package that are annotated with the specified annotation.
+ */
+ public static final List> findAnnotatedClasses(String packageName, Class extends Annotation> annotation) {
+ // find ALL ServerLoader classes and use reflection to load them.
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+ if (packageName != null && !packageName.isEmpty()) {
+ packageName = packageName.replace('.', '/');
+ }
+
+ // look for all annotated classes in the projects package.
+ try {
+ LinkedList> classesThatWereAnnotated = new LinkedList>();
+
+ URL url;
+ Enumeration resources = classLoader.getResources(packageName);
+
+ // this means we want to search EVERYTHING
+ if (packageName == null || packageName.isEmpty()) {
+ // lengthy, but it will traverse how we want.
+ while (resources.hasMoreElements()) {
+ url = resources.nextElement();
+
+ // go through and look at the subdirs there. run a package search on THOSE.
+ File urlFile = new File(url.getPath());
+ File[] listFiles = urlFile.listFiles();
+ if (listFiles != null) {
+ for (File file : listFiles) {
+ if (file.isDirectory()) {
+ findSubClasses(classLoader, null, annotation, file, urlFile.getAbsolutePath(), classesThatWereAnnotated);
+ }
+ }
+ }
+ }
+ } else {
+ // lengthy, but it will traverse how we want.
+ while (resources.hasMoreElements()) {
+ url = resources.nextElement();
+ String externalForm = url.toExternalForm();
+
+ if (externalForm.charAt(externalForm.length()-1) != '/') {
+ if (url.getProtocol().equals("file")) {
+ File directory = new File(url.getFile()).getAbsoluteFile();
+ findSubClasses(classLoader, packageName, annotation, directory, directory.getParent(), classesThatWereAnnotated);
+ }
+ }
+ }
+ }
+
+ return classesThatWereAnnotated;
+ } catch (Exception e) {
+ System.err.println("Problem registering build classes. ABORTING!");
+ System.exit(-1);
+ }
+
+ return null;
+ }
+
+ private static final void findSubClasses(ClassLoader classLoader, String packageName,
+ Class extends Annotation> annotation, File directory,
+ String rootPath,
+ List> classesThatWereAnnotated) throws ClassNotFoundException {
+
+ File[] files = directory.listFiles();
+
+ if (files != null) {
+ for (File file : files) {
+ String absolutePath = file.getAbsolutePath();
+ String fileName = file.getName();
+
+ if (file.isDirectory()) {
+ findSubClasses(classLoader, packageName , annotation, file, rootPath, classesThatWereAnnotated);
+ }
+ else if (isValid(fileName)) {
+ String classPath = absolutePath.substring(rootPath.length() + 1, absolutePath.length() - 6);
+
+ if (packageName != null) {
+ if (!classPath.startsWith(packageName)) {
+ return;
+ }
+ }
+
+ String toDots = classPath.replaceAll(File.separator, ".");
+
+ Class> clazz = Class.forName(toDots, false, classLoader);
+ if (clazz.getAnnotation(annotation) != null) {
+ classesThatWereAnnotated.add(clazz);
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * remove directories from the search. make sure it's a class file shortcut so we don't load ALL .class files!
+ *
+ **/
+ private static boolean isValid(String name) {
+
+ if (name == null) {
+ return false;
+ }
+
+ int length = name.length();
+ boolean isValid = length > 6 &&
+ name.charAt(length-1) != '/' && // remove directories from the search.
+ name.charAt(length-6) == '.' &&
+ name.charAt(length-5) == 'c' &&
+ name.charAt(length-4) == 'l' &&
+ name.charAt(length-3) == 'a' &&
+ name.charAt(length-2) == 's' &&
+ name.charAt(length-1) == 's'; // make sure it's a class file
+
+
+
+ return isValid;
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/BigEndian.java b/Dorkbox-Util/src/dorkbox/util/bytes/BigEndian.java
new file mode 100644
index 0000000..463c1bf
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/bytes/BigEndian.java
@@ -0,0 +1,120 @@
+package dorkbox.util.bytes;
+
+import java.nio.ByteBuffer;
+
+public class BigEndian {
+ // the following are ALL in Big-Endian (big is to the left, first byte is most significant, unsigned bytes)
+
+ /** SHORT to and from bytes */
+ public static class Short_ {
+ public static final short fromBytes(byte[] bytes) {
+ return fromBytes(bytes[0], bytes[1]);
+ }
+
+ public static final short fromBytes(byte b0, byte b1) {
+ return (short) ((b0 & 0xFF) << 8 |
+ (b1 & 0xFF) << 0);
+ }
+
+
+ public static final byte[] toBytes(short x) {
+ return new byte[] {(byte) (x >> 8),
+ (byte) (x >> 0)
+ };
+ }
+
+ public static final int fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get());
+ }
+ }
+
+ /** CHAR to and from bytes */
+ public static class Char_ {
+ public static final char fromBytes(byte[] bytes) {
+ return fromBytes(bytes[0], bytes[1]);
+ }
+
+ public static final char fromBytes(byte b0, byte b1) {
+ return (char) ((b0 & 0xFF) << 8 |
+ (b1 & 0xFF) << 0);
+ }
+
+
+ public static final byte[] toBytes(char x) {
+ return new byte[] {(byte) (x >> 8),
+ (byte) (x >> 0)
+ };
+ }
+
+ public static final int fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get());
+ }
+ }
+
+
+ /** INT to and from bytes */
+ public static class Int_ {
+ public static final int fromBytes(byte[] bytes) {
+ return fromBytes(bytes[0], bytes[1], bytes[2], bytes[3]);
+ }
+
+ public static final int fromBytes(byte b0, byte b1, byte b2, byte b3) {
+ return (b0 & 0xFF) << 24 |
+ (b1 & 0xFF) << 16 |
+ (b2 & 0xFF) << 8 |
+ (b3 & 0xFF) << 0;
+ }
+
+ public static int fromBytes(byte b0, byte b1) {
+ return (b0 & 0xFF) << 24 |
+ (b1 & 0xFF) << 16;
+ }
+
+ public static final byte[] toBytes(int x) {
+ return new byte[] {(byte) (x >> 24),
+ (byte) (x >> 16),
+ (byte) (x >> 8),
+ (byte) (x >> 0)
+ } ;
+ }
+
+ public static final int fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get(), buff.get(), buff.get());
+ }
+ }
+
+ /** LONG to and from bytes */
+ public static class Long_ {
+ public static final long fromBytes(byte[] bytes) {
+ return fromBytes(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]);
+ }
+
+ public static final long fromBytes(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7) {
+ return (long) (b0 & 0xFF) << 56 |
+ (long) (b1 & 0xFF) << 48 |
+ (long) (b2 & 0xFF) << 40 |
+ (long) (b3 & 0xFF) << 32 |
+ (long) (b4 & 0xFF) << 24 |
+ (long) (b5 & 0xFF) << 16 |
+ (long) (b6 & 0xFF) << 8 |
+ (long) (b7 & 0xFF) << 0;
+ }
+
+ public static final byte[] toBytes (long x) {
+ return new byte[] {(byte) (x >> 56),
+ (byte) (x >> 48),
+ (byte) (x >> 40),
+ (byte) (x >> 32),
+ (byte) (x >> 24),
+ (byte) (x >> 16),
+ (byte) (x >> 8),
+ (byte) (x >> 0),
+ };
+ }
+
+ public static final long fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get());
+ }
+ }
+}
+
diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2.java b/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2.java
new file mode 100644
index 0000000..5f31b8c
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2.java
@@ -0,0 +1,318 @@
+package dorkbox.util.bytes;
+
+import java.nio.BufferUnderflowException;
+
+/**
+ * Cleanroom implementation of a self-growing bytebuffer
+ */
+public class ByteBuffer2 {
+
+ private byte[] bytes;
+
+ private int position = 0;
+ private int mark = -1;
+ private int limit = 0;
+
+ public static ByteBuffer2 wrap(byte[] buffer) {
+ return new ByteBuffer2(buffer);
+ }
+
+ public static ByteBuffer2 allocate(int capacity) {
+ ByteBuffer2 byteBuffer2 = new ByteBuffer2(new byte[capacity]);
+ byteBuffer2.clear();
+ return byteBuffer2;
+ }
+
+ public ByteBuffer2() {
+ this(0);
+ }
+
+ public ByteBuffer2(int size) {
+ this(new byte[size]);
+ }
+
+ public ByteBuffer2(byte[] bytes) {
+ this.bytes = bytes;
+ clear();
+ position = bytes.length;
+ }
+
+ public byte getByte() {
+ if (position > limit) {
+ throw new BufferUnderflowException();
+ }
+
+ return bytes[position++];
+ }
+
+ public byte getByte(int i) {
+ if (i > limit) {
+ throw new BufferUnderflowException();
+ }
+
+ return bytes[i];
+ }
+
+ public void getBytes(byte[] buffer) {
+ getBytes(buffer, 0, buffer.length);
+ }
+
+ public void getBytes(byte[] buffer, int length) {
+ getBytes(buffer, 0, length);
+ }
+
+ public void getBytes(byte[] buffer, int offset, int length) {
+ if (position + length - offset > limit) {
+ throw new BufferUnderflowException();
+ }
+
+ System.arraycopy(bytes, position, buffer, 0, length-offset);
+ position += length-offset;
+ }
+
+ /**
+ * MUST call checkBuffer before calling this!
+ *
+ * NOT PROTECTED
+ */
+ private final void _put(byte b) {
+ bytes[position++] = b;
+ }
+
+ /** NOT PROTECTED! */
+ private final void checkBuffer(int threshold) {
+ if (bytes.length < threshold) {
+ byte[] t = new byte[threshold];
+ // grow at back of array
+ System.arraycopy(bytes, 0, t, 0, bytes.length);
+ limit = t.length;
+
+ bytes = t;
+ }
+ }
+
+ public final synchronized void put(ByteBuffer2 buffer) {
+ putBytes(buffer.array(), buffer.position, buffer.limit);
+ buffer.position = buffer.limit;
+ }
+
+ public final synchronized ByteBuffer2 putBytes(byte[] src) {
+ return putBytes(src, 0, src.length);
+ }
+
+ public final synchronized ByteBuffer2 putBytes(byte[] src, int offset, int length) {
+ checkBuffer(position + length - offset);
+
+ System.arraycopy(src, offset, bytes, position, length);
+ position += length;
+
+ return this;
+ }
+
+ public final synchronized ByteBuffer2 putByte(byte b) {
+ checkBuffer(position + 1);
+
+ _put(b);
+ return this;
+ }
+
+ public final synchronized void putByte(int position, byte b) {
+ this.position = position;
+ putByte(b);
+ }
+
+ public final synchronized void putChar(char c) {
+ checkBuffer(position + 2);
+
+ putBytes(BigEndian.Char_.toBytes(c));
+ }
+
+ public final synchronized char getChar() {
+ return BigEndian.Char_.fromBytes(getByte(), getByte());
+ }
+
+ public final synchronized ByteBuffer2 putShort(short x) {
+ checkBuffer(position + 2);
+
+ putBytes(BigEndian.Short_.toBytes(x));
+
+ return this;
+ }
+
+ public final synchronized short getShort() {
+ return BigEndian.Short_.fromBytes(getByte(), getByte());
+ }
+
+ public final synchronized void putInt(int x) {
+ checkBuffer(position + 4);
+
+ putBytes(BigEndian.Int_.toBytes(x));
+ }
+
+ public final synchronized int getInt() {
+ byte b3 = getByte();
+ byte b2 = getByte();
+ byte b1 = getByte();
+ return BigEndian.Int_.fromBytes(getByte(), b1, b2, b3);
+ }
+
+ public final synchronized void putLong(long x) {
+ checkBuffer(position + 8);
+
+ putBytes(BigEndian.Long_.toBytes(x));
+ }
+
+ public final synchronized long getLong() {
+ return BigEndian.Long_.fromBytes(getByte(), getByte(), getByte(), getByte(), getByte(), getByte(), getByte(), getByte());
+ }
+
+ /**
+ * Returns the backing array of this buffer
+ */
+ public byte[] array() {
+ return bytes;
+ }
+
+ /**
+ * Returns a copy of the backing array of this buffer
+ */
+ public final synchronized byte[] arrayCopy() {
+ int length = bytes.length - position;
+
+ byte[] b = new byte[length];
+ System.arraycopy(bytes, position, b, 0, length);
+ return b;
+ }
+
+ /**
+ * Returns this buffer's position.
+ */
+ public int position() {
+ return position;
+ }
+
+ /**
+ * Sets this buffer's position.
+ */
+ public final synchronized ByteBuffer2 position(int position) {
+ if (position > bytes.length || position < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ this.position = position;
+ if (mark > position) {
+ mark = -1;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns the number of elements between the current position and the
+ * limit.
+ */
+ public final synchronized int remaining() {
+ return limit - position;
+ }
+
+ /**
+ * Tells whether there are any elements between the current position and
+ * the limit.
+ */
+ public final synchronized boolean hasRemaining() {
+ return position < limit;
+ }
+
+ /**
+ * Sets this buffer's limit.
+ */
+ public final synchronized void limit(int limit) {
+ this.limit = limit;
+ if (position > limit) {
+ position = limit;
+ }
+ if (mark > limit) {
+ mark = -1;
+ }
+ }
+
+ /**
+ * Returns this buffer's limit.
+ */
+ public int limit() {
+ return limit;
+ }
+
+ /**
+ * Returns this buffer's capacity.
+ */
+ public int capacity() {
+ return bytes.length;
+ }
+
+ /**
+ * The bytes between the buffer's current position and its limit, if any, are copied to the beginning of the buffer.
+ * That is, the byte at index p = position() is copied to index zero, the byte at index p + 1 is copied to index one,
+ * and so forth until the byte at index limit() - 1 is copied to index n = limit() - 1 - p. The buffer's position is
+ * then set to n+1 and its limit is set to its capacity. The mark, if defined, is discarded.
+ *
+ * The buffer's position is set to the number of bytes copied, rather than to zero, so that an invocation of this method
+ * can be followed immediately by an invocation of another relative put method.
+ */
+ public final synchronized void compact() {
+ mark = -1;
+ System.arraycopy(bytes, position, bytes, 0, remaining());
+
+ position(remaining());
+ limit(capacity());
+ }
+
+ /**
+ * Readies the buffer for reading.
+ *
+ * Flips this buffer. The limit is set to the current position and then
+ * the position is set to zero. If the mark is defined then it is
+ * discarded.
+ */
+ public final synchronized void flip() {
+ limit = position;
+ position = 0;
+ mark = -1;
+ }
+
+ /**
+ * Clears this buffer. The position is set to zero, the limit is set to
+ * the capacity, and the mark is discarded.
+ */
+ public final synchronized void clear() {
+ position = 0;
+ limit = capacity();
+ mark = -1;
+ }
+
+ /**
+ * Rewinds this buffer. The position is set to zero and the mark is
+ * discarded.
+ */
+ public final synchronized void rewind() {
+ position = 0;
+ mark = -1;
+ }
+
+ /**
+ * Sets this buffer's mark at its position.
+ */
+ public final synchronized void mark() {
+ mark = position;
+ }
+
+ /**
+ * Resets this buffer's position to the previously-marked position.
+ *
+ * Invoking this method neither changes nor discards the mark's
+ * value.
+ */
+ public void reset() {
+ position = mark;
+ }
+}
\ No newline at end of file
diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/LittleEndian.java b/Dorkbox-Util/src/dorkbox/util/bytes/LittleEndian.java
new file mode 100644
index 0000000..8666462
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/bytes/LittleEndian.java
@@ -0,0 +1,345 @@
+package dorkbox.util.bytes;
+
+import java.nio.ByteBuffer;
+
+public class LittleEndian {
+ // the following are ALL in Little-Endian (big is to the right, first byte is least significant, unsigned bytes)
+
+ /** CHAR to and from bytes */
+ public static class Char_ {
+ @SuppressWarnings("fallthrough")
+ public static final char fromBytes(byte[] bytes) {
+ char number = 0;
+
+ switch (bytes.length) {
+ case 2: number += (bytes[1] & 0xFF) << 8;
+ case 1: number += (bytes[0] & 0xFF) << 0;
+ }
+
+ return number;
+ }
+
+ public static final char fromBytes(byte b0, byte b1) {
+ return (char) ((b1 & 0xFF) << 8 |
+ (b0 & 0xFF) << 0);
+ }
+
+
+ public static final byte[] toBytes(char x) {
+ return new byte[] {(byte) (x >> 8),
+ (byte) (x >> 0)
+ };
+ }
+
+ public static final int fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get());
+ }
+ }
+
+ /** CHAR to and from bytes */
+ public static class UChar_ {
+ @SuppressWarnings("fallthrough")
+ public static final char fromBytes(byte[] bytes) {
+ char number = 0;
+
+ switch (bytes.length) {
+ case 2: number += (bytes[1] & 0xFF) << 8;
+ case 1: number += (bytes[0] & 0xFF) << 0;
+ }
+
+ return number;
+ }
+
+ public static final char fromBytes(byte b0, byte b1) {
+ return (char) ((b1 & 0xFF) << 8 |
+ (b0 & 0xFF) << 0);
+ }
+
+
+ public static final byte[] toBytes(char x) {
+ return new byte[] {(byte) (x >> 8),
+ (byte) (x >> 0)
+ };
+ }
+
+ public static final int fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get());
+ }
+ }
+
+ /** SHORT to and from bytes */
+ public static class Short_ {
+ @SuppressWarnings("fallthrough")
+ public static final short fromBytes(byte[] bytes) {
+ short number = 0;
+
+ switch (bytes.length) {
+ case 2: number += (bytes[1] & 0xFF) << 8;
+ case 1: number += (bytes[0] & 0xFF) << 0;
+ }
+
+ return number;
+ }
+
+ public static final short fromBytes(byte b0, byte b1) {
+ return (short) ((b1 & 0xFF) << 8 |
+ (b0 & 0xFF) << 0);
+ }
+
+
+ public static final byte[] toBytes(short x) {
+ return new byte[] {(byte) (x >> 0),
+ (byte) (x >> 8)
+ };
+ }
+
+ public static final int fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get());
+ }
+ }
+
+ /** SHORT to and from bytes */
+ public static class UShort_ {
+ @SuppressWarnings("fallthrough")
+ public static final short fromBytes(byte[] bytes) {
+ short number = 0;
+
+ switch (bytes.length) {
+ case 2: number += (bytes[1] & 0xFF) << 8;
+ case 1: number += (bytes[0] & 0xFF) << 0;
+ }
+
+ return number;
+ }
+
+ @SuppressWarnings("fallthrough")
+ public static short fromBytes(byte[] bytes, int offset, int bytenum) {
+ short number = 0;
+
+ switch (bytenum) {
+ case 2: number += (bytes[offset+1] & 0xFF) << 8;
+ case 1: number += (bytes[offset+0] & 0xFF) << 0;
+ }
+
+ return number;
+ }
+
+ public static final short fromBytes(byte b0, byte b1) {
+ return (short) ((b1 & 0xFF) << 8 |
+ (b0 & 0xFF) << 0);
+ }
+
+
+ public static final byte[] toBytes(short x) {
+ return new byte[] {(byte) (x >> 0),
+ (byte) (x >> 8)
+ };
+ }
+
+ public static final int fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get());
+ }
+ }
+
+ /** INT to and from bytes */
+ public static class Int_ {
+ @SuppressWarnings("fallthrough")
+ public static final int fromBytes(byte[] bytes) {
+ int number = 0;
+
+ switch (bytes.length) {
+ case 4: number += (bytes[3] & 0xFF) << 24;
+ case 3: number += (bytes[2] & 0xFF) << 16;
+ case 2: number += (bytes[1] & 0xFF) << 8;
+ case 1: number += (bytes[0] & 0xFF) << 0;
+ }
+
+ return number;
+ }
+
+ public static final int fromBytes(byte b0, byte b1, byte b2, byte b3) {
+ return (b3 & 0xFF) << 24 |
+ (b2 & 0xFF) << 16 |
+ (b1 & 0xFF) << 8 |
+ (b0 & 0xFF) << 0;
+ }
+
+ public static final byte[] toBytes(int x) {
+ return new byte[] {(byte) (x >> 0),
+ (byte) (x >> 8),
+ (byte) (x >> 16),
+ (byte) (x >> 24)
+ } ;
+ }
+
+ public static final int fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get(), buff.get(), buff.get());
+ }
+ }
+
+ /** INT to and from bytes */
+ public static class UInt_ {
+ @SuppressWarnings("fallthrough")
+ public static final int fromBytes(byte[] bytes) {
+ int number = 0;
+
+ switch (bytes.length) {
+ case 4: number += (bytes[3] & 0xFF) << 24;
+ case 3: number += (bytes[2] & 0xFF) << 16;
+ case 2: number += (bytes[1] & 0xFF) << 8;
+ case 1: number += (bytes[0] & 0xFF) << 0;
+ }
+
+ return number;
+ }
+
+ @SuppressWarnings("fallthrough")
+ public static int fromBytes(byte[] bytes, int offset, int bytenum) {
+ int number = 0;
+
+ switch (bytenum) {
+ case 4: number += (bytes[offset+3] & 0xFF) << 24;
+ case 3: number += (bytes[offset+2] & 0xFF) << 16;
+ case 2: number += (bytes[offset+1] & 0xFF) << 8;
+ case 1: number += (bytes[offset+0] & 0xFF) << 0;
+ }
+
+ return number;
+ }
+
+ public static final int fromBytes(byte b0, byte b1, byte b2, byte b3) {
+ return (b3 & 0xFF) << 24 |
+ (b2 & 0xFF) << 16 |
+ (b1 & 0xFF) << 8 |
+ (b0 & 0xFF) << 0;
+ }
+
+ public static final byte[] toBytes(int x) {
+ return new byte[] {(byte) (x >> 0),
+ (byte) (x >> 8),
+ (byte) (x >> 16),
+ (byte) (x >> 24)
+ } ;
+ }
+
+ public static final int fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get(), buff.get(), buff.get());
+ }
+ }
+
+ /** LONG to and from bytes */
+ public static class Long_ {
+
+ @SuppressWarnings("fallthrough")
+ public static final long fromBytes(byte[] bytes) {
+ long number = 0L;
+
+ switch (bytes.length) {
+ case 8: number += (long) (bytes[7] & 0xFF) << 56;
+ case 7: number += (long) (bytes[6] & 0xFF) << 48;
+ case 6: number += (long) (bytes[5] & 0xFF) << 40;
+ case 5: number += (long) (bytes[4] & 0xFF) << 32;
+ case 4: number += (long) (bytes[3] & 0xFF) << 24;
+ case 3: number += (long) (bytes[2] & 0xFF) << 16;
+ case 2: number += (long) (bytes[1] & 0xFF) << 8;
+ case 1: number += (long) (bytes[0] & 0xFF) << 0;
+ }
+
+ return number;
+ }
+
+ public static final long fromBytes(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7) {
+ return (long) (b7 & 0xFF) << 56 |
+ (long) (b6 & 0xFF) << 48 |
+ (long) (b5 & 0xFF) << 40 |
+ (long) (b4 & 0xFF) << 32 |
+ (long) (b3 & 0xFF) << 24 |
+ (long) (b2 & 0xFF) << 16 |
+ (long) (b1 & 0xFF) << 8 |
+ (long) (b0 & 0xFF) << 0;
+ }
+
+ public static final byte[] toBytes (long x) {
+ return new byte[] {(byte) (x >> 0),
+ (byte) (x >> 8),
+ (byte) (x >> 16),
+ (byte) (x >> 24),
+ (byte) (x >> 32),
+ (byte) (x >> 40),
+ (byte) (x >> 48),
+ (byte) (x >> 56),
+ };
+ }
+
+ public static final long fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get());
+ }
+ }
+
+ /** LONG to and from bytes */
+ public static class ULong_ {
+ @SuppressWarnings("fallthrough")
+ public static int fromBytes(byte[] bytes, int offset, int bytenum) {
+ int number = 0;
+
+ switch (bytenum) {
+ case 8: number += (long) (bytes[offset+7] & 0xFF) << 56;
+ case 7: number += (long) (bytes[offset+6] & 0xFF) << 48;
+ case 6: number += (long) (bytes[offset+5] & 0xFF) << 40;
+ case 5: number += (long) (bytes[offset+4] & 0xFF) << 32;
+ case 4: number += (long) (bytes[offset+3] & 0xFF) << 24;
+ case 3: number += (long) (bytes[offset+2] & 0xFF) << 16;
+ case 2: number += (long) (bytes[offset+1] & 0xFF) << 8;
+ case 1: number += (long) (bytes[offset+0] & 0xFF) << 0;
+ }
+
+ return number;
+ }
+
+ @SuppressWarnings("fallthrough")
+ public static final long fromBytes(byte[] bytes) {
+ long number = 0L;
+
+ switch (bytes.length) {
+ case 8: number += (long) (bytes[7] & 0xFF) << 56;
+ case 7: number += (long) (bytes[6] & 0xFF) << 48;
+ case 6: number += (long) (bytes[5] & 0xFF) << 40;
+ case 5: number += (long) (bytes[4] & 0xFF) << 32;
+ case 4: number += (long) (bytes[3] & 0xFF) << 24;
+ case 3: number += (long) (bytes[2] & 0xFF) << 16;
+ case 2: number += (long) (bytes[1] & 0xFF) << 8;
+ case 1: number += (long) (bytes[0] & 0xFF) << 0;
+ }
+
+ return number;
+ }
+
+ public static final long fromBytes(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7) {
+ return (long) (b7 & 0xFF) << 56 |
+ (long) (b6 & 0xFF) << 48 |
+ (long) (b5 & 0xFF) << 40 |
+ (long) (b4 & 0xFF) << 32 |
+ (long) (b3 & 0xFF) << 24 |
+ (long) (b2 & 0xFF) << 16 |
+ (long) (b1 & 0xFF) << 8 |
+ (long) (b0 & 0xFF) << 0;
+ }
+
+ public static final byte[] toBytes (long x) {
+ return new byte[] {(byte) (x >> 0),
+ (byte) (x >> 8),
+ (byte) (x >> 16),
+ (byte) (x >> 24),
+ (byte) (x >> 32),
+ (byte) (x >> 40),
+ (byte) (x >> 48),
+ (byte) (x >> 56),
+ };
+ }
+
+ public static final long fromBytes(ByteBuffer buff) {
+ return fromBytes(buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get());
+ }
+ }
+}
+
diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtils.java b/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtils.java
new file mode 100644
index 0000000..3b6a9ff
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtils.java
@@ -0,0 +1,541 @@
+package dorkbox.util.bytes;
+
+public class OptimizeUtils {
+
+ private static final OptimizeUtils instance = new OptimizeUtils();
+
+ public static OptimizeUtils get() {
+ return instance;
+ }
+
+ // int
+
+ /**
+ * FROM KRYO
+ *
+ * Returns the number of bytes that would be written with {@link #writeInt(int, boolean)}.
+ *
+ * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value
+ */
+ public final int intLength (int value, boolean optimizePositive) {
+ if (!optimizePositive) {
+ value = value << 1 ^ value >> 31;
+ }
+ if (value >>> 7 == 0) {
+ return 1;
+ }
+ if (value >>> 14 == 0) {
+ return 2;
+ }
+ if (value >>> 21 == 0) {
+ return 3;
+ }
+ if (value >>> 28 == 0) {
+ return 4;
+ }
+ return 5;
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * look at buffer, and see if we can read the length of the int off of it. (from the reader index)
+ */
+ public final boolean canReadInt (ByteBuffer2 buffer) {
+ int position = buffer.position();
+ try {
+ int remaining = buffer.remaining();
+ for (int offset = 0; offset < 32 && remaining > 0; offset += 7, remaining--) {
+ int b = buffer.getByte();
+ if ((b & 0x80) == 0) {
+ return true;
+ }
+ }
+ return false;
+ } finally {
+ buffer.position(position);
+ }
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * look at buffer, and see if we can read the length of the int off of it. (from the reader index)
+ *
+ * @return 0 if we could not read anything, >0 for the number of bytes for the int on the buffer
+ */
+ public boolean canReadInt (byte[] buffer) {
+ int length = buffer.length;
+
+ if (length >= 5) {
+ return true;
+ }
+ int p = 0;
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == length) {
+ return false;
+ }
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == length) {
+ return false;
+ }
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == length) {
+ return false;
+ }
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == length) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * Reads an int from the buffer that was optimized.
+ */
+ public final int readInt (ByteBuffer2 buffer, boolean optimizePositive) {
+ int b = buffer.getByte();
+ int result = b & 0x7F;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (b & 0x7F) << 7;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (b & 0x7F) << 14;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (b & 0x7F) << 21;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (b & 0x7F) << 28;
+ }
+ }
+ }
+ }
+ return optimizePositive ? result : result >>> 1 ^ -(result & 1);
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * Reads an int from the buffer that was optimized.
+ */
+ public int readInt (byte[] buffer, boolean optimizePositive) {
+ int position = 0;
+ int b = buffer[position++];
+ int result = b & 0x7F;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (b & 0x7F) << 7;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (b & 0x7F) << 14;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (b & 0x7F) << 21;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (b & 0x7F) << 28;
+ }
+ }
+ }
+ }
+ return optimizePositive ? result : result >>> 1 ^ -(result & 1);
+ }
+
+
+ /**
+ * FROM KRYO
+ *
+ * Writes the specified int to the buffer using 1 to 5 bytes, depending on the size of the number.
+ *
+ * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value
+ * @return the number of bytes written.
+ */
+ public final int writeInt (ByteBuffer2 buffer, int value, boolean optimizePositive) {
+ if (!optimizePositive) {
+ value = value << 1 ^ value >> 31;
+ }
+ if (value >>> 7 == 0) {
+ buffer.putByte((byte)value);
+ return 1;
+ }
+ if (value >>> 14 == 0) {
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7));
+ return 2;
+ }
+ if (value >>> 21 == 0) {
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7 | 0x80));
+ buffer.putByte((byte)(value >>> 14));
+ return 3;
+ }
+ if (value >>> 28 == 0) {
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7 | 0x80));
+ buffer.putByte((byte)(value >>> 14 | 0x80));
+ buffer.putByte((byte)(value >>> 21));
+ return 4;
+ }
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7 | 0x80));
+ buffer.putByte((byte)(value >>> 14 | 0x80));
+ buffer.putByte((byte)(value >>> 21 | 0x80));
+ buffer.putByte((byte)(value >>> 28));
+ return 5;
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * Writes the specified int to the buffer using 1 to 5 bytes, depending on the size of the number.
+ *
+ * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value
+ * @return the number of bytes written.
+ */
+ public int writeInt (byte[] buffer, int value, boolean optimizePositive) {
+ int position = 0;
+ if (!optimizePositive) {
+ value = value << 1 ^ value >> 31;
+ }
+ if (value >>> 7 == 0) {
+ buffer[position++] = (byte)value;
+ return 1;
+ }
+ if (value >>> 14 == 0) {
+ buffer[position++] = (byte)(value & 0x7F | 0x80);
+ buffer[position++] = (byte)(value >>> 7);
+ return 2;
+ }
+ if (value >>> 21 == 0) {
+ buffer[position++] = (byte)(value & 0x7F | 0x80);
+ buffer[position++] = (byte)(value >>> 7 | 0x80);
+ buffer[position++] = (byte)(value >>> 14);
+ return 3;
+ }
+ if (value >>> 28 == 0) {
+ buffer[position++] = (byte)(value & 0x7F | 0x80);
+ buffer[position++] = (byte)(value >>> 7 | 0x80);
+ buffer[position++] = (byte)(value >>> 14 | 0x80);
+ buffer[position++] = (byte)(value >>> 21);
+ return 4;
+ }
+ buffer[position++] = (byte)(value & 0x7F | 0x80);
+ buffer[position++] = (byte)(value >>> 7 | 0x80);
+ buffer[position++] = (byte)(value >>> 14 | 0x80);
+ buffer[position++] = (byte)(value >>> 21 | 0x80);
+ buffer[position++] = (byte)(value >>> 28);
+ return 5;
+ }
+
+
+ // long
+
+ /**
+ * Returns the number of bytes that would be written with {@link #writeLong(long, boolean)}.
+ *
+ * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value
+ */
+ public final int longLength (long value, boolean optimizePositive) {
+ if (!optimizePositive) {
+ value = value << 1 ^ value >> 63;
+ }
+ if (value >>> 7 == 0) {
+ return 1;
+ }
+ if (value >>> 14 == 0) {
+ return 2;
+ }
+ if (value >>> 21 == 0) {
+ return 3;
+ }
+ if (value >>> 28 == 0) {
+ return 4;
+ }
+ if (value >>> 35 == 0) {
+ return 5;
+ }
+ if (value >>> 42 == 0) {
+ return 6;
+ }
+ if (value >>> 49 == 0) {
+ return 7;
+ }
+ if (value >>> 56 == 0) {
+ return 8;
+ }
+ return 9;
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * Reads a 1-9 byte long.
+ *
+ * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value
+ */
+ public final long readLong (ByteBuffer2 buffer, boolean optimizePositive) {
+ int b = buffer.getByte();
+ long result = b & 0x7F;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (b & 0x7F) << 7;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (b & 0x7F) << 14;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (b & 0x7F) << 21;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (long)(b & 0x7F) << 28;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (long)(b & 0x7F) << 35;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (long)(b & 0x7F) << 42;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (long)(b & 0x7F) << 49;
+ if ((b & 0x80) != 0) {
+ b = buffer.getByte();
+ result |= (long)b << 56;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (!optimizePositive) {
+ result = result >>> 1 ^ -(result & 1);
+ }
+ return result;
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * Reads a 1-9 byte long.
+ *
+ * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value
+ */
+ public long readLong (byte[] buffer, boolean optimizePositive) {
+ int position = 0;
+ int b = buffer[position++];
+ long result = b & 0x7F;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (b & 0x7F) << 7;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (b & 0x7F) << 14;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (b & 0x7F) << 21;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (long)(b & 0x7F) << 28;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (long)(b & 0x7F) << 35;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (long)(b & 0x7F) << 42;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (long)(b & 0x7F) << 49;
+ if ((b & 0x80) != 0) {
+ b = buffer[position++];
+ result |= (long)b << 56;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (!optimizePositive) {
+ result = result >>> 1 ^ -(result & 1);
+ }
+ return result;
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * Writes a 1-9 byte long.
+ *
+ * @param optimizePositive If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be
+ * inefficient (9 bytes).
+ * @return the number of bytes written.
+ */
+ public final int writeLong (ByteBuffer2 buffer, long value, boolean optimizePositive) {
+ if (!optimizePositive) {
+ value = value << 1 ^ value >> 63;
+ }
+ if (value >>> 7 == 0) {
+ buffer.putByte((byte)value);
+ return 1;
+ }
+ if (value >>> 14 == 0) {
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7));
+ return 2;
+ }
+ if (value >>> 21 == 0) {
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7 | 0x80));
+ buffer.putByte((byte)(value >>> 14));
+ return 3;
+ }
+ if (value >>> 28 == 0) {
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7 | 0x80));
+ buffer.putByte((byte)(value >>> 14 | 0x80));
+ buffer.putByte((byte)(value >>> 21));
+ return 4;
+ }
+ if (value >>> 35 == 0) {
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7 | 0x80));
+ buffer.putByte((byte)(value >>> 14 | 0x80));
+ buffer.putByte((byte)(value >>> 21 | 0x80));
+ buffer.putByte((byte)(value >>> 28));
+ return 5;
+ }
+ if (value >>> 42 == 0) {
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7 | 0x80));
+ buffer.putByte((byte)(value >>> 14 | 0x80));
+ buffer.putByte((byte)(value >>> 21 | 0x80));
+ buffer.putByte((byte)(value >>> 28 | 0x80));
+ buffer.putByte((byte)(value >>> 35));
+ return 6;
+ }
+ if (value >>> 49 == 0) {
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7 | 0x80));
+ buffer.putByte((byte)(value >>> 14 | 0x80));
+ buffer.putByte((byte)(value >>> 21 | 0x80));
+ buffer.putByte((byte)(value >>> 28 | 0x80));
+ buffer.putByte((byte)(value >>> 35 | 0x80));
+ buffer.putByte((byte)(value >>> 42));
+ return 7;
+ }
+ if (value >>> 56 == 0) {
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7 | 0x80));
+ buffer.putByte((byte)(value >>> 14 | 0x80));
+ buffer.putByte((byte)(value >>> 21 | 0x80));
+ buffer.putByte((byte)(value >>> 28 | 0x80));
+ buffer.putByte((byte)(value >>> 35 | 0x80));
+ buffer.putByte((byte)(value >>> 42 | 0x80));
+ buffer.putByte((byte)(value >>> 49));
+ return 8;
+ }
+ buffer.putByte((byte)(value & 0x7F | 0x80));
+ buffer.putByte((byte)(value >>> 7 | 0x80));
+ buffer.putByte((byte)(value >>> 14 | 0x80));
+ buffer.putByte((byte)(value >>> 21 | 0x80));
+ buffer.putByte((byte)(value >>> 28 | 0x80));
+ buffer.putByte((byte)(value >>> 35 | 0x80));
+ buffer.putByte((byte)(value >>> 42 | 0x80));
+ buffer.putByte((byte)(value >>> 49 | 0x80));
+ buffer.putByte((byte)(value >>> 56));
+ return 9;
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * look at buffer, and see if we can read the length of the long off of it (from the reader index).
+ */
+ public final boolean canReadLong (ByteBuffer2 buffer) {
+ int position = buffer.position();
+ try {
+ int remaining = buffer.remaining();
+ for (int offset = 0; offset < 64 && remaining > 0; offset += 7, remaining--) {
+ int b = buffer.getByte();
+ if ((b & 0x80) == 0) {
+ return true;
+ }
+ }
+ return false;
+ } finally {
+ buffer.position(position);
+ }
+ }
+
+ public boolean canReadLong (byte[] buffer) {
+ int limit = buffer.length;
+
+ if (limit >= 9) {
+ return true;
+ }
+ int p = 0;
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == limit) {
+ return false;
+ }
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == limit) {
+ return false;
+ }
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == limit) {
+ return false;
+ }
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == limit) {
+ return false;
+ }
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == limit) {
+ return false;
+ }
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == limit) {
+ return false;
+ }
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == limit) {
+ return false;
+ }
+ if ((buffer[p++] & 0x80) == 0) {
+ return true;
+ }
+ if (p == limit) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtilsByteBuf.java b/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtilsByteBuf.java
new file mode 100644
index 0000000..6fc6a56
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtilsByteBuf.java
@@ -0,0 +1,297 @@
+package dorkbox.util.bytes;
+
+import io.netty.buffer.ByteBuf;
+
+public class OptimizeUtilsByteBuf {
+
+ private static final OptimizeUtilsByteBuf instance = new OptimizeUtilsByteBuf();
+
+ public static OptimizeUtilsByteBuf get() {
+ return instance;
+ }
+
+ // int
+
+ /**
+ * FROM KRYO
+ *
+ * Returns the number of bytes that would be written with {@link #writeInt(int, boolean)}.
+ *
+ * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value
+ */
+ public final int intLength (int value, boolean optimizePositive) {
+ if (!optimizePositive) value = (value << 1) ^ (value >> 31);
+ if (value >>> 7 == 0) return 1;
+ if (value >>> 14 == 0) return 2;
+ if (value >>> 21 == 0) return 3;
+ if (value >>> 28 == 0) return 4;
+ return 5;
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * look at buffer, and see if we can read the length of the int off of it. (from the reader index)
+ *
+ * @return 0 if we could not read anything, >0 for the number of bytes for the int on the buffer
+ */
+ public final int canReadInt (ByteBuf buffer) {
+ int startIndex = buffer.readerIndex();
+ try {
+ int remaining = buffer.readableBytes();
+ for (int offset = 0, count = 1; offset < 32 && remaining > 0; offset += 7, remaining--, count++) {
+ int b = buffer.readByte();
+ if ((b & 0x80) == 0) {
+ return count;
+ }
+ }
+ return 0;
+ } finally {
+ buffer.readerIndex(startIndex);
+ }
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * Reads an int from the buffer that was optimized.
+ */
+ public final int readInt (ByteBuf buffer, boolean optimizePositive) {
+ int b = buffer.readByte();
+ int result = b & 0x7F;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (b & 0x7F) << 7;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (b & 0x7F) << 14;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (b & 0x7F) << 21;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (b & 0x7F) << 28;
+ }
+ }
+ }
+ }
+ return optimizePositive ? result : ((result >>> 1) ^ -(result & 1));
+ }
+
+
+ /**
+ * FROM KRYO
+ *
+ * Writes the specified int to the buffer using 1 to 5 bytes, depending on the size of the number.
+ *
+ * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value
+ * @return the number of bytes written.
+ */
+ public final int writeInt (ByteBuf buffer, int value, boolean optimizePositive) {
+ if (!optimizePositive) value = (value << 1) ^ (value >> 31);
+ if (value >>> 7 == 0) {
+ buffer.writeByte((byte)value);
+ return 1;
+ }
+ if (value >>> 14 == 0) {
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7));
+ return 2;
+ }
+ if (value >>> 21 == 0) {
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7 | 0x80));
+ buffer.writeByte((byte)(value >>> 14));
+ return 3;
+ }
+ if (value >>> 28 == 0) {
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7 | 0x80));
+ buffer.writeByte((byte)(value >>> 14 | 0x80));
+ buffer.writeByte((byte)(value >>> 21));
+ return 4;
+ }
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7 | 0x80));
+ buffer.writeByte((byte)(value >>> 14 | 0x80));
+ buffer.writeByte((byte)(value >>> 21 | 0x80));
+ buffer.writeByte((byte)(value >>> 28));
+ return 5;
+ }
+
+
+
+
+ // long
+
+ /**
+ * Returns the number of bytes that would be written with {@link #writeLong(long, boolean)}.
+ *
+ * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value
+ */
+ public final int longLength (long value, boolean optimizePositive) {
+ if (!optimizePositive) value = (value << 1) ^ (value >> 63);
+ if (value >>> 7 == 0) return 1;
+ if (value >>> 14 == 0) return 2;
+ if (value >>> 21 == 0) return 3;
+ if (value >>> 28 == 0) return 4;
+ if (value >>> 35 == 0) return 5;
+ if (value >>> 42 == 0) return 6;
+ if (value >>> 49 == 0) return 7;
+ if (value >>> 56 == 0) return 8;
+ return 9;
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * Reads a 1-9 byte long.
+ *
+ * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value
+ */
+ public final long readLong (ByteBuf buffer, boolean optimizePositive) {
+ int b = buffer.readByte();
+ long result = b & 0x7F;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (b & 0x7F) << 7;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (b & 0x7F) << 14;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (b & 0x7F) << 21;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (long)(b & 0x7F) << 28;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (long)(b & 0x7F) << 35;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (long)(b & 0x7F) << 42;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (long)(b & 0x7F) << 49;
+ if ((b & 0x80) != 0) {
+ b = buffer.readByte();
+ result |= (long)b << 56;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (!optimizePositive) result = (result >>> 1) ^ -(result & 1);
+ return result;
+ }
+
+
+ /**
+ * FROM KRYO
+ *
+ * Writes a 1-9 byte long.
+ *
+ * @param optimizePositive If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be
+ * inefficient (9 bytes).
+ * @return the number of bytes written.
+ */
+ public final int writeLong (ByteBuf buffer, long value, boolean optimizePositive) {
+ if (!optimizePositive) value = (value << 1) ^ (value >> 63);
+ if (value >>> 7 == 0) {
+ buffer.writeByte((byte)value);
+ return 1;
+ }
+ if (value >>> 14 == 0) {
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7));
+ return 2;
+ }
+ if (value >>> 21 == 0) {
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7 | 0x80));
+ buffer.writeByte((byte)(value >>> 14));
+ return 3;
+ }
+ if (value >>> 28 == 0) {
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7 | 0x80));
+ buffer.writeByte((byte)(value >>> 14 | 0x80));
+ buffer.writeByte((byte)(value >>> 21));
+ return 4;
+ }
+ if (value >>> 35 == 0) {
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7 | 0x80));
+ buffer.writeByte((byte)(value >>> 14 | 0x80));
+ buffer.writeByte((byte)(value >>> 21 | 0x80));
+ buffer.writeByte((byte)(value >>> 28));
+ return 5;
+ }
+ if (value >>> 42 == 0) {
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7 | 0x80));
+ buffer.writeByte((byte)(value >>> 14 | 0x80));
+ buffer.writeByte((byte)(value >>> 21 | 0x80));
+ buffer.writeByte((byte)(value >>> 28 | 0x80));
+ buffer.writeByte((byte)(value >>> 35));
+ return 6;
+ }
+ if (value >>> 49 == 0) {
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7 | 0x80));
+ buffer.writeByte((byte)(value >>> 14 | 0x80));
+ buffer.writeByte((byte)(value >>> 21 | 0x80));
+ buffer.writeByte((byte)(value >>> 28 | 0x80));
+ buffer.writeByte((byte)(value >>> 35 | 0x80));
+ buffer.writeByte((byte)(value >>> 42));
+ return 7;
+ }
+ if (value >>> 56 == 0) {
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7 | 0x80));
+ buffer.writeByte((byte)(value >>> 14 | 0x80));
+ buffer.writeByte((byte)(value >>> 21 | 0x80));
+ buffer.writeByte((byte)(value >>> 28 | 0x80));
+ buffer.writeByte((byte)(value >>> 35 | 0x80));
+ buffer.writeByte((byte)(value >>> 42 | 0x80));
+ buffer.writeByte((byte)(value >>> 49));
+ return 8;
+ }
+ buffer.writeByte((byte)((value & 0x7F) | 0x80));
+ buffer.writeByte((byte)(value >>> 7 | 0x80));
+ buffer.writeByte((byte)(value >>> 14 | 0x80));
+ buffer.writeByte((byte)(value >>> 21 | 0x80));
+ buffer.writeByte((byte)(value >>> 28 | 0x80));
+ buffer.writeByte((byte)(value >>> 35 | 0x80));
+ buffer.writeByte((byte)(value >>> 42 | 0x80));
+ buffer.writeByte((byte)(value >>> 49 | 0x80));
+ buffer.writeByte((byte)(value >>> 56));
+ return 9;
+ }
+
+ /**
+ * FROM KRYO
+ *
+ * look at buffer, and see if we can read the length of the long off of it (from the reader index).
+ *
+ * @return 0 if we could not read anything, >0 for the number of bytes for the long on the buffer
+ */
+ public final int canReadLong (ByteBuf buffer) {
+ int position = buffer.readerIndex();
+ try {
+ int remaining = buffer.readableBytes();
+ for (int offset = 0, count = 1; offset < 64 && remaining > 0; offset += 7, remaining--, count++) {
+ int b = buffer.readByte();
+ if ((b & 0x80) == 0) {
+ return count;
+ }
+ }
+ return 0;
+ } finally {
+ buffer.readerIndex(position);
+ }
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/BCrypt.java b/Dorkbox-Util/src/dorkbox/util/crypto/BCrypt.java
new file mode 100644
index 0000000..12abf12
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/BCrypt.java
@@ -0,0 +1,782 @@
+package dorkbox.util.crypto;
+
+// Copyright (c) 2006 Damien Miller
+//
+// GWT modified version.
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+import java.io.UnsupportedEncodingException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+/**
+ * BCrypt implements OpenBSD-style Blowfish password hashing using
+ * the scheme described in "A Future-Adaptable Password Scheme" by
+ * Niels Provos and David Mazieres.
+ *
+ * This password hashing system tries to thwart off-line password
+ * cracking using a computationally-intensive hashing algorithm,
+ * based on Bruce Schneier's Blowfish cipher. The work factor of
+ * the algorithm is parameterised, so it can be increased as
+ * computers get faster.
+ *
+ * Usage is really simple. To hash a password for the first time,
+ * call the hashpw method with a random salt, like this:
+ *
+ *
+ * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
+ *
+ *
+ * To check whether a plaintext password matches one that has been
+ * hashed previously, use the checkpw method:
+ *
+ *
+ * if (BCrypt.checkpw(candidate_password, stored_hash))
+ * System.out.println("It matches");
+ * else
+ * System.out.println("It does not match");
+ *
+ *
+ * The gensalt() method takes an optional parameter (log_rounds)
+ * that determines the computational complexity of the hashing:
+ *
+ *
+ * String strong_salt = BCrypt.gensalt(10)
+ * String stronger_salt = BCrypt.gensalt(12)
+ *
+ *
+ * The amount of work increases exponentially (2**log_rounds), so
+ * each increment is twice as much work. The default log_rounds is
+ * 10, and the valid range is 4 to 31.
+ *
+ * @author Damien Miller
+ * @version 0.2
+ */
+public class BCrypt {
+ // BCrypt parameters
+ private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
+ private static final int BCRYPT_SALT_LEN = 16;
+
+ // Blowfish parameters
+ private static final int BLOWFISH_NUM_ROUNDS = 16;
+
+ // Initial contents of key schedule
+ private static final int P_orig[] = {
+ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
+ 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
+ 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
+ 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
+ 0x9216d5d9, 0x8979fb1b
+ };
+ private static final int S_orig[] = {
+ 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
+ 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
+ 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
+ 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
+ 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
+ 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
+ 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
+ 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
+ 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
+ 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
+ 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
+ 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
+ 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
+ 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
+ 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
+ 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
+ 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
+ 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
+ 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
+ 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
+ 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
+ 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
+ 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
+ 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
+ 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
+ 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
+ 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
+ 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
+ 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
+ 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
+ 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
+ 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
+ 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
+ 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
+ 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
+ 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
+ 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
+ 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
+ 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
+ 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
+ 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
+ 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
+ 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
+ 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
+ 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
+ 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
+ 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
+ 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
+ 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
+ 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
+ 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
+ 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
+ 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
+ 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
+ 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
+ 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
+ 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
+ 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
+ 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
+ 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
+ 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
+ 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
+ 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
+ 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
+ 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
+ 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
+ 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
+ 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
+ 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
+ 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
+ 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
+ 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
+ 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
+ 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
+ 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
+ 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
+ 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
+ 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
+ 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
+ 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
+ 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
+ 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
+ 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
+ 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
+ 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
+ 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
+ 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
+ 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
+ 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
+ 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
+ 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
+ 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
+ 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
+ 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
+ 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
+ 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
+ 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
+ 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
+ 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
+ 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
+ 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
+ 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
+ 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
+ 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
+ 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
+ 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
+ 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
+ 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
+ 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
+ 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
+ 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
+ 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
+ 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
+ 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
+ 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
+ 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
+ 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
+ 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
+ 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
+ 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
+ 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
+ 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
+ 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
+ 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
+ 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
+ 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
+ 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
+ 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
+ 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
+ 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
+ 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
+ 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
+ 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
+ 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
+ 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
+ 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
+ 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
+ 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
+ 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
+ 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
+ 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
+ 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
+ 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
+ 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
+ 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
+ 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
+ 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
+ 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
+ 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
+ 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
+ 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
+ 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
+ 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
+ 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
+ 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
+ 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
+ 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
+ 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
+ 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
+ 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
+ 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
+ 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
+ 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
+ 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
+ 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
+ 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
+ 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
+ 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
+ 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
+ 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
+ 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
+ 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
+ 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
+ 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
+ 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
+ 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
+ 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
+ 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
+ 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
+ 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
+ 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
+ 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
+ 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
+ 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
+ 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
+ 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
+ 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
+ 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
+ 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
+ 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
+ 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
+ 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
+ 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
+ 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
+ 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
+ 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
+ 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
+ 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
+ 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
+ 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
+ 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
+ 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
+ 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
+ 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
+ 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
+ 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
+ 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
+ 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
+ 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
+ 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
+ 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
+ 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
+ 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
+ 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
+ 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
+ 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
+ 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
+ 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
+ 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
+ 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
+ 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
+ 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
+ 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
+ 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
+ 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
+ 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
+ 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
+ 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
+ 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
+ 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
+ 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
+ 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
+ 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
+ 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
+ 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
+ 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
+ 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
+ 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
+ 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
+ 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
+ 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
+ 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
+ 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
+ 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
+ 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
+ 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
+ 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
+ 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
+ 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
+ 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
+ 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
+ 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
+ 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
+ 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
+ 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
+ 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
+ };
+
+ // bcrypt IV: "OrpheanBeholderScryDoubt"
+ static private final int bf_crypt_ciphertext[] = {
+ 0x4f727068, 0x65616e42, 0x65686f6c,
+ 0x64657253, 0x63727944, 0x6f756274
+ };
+
+ // Table for Base64 encoding
+ static private final char base64_code[] = {
+ '.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+ 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9'
+ };
+
+ // Table for Base64 decoding
+ static private final byte index_64[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
+ -1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
+ -1, -1, -1, -1, -1, -1, 28, 29, 30,
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
+ 51, 52, 53, -1, -1, -1, -1, -1
+ };
+
+ // Expanded Blowfish key
+ private int P[];
+ private int S[];
+
+ /**
+ * Encode a byte array using bcrypt's slightly-modified base64
+ * encoding scheme. Note that this is *not* compatible with
+ * the standard MIME-base64 encoding.
+ *
+ * @param d the byte array to encode
+ * @param len the number of bytes to encode
+ * @return base64-encoded string
+ * @exception IllegalArgumentException if the length is invalid
+ */
+ private static String encode_base64(byte d[], int len)
+ throws IllegalArgumentException {
+ int off = 0;
+ StringBuilder rs = new StringBuilder();
+ int c1, c2;
+
+ if (len <= 0 || len > d.length) {
+ throw new IllegalArgumentException ("Invalid len");
+ }
+
+ while (off < len) {
+ c1 = d[off++] & 0xff;
+ rs.append(base64_code[c1 >> 2 & 0x3f]);
+ c1 = (c1 & 0x03) << 4;
+ if (off >= len) {
+ rs.append(base64_code[c1 & 0x3f]);
+ break;
+ }
+ c2 = d[off++] & 0xff;
+ c1 |= c2 >> 4 & 0x0f;
+ rs.append(base64_code[c1 & 0x3f]);
+ c1 = (c2 & 0x0f) << 2;
+ if (off >= len) {
+ rs.append(base64_code[c1 & 0x3f]);
+ break;
+ }
+ c2 = d[off++] & 0xff;
+ c1 |= c2 >> 6 & 0x03;
+ rs.append(base64_code[c1 & 0x3f]);
+ rs.append(base64_code[c2 & 0x3f]);
+ }
+ return rs.toString();
+ }
+
+ /**
+ * Look up the 3 bits base64-encoded by the specified character, range-checking againt conversion table
+ *
+ * @param x the base64-encoded value
+ * @return the decoded value of x
+ */
+ private static byte char64(char x) {
+ if (x < 0 || x > index_64.length) {
+ return -1;
+ }
+ return index_64[x];
+ }
+
+ /**
+ * Decode a string encoded using bcrypt's base64 scheme to a byte array. Note that this is *not* compatible with
+ * the standard MIME-base64 encoding.
+ *
+ * @param s the string to decode
+ * @param maxolen the maximum number of bytes to decode
+ * @return an array containing the decoded bytes
+ * @throws IllegalArgumentException if maxolen is invalid
+ */
+ private static byte[] decode_base64(String s, int maxolen)
+ throws IllegalArgumentException {
+ StringBuilder rs = new StringBuilder();
+ int off = 0, slen = s.length(), olen = 0;
+ byte ret[];
+ byte c1, c2, c3, c4, o;
+
+ if (maxolen <= 0) {
+ throw new IllegalArgumentException ("Invalid maxolen");
+ }
+
+ while (off < slen - 1 && olen < maxolen) {
+ c1 = char64(s.charAt(off++));
+ c2 = char64(s.charAt(off++));
+ if (c1 == -1 || c2 == -1) {
+ break;
+ }
+ o = (byte)(c1 << 2);
+ o |= (c2 & 0x30) >> 4;
+ rs.append((char)o);
+ if (++olen >= maxolen || off >= slen) {
+ break;
+ }
+ c3 = char64(s.charAt(off++));
+ if (c3 == -1) {
+ break;
+ }
+ o = (byte)((c2 & 0x0f) << 4);
+ o |= (c3 & 0x3c) >> 2;
+ rs.append((char)o);
+ if (++olen >= maxolen || off >= slen) {
+ break;
+ }
+ c4 = char64(s.charAt(off++));
+ o = (byte)((c3 & 0x03) << 6);
+ o |= c4;
+ rs.append((char)o);
+ ++olen;
+ }
+
+ ret = new byte[olen];
+ for (off = 0; off < olen; off++) {
+ ret[off] = (byte)rs.charAt(off);
+ }
+ return ret;
+ }
+
+ /**
+ * Blowfish encipher a single 64-bit block encoded as two 32-bit halves
+ *
+ * @param lr an array containing the two 32-bit half blocks
+ * @param off the position in the array of the blocks
+ */
+ private final void encipher(int lr[], int off) {
+ int i, n, l = lr[off], r = lr[off + 1];
+
+ l ^= P[0];
+ for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
+ // Feistel substitution on left word
+ n = S[l >> 24 & 0xff];
+ n += S[0x100 | l >> 16 & 0xff];
+ n ^= S[0x200 | l >> 8 & 0xff];
+ n += S[0x300 | l & 0xff];
+ r ^= n ^ P[++i];
+
+ // Feistel substitution on right word
+ n = S[r >> 24 & 0xff];
+ n += S[0x100 | r >> 16 & 0xff];
+ n ^= S[0x200 | r >> 8 & 0xff];
+ n += S[0x300 | r & 0xff];
+ l ^= n ^ P[++i];
+ }
+ lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1];
+ lr[off + 1] = l;
+ }
+
+ /**
+ * Cycically extract a word of key material
+ *
+ * @param data the string to extract the data from
+ * @param offp a "pointer" (as a one-entry array) to the current offset into data
+ * @return the next word of material from data
+ */
+ private static int streamtoword(byte data[], int offp[]) {
+ int i;
+ int word = 0;
+ int off = offp[0];
+
+ for (i = 0; i < 4; i++) {
+ word = word << 8 | data[off] & 0xff;
+ off = (off + 1) % data.length;
+ }
+
+ offp[0] = off;
+ return word;
+ }
+
+ /**
+ * Initialize the Blowfish key schedule
+ */
+ private void init_key() {
+ P = Arrays.copyOf(P_orig, P_orig.length);
+ S = Arrays.copyOf(S_orig, S_orig.length);
+ }
+
+ /**
+ * Key the Blowfish cipher
+ * @param key an array containing the key
+ */
+ private void key(byte key[]) {
+ int i;
+ int koffp[] = { 0 };
+ int lr[] = { 0, 0 };
+ int plen = P.length, slen = S.length;
+
+ for (i = 0; i < plen; i++) {
+ P[i] = P[i] ^ streamtoword(key, koffp);
+ }
+
+ for (i = 0; i < plen; i += 2) {
+ encipher(lr, 0);
+ P[i] = lr[0];
+ P[i + 1] = lr[1];
+ }
+
+ for (i = 0; i < slen; i += 2) {
+ encipher(lr, 0);
+ S[i] = lr[0];
+ S[i + 1] = lr[1];
+ }
+ }
+
+ /**
+ * Perform the "enhanced key schedule" step described by Provos and Mazieres in "A Future-Adaptable Password Scheme"
+ * http://www.openbsd.org/papers/bcrypt-paper.ps
+ *
+ * @param data salt information
+ * @param key password information
+ */
+ private void ekskey(byte data[], byte key[]) {
+ int i;
+ int koffp[] = { 0 }, doffp[] = { 0 };
+ int lr[] = { 0, 0 };
+ int plen = P.length, slen = S.length;
+
+ for (i = 0; i < plen; i++) {
+ P[i] = P[i] ^ streamtoword(key, koffp);
+ }
+
+ for (i = 0; i < plen; i += 2) {
+ lr[0] ^= streamtoword(data, doffp);
+ lr[1] ^= streamtoword(data, doffp);
+ encipher(lr, 0);
+ P[i] = lr[0];
+ P[i + 1] = lr[1];
+ }
+
+ for (i = 0; i < slen; i += 2) {
+ lr[0] ^= streamtoword(data, doffp);
+ lr[1] ^= streamtoword(data, doffp);
+ encipher(lr, 0);
+ S[i] = lr[0];
+ S[i + 1] = lr[1];
+ }
+ }
+
+ /**
+ * Perform the central password hashing step in the bcrypt scheme
+ *
+ * @param password the password to hash
+ * @param salt the binary salt to hash with the password
+ * @param log_rounds the binary logarithm of the number of rounds of hashing to apply
+ * @return an array containing the binary hashed password
+ */
+ private byte[] crypt_raw(byte password[], byte salt[], int log_rounds) {
+ int rounds, i, j;
+ int cdata[] = Arrays.copyOf(bf_crypt_ciphertext, bf_crypt_ciphertext.length);
+ int clen = cdata.length;
+ byte ret[];
+
+ if (log_rounds < 4 || log_rounds > 31) {
+ throw new IllegalArgumentException ("Bad number of rounds");
+ }
+ rounds = 1 << log_rounds;
+ if (salt.length != BCRYPT_SALT_LEN) {
+ throw new IllegalArgumentException ("Bad salt length");
+ }
+
+ init_key();
+ ekskey(salt, password);
+ for (i = 0; i < rounds; i++) {
+ key(password);
+ key(salt);
+ }
+
+ for (i = 0; i < 64; i++) {
+ for (j = 0; j < clen >> 1; j++) {
+ encipher(cdata, j << 1);
+ }
+ }
+
+ ret = new byte[clen * 4];
+ for (i = 0, j = 0; i < clen; i++) {
+ ret[j++] = (byte)(cdata[i] >> 24 & 0xff);
+ ret[j++] = (byte)(cdata[i] >> 16 & 0xff);
+ ret[j++] = (byte)(cdata[i] >> 8 & 0xff);
+ ret[j++] = (byte)(cdata[i] & 0xff);
+ }
+ return ret;
+ }
+
+ /**
+ * Hash a password using the OpenBSD bcrypt scheme
+ *
+ * @param password the password to hash
+ * @return the hashed password
+ */
+ public static String hashpw(String password) {
+ return hashpw(password, BCrypt.gensalt());
+ }
+
+ /**
+ * Hash a password using the OpenBSD bcrypt scheme
+ *
+ * @param password the password to hash
+ * @param salt the salt to hash with (perhaps generated using BCrypt.gensalt)
+ * @return the hashed password
+ */
+ public static String hashpw(String password, String salt) {
+ BCrypt B;
+ String real_salt;
+ byte passwordb[], saltb[], hashed[];
+ char minor = (char)0;
+ int rounds, off = 0;
+ StringBuilder rs = new StringBuilder();
+
+ if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {
+ throw new IllegalArgumentException ("Invalid salt version");
+ }
+ if (salt.charAt(2) == '$') {
+ off = 3;
+ } else {
+ minor = salt.charAt(2);
+ if (minor != 'a' || salt.charAt(3) != '$') {
+ throw new IllegalArgumentException ("Invalid salt revision");
+ }
+ off = 4;
+ }
+
+ // Extract number of rounds
+ if (salt.charAt(off + 2) > '$') {
+ throw new IllegalArgumentException ("Missing salt rounds");
+ }
+ rounds = Integer.parseInt(salt.substring(off, off + 2));
+
+ real_salt = salt.substring(off + 3, off + 25);
+ try {
+ passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
+ } catch (UnsupportedEncodingException uee) {
+ throw new AssertionError("UTF-8 is not supported");
+ }
+
+ saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
+
+ B = new BCrypt();
+ hashed = B.crypt_raw(passwordb, saltb, rounds);
+
+ rs.append("$2");
+ if (minor >= 'a') {
+ rs.append(minor);
+ }
+ rs.append("$");
+ if (rounds < 10) {
+ rs.append("0");
+ }
+ rs.append(String.valueOf(rounds));
+ rs.append("$");
+ rs.append(encode_base64(saltb, saltb.length));
+ rs.append(encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1));
+ return rs.toString();
+ }
+
+ /**
+ * Generate a salt for use with the BCrypt.hashpw() method
+ * @param log_rounds the log2 of the number of rounds of
+ * hashing to apply - the work factor therefore increases as
+ * 2**log_rounds.
+ * @param random an instance of SecureRandom to use
+ * @return an encoded salt value
+ */
+ public static String gensalt(int log_rounds, SecureRandom random) {
+ StringBuilder rs = new StringBuilder();
+ byte rnd[] = new byte[BCRYPT_SALT_LEN];
+
+ random.nextBytes(rnd);
+
+ rs.append("$2a$");
+ if (log_rounds < 10) {
+ rs.append("0");
+ }
+ rs.append(String.valueOf(log_rounds));
+ rs.append("$");
+ rs.append(encode_base64(rnd, rnd.length));
+ return rs.toString();
+ }
+
+ /**
+ * Generate a salt for use with the BCrypt.hashpw() method
+ * @param log_rounds the log2 of the number of rounds of
+ * hashing to apply - the work factor therefore increases as
+ * 2**log_rounds.
+ * @return an encoded salt value
+ */
+ public static String gensalt(int log_rounds) {
+ return gensalt(log_rounds, new SecureRandom());
+ }
+
+ /**
+ * Generate a salt for use with the BCrypt.hashpw() method,
+ * selecting a reasonable default for the number of hashing
+ * rounds to apply
+ * @return an encoded salt value
+ */
+ public static String gensalt() {
+ return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
+ }
+
+ /**
+ * Check that a plaintext password matches a previously hashed
+ * one
+ * @param plaintext the plaintext password to verify
+ * @param hashed the previously-hashed password
+ * @return true if the passwords match, false otherwise
+ */
+ public static boolean checkpw(String plaintext, String hashed) {
+ return hashed.compareTo(hashpw(plaintext, hashed)) == 0;
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/Crypto.java b/Dorkbox-Util/src/dorkbox/util/crypto/Crypto.java
new file mode 100644
index 0000000..0497867
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/Crypto.java
@@ -0,0 +1,1761 @@
+package dorkbox.util.crypto;
+
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.PBEParametersGenerator;
+import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.engines.IESEngine;
+import org.bouncycastle.crypto.generators.DSAKeyPairGenerator;
+import org.bouncycastle.crypto.generators.DSAParametersGenerator;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
+import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
+import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.params.DSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.IESParameters;
+import org.bouncycastle.crypto.params.IESWithCipherParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.crypto.signers.DSASigner;
+import org.bouncycastle.crypto.signers.ECDSASigner;
+import org.bouncycastle.crypto.signers.PSSSigner;
+import org.bouncycastle.jcajce.provider.util.DigestFactory;
+import org.bouncycastle.jce.ECNamedCurveTable;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.math.ec.ECFieldElement;
+import org.bouncycastle.math.ec.ECPoint;
+
+/**
+ * http://en.wikipedia.org/wiki/NSA_Suite_B
+ * http://www.nsa.gov/ia/programs/suiteb_cryptography/
+ *
+ * NSA Suite B
+ *
+ * TOP-SECRET LEVEL
+ * AES256/GCM
+ * ECC with 384-bit prime curve (FIPS PUB 186-3), and SHA-384
+ *
+ * SECRET LEVEL
+ * AES 128
+ * ECDH and ECDSA using the 256-bit prime (FIPS PUB 186-3), and SHA-256. RSA with 2048 can be used for DH key negotiation
+ *
+ * WARNING!
+ * Note that this call is INCOMPATIBLE with GWT, so we have EXCLUDED IT from gwt, and created a CryptoGwt class in the web-client project
+ * which only has the necessary crypto utility methods that are
+ * 1) Necessary
+ * 2) Compatible with GWT
+ *
+ */
+public class Crypto {
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Crypto.class);
+
+ public static final void addProvider() {
+ // make sure we only add it once (in case it's added elsewhere...)
+ Provider provider = Security.getProvider("BC");
+ if (provider == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ }
+
+ public static class Util {
+
+ /**
+ * Return the hash of the file or NULL if file is invalid
+ */
+ public static final byte[] hashFile(File file, Digest digest) throws IOException {
+ return hashFile(file, digest, 0L);
+ }
+
+ /**
+ * Return the hash of the file or NULL if file is invalid
+ */
+ public static final byte[] hashFile(File file, Digest digest, long lengthFromEnd) throws IOException {
+ if (file.isFile() && file.canRead()) {
+ InputStream inputStream = new FileInputStream(file);
+ long size = file.length();
+
+ if (lengthFromEnd > 0 && lengthFromEnd < size) {
+ size -= lengthFromEnd;
+ }
+
+ try {
+ int bufferSize = 4096;
+ byte[] buffer = new byte[bufferSize];
+
+ int readBytes = 0;
+ digest.reset();
+
+ while (size > 0) {
+ int maxToRead = (int) Math.min(bufferSize, size);
+ readBytes = inputStream.read(buffer, 0, maxToRead);
+ size -= readBytes;
+
+ if (readBytes == 0) {
+ //wtf. finally still gets called.
+ return null;
+ }
+
+ digest.update(buffer, 0, readBytes);
+ }
+ } finally {
+ inputStream.close();
+ }
+
+ byte[] digestBytes = new byte[digest.getDigestSize()];
+
+ digest.doFinal(digestBytes, 0);
+ return digestBytes;
+
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Specifically, to return the hash of the ALL files/directories inside the jar, minus the action specified (LGPL) files.
+ */
+ public static final byte[] hashJarContentsExcludeAction(JarFile jarFile, Digest digest, int action) throws IOException {
+ // repack token: ':|', from BoxHandler.
+ // if this is CHANGED, make sure to update it there as well.
+ String token = ":|";
+
+ Enumeration jarElements = jarFile.entries();
+
+ boolean okToHash = false;
+ byte[] buffer = new byte[2048];
+ int read = 0;
+ digest.reset();
+
+ while (jarElements.hasMoreElements()) {
+ JarEntry jarEntry = jarElements.nextElement();
+ String name = jarEntry.getName();
+ okToHash = !jarEntry.isDirectory();
+
+ if (!okToHash) {
+ continue;
+ }
+
+ okToHash = false;
+ int startIndex = name.lastIndexOf(token); // lastIndexOf, in case there are multiple box files stacked in eachother
+ if (startIndex > -1) {
+ String type = name.substring(startIndex + 2);
+ int parseInt = Integer.parseInt(type);
+
+ if ((parseInt & action) != action) {
+ okToHash = true;
+ }
+ }
+
+ // skips hashing lgpl files. (technically, whatever our action bitmask is...)
+ if (okToHash) {
+ InputStream inputStream = jarFile.getInputStream(jarEntry);
+
+ while ((read = inputStream.read(buffer)) > 0) {
+ digest.update(buffer, 0, read);
+ }
+ inputStream.close();
+ }
+ }
+
+ byte[] digestBytes = new byte[digest.getDigestSize()];
+
+ digest.doFinal(digestBytes, 0);
+ return digestBytes;
+ }
+
+ /**
+ * return the hash of the specified files/directories inside the jar.
+ */
+ public static final byte[] hashJarContents(JarFile jarFile, Digest digest, String... filesOrDirsToHash) throws IOException {
+ Enumeration jarElements = jarFile.entries();
+
+ boolean okToHash = false;
+ byte[] buffer = new byte[2048];
+ int read = 0;
+ digest.reset();
+
+ while (jarElements.hasMoreElements()) {
+ JarEntry jarEntry = jarElements.nextElement();
+ String name = jarEntry.getName();
+ okToHash = !jarEntry.isDirectory();
+
+ if (!okToHash) {
+ continue;
+ }
+
+ okToHash = false;
+ if (filesOrDirsToHash != null) {
+ for (String dir : filesOrDirsToHash) {
+ if (name.startsWith(dir)) {
+ okToHash = true;
+ break;
+ }
+ }
+ } else {
+ okToHash = true;
+ }
+
+ if (okToHash) {
+ InputStream inputStream = jarFile.getInputStream(jarEntry);
+
+ while ((read = inputStream.read(buffer)) > 0) {
+ digest.update(buffer, 0, read);
+ }
+ inputStream.close();
+ }
+ }
+
+ byte[] digestBytes = new byte[digest.getDigestSize()];
+
+ digest.doFinal(digestBytes, 0);
+ return digestBytes;
+ }
+
+ /**
+ * Encrypts the contents (avoiding the specified files/dirs) of a jar.
+ *
+ * This will KEEP the order of the contents of the jar!
+ * @throws IOException
+ */
+ public static void encryptJarContents(JarFile jarFile, GCMBlockCipher aesEngine, byte[] aesRaw, byte[] ivRaw,
+ String... filesOrdirectoriesToAvoid) throws IOException {
+
+ // encrypt to a stream, then save that stream BACK to the file... "in place encryption"
+ int blockSize = aesEngine.getUnderlyingCipher().getBlockSize();
+ byte[] aesKey = new byte[blockSize];
+ byte[] ivKey = new byte[blockSize];
+
+ System.arraycopy(aesRaw, 0, aesKey, 0, blockSize);
+ System.arraycopy(ivKey, 0, ivRaw, 0, blockSize);
+
+ // now encrypt the jar
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ JarOutputStream jarOutputStream = new JarOutputStream(new BufferedOutputStream(byteArrayOutputStream));
+ jarOutputStream.setLevel(9); // COMPRESSION LEVEL
+
+ boolean okToCrypt = false;
+
+ Enumeration jarEntries = jarFile.entries();
+ while (jarEntries.hasMoreElements()) {
+ JarEntry jarEntry = jarEntries.nextElement();
+ String name = jarEntry.getName();
+
+ okToCrypt = !jarEntry.isDirectory();
+
+ if (!okToCrypt) {
+ continue;
+ }
+
+ okToCrypt = true;
+ for (String dir : filesOrdirectoriesToAvoid) {
+ if (name.startsWith(dir)) {
+ okToCrypt = false;
+ break;
+ }
+ }
+
+ if (okToCrypt) {
+ InputStream inputStream = jarFile.getInputStream(jarEntry);
+ Crypto.AES.encryptStream(aesEngine, aesKey, ivKey, inputStream, jarOutputStream);
+ inputStream.close();
+ jarOutputStream.flush();
+ jarOutputStream.closeEntry();
+ }
+ }
+
+ // finish the stream that we have been writing to
+ jarOutputStream.finish();
+ jarOutputStream.close();
+
+ jarFile.close();
+ }
+
+
+ /**
+ * Hash an input stream, based on the specified digest
+ */
+ public static byte[] hashStream(Digest digest, InputStream inputStream) throws IOException {
+
+ byte[] buffer = new byte[2048];
+ int read = 0;
+ digest.reset();
+
+
+ while ((read = inputStream.read(buffer)) > 0) {
+ digest.update(buffer, 0, read);
+ }
+ inputStream.close();
+
+ byte[] digestBytes = new byte[digest.getDigestSize()];
+
+ digest.doFinal(digestBytes, 0);
+ return digestBytes;
+ }
+
+ /**
+ * Hash an input stream (auto-converts to an output stream first), based on the specified digest
+ */
+ public static byte[] hashStream(Digest digest, ByteArrayOutputStream outputStream) throws IOException {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+
+ return hashStream(digest, inputStream);
+ }
+
+
+ /**
+ * Secure way to generate an AES key based on a password. Will '*' out the passed-in password
+ *
+ * @param password will be filled with '*'
+ * @param salt should be a RANDOM number, at least 256bits (32 bytes) in size.
+ * @param iterationCount should be a lot, like 10,000
+ * @return the secure key to use
+ */
+ public static final byte[] PBKDF2(char[] password, byte[] salt, int iterationCount) {
+ // will also zero out the password.
+ byte[] charToBytes = Crypto.Util.charToBytesPassword(password);
+
+ return PBKDF2(charToBytes, salt, iterationCount);
+ }
+
+ /**
+ * Secure way to generate an AES key based on a password.
+ *
+ * @param password
+ * @param salt should be a RANDOM number, at least 256bits (32 bytes) in size.
+ * @param iterationCount should be a lot, like 10,000
+ * @return the secure key to use
+ */
+ public static final byte[] PBKDF2(byte[] password, byte[] salt, int iterationCount) {
+ SHA256Digest digest = new SHA256Digest();
+ PBEParametersGenerator pGen = new PKCS5S2ParametersGenerator(digest);
+ pGen.init(password, salt, iterationCount);
+
+ KeyParameter key = (KeyParameter) pGen.generateDerivedMacParameters(digest.getDigestSize() * 8); // *8 for bit length.
+
+ // zero out the password.
+ Arrays.fill(password, (byte)0);
+
+ return key.getKey();
+ }
+
+
+ /** this saves the char array in UTF-16 format of bytes and BLANKS out the password char array. */
+ public static final byte[] charToBytesPassword(char[] password) {
+ // note: this saves the char array in UTF-16 format of bytes.
+ byte[] passwordBytes = new byte[password.length*2];
+ for(int i=0; i>8);
+ passwordBytes[2*i+1] = (byte) (password[i] & 0x00FF);
+ }
+
+ // asterisk out the password
+ Arrays.fill(password, '*');
+
+ return passwordBytes;
+ }
+ }
+
+
+ public static class AES {
+ private static final int ivSize = 16;
+
+ /**
+ * AES encrypts data with a specified key.
+ *
+ * @return empty byte[] if error
+ */
+ public static final byte[] encryptWithIV(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data) {
+ byte[] encryptAES = encrypt(aesEngine, aesKey, aesIV, data);
+
+ int length = encryptAES.length;
+
+ byte[] out = new byte[length+ivSize];
+ System.arraycopy(aesIV, 0, out, 0, ivSize);
+ System.arraycopy(encryptAES, 0, out, ivSize, length);
+
+ return out;
+ }
+
+ /**
+ * CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO
+ *
+ * Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted data.
+ *
+ * AES encrypts data with a specified key.
+ *
+ * @return empty byte[] if error
+ */
+ @Deprecated
+ public static final byte[] encryptWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data) {
+
+ byte[] encryptAES = encrypt(aesEngine, aesKey, aesIV, data);
+
+ int length = encryptAES.length;
+
+ byte[] out = new byte[length+ivSize];
+ System.arraycopy(aesIV, 0, out, 0, ivSize);
+ System.arraycopy(encryptAES, 0, out, ivSize, length);
+
+ return out;
+ }
+
+ /**
+ * AES encrypts data with a specified key.
+ *
+ * @return true if successful
+ */
+ public static final boolean encryptStreamWithIV(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV,
+ InputStream in, OutputStream out) {
+
+ try {
+ out.write(aesIV);
+ } catch (IOException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ }
+
+ boolean success = encryptStream(aesEngine, aesKey, aesIV, in, out);
+ return success;
+ }
+
+ /**
+ * CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO
+ *
+ * Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted data.
+ *
+ * AES encrypts data with a specified key.
+ *
+ * @return true if successful
+ */
+ @Deprecated
+ public static final boolean encryptStreamWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV,
+ InputStream in, OutputStream out) {
+
+ try {
+ out.write(aesIV);
+ } catch (IOException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ }
+
+ boolean success = encryptStream(aesEngine, aesKey, aesIV, in, out);
+ return success;
+ }
+
+
+ /**
+ * AES encrypts data with a specified key.
+ *
+ * @return empty byte[] if error
+ */
+ public static final byte[] encrypt(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data) {
+ int length = data.length;
+
+ CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
+ aesEngine.init(true, aesIVAndKey);
+
+ int minSize = aesEngine.getOutputSize(length);
+ byte[] outBuf = new byte[minSize];
+
+ int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
+
+ try {
+ actualLength += aesEngine.doFinal(outBuf, actualLength);
+ } catch (DataLengthException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ } catch (IllegalStateException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ }
+
+ if (outBuf.length == actualLength) {
+ return outBuf;
+ } else {
+ byte[] result = new byte[actualLength];
+ System.arraycopy(outBuf, 0, result, 0, result.length);
+ return result;
+ }
+ }
+
+
+ /**
+ * AES encrypts data with a specified key.
+ *
+ * @return length of encrypted data, -1 if there was an error.
+ */
+ public static final int encrypt(dorkbox.util.crypto.bouncycastle.GCMBlockCipher_ByteBuf aesEngine, CipherParameters aesIVAndKey,
+ io.netty.buffer.ByteBuf inBuffer, io.netty.buffer.ByteBuf outBuffer, int length) {
+
+ aesEngine.reset();
+ aesEngine.init(true, aesIVAndKey);
+
+ length = aesEngine.processBytes(inBuffer, outBuffer, length);
+
+ try {
+ length += aesEngine.doFinal(outBuffer);
+ } catch (DataLengthException e) {
+ logger.debug("Unable to perform AES cipher.", e);
+ return -1;
+ } catch (IllegalStateException e) {
+ logger.debug("Unable to perform AES cipher.", e);
+ return -1;
+ } catch (InvalidCipherTextException e) {
+ logger.debug("Unable to perform AES cipher.", e);
+ return -1;
+ }
+
+ // specify where the encrypted data is at
+ outBuffer.readerIndex(0);
+ outBuffer.writerIndex(length);
+
+ return length;
+ }
+
+
+
+ /**
+ * CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO
+ *
+ * Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted data.
+ *
+ * AES encrypts data with a specified key.
+ *
+ * @return empty byte[] if error
+ */
+ @Deprecated
+ public static final byte[] encrypt(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data) {
+ int length = data.length;
+
+ CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
+ aesEngine.init(true, aesIVAndKey);
+
+ int minSize = aesEngine.getOutputSize(length);
+ byte[] outBuf = new byte[minSize];
+
+ int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
+
+ try {
+ actualLength += aesEngine.doFinal(outBuf, actualLength);
+ } catch (DataLengthException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ } catch (IllegalStateException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ }
+
+ if (outBuf.length == actualLength) {
+ return outBuf;
+ } else {
+ byte[] result = new byte[actualLength];
+ System.arraycopy(outBuf, 0, result, 0, result.length);
+ return result;
+ }
+ }
+
+ /**
+ * CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO
+ *
+ * Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted data.
+ *
+ * AES encrypt from one stream to another.
+ *
+ * @return true if successful
+ */
+ @Deprecated
+ public static final boolean encryptStream(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV,
+ InputStream in, OutputStream out) {
+ byte[] buf = new byte[ivSize];
+ byte[] outbuf = new byte[512];
+
+ CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
+ aesEngine.init(true, aesIVAndKey);
+
+ try {
+ int bytesRead = 0;
+ int bytesProcessed = 0;
+
+ while ((bytesRead = in.read(buf)) >= 0) {
+ bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
+ out.write(outbuf, 0, bytesProcessed);
+ }
+
+ bytesProcessed = aesEngine.doFinal(outbuf, 0);
+
+ out.write(outbuf, 0, bytesProcessed);
+ out.flush();
+ } catch (IOException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (DataLengthException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (IllegalStateException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * AES encrypt from one stream to another.
+ *
+ * @return true if successful
+ */
+ public static final boolean encryptStream(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV,
+ InputStream in, OutputStream out) {
+
+ byte[] buf = new byte[ivSize];
+ byte[] outbuf = new byte[512];
+
+ CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
+ aesEngine.init(true, aesIVAndKey);
+
+ try {
+ int bytesRead = 0;
+ int bytesProcessed = 0;
+
+ while ((bytesRead = in.read(buf)) >= 0) {
+ bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
+ out.write(outbuf, 0, bytesProcessed);
+ }
+
+ bytesProcessed = aesEngine.doFinal(outbuf, 0);
+
+ out.write(outbuf, 0, bytesProcessed);
+ out.flush();
+ } catch (IOException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (DataLengthException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (IllegalStateException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * AES decrypt (if the aes IV is included in the data)
+ *
+ * @return empty byte[] if error
+ */
+ public static final byte[] decryptWithIV(GCMBlockCipher aesEngine, byte[] aesKey, byte[] data) {
+ byte[] aesIV = new byte[ivSize];
+ System.arraycopy(data, 0, aesIV, 0, ivSize);
+
+ byte[] in = new byte[data.length-ivSize];
+ System.arraycopy(data, ivSize, in, 0, in.length);
+
+ return decrypt(aesEngine, aesKey, aesIV, in);
+ }
+
+ /**
+ * CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO
+ *
+ * Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted data.
+ *
+ * AES decrypt (if the aes IV is included in the data)
+ *
+ * @return empty byte[] if error
+ */
+ @Deprecated
+ public static final byte[] decryptWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] data) {
+ byte[] aesIV = new byte[ivSize];
+ System.arraycopy(data, 0, aesIV, 0, ivSize);
+
+ byte[] in = new byte[data.length-ivSize];
+ System.arraycopy(data, ivSize, in, 0, in.length);
+
+ return decrypt(aesEngine, aesKey, aesIV, in);
+ }
+
+ /**
+ * AES decrypt (if the aes IV is included in the data)
+ *
+ * @return true if successful
+ */
+ public static final boolean decryptStreamWithIV(GCMBlockCipher aesEngine, byte[] aesKey,
+ InputStream in, OutputStream out) {
+ byte[] aesIV = new byte[ivSize];
+ try {
+ in.read(aesIV, 0, ivSize);
+ } catch (IOException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ }
+
+ boolean success = decryptStream(aesEngine, aesKey, aesIV, in, out);
+ return success;
+ }
+
+ /**
+ * CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO
+ *
+ * Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted data.
+ *
+ * AES decrypt (if the aes IV is included in the data)
+ *
+ * @return true if successful
+ */
+ @Deprecated
+ public static final boolean decryptStreamWithIV(BufferedBlockCipher aesEngine, byte[] aesKey,
+ InputStream in, OutputStream out) {
+ byte[] aesIV = new byte[ivSize];
+ try {
+ in.read(aesIV, 0, ivSize);
+ } catch (IOException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ }
+
+ boolean success = decryptStream(aesEngine, aesKey, aesIV, in, out);
+ return success;
+ }
+
+ /**
+ * AES decrypt (if we already know the aes IV -- and it's NOT included in the data)
+ *
+ * @return empty byte[] if error
+ */
+ public static final byte[] decrypt(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data) {
+ int length = data.length;
+
+ CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
+ aesEngine.init(false, aesIVAndKey);
+
+ int minSize = aesEngine.getOutputSize(length);
+ byte[] outBuf = new byte[minSize];
+
+ int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
+
+ try {
+ actualLength += aesEngine.doFinal(outBuf, actualLength);
+ } catch (DataLengthException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ } catch (IllegalStateException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ }
+
+ if (outBuf.length == actualLength) {
+ return outBuf;
+ } else {
+ byte[] result = new byte[actualLength];
+ System.arraycopy(outBuf, 0, result, 0, result.length);
+ return result;
+ }
+ }
+
+ /**
+ * AES decrypt (if we already know the aes IV -- and it's NOT included in the data)
+ *
+ * @return length of decrypted data, -1 if there was an error.
+ */
+ public static final int decrypt(dorkbox.util.crypto.bouncycastle.GCMBlockCipher_ByteBuf aesEngine, ParametersWithIV aesIVAndKey,
+ io.netty.buffer.ByteBuf bufferWithData, io.netty.buffer.ByteBuf bufferTempData, int length) {
+
+ aesEngine.reset();
+ aesEngine.init(false, aesIVAndKey);
+
+ // ignore the start position
+ // we also do NOT want to have the same start position for the altBuffer, since it could then grow larger than the buffer capacity.
+ length = aesEngine.processBytes(bufferWithData, bufferTempData, length);
+
+ try {
+ length += aesEngine.doFinal(bufferTempData);
+ } catch (DataLengthException e) {
+ logger.debug("Unable to perform AES cipher.", e);
+ return -1;
+ } catch (IllegalStateException e) {
+ logger.debug("Unable to perform AES cipher.", e);
+ return -1;
+ } catch (InvalidCipherTextException e) {
+ logger.debug("Unable to perform AES cipher.", e);
+ return -1;
+ }
+
+ bufferTempData.readerIndex(0);
+ bufferTempData.writerIndex(length);
+
+ return length;
+ }
+
+ /**
+ * CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO
+ *
+ * Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted data.
+ *
+ * AES decrypt (if we already know the aes IV -- and it's NOT included in the data)
+ *
+ * @return empty byte[] if error
+ */
+ @Deprecated
+ public static final byte[] decrypt(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data) {
+
+ int length = data.length;
+
+ CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
+ aesEngine.init(false, aesIVAndKey);
+
+ int minSize = aesEngine.getOutputSize(length);
+ byte[] outBuf = new byte[minSize];
+
+ int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
+
+ try {
+ actualLength += aesEngine.doFinal(outBuf, actualLength);
+ } catch (DataLengthException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ } catch (IllegalStateException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return new byte[0];
+ }
+
+ if (outBuf.length == actualLength) {
+ return outBuf;
+ } else {
+ byte[] result = new byte[actualLength];
+ System.arraycopy(outBuf, 0, result, 0, result.length);
+ return result;
+ }
+ }
+
+ /**
+ * AES decrypt from one stream to another.
+ *
+ * @return true if successful
+ */
+ public static final boolean decryptStream(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV,
+ InputStream in, OutputStream out) {
+ byte[] buf = new byte[ivSize];
+ byte[] outbuf = new byte[512];
+
+ CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
+ aesEngine.init(false, aesIVAndKey);
+
+ try {
+ int bytesRead = 0;
+ int bytesProcessed = 0;
+
+ while ((bytesRead = in.read(buf)) >= 0) {
+ bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
+ out.write(outbuf, 0, bytesProcessed);
+ }
+
+ bytesProcessed = aesEngine.doFinal(outbuf, 0);
+
+ out.write(outbuf, 0, bytesProcessed);
+ out.flush();
+ } catch (IOException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (DataLengthException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (IllegalStateException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO
+ *
+ * Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted data.
+ *
+ * AES decrypt from one stream to another.
+ *
+ * @return true if successful
+ */
+ @Deprecated
+ public static final boolean decryptStream(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV,
+ InputStream in, OutputStream out) {
+ byte[] buf = new byte[ivSize];
+ byte[] outbuf = new byte[512];
+
+ CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
+ aesEngine.init(false, aesIVAndKey);
+
+ try {
+ int bytesRead = 0;
+ int bytesProcessed = 0;
+
+ while ((bytesRead = in.read(buf)) >= 0) {
+ bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
+ out.write(outbuf, 0, bytesProcessed);
+ }
+
+ bytesProcessed = aesEngine.doFinal(outbuf, 0);
+
+ out.write(outbuf, 0, bytesProcessed);
+ out.flush();
+ } catch (IOException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (DataLengthException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (IllegalStateException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform AES cipher.", e);
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+
+
+
+
+ // Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
+ @Deprecated
+ public static class RSA {
+
+ public static final AsymmetricCipherKeyPair generateKeyPair(SecureRandom secureRandom, int keyLength) {
+ RSAKeyPairGenerator keyGen = new RSAKeyPairGenerator();
+ RSAKeyGenerationParameters params = new RSAKeyGenerationParameters(new BigInteger("65537"), // public exponent
+ secureRandom, //pnrg
+ keyLength, // key length
+ 8); //the number of iterations of the Miller-Rabin primality test.
+ keyGen.init(params);
+ return keyGen.generateKeyPair();
+ }
+
+
+ /**
+ * RSA encrypt using public key A, and sign data with private key B.
+ *
+ * byte[0][] = encrypted data
+ * byte[1][] = signature
+ *
+ * @return empty byte[][] if error
+ */
+ public static final byte[][] encryptAndSign(AsymmetricBlockCipher rsaEngine, Digest digest,
+ RSAKeyParameters rsaPublicKeyA, RSAPrivateCrtKeyParameters rsaPrivateKeyB,
+ byte[] bytes) {
+ if (bytes.length == 0) {
+ return new byte[0][0];
+ }
+
+ byte[] encryptBytes = encrypt(rsaEngine, rsaPublicKeyA, bytes);
+
+ if (encryptBytes.length == 0) {
+ return new byte[0][0];
+ }
+
+ // now sign it.
+ PSSSigner signer = new PSSSigner(rsaEngine, digest, digest.getDigestSize());
+
+ byte[] signatureRSA = Crypto.RSA.sign(signer, rsaPrivateKeyB, encryptBytes);
+
+ if (signatureRSA.length == 0) {
+ return new byte[0][0];
+ }
+
+ byte[][] total = new byte[2][];
+ total[0] = encryptBytes;
+ total[1] = signatureRSA;
+
+
+ return total;
+ }
+
+ /**
+ * RSA verify data with public key B, and decrypt using private key A.
+ *
+ * @return empty byte[] if error
+ */
+ public static final byte[] decryptAndVerify(AsymmetricBlockCipher rsaEngine, Digest digest,
+ RSAKeyParameters rsaPublicKeyA, RSAPrivateCrtKeyParameters rsaPrivateKeyB,
+ byte[] encryptedData, byte[] signature) {
+ if (encryptedData.length == 0 || signature.length == 0) {
+ return new byte[0];
+ }
+
+ // verify encrypted data.
+ PSSSigner signer = new PSSSigner(rsaEngine, digest, digest.getDigestSize());
+
+ boolean verify = verify(signer, rsaPublicKeyA, signature, encryptedData);
+ if (!verify) {
+ return new byte[0];
+ }
+
+ byte[] decryptBytes = decrypt(rsaEngine, rsaPrivateKeyB, encryptedData);
+
+ return decryptBytes;
+
+ }
+
+ /**
+ * RSA encrypts data with a specified key.
+ *
+ * @return empty byte[] if error
+ */
+ public static final byte[] encrypt(AsymmetricBlockCipher rsaEngine, RSAKeyParameters rsaPublicKey, byte[] bytes) {
+ rsaEngine.init(true, rsaPublicKey);
+
+ try {
+ int inputBlockSize = rsaEngine.getInputBlockSize();
+ if (inputBlockSize < bytes.length) {
+ int outSize = rsaEngine.getOutputBlockSize();
+ int realsize = (int) Math.round(bytes.length/(outSize*1.0D)+.5);
+ ByteBuffer buffer = ByteBuffer.allocateDirect(outSize * realsize);
+
+ int position = 0;
+
+ while (position < bytes.length) {
+ int size = Math.min(inputBlockSize, bytes.length - position);
+
+ byte[] block = rsaEngine.processBlock(bytes, position, size);
+ buffer.put(block, 0, block.length);
+
+ position += size;
+ }
+
+
+ return buffer.array();
+
+ } else {
+ return rsaEngine.processBlock(bytes, 0, bytes.length);
+ }
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform RSA cipher.", e);
+ return new byte[0];
+ }
+ }
+
+ /**
+ * RSA decrypt data with a specified key.
+ *
+ * @return empty byte[] if error
+ */
+ public static final byte[] decrypt(AsymmetricBlockCipher rsaEngine, RSAPrivateCrtKeyParameters rsaPrivateKey, byte[] bytes) {
+ rsaEngine.init(false, rsaPrivateKey);
+
+ try {
+ int inputBlockSize = rsaEngine.getInputBlockSize();
+ if (inputBlockSize < bytes.length) {
+ int outSize = rsaEngine.getOutputBlockSize();
+ int realsize = (int) Math.round(bytes.length/(outSize*1.0D)+.5);
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream(outSize * realsize);
+
+ int position = 0;
+
+ while (position < bytes.length) {
+ int size = Math.min(inputBlockSize, bytes.length - position);
+
+ byte[] block = rsaEngine.processBlock(bytes, position, size);
+ buffer.write(block, 0, block.length);
+
+ position += size;
+ }
+
+
+ return buffer.toByteArray();
+ } else {
+ return rsaEngine.processBlock(bytes, 0, bytes.length);
+ }
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform RSA cipher.", e);
+ return new byte[0];
+ }
+ }
+
+ /**
+ * RSA sign data with a specified key.
+ *
+ * @return empty byte[] if error
+ */
+ public static final byte[] sign(PSSSigner signer, RSAPrivateCrtKeyParameters rsaPrivateKey, byte[] mesg) {
+ signer.init(true, rsaPrivateKey);
+ signer.update(mesg, 0, mesg.length);
+
+ try {
+ return signer.generateSignature();
+ } catch (Exception e) {
+ logger.error("Unable to perform RSA cipher.", e);
+ return new byte[0];
+ }
+ }
+
+ /**
+ * RSA verify data with a specified key.
+ */
+ public static final boolean verify(PSSSigner signer, RSAKeyParameters rsaPublicKey, byte[] sig, byte[] mesg) {
+ signer.init(false, rsaPublicKey);
+ signer.update(mesg, 0, mesg.length);
+
+ return signer.verifySignature(sig);
+ }
+
+ public static boolean compare(RSAKeyParameters publicA, RSAKeyParameters publicB) {
+ if (!publicA.getExponent().equals(publicB.getExponent())) {
+ return false;
+ }
+ if (!publicA.getModulus().equals(publicB.getModulus())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static boolean compare(RSAPrivateCrtKeyParameters private1, RSAPrivateCrtKeyParameters private2) {
+ if (!private1.getModulus().equals(private2.getModulus())) {
+ return false;
+ }
+ if (!private1.getExponent().equals(private2.getExponent())) {
+ return false;
+ }
+ if (!private1.getDP().equals(private2.getDP())) {
+ return false;
+ }
+ if (!private1.getDQ().equals(private2.getDQ())) {
+ return false;
+ }
+ if (!private1.getP().equals(private2.getP())) {
+ return false;
+ }
+ if (!private1.getPublicExponent().equals(private2.getPublicExponent())) {
+ return false;
+ }
+ if (!private1.getQ().equals(private2.getQ())) {
+ return false;
+ }
+ if (!private1.getQInv().equals(private2.getQInv())) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+
+
+
+ // Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
+ @Deprecated
+ public static class DSA {
+ /**
+ * Generates the DSA key (using RSA and SHA1)
+ *
+ * Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
+ */
+ public static final AsymmetricCipherKeyPair generateKeyPair(SecureRandom secureRandom, int keyLength) {
+ DSAKeyPairGenerator keyGen = new DSAKeyPairGenerator();
+
+ DSAParametersGenerator dsaParametersGenerator = new DSAParametersGenerator();
+ dsaParametersGenerator.init(keyLength, 20, secureRandom);
+ DSAParameters generateParameters = dsaParametersGenerator.generateParameters();
+
+ DSAKeyGenerationParameters params = new DSAKeyGenerationParameters(secureRandom,
+ generateParameters);
+ keyGen.init(params);
+ return keyGen.generateKeyPair();
+ }
+
+ /**
+ * The message will have the SHA1 hash calculated and used for the signature.
+ *
+ * Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
+ *
+ * The returned signature is the {r,s} signature array.
+ */
+ public static final BigInteger[] generateSignature(DSAPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] message) {
+ ParametersWithRandom param = new ParametersWithRandom(privateKey, secureRandom);
+
+ DSASigner dsa = new DSASigner();
+
+ dsa.init(true, param);
+
+
+ SHA1Digest sha1Digest = new SHA1Digest();
+ byte[] checksum = new byte[sha1Digest.getDigestSize()];
+
+ sha1Digest.update(message, 0, message.length);
+ sha1Digest.doFinal(checksum, 0);
+
+
+ BigInteger[] signature = dsa.generateSignature(checksum);
+ return signature;
+ }
+
+ /**
+ * The message will have the SHA1 hash calculated and used for the signature.
+ *
+ * Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
+ *
+ * @param signature is the {r,s} signature array.
+ * @return true if the signature is valid
+ */
+ public static final boolean verifySignature(DSAPublicKeyParameters publicKey, byte[] message, BigInteger[] signature) {
+ SHA1Digest sha1Digest = new SHA1Digest();
+ byte[] checksum = new byte[sha1Digest.getDigestSize()];
+
+ sha1Digest.update(message, 0, message.length);
+ sha1Digest.doFinal(checksum, 0);
+
+
+ DSASigner dsa = new DSASigner();
+
+ dsa.init(false, publicKey);
+
+ boolean verifySignature = dsa.verifySignature(checksum, signature[0], signature[1]);
+ return verifySignature;
+ }
+ }
+
+
+
+ public static class ECC {
+ static final String ECC_NAME = "EC";
+ public static final String p521_curve = "secp521r1";
+
+ // more info about ECC from: http://www.johannes-bauer.com/compsci/ecc/?menuid=4
+ // http://stackoverflow.com/questions/7419183/problems-implementing-ecdh-on-android-using-bouncycastle
+ // http://tools.ietf.org/html/draft-jivsov-openpgp-ecc-06#page-4
+ // http://www.nsa.gov/ia/programs/suiteb_cryptography/
+ // https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java
+ // http://nelenkov.blogspot.com/2011/12/using-ecdh-on-android.html
+ // http://www.secg.org/collateral/sec1_final.pdf
+
+ public static final int macSize = 512;
+
+ /**
+ * Uses SHA512
+ */
+ public static final IESEngine createEngine() {
+ return new IESEngine(new ECDHCBasicAgreement(),
+ new KDF2BytesGenerator(new SHA384Digest()),
+ new HMac(new SHA512Digest()));
+ }
+
+ /**
+ * Uses SHA512
+ */
+ public static final IESEngine createEngine(PaddedBufferedBlockCipher aesEngine) {
+ return new IESEngine(new ECDHCBasicAgreement(),
+ new KDF2BytesGenerator(new SHA384Digest()),
+ new HMac(new SHA512Digest()),
+ aesEngine);
+ }
+
+ /**
+ * These parameters are shared between the two parties. These are a NONCE (use ONCE number!!)
+ */
+ public static final IESParameters generateSharedParameters(SecureRandom secureRandom) {
+
+ int macSize = Crypto.ECC.macSize; // must be the MAC size
+
+ // MUST be random EACH TIME encrypt/sign happens!
+ byte[] derivation = new byte[macSize/8];
+ byte[] encoding = new byte[macSize/8];
+
+ secureRandom.nextBytes(derivation);
+ secureRandom.nextBytes(encoding);
+
+ return new IESParameters(derivation, encoding, macSize);
+ }
+
+ /**
+ * AES-256 ONLY!
+ */
+ public static IESWithCipherParameters generateSharedParametersWithCipher(SecureRandom secureRandom) {
+ int macSize = Crypto.ECC.macSize; // must be the MAC size
+
+ byte[] derivation = new byte[macSize/8]; // MUST be random EACH TIME encrypt/sign happens!
+ byte[] encoding = new byte[macSize/8];
+
+ secureRandom.nextBytes(derivation);
+ secureRandom.nextBytes(encoding);
+
+ return new IESWithCipherParameters(derivation, encoding, macSize, 256);
+ }
+
+
+ public static final AsymmetricCipherKeyPair generateKeyPair(String eccCurveName, SecureRandom secureRandom) {
+ ECParameterSpec eccSpec = ECNamedCurveTable.getParameterSpec(eccCurveName);
+
+ return generateKeyPair(eccSpec, secureRandom);
+ }
+
+ public static final AsymmetricCipherKeyPair generateKeyPair(ECParameterSpec eccSpec, SecureRandom secureRandom) {
+ ECKeyGenerationParameters ecParams = new ECKeyGenerationParameters(new ECDomainParameters(eccSpec.getCurve() ,
+ eccSpec.getG(),
+ eccSpec.getN()),
+ secureRandom);
+
+ ECKeyPairGenerator ecKeyGen = new ECKeyPairGenerator();
+ ecKeyGen.init(ecParams);
+
+ return ecKeyGen.generateKeyPair();
+ }
+
+ /**
+ * ECC encrypts data with a specified key.
+ *
+ * @return empty byte[] if error
+ */
+ public static final byte[] encrypt(IESEngine eccEngine, CipherParameters private1, CipherParameters public2,
+ IESParameters cipherParams, byte[] message) {
+
+ eccEngine.init(true, private1, public2, cipherParams);
+
+ try {
+ return eccEngine.processBlock(message, 0, message.length);
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform ECC cipher.", e);
+ return new byte[0];
+ }
+ }
+
+ /**
+ * ECC decrypt data with a specified key.
+ *
+ * @return empty byte[] if error
+ */
+ public static final byte[] decrypt(IESEngine eccEngine, CipherParameters private2, CipherParameters public1,
+ IESParameters cipherParams, byte[] encrypted) {
+
+ eccEngine.init(false, private2, public1, cipherParams);
+
+ try {
+ return eccEngine.processBlock(encrypted, 0, encrypted.length);
+ } catch (InvalidCipherTextException e) {
+ logger.error("Unable to perform ECC cipher.", e);
+ return new byte[0];
+ }
+ }
+
+ public static final boolean compare(ECPrivateKeyParameters privateA, ECPrivateKeyParameters privateB) {
+ ECDomainParameters parametersA = privateA.getParameters();
+ ECDomainParameters parametersB = privateB.getParameters();
+
+ // is it the same curve?
+ boolean equals = parametersA.getCurve().equals(parametersB.getCurve());
+ if (!equals) {
+ return false;
+ }
+
+ equals = parametersA.getG().equals(parametersB.getG());
+ if (!equals) {
+ return false;
+ }
+
+
+ equals = parametersA.getH().equals(parametersB.getH());
+ if (!equals) {
+ return false;
+ }
+
+ equals = parametersA.getN().equals(parametersB.getN());
+ if (!equals) {
+ return false;
+ }
+
+ equals = privateA.getD().equals(privateB.getD());
+
+ return equals;
+ }
+
+ /**
+ * @return true if publicA and publicB are NOT NULL, and are both equal to eachother
+ */
+ public static final boolean compare(ECPublicKeyParameters publicA, ECPublicKeyParameters publicB) {
+ if (publicA == null || publicB == null) {
+ return false;
+ }
+
+
+ ECDomainParameters parametersA = publicA.getParameters();
+ ECDomainParameters parametersB = publicB.getParameters();
+
+ // is it the same curve?
+ boolean equals = parametersA.getCurve().equals(parametersB.getCurve());
+ if (!equals) {
+ return false;
+ }
+
+ equals = parametersA.getG().equals(parametersB.getG());
+ if (!equals) {
+ return false;
+ }
+
+
+ equals = parametersA.getH().equals(parametersB.getH());
+ if (!equals) {
+ return false;
+ }
+
+ equals = parametersA.getN().equals(parametersB.getN());
+ if (!equals) {
+ return false;
+ }
+
+
+ ECPoint normalizeA = publicA.getQ().normalize();
+ ECPoint normalizeB = publicB.getQ().normalize();
+
+
+ ECFieldElement xCoordA = normalizeA.getXCoord();
+ ECFieldElement xCoordB = normalizeB.getXCoord();
+
+ equals = xCoordA.equals(xCoordB);
+ if (!equals) {
+ return false;
+ }
+
+ ECFieldElement yCoordA = normalizeA.getYCoord();
+ ECFieldElement yCoordB = normalizeB.getYCoord();
+
+ equals = yCoordA.equals(yCoordB);
+ if (!equals) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static final boolean compare(IESParameters cipherAParams, IESParameters cipherBParams) {
+ if (!Arrays.equals(cipherAParams.getDerivationV(), cipherBParams.getDerivationV())) {
+ return false;
+ }
+ if (!Arrays.equals(cipherAParams.getEncodingV(), cipherBParams.getEncodingV())) {
+ return false;
+ }
+
+ if (cipherAParams.getMacKeySize() != cipherBParams.getMacKeySize()) {
+ return false;
+ }
+ return true;
+ }
+
+ public static final boolean compare(IESWithCipherParameters cipherAParams, IESWithCipherParameters cipherBParams) {
+ if (cipherAParams.getCipherKeySize() != cipherBParams.getCipherKeySize()) {
+ return false;
+ }
+
+ // only need to cast one side.
+ return compare((IESParameters)cipherAParams, cipherBParams);
+ }
+
+
+ /**
+ * The message will have the (digestName) hash calculated and used for the signature.
+ *
+ * The returned signature is the {r,s} signature array.
+ */
+ public static final BigInteger[] generateSignature(String digestName, ECPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] bytes) {
+
+ Digest digest = DigestFactory.getDigest(digestName);
+
+ byte[] checksum = new byte[digest.getDigestSize()];
+
+ digest.update(bytes, 0, bytes.length);
+ digest.doFinal(checksum, 0);
+
+ return generateSignatureForHash(privateKey, secureRandom, checksum);
+ }
+
+ /**
+ * The message will use the bytes AS THE HASHED VALUE to calculate the signature.
+ *
+ * The returned signature is the {r,s} signature array.
+ */
+ public static final BigInteger[] generateSignatureForHash(ECPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] hashBytes) {
+
+ ParametersWithRandom param = new ParametersWithRandom(privateKey, secureRandom);
+
+ ECDSASigner ecdsa = new ECDSASigner();
+ ecdsa.init(true, param);
+
+ BigInteger[] signature = ecdsa.generateSignature(hashBytes);
+ return signature;
+ }
+
+ /**
+ * The message will have the (digestName) hash calculated and used for the signature.
+ *
+ * @param signature is the {r,s} signature array.
+ * @return true if the signature is valid
+ */
+ public static final boolean verifySignature(String digestName, ECPublicKeyParameters publicKey, byte[] message, BigInteger[] signature) {
+
+ Digest digest = DigestFactory.getDigest(digestName);
+
+ byte[] checksum = new byte[digest.getDigestSize()];
+
+ digest.update(message, 0, message.length);
+ digest.doFinal(checksum, 0);
+
+
+ return verifySignatureHash(publicKey, checksum, signature);
+ }
+
+ /**
+ * The provided hash will be used in the signature verification.
+ *
+ * @param signature is the {r,s} signature array.
+ * @return true if the signature is valid
+ */
+ public static final boolean verifySignatureHash(ECPublicKeyParameters publicKey, byte[] hash, BigInteger[] signature) {
+
+ ECDSASigner ecdsa = new ECDSASigner();
+ ecdsa.init(false, publicKey);
+
+
+ boolean verifySignature = ecdsa.verifySignature(hash, signature[0], signature[1]);
+ return verifySignature;
+ }
+ }
+
+
+
+ /**
+ * An implementation of the scrypt
+ * key derivation function.
+ */
+ public static class SCrypt {
+
+ /**
+ * Hash the supplied plaintext password and generate output using default parameters
+ *
+ * The password chars are no longer valid after this call
+ *
+ * @param password Password.
+ * @param salt Salt parameter
+ */
+ public static final String encrypt(char[] password) {
+ return encrypt(password, 16384, 32, 1);
+ }
+
+ /**
+ * Hash the supplied plaintext password and generate output using default parameters
+ *
+ * The password chars are no longer valid after this call
+ *
+ * @param password Password.
+ * @param salt Salt parameter
+ */
+ public static final String encrypt(char[] password, byte[] salt) {
+ return encrypt(password, salt, 16384, 32, 1, 64);
+ }
+
+ /**
+ * Hash the supplied plaintext password and generate output.
+ *
+ * The password chars are no longer valid after this call
+ *
+ * @param password Password.
+ * @param N CPU cost parameter.
+ * @param r Memory cost parameter.
+ * @param p Parallelization parameter.
+ *
+ * @return The hashed password.
+ */
+ public static final String encrypt(char[] password, int N, int r, int p) {
+ SecureRandom secureRandom = new SecureRandom();
+ byte[] salt = new byte[32];
+ secureRandom.nextBytes(salt);
+
+ return encrypt(password, salt, N, r, p, 64);
+ }
+
+ /**
+ * Hash the supplied plaintext password and generate output.
+ *
+ * The password chars are no longer valid after this call
+ *
+ * @param password Password.
+ * @param salt Salt parameter
+ * @param N CPU cost parameter.
+ * @param r Memory cost parameter.
+ * @param p Parallelization parameter.
+ * @param dkLen Intended length of the derived key.
+ *
+ * @return The hashed password.
+ */
+ public static final String encrypt(char[] password, byte[] salt, int N, int r, int p, int dkLen) {
+ // Note: this saves the char array in UTF-16 format of bytes.
+ // can't use password after this as it's been changed to '*'
+ byte[] passwordBytes = Crypto.Util.charToBytesPassword(password);
+
+ byte[] derived = encrypt(passwordBytes, salt, N, r, p, dkLen);
+
+ String params = Integer.toString(log2(N) << 16 | r << 8 | p, 16);
+
+ StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2);
+ sb.append("$s0$").append(params).append('$');
+ sb.append(dorkbox.util.Base64Fast.encodeToString(salt, false)).append('$');
+ sb.append(dorkbox.util.Base64Fast.encodeToString(derived, false));
+
+ return sb.toString();
+ }
+
+ /**
+ * Compare the supplied plaintext password to a hashed password.
+ *
+ * @param password Plaintext password.
+ * @param hashed scrypt hashed password.
+ *
+ * @return true if password matches hashed value.
+ */
+ public static final boolean verify(char[] password, String hashed) {
+ // Note: this saves the char array in UTF-16 format of bytes.
+ // can't use password after this as it's been changed to '*'
+ byte[] passwordBytes = Crypto.Util.charToBytesPassword(password);
+
+ String[] parts = hashed.split("\\$");
+
+ if (parts.length != 5 || !parts[1].equals("s0")) {
+ throw new IllegalArgumentException("Invalid hashed value");
+ }
+
+ int params = Integer.parseInt(parts[2], 16);
+ byte[] salt = dorkbox.util.Base64Fast.decodeFast(parts[3]);
+ byte[] derived0 = dorkbox.util.Base64Fast.decodeFast(parts[4]);
+
+ int N = (int) Math.pow(2, params >> 16 & 0xFF);
+ int r = params >> 8 & 0xFF;
+ int p = params & 0xFF;
+
+ int length = derived0.length;
+ if (length == 0) {
+ return false;
+ }
+
+ byte[] derived1 = encrypt(passwordBytes, salt, N, r, p, length);
+
+ if (length != derived1.length) {
+ return false;
+ }
+
+ int result = 0;
+ for (int i = 0; i < length; i++) {
+ result |= derived0[i] ^ derived1[i];
+ }
+
+ return result == 0;
+ }
+
+ private static final int log2(int n) {
+ int log = 0;
+ if ((n & 0xFFFF0000 ) != 0) { n >>>= 16; log = 16; }
+ if (n >= 256) { n >>>= 8; log += 8; }
+ if (n >= 16 ) { n >>>= 4; log += 4; }
+ if (n >= 4 ) { n >>>= 2; log += 2; }
+ return log + (n >>> 1);
+ }
+
+
+ /**
+ * Pure Java implementation of the scrypt KDF.
+ *
+ * @param password Password.
+ * @param salt Salt.
+ * @param N CPU cost parameter.
+ * @param r Memory cost parameter.
+ * @param p Parallelization parameter.
+ * @param dkLen Intended length of the derived key.
+ *
+ * @return The derived key.
+ */
+ public static byte[] encrypt(byte[] password, byte[] salt, int N, int r, int p, int dkLen) {
+ if (N == 0 || (N & N - 1) != 0) {
+ throw new IllegalArgumentException("N must be > 0 and a power of 2");
+ }
+
+ if (N > Integer.MAX_VALUE / 128 / r) {
+ throw new IllegalArgumentException("Parameter N is too large");
+ }
+ if (r > Integer.MAX_VALUE / 128 / p) {
+ throw new IllegalArgumentException("Parameter r is too large");
+ }
+
+ try {
+ return org.bouncycastle.crypto.generators.SCrypt.generate(password, salt, N, r, p, dkLen);
+ } finally {
+ // now zero out the bytes in password.
+ Arrays.fill(password, (byte)0);
+ }
+ }
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/CryptoX509.java b/Dorkbox-Util/src/dorkbox/util/crypto/CryptoX509.java
new file mode 100644
index 0000000..71d308d
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/CryptoX509.java
@@ -0,0 +1,1292 @@
+package dorkbox.util.crypto;
+
+
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.DSAParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Date;
+import java.util.Enumeration;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.BERSet;
+import org.bouncycastle.asn1.DERBMPString;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.SignedData;
+import org.bouncycastle.asn1.cms.SignerIdentifier;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.asn1.x509.DSAParameter;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSTypedData;
+import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.dsa.BCDSAPublicKey;
+import org.bouncycastle.jcajce.provider.asymmetric.dsa.BCDSAPublicKeyAccessor;
+import org.bouncycastle.jcajce.provider.asymmetric.dsa.DSAUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey;
+import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKeyAccessor;
+import org.bouncycastle.jcajce.provider.asymmetric.rsa.RSAUtil;
+import org.bouncycastle.jcajce.provider.asymmetric.x509.X509Accessor;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.provider.JCEECPublicKey;
+import org.bouncycastle.jce.provider.X509CertificateObject;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.ContentVerifierProvider;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcDSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcDSAContentVerifierProviderBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import dorkbox.util.Base64Fast;
+import dorkbox.util.crypto.signers.BcECDSAContentSignerBuilder;
+import dorkbox.util.crypto.signers.BcECDSAContentVerifierProviderBuilder;
+
+public class CryptoX509 {
+
+ private static final Logger logger = LoggerFactory.getLogger(CryptoX509.class);
+
+ public static final void addProvider() {
+ // make sure we only add it once (in case it's added elsewhere...)
+ Provider provider = Security.getProvider("BC");
+ if (provider == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ }
+
+ public static class Util {
+
+ /**
+ * @return true if saving the x509 certificate to a PEM format file was successful
+ */
+ public static boolean convertToPemFile(X509Certificate x509cert, String fileName) {
+ boolean failed = false;
+ Writer output = null;
+
+ try {
+ String lineSeparator = "\r\n";
+
+ String cert_begin = "-----BEGIN CERTIFICATE-----";
+ String cert_end = "-----END CERTIFICATE-----";
+
+ byte[] derCert = x509cert.getEncoded();
+ char[] encodeToChar = Base64Fast.encodeToChar(derCert, false);
+
+ int newLineCount = encodeToChar.length/64;
+
+ int length = encodeToChar.length;
+
+ output = new BufferedWriter(new FileWriter("dorkbox.crt", false),
+ cert_begin.length() + cert_end.length() + length + newLineCount + 3);
+
+ output.write(cert_begin);
+ output.write(lineSeparator);
+
+ int copyCount = 64;
+ for (int i=0;i length) {
+ copyCount = length - i;
+ }
+
+ output.write(encodeToChar, i, copyCount);
+ output.write(lineSeparator);
+ }
+ output.write(cert_end);
+ output.write(lineSeparator);
+ } catch (Exception e) {
+ logger.error("Error during conversion.", e);
+ failed = true;
+ } finally {
+ if (output != null) {
+ try {
+ output.close();
+ } catch (IOException e) {
+ logger.error("Error closing resource.", e);
+ }
+ }
+ }
+
+ return !failed;
+ }
+
+ public static String convertToPem(X509Certificate x509cert) throws CertificateEncodingException {
+ String lineSeparator = "\r\n";
+
+ String cert_begin = "-----BEGIN CERTIFICATE-----";
+ String cert_end = "-----END CERTIFICATE-----";
+
+ byte[] derCert = x509cert.getEncoded();
+ char[] encodeToChar = Base64Fast.encodeToChar(derCert, false);
+
+ int newLineCount = encodeToChar.length/64;
+
+ int length = encodeToChar.length;
+ int lastIndex = 0;
+ StringBuilder sb = new StringBuilder(cert_begin.length() + cert_end.length() + length + newLineCount + 2);
+
+ sb.append(cert_begin);
+ sb.append(lineSeparator);
+ for (int i=64;i length) {
+ i = length;
+ }
+
+ sb.append(encodeToChar, lastIndex, i);
+ sb.append(lineSeparator);
+ lastIndex = i;
+ }
+ sb.append(cert_end);
+
+ return sb.toString();
+ }
+
+ public static String getDigestNameFromCert(X509CertificateHolder x509CertificateHolder) {
+ String digestName = CryptoX509.Util.getDigestNameFromSigAlgId(x509CertificateHolder.getSignatureAlgorithm().getAlgorithm());
+ return digestName;
+ }
+
+ public static String getDigestNameFromSigAlgId(ASN1ObjectIdentifier algorithm) {
+ String digest = null;
+ try {
+ // have to use reflection in order to access the DIGEST method used by the key.
+ DefaultCMSSignatureAlgorithmNameGenerator defaultCMSSignatureAlgorithmNameGenerator = new DefaultCMSSignatureAlgorithmNameGenerator();
+ Method declaredMethod = DefaultCMSSignatureAlgorithmNameGenerator.class.getDeclaredMethod("getDigestAlgName", new Class>[] {ASN1ObjectIdentifier.class});
+ declaredMethod.setAccessible(true);
+ digest = (String) declaredMethod.invoke(defaultCMSSignatureAlgorithmNameGenerator, algorithm);
+ } catch (Throwable t) {
+ throw new RuntimeException("Weird error using reflection to get the digest name: " + algorithm.getId() + t.getMessage());
+ }
+
+ if (algorithm.getId().equals(digest)) {
+ throw new RuntimeException("Unable to get digest name from algorithm ID: " + algorithm.getId());
+ }
+
+ return digest;
+ }
+
+
+// @SuppressWarnings("rawtypes")
+// public static void verify(JarFile jf, X509Certificate[] trustedCaCerts) throws IOException, CertificateException {
+// Vector entriesVec = new Vector();
+//
+// // Ensure there is a manifest file
+// Manifest man = jf.getManifest();
+// if (man == null) {
+// throw new SecurityException("The JAR is not signed");
+// }
+//
+// // Ensure all the entries' signatures verify correctly
+// byte[] buffer = new byte[8192];
+// Enumeration entries = jf.entries();
+//
+// while (entries.hasMoreElements()) {
+// JarEntry je = (JarEntry) entries.nextElement();
+// entriesVec.addElement(je);
+// InputStream is = jf.getInputStream(je);
+// @SuppressWarnings("unused")
+// int n;
+// while ((n = is.read(buffer, 0, buffer.length)) != -1) {
+// // we just read. this will throw a SecurityException
+// // if a signature/digest check fails.
+// }
+// is.close();
+// }
+// jf.close();
+//
+// // Get the list of signer certificates
+// Enumeration e = entriesVec.elements();
+// while (e.hasMoreElements()) {
+// JarEntry je = (JarEntry) e.nextElement();
+//
+// if (je.isDirectory()) {
+// continue;
+// }
+// // Every file must be signed - except
+// // files in META-INF
+// Certificate[] certs = je.getCertificates();
+// if (certs == null || certs.length == 0) {
+// if (!je.getName().startsWith("META-INF")) {
+// throw new SecurityException("The JCE framework has unsigned class files.");
+// }
+// } else {
+// // Check whether the file
+// // is signed as expected.
+// // The framework may be signed by
+// // multiple signers. At least one of
+// // the signers must be a trusted signer.
+//
+// // First, determine the roots of the certificate chains
+// X509Certificate[] chainRoots = getChainRoots(certs);
+// boolean signedAsExpected = false;
+//
+// for (int i = 0; i < chainRoots.length; i++) {
+// if (isTrusted(chainRoots[i], trustedCaCerts)) {
+// signedAsExpected = true;
+// break;
+// }
+// }
+//
+// if (!signedAsExpected) {
+// throw new SecurityException("The JAR is not signed by a trusted signer");
+// }
+// }
+// }
+// }
+
+ public static boolean isTrusted(X509Certificate cert, X509Certificate[] trustedCaCerts) {
+ // Return true iff either of the following is true:
+ // 1) the cert is in the trustedCaCerts.
+ // 2) the cert is issued by a trusted CA.
+
+ // Check whether the cert is in the trustedCaCerts
+ for (int i = 0; i < trustedCaCerts.length; i++) {
+ // If the cert has the same SubjectDN
+ // as a trusted CA, check whether
+ // the two certs are the same.
+ if (cert.getSubjectDN().equals(trustedCaCerts[i].getSubjectDN())) {
+ if (cert.equals(trustedCaCerts[i])) {
+ return true;
+ }
+ }
+ }
+
+ // Check whether the cert is issued by a trusted CA.
+ // Signature verification is expensive. So we check
+ // whether the cert is issued
+ // by one of the trusted CAs if the above loop failed.
+ for (int i = 0; i < trustedCaCerts.length; i++) {
+ // If the issuer of the cert has the same name as
+ // a trusted CA, check whether that trusted CA
+ // actually issued the cert.
+ if (cert.getIssuerDN().equals(trustedCaCerts[i].getSubjectDN())) {
+ try {
+ cert.verify(trustedCaCerts[i].getPublicKey());
+ return true;
+ } catch (Exception e) {
+ // Do nothing.
+ }
+ }
+ }
+
+ return false;
+ }
+
+// public static X509Certificate[] getChainRoots(Certificate[] certs) {
+// Vector result = new Vector(3);
+// // choose a Vector size that seems reasonable
+// for (int i = 0; i < certs.length - 1; i++) {
+// if (!((X509Certificate) certs[i + 1]).getSubjectDN().equals(
+// ((X509Certificate) certs[i]).getIssuerDN())) {
+// // We've reached the end of a chain
+// result.addElement((X509Certificate) certs[i]);
+// }
+// }
+//
+// // The final entry in the certs array is always
+// // a "root" certificate
+// result.addElement((X509Certificate) certs[certs.length - 1]);
+// X509Certificate[] ret = new X509Certificate[result.size()];
+// result.copyInto(ret);
+//
+// return ret;
+// }
+ }
+
+
+ public static class DSA {
+ static {
+ addProvider();
+ }
+
+ /**
+ * Creates a X509 certificate holder object.
+ *
+ * Look at BCStyle for a list of all valid X500 Names.
+ */
+ public static X509CertificateHolder createCertHolder(Date startDate, Date expiryDate,
+ X500Name issuerName, X500Name subjectName, BigInteger serialNumber,
+ DSAPrivateKeyParameters privateKey, DSAPublicKeyParameters publicKey) {
+
+ String signatureAlgorithm = "SHA1withDSA";
+
+
+ AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgorithm);
+ AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+
+ SubjectPublicKeyInfo subjectPublicKeyInfo = null;
+ DSAParameters parameters = publicKey.getParameters();
+ try {
+ byte[] encoded = new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa,
+ new DSAParameter(parameters.getP(),
+ parameters.getQ(),
+ parameters.getG())),
+ new ASN1Integer(publicKey.getY())).getEncoded(ASN1Encoding.DER);
+
+ ASN1Sequence seq = (ASN1Sequence)ASN1Primitive.fromByteArray(encoded);
+ subjectPublicKeyInfo = new SubjectPublicKeyInfo(seq);
+ } catch (IOException e) {
+ logger.error("Error during DSA.", e);
+ return null;
+ }
+
+ X509v3CertificateBuilder v3CertBuilder = new X509v3CertificateBuilder(issuerName,
+ serialNumber, startDate, expiryDate,
+ subjectName, subjectPublicKeyInfo);
+
+ BcDSAContentSignerBuilder contentSignerBuilder = new BcDSAContentSignerBuilder(sigAlgId, digAlgId);
+
+ ContentSigner build = null;
+ try {
+ build = contentSignerBuilder.build(privateKey);
+ } catch (OperatorCreationException e) {
+ logger.error("Error creating certificate.", e);
+ return null;
+ }
+
+ X509CertificateHolder certHolder = v3CertBuilder.build(build);
+ return certHolder;
+ }
+
+
+ /**
+ * Verifies that the certificate is legitimate.
+ *
+ * MUST have BouncyCastle provider loaded by the security manager!
+ *
+ * @return true if it was a valid cert.
+ */
+ public static final boolean validate(X509CertificateHolder x509CertificateHolder) {
+ try {
+
+ // this is unique in that it verifies that the certificate is a LEGIT certificate, but not necessarily
+ // valid during this time period.
+ ContentVerifierProvider contentVerifierProvider = new BcDSAContentVerifierProviderBuilder(
+ new DefaultDigestAlgorithmIdentifierFinder()).build(x509CertificateHolder);
+
+ boolean signatureValid = x509CertificateHolder.isSignatureValid(contentVerifierProvider);
+
+ if (!signatureValid) {
+ return false;
+ }
+
+
+ org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory certificateFactory = new org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory();
+ java.security.cert.Certificate certificate = certificateFactory.engineGenerateCertificate(new ByteArrayInputStream(x509CertificateHolder.getEncoded()));
+ // Note: this requires the BC provider to be loaded!
+ if (certificate == null || certificate.getPublicKey() == null) {
+ return false;
+ }
+
+ // Verify the TIME/DATE of the certificate
+ X509Accessor.verifyDate(certificate);
+
+ // if we get here, it means that our cert is LEGIT and VALID.
+ return true;
+
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ /**
+ * Verifies the given x509 based signature against the OPTIONAL original public key. If not specified, then
+ * the public key from the signature is used.
+ *
+ * MUST have BouncyCastle provider loaded by the security manager!
+ *
+ * @return true if the signature was valid.
+ */
+ public static boolean verifySignature(byte[] signatureBytes, DSAPublicKeyParameters optionalOriginalPublicKey) {
+ ASN1InputStream asn1InputStream = null;
+ try {
+ asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(signatureBytes));
+ ASN1Primitive signatureASN = asn1InputStream.readObject();
+ ASN1Sequence seq = ASN1Sequence.getInstance(signatureASN);
+ ASN1TaggedObject tagged = (ASN1TaggedObject)seq.getObjectAt(1);
+
+ // Extract certificates
+ SignedData newSignedData = SignedData.getInstance(tagged.getObject());
+
+ @SuppressWarnings("rawtypes")
+ Enumeration newSigOjects = newSignedData.getCertificates().getObjects();
+ Object newSigElement = newSigOjects.nextElement();
+
+ if (newSigElement instanceof DERSequence) {
+ DERSequence newSigDERElement = (DERSequence) newSigElement;
+ InputStream newSigIn = new ByteArrayInputStream(newSigDERElement.getEncoded());
+
+ org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory certificateFactory = new org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory();
+ java.security.cert.Certificate engineGenerateCert = certificateFactory.engineGenerateCertificate(newSigIn);
+
+ PublicKey publicKey2 = engineGenerateCert.getPublicKey();
+
+ if (optionalOriginalPublicKey != null) {
+ // absolutely RETARDED that we have package private constructors .. but fortunately, we can get around that
+ DSAParameters parameters = optionalOriginalPublicKey.getParameters();
+ BCDSAPublicKey origPublicKey = BCDSAPublicKeyAccessor.newInstance(optionalOriginalPublicKey.getY(),
+ new DSAParameterSpec(parameters.getP(),
+ parameters.getQ(),
+ parameters.getG()));
+ boolean equals = origPublicKey.equals(publicKey2);
+ if (!equals) {
+ return false;
+ }
+ publicKey2 = origPublicKey;
+ }
+
+ engineGenerateCert.verify(publicKey2);
+ }
+ } catch (Throwable t) {
+ return false;
+ } finally {
+ if (asn1InputStream != null) {
+ try {
+ asn1InputStream.close();
+ } catch (IOException e) {
+ logger.error("Error closing stream during DSA.", e);
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+
+ public static class RSA {
+ static {
+ addProvider();
+ }
+
+// public static class CertificateAuthority {
+// public static X509Certificate generateCert(KeyFactory factory, Date startDate, Date expiryDate,
+// String issuer, String subject, String friendlyName,
+// RSAKeyParameters publicKey, RSAPrivateCrtKeyParameters privateKey) throws InvalidKeySpecException, IOException, InvalidKeyException, OperatorCreationException {
+//
+// return CryptoX509.RSA.generateCert(factory, startDate, expiryDate, new X500Name(issuer), new X500Name(subject), friendlyName, publicKey, privateKey, null);
+// }
+//
+// public static X509Certificate generateCert(KeyFactory factory, Date startDate, Date expiryDate,
+// X509Principal issuer, String subject, String friendlyName,
+// RSAKeyParameters publicKey, RSAPrivateCrtKeyParameters privateKey) throws InvalidKeySpecException, InvalidKeyException, IOException, OperatorCreationException {
+//
+// return CryptoX509.RSA.generateCert(factory, startDate, expiryDate, X500Name.getInstance(issuer), new X500Name(subject), friendlyName, publicKey, privateKey, null);
+// }
+// }
+//
+//
+// public static class IntermediateAuthority {
+// public static X509Certificate generateCert(KeyFactory factory, Date startDate, Date expiryDate,
+// String issuer, String subject, String friendlyName,
+// RSAKeyParameters publicKey, RSAPrivateCrtKeyParameters privateKey,
+// X509Certificate caCertificate) throws InvalidKeySpecException, IOException, InvalidKeyException, OperatorCreationException {
+//
+// return CryptoX509.RSA.generateCert(factory, startDate, expiryDate, new X500Name(issuer), new X500Name(subject), friendlyName, publicKey, privateKey, caCertificate);
+// }
+//
+// public static X509Certificate generateCert(KeyFactory factory, Date startDate, Date expiryDate,
+// X509Principal issuer, String subject, String friendlyName,
+// RSAKeyParameters publicKey, RSAPrivateCrtKeyParameters privateKey,
+// X509Certificate caCertificate) throws InvalidKeySpecException, InvalidKeyException, IOException, OperatorCreationException {
+//
+// return CryptoX509.RSA.generateCert(factory, startDate, expiryDate, X500Name.getInstance(issuer), new X500Name(subject), friendlyName, publicKey, privateKey, caCertificate);
+// }
+// }
+//
+ public static class CertificateAuthrority {
+ public static X509Certificate generateCert(KeyFactory factory, Date startDate, Date endDate,
+ String subject, String friendlyName,
+ RSAKeyParameters publicKey, RSAPrivateCrtKeyParameters privateKey) {
+
+ String signatureAlgorithm = "SHA1withRSA";
+
+ AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgorithm); // specify it's RSA
+ AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId); // specify SHA
+
+ try {
+ // JCE format needed for the certificate - because getEncoded() is necessary...
+ PublicKey jcePublicKey = convertToJCE(factory, publicKey);
+// PrivateKey jcePrivateKey = convertToJCE(factory, publicKey, privateKey);
+
+ SubjectPublicKeyInfo subjectPublicKeyInfo = createSubjectPublicKey(jcePublicKey);
+ X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(new X500Name(subject), BigInteger.valueOf(System.currentTimeMillis()),
+ startDate, endDate, new X500Name(subject),
+ subjectPublicKeyInfo);
+
+ //
+ // extensions
+ //
+ JcaX509ExtensionUtils jcaX509ExtensionUtils = new JcaX509ExtensionUtils(); // SHA1
+ SubjectKeyIdentifier createSubjectKeyIdentifier = jcaX509ExtensionUtils.createSubjectKeyIdentifier(subjectPublicKeyInfo);
+
+ certBuilder.addExtension(Extension.subjectKeyIdentifier,
+ false,
+ createSubjectKeyIdentifier);
+
+ certBuilder.addExtension(Extension.basicConstraints,
+ true,
+ new BasicConstraints(1));
+
+
+ ContentSigner hashSigner = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(privateKey);
+ X509CertificateHolder certHolder = certBuilder.build(hashSigner);
+ X509CertificateObject certificate = new X509CertificateObject(Certificate.getInstance(certHolder.getEncoded()));
+
+ certificate.verify(jcePublicKey);
+
+
+ PKCS12BagAttributeCarrier bagAttr = certificate;
+
+ //
+ // this is actually optional - but if you want to have control
+ // over setting the friendly name this is the way to do it...
+ //
+ bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName,
+ new DERBMPString(friendlyName));
+
+ return certificate;
+ } catch (Exception e) {
+ logger.error("Error generating certificate.", e);
+ return null;
+ }
+ }
+ }
+
+ public static class SelfSigned {
+ public static X509Certificate generateCert(KeyFactory factory, Date startDate, Date endDate,
+ String subject, String friendlyName,
+ RSAKeyParameters publicKey, RSAPrivateCrtKeyParameters privateKey) {
+
+ String signatureAlgorithm = "SHA1withRSA";
+
+ AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgorithm); // specify it's RSA
+ AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId); // specify SHA
+
+ try {
+ // JCE format needed for the certificate - because getEncoded() is necessary...
+ PublicKey jcePublicKey = convertToJCE(factory, publicKey);
+// PrivateKey jcePrivateKey = convertToJCE(factory, publicKey, privateKey);
+
+ SubjectPublicKeyInfo subjectPublicKeyInfo = createSubjectPublicKey(jcePublicKey);
+ X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(new X500Name(subject), BigInteger.valueOf(System.currentTimeMillis()),
+ startDate, endDate, new X500Name(subject),
+ subjectPublicKeyInfo);
+
+ //
+ // extensions
+ //
+ JcaX509ExtensionUtils jcaX509ExtensionUtils = new JcaX509ExtensionUtils(); // SHA1
+ SubjectKeyIdentifier createSubjectKeyIdentifier = jcaX509ExtensionUtils.createSubjectKeyIdentifier(subjectPublicKeyInfo);
+
+ certBuilder.addExtension(Extension.subjectKeyIdentifier,
+ false,
+ createSubjectKeyIdentifier);
+
+ certBuilder.addExtension(Extension.basicConstraints,
+ true,
+ new BasicConstraints(false));
+
+
+ ContentSigner hashSigner = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(privateKey);
+ X509CertificateHolder certHolder = certBuilder.build(hashSigner);
+ X509CertificateObject certificate = new X509CertificateObject(Certificate.getInstance(certHolder.getEncoded()));
+
+ certificate.verify(jcePublicKey);
+
+
+ PKCS12BagAttributeCarrier bagAttr = certificate;
+
+ //
+ // this is actually optional - but if you want to have control
+ // over setting the friendly name this is the way to do it...
+ //
+ bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName,
+ new DERBMPString(friendlyName));
+
+ return certificate;
+ } catch (Exception e) {
+ logger.error("Error generating certificate.", e);
+ return null;
+ }
+ }
+ }
+
+
+ /**
+ * Generate a cert that is signed by a CA cert.
+ */
+ public static X509Certificate generateCert(KeyFactory factory, Date startDate, Date expiryDate,
+ X509Certificate issuerCert, String subject, String friendlyName,
+ RSAKeyParameters publicKey, RSAPrivateCrtKeyParameters signingCaKey) throws InvalidKeySpecException, InvalidKeyException, IOException, OperatorCreationException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
+
+ return CryptoX509.RSA.generateCert(factory, startDate, expiryDate,
+ X500Name.getInstance(PrincipalUtil.getSubjectX509Principal(issuerCert)), new X500Name(subject), friendlyName,
+ publicKey,
+ issuerCert, signingCaKey);
+ }
+
+
+ /**
+ * Generate a cert that is self signed.
+ */
+ public static X509Certificate generateCert(KeyFactory factory, Date startDate, Date expiryDate,
+ String subject, String friendlyName,
+ RSAKeyParameters publicKey, RSAPrivateCrtKeyParameters privateKey) throws InvalidKeySpecException, InvalidKeyException, IOException, OperatorCreationException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
+
+ return CryptoX509.RSA.generateCert(factory, startDate, expiryDate,
+ new X500Name(subject), new X500Name(subject), friendlyName,
+ publicKey, null, privateKey);
+ }
+
+
+
+ private static X509Certificate generateCert(KeyFactory factory, Date startDate, Date expiryDate,
+ X500Name issuer, X500Name subject, String friendlyName,
+ RSAKeyParameters certPublicKey,
+ X509Certificate signingCertificate, RSAPrivateCrtKeyParameters signingPrivateKey) throws InvalidKeySpecException, IOException, InvalidKeyException, OperatorCreationException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
+
+
+ String signatureAlgorithm = "SHA1withRSA";
+
+ AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgorithm); // specify it's RSA
+ AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId); // specify SHA
+
+ // JCE format needed for the certificate - because getEncoded() is necessary...
+ PublicKey jcePublicKey = convertToJCE(factory, certPublicKey);
+// PrivateKey jcePrivateKey = convertToJCE(factory, publicKey, privateKey);
+
+
+
+
+ SubjectPublicKeyInfo subjectPublicKeyInfo = createSubjectPublicKey(jcePublicKey);
+ X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(issuer, BigInteger.valueOf(System.currentTimeMillis()),
+ startDate, expiryDate, subject,
+ subjectPublicKeyInfo);
+
+
+
+ //
+ // extensions
+ //
+ JcaX509ExtensionUtils jcaX509ExtensionUtils = new JcaX509ExtensionUtils(); // SHA1
+ SubjectKeyIdentifier createSubjectKeyIdentifier = jcaX509ExtensionUtils.createSubjectKeyIdentifier(subjectPublicKeyInfo);
+
+ certBuilder.addExtension(Extension.subjectKeyIdentifier,
+ false,
+ createSubjectKeyIdentifier);
+
+ if (signingCertificate != null) {
+ AuthorityKeyIdentifier createAuthorityKeyIdentifier = jcaX509ExtensionUtils.createAuthorityKeyIdentifier(signingCertificate.getPublicKey());
+ certBuilder.addExtension(Extension.authorityKeyIdentifier,
+ false,
+ createAuthorityKeyIdentifier);
+// new AuthorityKeyIdentifierStructure(signingCertificate));
+ }
+
+ certBuilder.addExtension(Extension.basicConstraints,
+ true,
+ new BasicConstraints(false));
+
+
+
+ ContentSigner signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(signingPrivateKey);
+ X509CertificateHolder certHolder = certBuilder.build(signer);
+ X509CertificateObject certificate = new X509CertificateObject(Certificate.getInstance(certHolder.getEncoded()));
+
+ if (signingCertificate != null) {
+ certificate.verify(signingCertificate.getPublicKey());
+ } else {
+ certificate.verify(jcePublicKey);
+ }
+
+ PKCS12BagAttributeCarrier bagAttr = certificate;
+
+ //
+ // this is actually optional - but if you want to have control
+ // over setting the friendly name this is the way to do it...
+ //
+ bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName,
+ new DERBMPString(friendlyName));
+
+ if (signingCertificate != null) {
+ bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId,
+ subjectPublicKeyInfo);
+ }
+
+
+ return certificate;
+
+
+// //// subject name table.
+// //Hashtable attrs = new Hashtable();
+// //Vector order = new Vector();
+// //
+// //attrs.put(BCStyle.C, "US");
+// //attrs.put(BCStyle.O, "Dorkbox");
+// //attrs.put(BCStyle.OU, "Dorkbox Certificate Authority");
+// //attrs.put(BCStyle.EmailAddress, "admin@dorkbox.com");
+// //
+// //order.addElement(BCStyle.C);
+// //order.addElement(BCStyle.O);
+// //order.addElement(BCStyle.OU);
+// //order.addElement(BCStyle.EmailAddress);
+// //
+// //X509Principal issuer = new X509Principal(order, attrs);
+// // MASTER CERT
+//
+// //// signers name
+// //String issuer = "C=US, O=dorkbox llc, OU=Dorkbox Certificate Authority";
+// //
+// //// subjects name - the same as we are self signed.
+// //String subject = "C=US, O=dorkbox llc, OU=Dorkbox Certificate Authority";
+ }
+
+ private static SubjectPublicKeyInfo createSubjectPublicKey(PublicKey jcePublicKey) throws IOException {
+ ASN1InputStream asn1InputStream = null;
+ try {
+ asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(jcePublicKey.getEncoded()));
+ SubjectPublicKeyInfo subjectPublicKeyInfo1 = new SubjectPublicKeyInfo((ASN1Sequence) asn1InputStream.readObject());
+ return subjectPublicKeyInfo1;
+ } finally {
+ if (asn1InputStream != null) {
+ asn1InputStream.close();
+ }
+ }
+ }
+
+
+ public static PublicKey convertToJCE(RSAKeyParameters publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ return convertToJCE(keyFactory, publicKey);
+ }
+
+ public static PublicKey convertToJCE(KeyFactory keyFactory, RSAKeyParameters publicKey) throws InvalidKeySpecException {
+ return keyFactory.generatePublic(new RSAPublicKeySpec(publicKey.getModulus(), publicKey.getExponent()));
+ }
+
+ public static RSAKeyParameters convertToBC(PublicKey publicKey) {
+ RSAPublicKey pubKey = RSAPublicKey.getInstance(publicKey);
+ return new RSAKeyParameters(false, pubKey.getModulus(), pubKey.getPublicExponent());
+ }
+
+ public static PrivateKey convertToJCE(RSAKeyParameters publicKey, RSAPrivateCrtKeyParameters privateKey) throws InvalidKeySpecException, NoSuchAlgorithmException {
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ return convertToJCE(keyFactory, publicKey, privateKey);
+ }
+
+ public static PrivateKey convertToJCE(KeyFactory keyFactory, RSAKeyParameters publicKey, RSAPrivateCrtKeyParameters privateKey) throws InvalidKeySpecException {
+ return keyFactory.generatePrivate(new RSAPrivateCrtKeySpec(publicKey.getModulus(), publicKey.getExponent(),
+ privateKey.getExponent(), privateKey.getP(), privateKey.getQ(),
+ privateKey.getDP(), privateKey.getDQ(), privateKey.getQInv()));
+ }
+
+ /**
+ * Creates a X509 certificate holder object.
+ *
+ * Look at BCStyle for a list of all valid X500 Names.
+ */
+ public static X509CertificateHolder createCertHolder(Date startDate, Date expiryDate,
+ X500Name issuerName, X500Name subjectName, BigInteger serialNumber,
+ RSAPrivateCrtKeyParameters privateKey, RSAKeyParameters publicKey) {
+
+ String signatureAlgorithm = "SHA256withRSA";
+
+
+ AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgorithm);
+ AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+
+ SubjectPublicKeyInfo subjectPublicKeyInfo = null;
+
+ try {
+ // JCE format needed for the certificate - because getEncoded() is necessary...
+ PublicKey jcePublicKey = convertToJCE(publicKey);
+// PrivateKey jcePrivateKey = convertToJCE(factory, publicKey, privateKey);
+
+ subjectPublicKeyInfo = createSubjectPublicKey(jcePublicKey);
+ } catch (Exception e) {
+ logger.error("Unable to create RSA keyA.", e);
+ return null;
+ }
+
+
+ try {
+ X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(issuerName, serialNumber,
+ startDate, expiryDate, subjectName,
+ subjectPublicKeyInfo);
+ //
+ // extensions
+ //
+ JcaX509ExtensionUtils jcaX509ExtensionUtils = new JcaX509ExtensionUtils(); // SHA1
+ SubjectKeyIdentifier createSubjectKeyIdentifier = jcaX509ExtensionUtils.createSubjectKeyIdentifier(subjectPublicKeyInfo);
+
+ certBuilder.addExtension(Extension.subjectKeyIdentifier,
+ false,
+ createSubjectKeyIdentifier);
+
+ certBuilder.addExtension(Extension.basicConstraints,
+ true,
+ new BasicConstraints(false));
+
+
+ ContentSigner hashSigner = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(privateKey);
+ X509CertificateHolder certHolder = certBuilder.build(hashSigner);
+
+ return certHolder;
+ } catch (Exception e) {
+ logger.error("Error generating certificate.", e);
+ return null;
+ }
+ }
+
+
+ /**
+ * Verifies that the certificate is legitimate.
+ *
+ * MUST have BouncyCastle provider loaded by the security manager!
+ *
+ * @return true if it was a valid cert.
+ */
+ public static final boolean validate(X509CertificateHolder x509CertificateHolder) {
+ try {
+
+ // this is unique in that it verifies that the certificate is a LEGIT certificate, but not necessarily
+ // valid during this time period.
+ ContentVerifierProvider contentVerifierProvider = new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(x509CertificateHolder);
+
+ boolean signatureValid = x509CertificateHolder.isSignatureValid(contentVerifierProvider);
+
+ if (!signatureValid) {
+ return false;
+ }
+
+ org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory certificateFactory = new org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory();
+ java.security.cert.Certificate certificate = certificateFactory.engineGenerateCertificate(new ByteArrayInputStream(x509CertificateHolder.getEncoded()));
+ // Note: this requires the BC provider to be loaded!
+ if (certificate == null || certificate.getPublicKey() == null) {
+ return false;
+ }
+
+ // Verify the TIME/DATE of the certificate
+ X509Accessor.verifyDate(certificate);
+
+ // if we get here, it means that our cert is LEGIT and VALID.
+ return true;
+
+ } catch (Throwable t) {
+ logger.error("Error validating certificate.", t);
+ return false;
+ }
+ }
+
+ /**
+ * Verifies the given x509 based signature against the OPTIONAL original public key. If not specified, then
+ * the public key from the signature is used.
+ *
+ * MUST have BouncyCastle provider loaded by the security manager!
+ *
+ * @return true if the signature was valid.
+ */
+ public static boolean verifySignature(byte[] signatureBytes, RSAKeyParameters publicKey) {
+
+ ASN1InputStream asn1InputStream = null;
+ try {
+ asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(signatureBytes));
+ ASN1Primitive signatureASN = asn1InputStream.readObject();
+ ASN1Sequence seq = ASN1Sequence.getInstance(signatureASN);
+ ASN1TaggedObject tagged = (ASN1TaggedObject)seq.getObjectAt(1);
+
+ // Extract certificates
+ SignedData newSignedData = SignedData.getInstance(tagged.getObject());
+
+ @SuppressWarnings("rawtypes")
+ Enumeration newSigOjects = newSignedData.getCertificates().getObjects();
+ Object newSigElement = newSigOjects.nextElement();
+
+ if (newSigElement instanceof DERSequence) {
+ DERSequence newSigDERElement = (DERSequence) newSigElement;
+ InputStream newSigIn = new ByteArrayInputStream(newSigDERElement.getEncoded());
+
+ org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory certificateFactory = new org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory();
+ java.security.cert.Certificate certificate = certificateFactory.engineGenerateCertificate(newSigIn);
+
+ PublicKey publicKey2 = certificate.getPublicKey();
+
+ if (publicKey != null) {
+ BCRSAPublicKey origPublicKey = BCRSAPublicKeyAccessor.newInstance(publicKey);
+ boolean equals = origPublicKey.equals(publicKey2);
+ if (!equals) {
+ return false;
+ }
+ publicKey2 = origPublicKey;
+ }
+
+ certificate.verify(publicKey2);
+ }
+ return true;
+ } catch (Throwable t) {
+ logger.error("Error validating certificate.", t);
+ return false;
+ } finally {
+ if (asn1InputStream != null) {
+ try {
+ asn1InputStream.close();
+ } catch (IOException e) {
+ logger.error("Error closing stream during RSA.", e);
+ }
+ }
+ }
+ }
+ }
+
+ public static class ECDSA {
+ static {
+ // make sure we only add it once (in case it's added elsewhere...)
+ Provider provider = Security.getProvider("BC");
+ if (provider == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ }
+
+ /**
+ * Creates a X509 certificate holder object.
+ */
+ public static X509CertificateHolder createCertHolder(String digestName,
+ Date startDate, Date expiryDate,
+ X500Name issuerName, X500Name subjectName,
+ BigInteger serialNumber,
+ ECPrivateKeyParameters privateKey,
+ ECPublicKeyParameters publicKey) {
+
+ String signatureAlgorithm = digestName + "withECDSA";
+
+
+ // we WANT the ECparameterSpec to be null, so it's created from the public key
+ JCEECPublicKey pubKey = new JCEECPublicKey("EC", publicKey, (ECParameterSpec) null);
+
+ AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgorithm);
+ AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+ SubjectPublicKeyInfo subjectPublicKeyInfo = null;
+
+ try {
+ byte[] encoded = pubKey.getEncoded();
+ ASN1Sequence seq = (ASN1Sequence)ASN1Primitive.fromByteArray(encoded);
+ subjectPublicKeyInfo = new SubjectPublicKeyInfo(seq);
+ } catch (IOException e) {
+ logger.error("Unable to perform DSA.", e);
+ return null;
+ }
+
+ X509v3CertificateBuilder v3CertBuilder = new X509v3CertificateBuilder(issuerName,
+ serialNumber, startDate, expiryDate,
+ subjectName, subjectPublicKeyInfo);
+
+ BcECDSAContentSignerBuilder contentSignerBuilder = new BcECDSAContentSignerBuilder(sigAlgId, digAlgId);
+
+ ContentSigner build = null;
+ try {
+ build = contentSignerBuilder.build(privateKey);
+ } catch (OperatorCreationException e) {
+ logger.error("Error creating certificate.", e);
+ return null;
+ }
+
+ X509CertificateHolder certHolder = v3CertBuilder.build(build);
+ return certHolder;
+ }
+
+ /**
+ * Verifies that the certificate is legitimate.
+ *
+ * MUST have BouncyCastle provider loaded by the security manager!
+ *
+ * @return true if it was a valid cert.
+ */
+ public static final boolean validate(X509CertificateHolder x509CertificateHolder) {
+ try {
+
+ // this is unique in that it verifies that the certificate is a LEGIT certificate, but not necessarily
+ // valid during this time period.
+ ContentVerifierProvider contentVerifierProvider = new BcECDSAContentVerifierProviderBuilder(
+ new DefaultDigestAlgorithmIdentifierFinder()).build(x509CertificateHolder);
+
+ boolean signatureValid = x509CertificateHolder.isSignatureValid(contentVerifierProvider);
+
+ if (!signatureValid) {
+ return false;
+ }
+
+ org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory certificateFactory = new org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory();
+ java.security.cert.Certificate certificate = certificateFactory.engineGenerateCertificate(new ByteArrayInputStream(x509CertificateHolder.getEncoded()));
+ // Note: this requires the BC provider to be loaded!
+ if (certificate == null || certificate.getPublicKey() == null) {
+ return false;
+ }
+
+ // Verify the TIME/DATE of the certificate
+ X509Accessor.verifyDate(certificate);
+
+ // if we get here, it means that our cert is LEGIT and VALID.
+ return true;
+ } catch (Throwable t) {
+ logger.error("Error validating certificate.", t);
+ return false;
+ }
+
+ }
+
+ /**
+ * Verifies the given x509 based signature against the OPTIONAL original public key. If not specified, then
+ * the public key from the signature is used.
+ *
+ * MUST have BouncyCastle provider loaded by the security manager!
+ *
+ * @return true if the signature was valid.
+ */
+ public static boolean verifySignature(byte[] signatureBytes, ECPublicKeyParameters optionalOriginalPublicKey) {
+ ASN1InputStream asn1InputStream = null;
+ try {
+ asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(signatureBytes));
+ ASN1Primitive signatureASN = asn1InputStream.readObject();
+ ASN1Sequence seq = ASN1Sequence.getInstance(signatureASN);
+ ASN1TaggedObject tagged = (ASN1TaggedObject)seq.getObjectAt(1);
+
+ // Extract certificates
+ SignedData newSignedData = SignedData.getInstance(tagged.getObject());
+
+ @SuppressWarnings("rawtypes")
+ Enumeration newSigOjects = newSignedData.getCertificates().getObjects();
+ Object newSigElement = newSigOjects.nextElement();
+
+ if (newSigElement instanceof DERSequence) {
+ DERSequence newSigDERElement = (DERSequence) newSigElement;
+ InputStream newSigIn = new ByteArrayInputStream(newSigDERElement.getEncoded());
+
+ org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory certificateFactory = new org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory();
+ java.security.cert.Certificate certificate = certificateFactory.engineGenerateCertificate(newSigIn);
+
+ PublicKey publicKey2 = certificate.getPublicKey();
+
+ if (optionalOriginalPublicKey != null) {
+ ECDomainParameters parameters = optionalOriginalPublicKey.getParameters();
+ ECParameterSpec ecParameterSpec = new ECParameterSpec(parameters.getCurve(), parameters.getG(), parameters.getN(), parameters.getH());
+ BCECPublicKey origPublicKey = new BCECPublicKey("EC", optionalOriginalPublicKey, ecParameterSpec, null);
+
+ boolean equals = origPublicKey.equals(publicKey2);
+ if (!equals) {
+ return false;
+ }
+
+ publicKey2 = origPublicKey;
+ }
+
+ certificate.verify(publicKey2);
+ }
+ } catch (Throwable t) {
+ logger.error("Error validating certificate.", t);
+ return false;
+ } finally {
+ if (asn1InputStream != null) {
+ try {
+ asn1InputStream.close();
+ } catch (IOException e) {
+ logger.error("Error during ECDSA.", e);
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+
+
+ /**
+ * Creates a NEW signature block that contains the pkcs7 (minus content, which is the .SF file)
+ * signature of the .SF file.
+ *
+ * It contains the hash of the data, and the verification signature.
+ */
+ public static byte[] createSignature(byte[] signatureSourceData,
+ X509CertificateHolder x509CertificateHolder, AsymmetricKeyParameter privateKey) {
+
+ try {
+ CMSTypedData content = new CMSProcessableByteArray(signatureSourceData);
+
+ ASN1ObjectIdentifier contentTypeOID = new ASN1ObjectIdentifier(content.getContentType().getId());
+ ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
+ ASN1EncodableVector signerInfos = new ASN1EncodableVector();
+
+ AlgorithmIdentifier sigAlgId = x509CertificateHolder.getSignatureAlgorithm();
+ AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+ // use the bouncy-castle lightweight API to generate a hash of the signature source data (usually the signature file bytes)
+ BcContentSignerBuilder contentSignerBuilder;
+ AlgorithmIdentifier digEncryptionAlgorithm;
+
+
+ if (privateKey instanceof ECPrivateKeyParameters) {
+ contentSignerBuilder = new BcECDSAContentSignerBuilder(sigAlgId, digAlgId);
+ digEncryptionAlgorithm = new AlgorithmIdentifier(DSAUtil.dsaOids[0], null); // 1.2.840.10040.4.1 // DSA hashID
+ } else if (privateKey instanceof DSAPrivateKeyParameters) {
+ contentSignerBuilder = new BcDSAContentSignerBuilder(sigAlgId, digAlgId);
+ digEncryptionAlgorithm = new AlgorithmIdentifier(DSAUtil.dsaOids[0], null); // 1.2.840.10040.4.1 // DSA hashID
+ } else if (privateKey instanceof RSAPrivateCrtKeyParameters) {
+ contentSignerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
+ digEncryptionAlgorithm = new AlgorithmIdentifier(RSAUtil.rsaOids[0], null); // 1.2.840.113549.1.1.1 // RSA hashID
+ } else {
+ throw new RuntimeException("Invalid signature type. Only ECDSA, DSA, RSA supported.");
+ }
+
+ ContentSigner hashSigner = contentSignerBuilder.build(privateKey);
+ OutputStream outputStream = hashSigner.getOutputStream();
+ outputStream.write(signatureSourceData, 0, signatureSourceData.length);
+ outputStream.flush();
+ byte[] sigBytes = hashSigner.getSignature();
+
+
+ SignerIdentifier sigId = new SignerIdentifier(new IssuerAndSerialNumber(x509CertificateHolder.toASN1Structure()));
+
+ SignerInfo inf = new SignerInfo(sigId,
+ digAlgId,
+ (ASN1Set) null,
+ digEncryptionAlgorithm,
+ new DEROctetString(sigBytes),
+ (ASN1Set) null);
+
+ digestAlgs.add(inf.getDigestAlgorithm());
+ signerInfos.add(inf);
+
+
+ ASN1EncodableVector certs = new ASN1EncodableVector();
+ certs.add(x509CertificateHolder.toASN1Structure());
+
+
+ ContentInfo encInfo = new ContentInfo(contentTypeOID, null);
+ SignedData sd = new SignedData(
+ new DERSet(digestAlgs),
+ encInfo,
+ new BERSet(certs),
+ null,
+ new DERSet(signerInfos)
+ );
+
+
+ ContentInfo contentInfo = new ContentInfo(CMSObjectIdentifiers.signedData, sd);
+ CMSSignedData cmsSignedData2 = new CMSSignedData(content, contentInfo);
+ byte[] signatureBlock = cmsSignedData2.getEncoded();
+
+ return signatureBlock;
+ } catch (Throwable t) {
+ logger.error("Error signing data.", t);
+ throw new RuntimeException("Error trying to sign data. " + t.getMessage());
+ }
+ }
+
+ /**
+ * Load a key and certificate from a Java KeyStore, and convert the key to a bouncy-castle key.
+ *
+ * Code is present but commented out, as it was a PITA to figure it out, as documentation is lacking....
+ */
+ public static void loadKeystore(String keystoreLocation, String alias, char[] passwd, char[] keypasswd) {
+// FileInputStream fileIn = new FileInputStream(keystoreLocation);
+// KeyStore keyStore = KeyStore.getInstance("JKS");
+// keyStore.load(fileIn, passwd);
+// java.security.cert.Certificate[] chain = keyStore.getCertificateChain(alias);
+// X509Certificate certChain[] = new X509Certificate[chain.length];
+//
+// CertificateFactory cf = CertificateFactory.getInstance("X.509");
+// for (int count = 0; count < chain.length; count++) {
+// ByteArrayInputStream certIn = new ByteArrayInputStream(chain[0].getEncoded());
+// X509Certificate cert = (X509Certificate) cf.generateCertificate(certIn);
+// certChain[count] = cert;
+// }
+//
+// Key key = keyStore.getKey(alias, keypasswd);
+// KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
+// KeySpec keySpec;
+// if (key instanceof DSAPrivateKey) {
+// keySpec = keyFactory.getKeySpec(key, DSAPrivateKeySpec.class);
+// } else {
+// //keySpec = keyFactory.getKeySpec(key, RSAPrivateKeySpec.class);
+// throw new RuntimeException("Only able to support DSA algorithm!");
+// }
+//
+// DSAPrivateKey privateKey = (DSAPrivateKey) keyFactory.generatePrivate(keySpec);
+
+ // convert private key to bouncycastle specific
+// DSAParams params = privateKey.getParams();
+// DSAPrivateKeyParameters wimpyPrivKey = new DSAPrivateKeyParameters(privateKey.getX(), new DSAParameters(params.getP(), params.getQ(), params.getG()));
+// X509CertificateHolder x509CertificateHolder = new X509CertificateHolder(certChain[0].getEncoded());
+//
+
+// fileIn.close(); // close JKS
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/bouncycastle/GCMBlockCipher_ByteBuf.java b/Dorkbox-Util/src/dorkbox/util/crypto/bouncycastle/GCMBlockCipher_ByteBuf.java
new file mode 100644
index 0000000..8c1e454
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/bouncycastle/GCMBlockCipher_ByteBuf.java
@@ -0,0 +1,420 @@
+package dorkbox.util.crypto.bouncycastle;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Pack;
+
+/**
+ * Implements the Galois/Counter mode (GCM) detailed in
+ * NIST Special Publication 800-38D.
+ */
+public class GCMBlockCipher_ByteBuf
+ implements AEADBlockCipher
+{
+ private static final int BLOCK_SIZE = 16;
+ private static final byte[] ZEROES = new byte[BLOCK_SIZE];
+
+ // not final due to a compiler bug
+ private BlockCipher cipher;
+ private Tables8kGCMMultiplier_ByteBuf multiplier;
+
+ // These fields are set by init and not modified by processing
+ private boolean forEncryption;
+ private int macSize;
+ private byte[] nonce;
+ private byte[] A;
+ private byte[] H;
+ private ByteBuf initS;
+ private byte[] J0;
+
+ // These fields are modified during processing
+ private byte[] bufBlock;
+ private byte[] macBlock;
+ private ByteBuf S;
+ private byte[] counter;
+ private int bufOff;
+ private long totalLength;
+
+ public GCMBlockCipher_ByteBuf(BlockCipher c)
+ {
+ if (c.getBlockSize() != BLOCK_SIZE)
+ {
+ throw new IllegalArgumentException(
+ "cipher required with a block size of " + BLOCK_SIZE + ".");
+ }
+
+ this.cipher = c;
+ this.multiplier = new Tables8kGCMMultiplier_ByteBuf();
+ }
+
+ @Override
+ public BlockCipher getUnderlyingCipher()
+ {
+ return this.cipher;
+ }
+
+ @Override
+ public String getAlgorithmName()
+ {
+ return this.cipher.getAlgorithmName() + "/GCM";
+ }
+
+ @Override
+ public void init(boolean forEncryption, CipherParameters params)
+ throws IllegalArgumentException
+ {
+ this.forEncryption = forEncryption;
+ this.macBlock = null;
+
+ KeyParameter keyParam;
+
+ if (params instanceof AEADParameters)
+ {
+ AEADParameters param = (AEADParameters)params;
+
+ this.nonce = param.getNonce();
+ this.A = param.getAssociatedText();
+
+ int macSizeBits = param.getMacSize();
+ if (macSizeBits < 96 || macSizeBits > 128 || macSizeBits % 8 != 0)
+ {
+ throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits);
+ }
+
+ this.macSize = macSizeBits / 8;
+ keyParam = param.getKey();
+ }
+ else if (params instanceof ParametersWithIV)
+ {
+ ParametersWithIV param = (ParametersWithIV)params;
+
+ this.nonce = param.getIV();
+ this.A = null;
+ this.macSize = 16;
+ keyParam = (KeyParameter)param.getParameters();
+ }
+ else
+ {
+ throw new IllegalArgumentException("invalid parameters passed to GCM");
+ }
+
+ int bufLength = forEncryption ? BLOCK_SIZE : BLOCK_SIZE + this.macSize;
+ this.bufBlock = new byte[bufLength];
+
+ if (this.nonce == null || this.nonce.length < 1)
+ {
+ throw new IllegalArgumentException("IV must be at least 1 byte");
+ }
+
+ if (this.A == null)
+ {
+ // Avoid lots of null checks
+ this.A = new byte[0];
+ }
+
+ // Cipher always used in forward mode
+ // if keyParam is null we're reusing the last key.
+ if (keyParam != null)
+ {
+ this.cipher.init(true, keyParam);
+ }
+
+ // TODO This should be configurable by init parameters
+ // (but must be 16 if nonce length not 12) (BLOCK_SIZE?)
+// this.tagLength = 16;
+
+ this.H = new byte[BLOCK_SIZE];
+ this.cipher.processBlock(ZEROES, 0, this.H, 0);
+ this.multiplier.init(this.H);
+
+ this.initS = Unpooled.wrappedBuffer(gHASH(this.A));
+
+ if (this.nonce.length == 12)
+ {
+ this.J0 = new byte[16];
+ System.arraycopy(this.nonce, 0, this.J0, 0, this.nonce.length);
+ this.J0[15] = 0x01;
+ }
+ else
+ {
+ this.J0 = gHASH(this.nonce);
+ byte[] X = new byte[16];
+ packLength((long)this.nonce.length * 8, X, 8);
+ xor(this.J0, X);
+ this.multiplier.multiplyH(this.J0);
+ }
+
+ this.S = this.initS.copy();
+ this.counter = Arrays.clone(this.J0);
+ this.bufOff = 0;
+ this.totalLength = 0;
+ }
+
+ @Override
+ public byte[] getMac() {
+ return Arrays.clone(this.macBlock);
+ }
+
+ @Override
+ public int getOutputSize(int len) {
+ if (this.forEncryption) {
+ return len + this.bufOff + this.macSize;
+ }
+
+ return len + this.bufOff - this.macSize;
+ }
+
+ @Override
+ public int getUpdateOutputSize(int len) {
+ return (len + this.bufOff) / BLOCK_SIZE * BLOCK_SIZE;
+ }
+
+ // MODIFIED
+ @Override
+ public int processByte(byte in, byte[] out, int outOff)
+ throws DataLengthException
+ {
+ // DO NOTHING
+ return 0;
+ }
+
+ // MODIFIED
+ public int processBytes(ByteBuf in, ByteBuf out, int len)
+ throws DataLengthException
+ {
+ out.clear();
+
+ int didRead = 0;
+ int resultLen = 0;
+
+ while (didRead < len) {
+ int buffOffRead = this.bufOff + len - didRead;
+
+ if (buffOffRead >= this.bufBlock.length) {
+ int amtToRead = this.bufBlock.length - this.bufOff;
+ didRead += amtToRead;
+
+ in.readBytes(this.bufBlock, this.bufOff, amtToRead);
+
+ gCTRBlock(this.bufBlock, BLOCK_SIZE, out);
+ if (!this.forEncryption) {
+ System.arraycopy(this.bufBlock, BLOCK_SIZE, this.bufBlock, 0, this.macSize);
+ }
+ resultLen += BLOCK_SIZE;
+ this.bufOff = this.bufBlock.length - BLOCK_SIZE;
+ } else {
+ int read = len - didRead;
+ in.readBytes(this.bufBlock, this.bufOff, read);
+ this.bufOff += read;
+ break;
+ }
+ }
+
+ return resultLen;
+ }
+
+ public int doFinal(ByteBuf out) throws IllegalStateException, InvalidCipherTextException {
+ int extra = this.bufOff;
+ if (!this.forEncryption) {
+ if (extra < this.macSize) {
+ throw new InvalidCipherTextException("data too short");
+ }
+ extra -= this.macSize;
+ }
+
+ if (extra > 0) {
+ byte[] tmp = new byte[BLOCK_SIZE];
+ System.arraycopy(this.bufBlock, 0, tmp, 0, extra);
+ gCTRBlock(tmp, extra, out);
+ }
+
+ // Final gHASH
+ byte[] X = new byte[16];
+ packLength((long) this.A.length * 8, X, 0);
+ packLength(this.totalLength * 8, X, 8);
+
+ xor(this.S, X);
+ this.multiplier.multiplyH(this.S);
+
+ // TODO Fix this if tagLength becomes configurable
+ // T = MSBt(GCTRk(J0,S))
+ byte[] tag = new byte[BLOCK_SIZE];
+ this.cipher.processBlock(this.J0, 0, tag, 0);
+ xor(tag, this.S);
+
+ int resultLen = extra;
+
+ // We place into macBlock our calculated value for T
+ this.macBlock = new byte[this.macSize];
+ System.arraycopy(tag, 0, this.macBlock, 0, this.macSize);
+
+ if (this.forEncryption) {
+ // Append T to the message
+ out.writeBytes(this.macBlock, 0, this.macSize);
+ resultLen += this.macSize;
+ } else {
+ // Retrieve the T value from the message and compare to calculated
+ // one
+ byte[] msgMac = new byte[this.macSize];
+ System.arraycopy(this.bufBlock, extra, msgMac, 0, this.macSize);
+ if (!Arrays.constantTimeAreEqual(this.macBlock, msgMac)) {
+ throw new InvalidCipherTextException("mac check in GCM failed");
+ }
+ }
+
+ reset(false);
+
+ return resultLen;
+ }
+
+ @Override
+ public void reset() {
+ reset(true);
+ }
+
+ private void reset(boolean clearMac) {
+ this.S = this.initS != null ? this.initS.copy() : Unpooled.buffer();
+ this.counter = Arrays.clone(this.J0);
+ this.bufOff = 0;
+ this.totalLength = 0;
+
+ if (this.bufBlock != null) {
+ Arrays.fill(this.bufBlock, (byte) 0);
+ }
+
+ if (clearMac) {
+ this.macBlock = null;
+ }
+
+ this.cipher.reset();
+ }
+
+ // MODIFIED
+ private void gCTRBlock(byte[] buf, int bufCount, ByteBuf out) {
+ // inc(counter);
+ for (int i = 15; i >= 12; --i) {
+ byte b = (byte) (this.counter[i] + 1 & 0xff);
+ this.counter[i] = b;
+
+ if (b != 0) {
+ break;
+ }
+ }
+
+ byte[] tmp = new byte[BLOCK_SIZE];
+ this.cipher.processBlock(this.counter, 0, tmp, 0);
+
+ byte[] hashBytes;
+ if (this.forEncryption) {
+ System.arraycopy(ZEROES, bufCount, tmp, bufCount, BLOCK_SIZE - bufCount);
+ hashBytes = tmp;
+ } else {
+ hashBytes = buf;
+ }
+
+ for (int i = bufCount - 1; i >= 0; --i) {
+ tmp[i] ^= buf[i];
+ }
+ out.writeBytes(tmp, 0, bufCount);
+
+ // gHASHBlock(hashBytes);
+ xor(this.S, hashBytes);
+ this.multiplier.multiplyH(this.S);
+
+ this.totalLength += bufCount;
+ }
+
+ private byte[] gHASH(byte[] b)
+ {
+ byte[] Y = new byte[16];
+
+ for (int pos = 0; pos < b.length; pos += 16)
+ {
+ byte[] X = new byte[16];
+ int num = Math.min(b.length - pos, 16);
+ System.arraycopy(b, pos, X, 0, num);
+ xor(Y, X);
+ this.multiplier.multiplyH(Y);
+ }
+
+ return Y;
+ }
+
+// private void gHASHBlock(byte[] block)
+// {
+// xor(S, block);
+// multiplier.multiplyH(S);
+// }
+
+// private static void inc(byte[] block)
+// {
+// for (int i = 15; i >= 12; --i)
+// {
+// byte b = (byte)((block[i] + 1) & 0xff);
+// block[i] = b;
+//
+// if (b != 0)
+// {
+// break;
+// }
+// }
+// }
+
+ private static void xor(ByteBuf block, byte[] val) {
+ for (int i = 15; i >= 0; --i) {
+ byte b = (byte) (block.getByte(i) ^ val[i]);
+ block.setByte(i, b);
+ }
+ }
+
+ private static void xor(byte[] block, ByteBuf val) {
+ for (int i = 15; i >= 0; --i) {
+ block[i] ^= val.getByte(i);
+ }
+ }
+
+ private static void xor(byte[] block, byte[] val) {
+ for (int i = 15; i >= 0; --i) {
+ block[i] ^= val[i];
+ }
+ }
+
+ private static void packLength(long count, byte[] bs, int off) {
+ Pack.intToBigEndian((int)(count >>> 32), bs, off);
+ Pack.intToBigEndian((int)count, bs, off + 4);
+ }
+
+ // MODIFIED
+ @Override
+ public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) throws DataLengthException {
+ // DO NOTHING
+ return 0;
+ }
+
+ // MODIFIED
+ @Override
+ public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException {
+ // DO NOTHING
+ return 0;
+ }
+
+ @Override
+ public void processAADByte(byte arg0) {
+
+ }
+
+ @Override
+ public void processAADBytes(byte[] arg0, int arg1, int arg2) {
+
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/bouncycastle/GCMUtil_ByteBuf.java b/Dorkbox-Util/src/dorkbox/util/crypto/bouncycastle/GCMUtil_ByteBuf.java
new file mode 100644
index 0000000..3ffb25e
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/bouncycastle/GCMUtil_ByteBuf.java
@@ -0,0 +1,155 @@
+package dorkbox.util.crypto.bouncycastle;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Pack;
+
+public abstract class GCMUtil_ByteBuf
+{
+ static byte[] oneAsBytes()
+ {
+ byte[] tmp = new byte[16];
+ tmp[0] = (byte)0x80;
+ return tmp;
+ }
+
+ static int[] oneAsInts()
+ {
+ int[] tmp = new int[4];
+ tmp[0] = 0x80000000;
+ return tmp;
+ }
+
+ static int[] asInts(byte[] bs)
+ {
+ int[] us = new int[4];
+ us[0] = Pack.bigEndianToInt(bs, 0);
+ us[1] = Pack.bigEndianToInt(bs, 4);
+ us[2] = Pack.bigEndianToInt(bs, 8);
+ us[3] = Pack.bigEndianToInt(bs, 12);
+ return us;
+ }
+
+ static void multiply(byte[] block, byte[] val)
+ {
+ byte[] tmp = Arrays.clone(block);
+ byte[] c = new byte[16];
+
+ for (int i = 0; i < 16; ++i)
+ {
+ byte bits = val[i];
+ for (int j = 7; j >= 0; --j)
+ {
+ if ((bits & 1 << j) != 0)
+ {
+ xor(c, tmp);
+ }
+
+ boolean lsb = (tmp[15] & 1) != 0;
+ shiftRight(tmp);
+ if (lsb)
+ {
+ // R = new byte[]{ 0xe1, ... };
+// GCMUtil.xor(v, R);
+ tmp[0] ^= (byte)0xe1;
+ }
+ }
+ }
+
+ System.arraycopy(c, 0, block, 0, 16);
+ }
+
+ // P is the value with only bit i=1 set
+ static void multiplyP(int[] x)
+ {
+ boolean lsb = (x[3] & 1) != 0;
+ shiftRight(x);
+ if (lsb)
+ {
+ // R = new int[]{ 0xe1000000, 0, 0, 0 };
+// xor(v, R);
+ x[0] ^= 0xe1000000;
+ }
+ }
+
+ static void multiplyP8(int[] x)
+ {
+// for (int i = 8; i != 0; --i)
+// {
+// multiplyP(x);
+// }
+
+ int lsw = x[3];
+ shiftRightN(x, 8);
+ for (int i = 7; i >= 0; --i)
+ {
+ if ((lsw & 1 << i) != 0)
+ {
+ x[0] ^= 0xe1000000 >>> 7 - i;
+ }
+ }
+ }
+
+ static void shiftRight(byte[] block)
+ {
+ int i = 0;
+ int bit = 0;
+ for (;;)
+ {
+ int b = block[i] & 0xff;
+ block[i] = (byte) (b >>> 1 | bit);
+ if (++i == 16)
+ {
+ break;
+ }
+ bit = (b & 1) << 7;
+ }
+ }
+
+ static void shiftRight(int[] block)
+ {
+ int i = 0;
+ int bit = 0;
+ for (;;)
+ {
+ int b = block[i];
+ block[i] = b >>> 1 | bit;
+ if (++i == 4)
+ {
+ break;
+ }
+ bit = b << 31;
+ }
+ }
+
+ static void shiftRightN(int[] block, int n)
+ {
+ int i = 0;
+ int bits = 0;
+ for (;;)
+ {
+ int b = block[i];
+ block[i] = b >>> n | bits;
+ if (++i == 4)
+ {
+ break;
+ }
+ bits = b << 32 - n;
+ }
+ }
+
+ static void xor(byte[] block, byte[] val)
+ {
+ for (int i = 15; i >= 0; --i)
+ {
+ block[i] ^= val[i];
+ }
+ }
+
+ static void xor(int[] block, int[] val)
+ {
+ for (int i = 3; i >= 0; --i)
+ {
+ block[i] ^= val[i];
+ }
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/bouncycastle/Tables8kGCMMultiplier_ByteBuf.java b/Dorkbox-Util/src/dorkbox/util/crypto/bouncycastle/Tables8kGCMMultiplier_ByteBuf.java
new file mode 100644
index 0000000..d04f395
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/bouncycastle/Tables8kGCMMultiplier_ByteBuf.java
@@ -0,0 +1,139 @@
+package dorkbox.util.crypto.bouncycastle;
+
+import io.netty.buffer.ByteBuf;
+
+import org.bouncycastle.util.Pack;
+
+public class Tables8kGCMMultiplier_ByteBuf
+{
+ private final int[][][] M = new int[32][16][];
+
+ public void init(byte[] H)
+ {
+ this.M[0][0] = new int[4];
+ this.M[1][0] = new int[4];
+ this.M[1][8] = GCMUtil_ByteBuf.asInts(H);
+
+ for (int j = 4; j >= 1; j >>= 1)
+ {
+ int[] tmp = new int[4];
+ System.arraycopy(this.M[1][j + j], 0, tmp, 0, 4);
+
+ GCMUtil_ByteBuf.multiplyP(tmp);
+ this.M[1][j] = tmp;
+ }
+
+ {
+ int[] tmp = new int[4];
+ System.arraycopy(this.M[1][1], 0, tmp, 0, 4);
+
+ GCMUtil_ByteBuf.multiplyP(tmp);
+ this.M[0][8] = tmp;
+ }
+
+ for (int j = 4; j >= 1; j >>= 1)
+ {
+ int[] tmp = new int[4];
+ System.arraycopy(this.M[0][j + j], 0, tmp, 0, 4);
+
+ GCMUtil_ByteBuf.multiplyP(tmp);
+ this.M[0][j] = tmp;
+ }
+
+ int i = 0;
+ for (;;)
+ {
+ for (int j = 2; j < 16; j += j)
+ {
+ for (int k = 1; k < j; ++k)
+ {
+ int[] tmp = new int[4];
+ System.arraycopy(this.M[i][j], 0, tmp, 0, 4);
+
+ GCMUtil_ByteBuf.xor(tmp, this.M[i][k]);
+ this.M[i][j + k] = tmp;
+ }
+ }
+
+ if (++i == 32)
+ {
+ return;
+ }
+
+ if (i > 1)
+ {
+ this.M[i][0] = new int[4];
+ for(int j = 8; j > 0; j >>= 1)
+ {
+ int[] tmp = new int[4];
+ System.arraycopy(this.M[i - 2][j], 0, tmp, 0, 4);
+
+ GCMUtil_ByteBuf.multiplyP8(tmp);
+ this.M[i][j] = tmp;
+ }
+ }
+ }
+ }
+
+ public void multiplyH(byte[] x)
+ {
+// assert x.Length == 16;
+
+ int[] z = new int[4];
+ for (int i = 15; i >= 0; --i)
+ {
+// GCMUtil.xor(z, M[i + i][x[i] & 0x0f]);
+ int[] m = this.M[i + i][x[i] & 0x0f];
+ z[0] ^= m[0];
+ z[1] ^= m[1];
+ z[2] ^= m[2];
+ z[3] ^= m[3];
+// GCMUtil.xor(z, M[i + i + 1][(x[i] & 0xf0) >>> 4]);
+ m = this.M[i + i + 1][(x[i] & 0xf0) >>> 4];
+ z[0] ^= m[0];
+ z[1] ^= m[1];
+ z[2] ^= m[2];
+ z[3] ^= m[3];
+ }
+
+ Pack.intToBigEndian(z, x, 0);
+ }
+
+ public void multiplyH(ByteBuf x)
+ {
+// assert x.Length == 16;
+
+ int[] z = new int[4];
+ for (int i = 15; i >= 0; --i)
+ {
+// GCMUtil.xor(z, M[i + i][x[i] & 0x0f]);
+ int[] m = this.M[i + i][x.getByte(i) & 0x0f];
+ z[0] ^= m[0];
+ z[1] ^= m[1];
+ z[2] ^= m[2];
+ z[3] ^= m[3];
+// GCMUtil.xor(z, M[i + i + 1][(x[i] & 0xf0) >>> 4]);
+ m = this.M[i + i + 1][(x.getByte(i) & 0xf0) >>> 4];
+ z[0] ^= m[0];
+ z[1] ^= m[1];
+ z[2] ^= m[2];
+ z[3] ^= m[3];
+ }
+
+ intToBigEndian(z, x, 0);
+ }
+
+ public static void intToBigEndian(int[] ns, ByteBuf bs, int off) {
+ for (int i = 0; i < ns.length; ++i) {
+ intToBigEndian(ns[i], bs, off);
+ off += 4;
+ }
+ }
+
+ public static void intToBigEndian(int n, ByteBuf bs, int off) {
+ bs.setByte(off, (byte) (n >>> 24));
+ bs.setByte(++off, (byte) (n >>> 16));
+ bs.setByte(++off, (byte) (n >>> 8));
+ bs.setByte(++off, (byte) n);
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/serialization/EccPrivateKeySerializer.java b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/EccPrivateKeySerializer.java
new file mode 100644
index 0000000..0a037ab
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/EccPrivateKeySerializer.java
@@ -0,0 +1,241 @@
+package dorkbox.util.crypto.serialization;
+
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.ec.CustomNamedCurves;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.math.ec.ECAccessor;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Only public keys are ever sent across the wire.
+ */
+public class EccPrivateKeySerializer extends Serializer {
+
+ @Override
+ public void write(Kryo kryo, Output output, ECPrivateKeyParameters key) {
+ write(output, key);
+ }
+
+ public static void write(Output output, ECPrivateKeyParameters key) {
+ byte[] bytes;
+ int length;
+
+ ECDomainParameters parameters = key.getParameters();
+ ECCurve curve = parameters.getCurve();
+
+ EccPrivateKeySerializer.serializeCurve(output, curve);
+
+ /////////////
+ BigInteger n = parameters.getN();
+ ECPoint g = parameters.getG();
+
+
+ /////////////
+ bytes = n.toByteArray();
+ length = bytes.length;
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+
+ serializeECPoint(g, output);
+
+ /////////////
+ bytes = key.getD().toByteArray();
+ length = bytes.length;
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public ECPrivateKeyParameters read(Kryo kryo, Input input, Class type) {
+ return read(input);
+ }
+
+ public static ECPrivateKeyParameters read(Input input) {
+ byte[] bytes;
+ int length;
+
+ ECCurve curve = EccPrivateKeySerializer.deserializeCurve(input);
+
+ // N
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger n = new BigInteger(bytes);
+
+
+ // G
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ ECPoint g = curve.decodePoint(bytes);
+
+
+ // D
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger D = new BigInteger(bytes);
+
+
+ ECDomainParameters ecDomainParameters = new ECDomainParameters(curve, g, n);
+
+ return new ECPrivateKeyParameters(D, ecDomainParameters);
+ }
+
+ static void serializeCurve(Output output, ECCurve curve) {
+ byte[] bytes;
+ int length;
+ // save out if it's a NAMED curve, or a UN-NAMED curve. If it is named, we can do less work.
+ String curveName = curve.getClass().getSimpleName();
+ if (curveName.endsWith("Curve")) {
+ String cleanedName = curveName.substring(0, curveName.indexOf("Curve"));
+
+ curveName = null;
+ if (!cleanedName.isEmpty()) {
+ ASN1ObjectIdentifier oid = CustomNamedCurves.getOID(cleanedName);
+ if (oid != null) {
+ // we use the OID (instead of serializing the entire curve)
+ output.writeBoolean(true);
+ curveName = oid.getId();
+ output.writeString(curveName);
+ }
+ }
+ }
+
+ // we have to serialize the ENTIRE curve.
+ if (curveName == null) {
+ // save out the curve info
+ BigInteger a = curve.getA().toBigInteger();
+ BigInteger b = curve.getB().toBigInteger();
+ BigInteger order = curve.getOrder();
+ BigInteger cofactor = curve.getCofactor();
+ BigInteger q = curve.getField().getCharacteristic();
+
+ /////////////
+ bytes = a.toByteArray();
+ length = bytes.length;
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+ /////////////
+ bytes = b.toByteArray();
+ length = bytes.length;
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+ /////////////
+ bytes = order.toByteArray();
+ length = bytes.length;
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+
+ /////////////
+ bytes = cofactor.toByteArray();
+ length = bytes.length;
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+
+ /////////////
+ bytes = q.toByteArray();
+ length = bytes.length;
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+
+ // coordinate system
+ int coordinateSystem = curve.getCoordinateSystem();
+ output.writeInt(coordinateSystem, true);
+ }
+ }
+
+ static ECCurve deserializeCurve(Input input) {
+ byte[] bytes;
+ int length;
+
+ ECCurve curve;
+ boolean usesOid = input.readBoolean();
+
+ // this means we just lookup the curve via the OID
+ if (usesOid) {
+ String oid = input.readString();
+ X9ECParameters x9Curve = CustomNamedCurves.getByOID(new ASN1ObjectIdentifier(oid));
+ curve = x9Curve.getCurve();
+ }
+ // we have to read in the entire curve information.
+ else {
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger a = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger b = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger order = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger cofactor = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger q = new BigInteger(bytes);
+
+
+ // coord system
+ int coordinateSystem = input.readInt(true);
+
+ curve = new ECCurve.Fp(q, a, b, order, cofactor);
+ ECAccessor.setCoordSystem(curve, coordinateSystem);
+ }
+ return curve;
+ }
+
+ static void serializeECPoint(ECPoint point, Output output) {
+ if (point.isInfinity()) {
+ return;
+ }
+
+ ECPoint normed = point.normalize();
+
+ byte[] X = normed.getXCoord().getEncoded();
+ byte[] Y = normed.getYCoord().getEncoded();
+
+ int length = 1 + X.length + Y.length;
+ output.writeInt(length, true);
+
+ output.write(0x04);
+ output.write(X);
+ output.write(Y);
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/serialization/EccPublicKeySerializer.java b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/EccPublicKeySerializer.java
new file mode 100644
index 0000000..0825610
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/EccPublicKeySerializer.java
@@ -0,0 +1,94 @@
+package dorkbox.util.crypto.serialization;
+
+
+import java.math.BigInteger;
+
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.math.ec.ECPoint;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Only public keys are ever sent across the wire.
+ */
+public class EccPublicKeySerializer extends Serializer {
+
+ @Override
+ public void write(Kryo kryo, Output output, ECPublicKeyParameters key) {
+ write(output, key);
+ }
+
+ public static void write(Output output, ECPublicKeyParameters key) {
+ byte[] bytes;
+ int length;
+
+ ECDomainParameters parameters = key.getParameters();
+ ECCurve curve = parameters.getCurve();
+
+ EccPrivateKeySerializer.serializeCurve(output, curve);
+
+ /////////////
+ BigInteger n = parameters.getN();
+ ECPoint g = parameters.getG();
+
+
+ /////////////
+ bytes = n.toByteArray();
+ length = bytes.length;
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+
+ EccPrivateKeySerializer.serializeECPoint(g, output);
+ EccPrivateKeySerializer.serializeECPoint(key.getQ(), output);
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public ECPublicKeyParameters read(Kryo kryo, Input input, Class type) {
+ ECPublicKeyParameters ecPublicKeyParameters = read(input);
+ return ecPublicKeyParameters;
+ }
+
+ public static ECPublicKeyParameters read(Input input) {
+ byte[] bytes;
+ int length;
+
+ ECCurve curve = EccPrivateKeySerializer.deserializeCurve(input);
+
+
+ // N
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger n = new BigInteger(bytes);
+
+
+ // G
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ ECPoint g = curve.decodePoint(bytes);
+
+
+ ECDomainParameters ecDomainParameters = new ECDomainParameters(curve, g, n);
+
+
+ // Q
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ ECPoint Q = curve.decodePoint(bytes);
+
+ ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(Q, ecDomainParameters);
+ return ecPublicKeyParameters;
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/serialization/IesParametersSerializer.java b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/IesParametersSerializer.java
new file mode 100644
index 0000000..33e2546
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/IesParametersSerializer.java
@@ -0,0 +1,59 @@
+package dorkbox.util.crypto.serialization;
+
+import org.bouncycastle.crypto.params.IESParameters;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Only public keys are ever sent across the wire.
+ */
+public class IesParametersSerializer extends Serializer {
+
+ @Override
+ public void write(Kryo kryo, Output output, IESParameters key) {
+ byte[] bytes;
+ int length;
+
+ ///////////
+ bytes = key.getDerivationV();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+ ///////////
+ bytes = key.getEncodingV();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+ ///////////
+ output.writeInt(key.getMacKeySize(), true);
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public IESParameters read (Kryo kryo, Input input, Class type) {
+
+ int length;
+
+ /////////////
+ length = input.readInt(true);
+ byte[] derivation = new byte[length];
+ input.readBytes(derivation, 0, length);
+
+ /////////////
+ length = input.readInt(true);
+ byte[] encoding = new byte[length];
+ input.readBytes(encoding, 0, length);
+
+ /////////////
+ int macKeySize = input.readInt(true);
+
+ return new IESParameters(derivation, encoding, macKeySize);
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/serialization/IesWithCipherParametersSerializer.java b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/IesWithCipherParametersSerializer.java
new file mode 100644
index 0000000..1a5a379
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/IesWithCipherParametersSerializer.java
@@ -0,0 +1,66 @@
+package dorkbox.util.crypto.serialization;
+
+import org.bouncycastle.crypto.params.IESWithCipherParameters;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Only public keys are ever sent across the wire.
+ */
+public class IesWithCipherParametersSerializer extends Serializer {
+
+ @Override
+ public void write(Kryo kryo, Output output, IESWithCipherParameters key) {
+ byte[] bytes;
+ int length;
+
+ ///////////
+ bytes = key.getDerivationV();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+ ///////////
+ bytes = key.getEncodingV();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+ ///////////
+ output.writeInt(key.getMacKeySize(), true);
+
+
+ ///////////
+ output.writeInt(key.getCipherKeySize(), true);
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public IESWithCipherParameters read (Kryo kryo, Input input, Class type) {
+
+ int length;
+
+ /////////////
+ length = input.readInt(true);
+ byte[] derivation = new byte[length];
+ input.readBytes(derivation, 0, length);
+
+ /////////////
+ length = input.readInt(true);
+ byte[] encoding = new byte[length];
+ input.readBytes(encoding, 0, length);
+
+ /////////////
+ int macKeySize = input.readInt(true);
+
+ /////////////
+ int cipherKeySize = input.readInt(true);
+
+ return new IESWithCipherParameters(derivation, encoding, macKeySize, cipherKeySize);
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/serialization/RsaPrivateKeySerializer.java b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/RsaPrivateKeySerializer.java
new file mode 100644
index 0000000..556d32e
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/RsaPrivateKeySerializer.java
@@ -0,0 +1,142 @@
+package dorkbox.util.crypto.serialization;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Only public keys are ever sent across the wire.
+ */
+public class RsaPrivateKeySerializer extends Serializer {
+
+ @Override
+ public void write(Kryo kryo, Output output, RSAPrivateCrtKeyParameters key) {
+ byte[] bytes;
+ int length;
+
+ /////////////
+ bytes = key.getDP().toByteArray();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+ /////////////
+ bytes = key.getDQ().toByteArray();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+
+ /////////////
+ bytes = key.getExponent().toByteArray();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+
+ /////////////
+ bytes = key.getModulus().toByteArray();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+
+ /////////////
+ bytes = key.getP().toByteArray();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+
+ /////////////
+ bytes = key.getPublicExponent().toByteArray();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+
+ /////////////
+ bytes = key.getQ().toByteArray();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+
+ /////////////
+ bytes = key.getQInv().toByteArray();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public RSAPrivateCrtKeyParameters read (Kryo kryo, Input input, Class type) {
+
+ byte[] bytes;
+ int length;
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger DP = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger DQ = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger exponent = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger modulus = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger P = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger publicExponent = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger q = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger qInv = new BigInteger(bytes);
+
+ return new RSAPrivateCrtKeyParameters(modulus, publicExponent, exponent, P, q, DP, DQ, qInv);
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/serialization/RsaPublicKeySerializer.java b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/RsaPublicKeySerializer.java
new file mode 100644
index 0000000..c042816
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/serialization/RsaPublicKeySerializer.java
@@ -0,0 +1,58 @@
+package dorkbox.util.crypto.serialization;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Only public keys are ever sent across the wire.
+ */
+public class RsaPublicKeySerializer extends Serializer {
+
+ @Override
+ public void write(Kryo kryo, Output output, RSAKeyParameters key) {
+ byte[] bytes;
+ int length;
+
+ ///////////
+ bytes = key.getModulus().toByteArray();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+
+ /////////////
+ bytes = key.getExponent().toByteArray();
+ length = bytes.length;
+
+ output.writeInt(length, true);
+ output.writeBytes(bytes, 0, length);
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public RSAKeyParameters read (Kryo kryo, Input input, Class type) {
+
+ byte[] bytes;
+ int length;
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger modulus = new BigInteger(bytes);
+
+ /////////////
+ length = input.readInt(true);
+ bytes = new byte[length];
+ input.readBytes(bytes, 0, length);
+ BigInteger exponent = new BigInteger(bytes);
+
+ return new RSAKeyParameters(false, modulus, exponent);
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/signers/BcECDSAContentSignerBuilder.java b/Dorkbox-Util/src/dorkbox/util/crypto/signers/BcECDSAContentSignerBuilder.java
new file mode 100644
index 0000000..bb51756
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/signers/BcECDSAContentSignerBuilder.java
@@ -0,0 +1,24 @@
+package dorkbox.util.crypto.signers;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.signers.DSADigestSigner;
+import org.bouncycastle.crypto.signers.ECDSASigner;
+import org.bouncycastle.jcajce.provider.util.DigestFactory;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcContentSignerBuilder;
+
+public class BcECDSAContentSignerBuilder extends BcContentSignerBuilder {
+
+ public BcECDSAContentSignerBuilder(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId) {
+ super(sigAlgId, digAlgId);
+ }
+
+ @Override
+ protected Signer createSigner(AlgorithmIdentifier sigAlgId, AlgorithmIdentifier digAlgId) throws OperatorCreationException {
+ Digest digest = DigestFactory.getDigest(digAlgId.getAlgorithm().getId()); // SHA1, SHA512, etc
+
+ return new DSADigestSigner(new ECDSASigner(), digest);
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/crypto/signers/BcECDSAContentVerifierProviderBuilder.java b/Dorkbox-Util/src/dorkbox/util/crypto/signers/BcECDSAContentVerifierProviderBuilder.java
new file mode 100644
index 0000000..03cb51c
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/crypto/signers/BcECDSAContentVerifierProviderBuilder.java
@@ -0,0 +1,36 @@
+package dorkbox.util.crypto.signers;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.signers.DSADigestSigner;
+import org.bouncycastle.crypto.signers.ECDSASigner;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.jcajce.provider.util.DigestFactory;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcContentVerifierProviderBuilder;
+
+public class BcECDSAContentVerifierProviderBuilder extends BcContentVerifierProviderBuilder {
+
+ public BcECDSAContentVerifierProviderBuilder(DigestAlgorithmIdentifierFinder digestAlgorithmFinder) {
+ }
+
+ @Override
+ protected Signer createSigner(AlgorithmIdentifier sigAlgId) throws OperatorCreationException {
+ AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+
+ Digest digest = DigestFactory.getDigest(digAlgId.getAlgorithm().getId()); // 1.2.23.4.5.6, etc
+ return new DSADigestSigner(new ECDSASigner(), digest);
+ }
+
+ @Override
+ protected AsymmetricKeyParameter extractKeyParameters(SubjectPublicKeyInfo publicKeyInfo) throws IOException {
+ return PublicKeyFactory.createKey(publicKeyInfo);
+ }
+}
\ No newline at end of file
diff --git a/Dorkbox-Util/src/dorkbox/util/gwt/GwtSymbolMapParser.java b/Dorkbox-Util/src/dorkbox/util/gwt/GwtSymbolMapParser.java
new file mode 100644
index 0000000..68e9c69
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/gwt/GwtSymbolMapParser.java
@@ -0,0 +1,118 @@
+package dorkbox.util.gwt;
+
+import io.netty.util.CharsetUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+
+public class GwtSymbolMapParser {
+
+ private final Map symbolMap;
+
+ public GwtSymbolMapParser() {
+ symbolMap = new HashMap();
+ }
+
+ /**
+ * Efficiently parses the inputstream for symbolmap information.
+ *
+ * Automatically closes the input stream when finished.
+ */
+ public void parse(InputStream inputStream) {
+ if (inputStream == null) {
+ return;
+ }
+
+ InputStreamReader in = new InputStreamReader(inputStream, CharsetUtil.UTF_8);
+
+ // 1024 is the longest the line will get. We start there, but StringBuilder will let us grow.
+ StringBuilder builder = new StringBuilder(1024);
+
+ int charRead = '\r';
+ char CHAR = (char) charRead;
+ try {
+ while ((charRead = in.read()) != -1) {
+ CHAR = (char) charRead;
+
+ if (CHAR != '\r' && CHAR != '\n') {
+ builder.append(CHAR);
+ } else {
+ processLine(builder.toString());
+ // new line!
+ builder.delete(0, builder.capacity());
+ }
+ }
+ } catch (IOException e) {
+
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ public Map getSymbolMap() {
+ return symbolMap;
+ }
+
+ public void processLine(String line) {
+ if (line.charAt(0) == '#') {
+ return;
+ }
+
+ String[] symbolInfo = line.split(",");
+
+ // There are TWO versions of this file!
+ // 1) the ORIGINAL version (as created by the GWT compiler)
+ // 2) the SHRUNK version (as created by the build scripts)
+
+ // version 1:
+ // # jsName, jsniIdent, className, memberName, sourceUri, sourceLine, fragmentNumber
+
+ // version 2:
+ // jsName, className
+
+ if (symbolInfo.length > 2) {
+ // version 1
+ String jsName = symbolInfo[0];
+// String jsniIdent = symbolInfo[1];
+ String className = symbolInfo[2];
+ String memberName = symbolInfo[3];
+// String sourceUri = symbolInfo[4];
+// String sourceLine = symbolInfo[5];
+// String fragmentNumber = symbolInfo[6];
+//
+// // The path relative to the source server. We assume it is just the
+// // class path base.
+// String sourcePath = className.replace('.', '/');
+// int lastSlashIndex = sourcePath.lastIndexOf("/") + 1;
+// String sourcePathBase = sourcePath.substring(0, lastSlashIndex);
+//
+// // The sourceUri contains the actual file name.
+// String sourceFileName = fileName.substring(fileName.lastIndexOf('/') + 1, fileName.length());
+//
+// String sourceSymbolName = className + "::" + memberName;
+//
+// // simple symbol "holder" class
+// GwtSymbol sourceSymbol = new GwtSymbol(sourcePathBase + sourceFileName,
+// Integer.parseInt(sourceLine),
+// sourceSymbolName,
+// fileName);
+
+ // only register class definitions.
+ // also, ignore if the source/dest name are the same, since that doesn't do any good for obfuscated names anyways.
+ if (memberName.isEmpty() && !jsName.equals(className)) {
+// System.err.println(jsName + " : " + memberName + " : " + className);
+ symbolMap.put(jsName, className);
+ }
+ } else {
+ // version 2
+ // The list has already been pruned, so always put everything into the symbol map
+ symbolMap.put(symbolInfo[0], symbolInfo[1]);
+ }
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/process/JavaProcessBuilder.java b/Dorkbox-Util/src/dorkbox/util/process/JavaProcessBuilder.java
new file mode 100644
index 0000000..8a1cd9f
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/process/JavaProcessBuilder.java
@@ -0,0 +1,244 @@
+package dorkbox.util.process;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import dorkbox.util.OS;
+
+/**
+ * This will FORK the java process initially used to start the currently running JVM. Changing the java executable will change this behaviors
+ */
+public class JavaProcessBuilder extends ShellProcessBuilder {
+
+ // this is NOT related to JAVA_HOME, but is instead the location of the JRE that was used to launch java initially.
+ private String javaLocation = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
+ private String mainClass;
+ private int startingHeapSizeInMegabytes = 40;
+ private int maximumHeapSizeInMegabytes = 128;
+
+ private List jvmOptions = new ArrayList();
+ private List classpathEntries = new ArrayList();
+ private List mainClassArguments = new ArrayList();
+
+ private String jarFile;
+
+ public JavaProcessBuilder() {
+ super(null, null, null);
+ }
+
+ // what version of java??
+ // so, this starts a NEW java, from an ALREADY existing java.
+
+ public JavaProcessBuilder(InputStream in, PrintStream out, PrintStream err) {
+ super(in, out, err);
+ }
+
+ public final void setMainClass(String mainClass) {
+ this.mainClass = mainClass;
+ }
+
+ public final void setStartingHeapSizeInMegabytes(int startingHeapSizeInMegabytes) {
+ this.startingHeapSizeInMegabytes = startingHeapSizeInMegabytes;
+ }
+
+ public final void setMaximumHeapSizeInMegabytes(int maximumHeapSizeInMegabytes) {
+ this.maximumHeapSizeInMegabytes = maximumHeapSizeInMegabytes;
+ }
+
+ public final void addJvmClasspath(String classpathEntry) {
+ classpathEntries.add(classpathEntry);
+ }
+
+ public final void addJvmClasspaths(List paths) {
+ classpathEntries.addAll(paths);
+ }
+
+ public final void addJvmOption(String argument) {
+ jvmOptions.add(argument);
+ }
+
+ public final void addJvmOptions(List paths) {
+ jvmOptions.addAll(paths);
+ }
+
+ public final void setJarFile(String jarFile) {
+ this.jarFile = jarFile;
+ }
+
+ private String getClasspath() {
+ StringBuilder builder = new StringBuilder();
+ int count = 0;
+ final int totalSize = classpathEntries.size();
+ final String pathseparator = File.pathSeparator;
+
+ // DO NOT QUOTE the elements in the classpath!
+ for (String classpathEntry : classpathEntries) {
+ try {
+ // make sure the classpath is ABSOLUTE pathname
+ classpathEntry = new File(classpathEntry).getCanonicalFile().getAbsolutePath();
+
+ // fix a nasty problem when spaces aren't properly escaped!
+ classpathEntry = classpathEntry.replaceAll(" ", "\\ ");
+
+ builder.append(classpathEntry);
+ count++;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ if (count < totalSize) {
+ builder.append(pathseparator); // ; on windows, : on linux
+ }
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Specify the JAVA exectuable to launch this process. By default, this will use the same java exectuable
+ * as was used to start the current JVM.
+ */
+ public void setJava(String javaLocation) {
+ this.javaLocation = javaLocation;
+ }
+
+ @Override
+ public void start() {
+ setExecutable(javaLocation);
+
+ // save off the original arguments
+ List origArguments = new ArrayList(arguments.size());
+ origArguments.addAll(arguments);
+ arguments = new ArrayList(0);
+
+
+ // two versions, java vs not-java
+ arguments.add("-Xms" + startingHeapSizeInMegabytes + "M");
+ arguments.add("-Xmx" + maximumHeapSizeInMegabytes + "M");
+ arguments.add("-server");
+
+ for (String option : jvmOptions) {
+ arguments.add(option);
+ }
+
+ //same as -cp
+ String classpath = getClasspath();
+
+ // two more versions. jar vs classs
+ if (jarFile != null) {
+ arguments.add("-jar");
+ arguments.add(jarFile);
+
+ // interesting note. You CANNOT have a classpath specified on the commandline
+ // when using JARs!! It must be set in the jar's MANIFEST.
+ if (!classpath.isEmpty()) {
+ System.err.println("WHOOPS. You CANNOT have a classpath specified on the commandline when using JARs.");
+ System.err.println(" It must be set in the JARs MANIFEST instead.");
+ System.exit(1);
+ }
+
+ }
+ // if we are running classes!
+ else if (mainClass != null) {
+ if (!classpath.isEmpty()) {
+ arguments.add("-classpath");
+ arguments.add(classpath);
+ }
+
+ // main class must happen AFTER the classpath!
+ arguments.add(mainClass);
+ } else {
+ System.err.println("WHOOPS. You must specify a jar or main class when running java!");
+ System.exit(1);
+ }
+
+
+ for (String arg : mainClassArguments) {
+ if (arg.contains(" ")) {
+ // individual arguments MUST be in their own element in order to
+ // be processed properly (this is how it works on the command line!)
+ String[] split = arg.split(" ");
+ for (String s : split) {
+ arguments.add(s);
+ }
+ } else {
+ arguments.add(arg);
+ }
+ }
+
+ arguments.addAll(origArguments);
+
+ super.start();
+ }
+
+
+ /** The directory into which a local VM installation should be unpacked. */
+ public static final String LOCAL_JAVA_DIR = "java_vm";
+
+ /**
+ * Reconstructs the path to the JVM used to launch this process.
+ *
+ * @param windebug if true we will use java.exe instead of javaw.exe on Windows.
+ */
+ public static String getJVMPath (File appdir, boolean windebug)
+ {
+ // first look in our application directory for an installed VM
+ String vmpath = checkJvmPath(new File(appdir, LOCAL_JAVA_DIR).getPath(), windebug);
+
+ // then fall back to the VM in which we're already running
+ if (vmpath == null) {
+ vmpath = checkJvmPath(System.getProperty("java.home"), windebug);
+ }
+
+ // then throw up our hands and hope for the best
+ if (vmpath == null) {
+ System.err.println("Unable to find java [appdir=" + appdir + ", java.home=" + System.getProperty("java.home") + "]!");
+ vmpath = "java";
+ }
+
+ // Oddly, the Mac OS X specific java flag -Xdock:name will only work if java is launched
+ // from /usr/bin/java, and not if launched by directly referring to /bin/java,
+ // even though the former is a symlink to the latter! To work around this, see if the
+ // desired jvm is in fact pointed to by /usr/bin/java and, if so, use that instead.
+ if (OS.isMacOsX()) {
+ try {
+ File localVM = new File("/usr/bin/java").getCanonicalFile();
+ if (localVM.equals(new File(vmpath).getCanonicalFile())) {
+ vmpath = "/usr/bin/java";
+ }
+ } catch (IOException ioe) {
+ System.err.println("Failed to check Mac OS canonical VM path." + ioe);
+ }
+ }
+
+ return vmpath;
+ }
+
+ /**
+ * Checks whether a Java Virtual Machine can be located in the supplied path.
+ */
+ private static String checkJvmPath(String vmhome, boolean windebug) {
+ // linux does this...
+ String vmbase = vmhome + File.separator + "bin" + File.separator;
+ String vmpath = vmbase + "java";
+ if (new File(vmpath).exists()) {
+ return vmpath;
+ }
+
+ // windows does this
+ if (!windebug) {
+ vmpath = vmbase + "javaw.exe";
+ } else {
+ vmpath = vmbase + "java.exe"; // open a console on windows
+ }
+
+ if (new File(vmpath).exists()) {
+ return vmpath;
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/Dorkbox-Util/src/dorkbox/util/process/LauncherProcessBuilder.java b/Dorkbox-Util/src/dorkbox/util/process/LauncherProcessBuilder.java
new file mode 100644
index 0000000..3fbd129
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/process/LauncherProcessBuilder.java
@@ -0,0 +1,136 @@
+package dorkbox.util.process;
+
+
+
+
+
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import dorkbox.util.OS;
+
+public class LauncherProcessBuilder extends ShellProcessBuilder {
+
+ private String mainClass;
+
+ private List classpathEntries = new ArrayList();
+ private List mainClassArguments = new ArrayList();
+
+ private String jarFile;
+
+ public LauncherProcessBuilder() {
+ super(null, null, null);
+ }
+
+ public LauncherProcessBuilder(InputStream in, PrintStream out, PrintStream err) {
+ super(in, out, err);
+ }
+
+ public final void setMainClass(String mainClass) {
+ this.mainClass = mainClass;
+ }
+
+ public final void addJvmClasspath(String classpathEntry) {
+ classpathEntries.add(classpathEntry);
+ }
+
+ public final void addJvmClasspaths(List paths) {
+ classpathEntries.addAll(paths);
+ }
+
+ public final void setJarFile(String jarFile) {
+ this.jarFile = jarFile;
+ }
+
+ private String getClasspath() {
+ StringBuilder builder = new StringBuilder();
+ int count = 0;
+ final int totalSize = classpathEntries.size();
+ final String pathseparator = File.pathSeparator;
+
+ for (String classpathEntry : classpathEntries) {
+ // fix a nasty problem when spaces aren't properly escaped!
+ classpathEntry = classpathEntry.replaceAll(" ", "\\ ");
+ builder.append(classpathEntry);
+ count++;
+ if (count < totalSize) {
+ builder.append(pathseparator); // ; on windows, : on linux
+ }
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public void start() {
+ if (OS.isWindows()) {
+ setExecutable("dorkboxc.exe");
+ } else {
+ setExecutable("dorkbox");
+ }
+
+
+ // save off the original arguments
+ List origArguments = new ArrayList(arguments.size());
+ origArguments.addAll(arguments);
+ arguments = new ArrayList(0);
+
+ arguments.add("-Xms40M");
+ arguments.add("-Xmx256M");
+// arguments.add("-XX:PermSize=256M"); // default is 96
+
+ arguments.add("-server");
+
+ //same as -cp
+ String classpath = getClasspath();
+
+ // two more versions. jar vs classs
+ if (jarFile != null) {
+ // JAR is added by the launcher (based in the ini file!)
+// arguments.add("-jar");
+// arguments.add(jarFile);
+
+ // interesting note. You CANNOT have a classpath specified on the commandline
+ // when using JARs!! It must be set in the jar's MANIFEST.
+ if (!classpath.isEmpty()) {
+ System.err.println("WHOOPS. You CANNOT have a classpath specified on the commandline when using JARs.");
+ System.err.println(" It must be set in the JARs MANIFEST instead.");
+ System.exit(1);
+ }
+
+ }
+ // if we are running classes!
+ else if (mainClass != null) {
+ arguments.add(mainClass);
+
+ if (!classpath.isEmpty()) {
+ arguments.add("-classpath");
+ arguments.add(classpath);
+ }
+ } else {
+ System.err.println("WHOOPS. You must specify a jar or main class when running java!");
+ System.exit(1);
+ }
+
+
+ for (String arg : mainClassArguments) {
+ if (arg.contains(" ")) {
+ // individual arguments MUST be in their own element in order to
+ // be processed properly (this is how it works on the command line!)
+ String[] split = arg.split(" ");
+ for (String s : split) {
+ arguments.add(s);
+ }
+ } else {
+ arguments.add(arg);
+ }
+ }
+
+ arguments.addAll(origArguments);
+
+ super.start();
+ }
+}
\ No newline at end of file
diff --git a/Dorkbox-Util/src/dorkbox/util/process/NullOutputStream.java b/Dorkbox-Util/src/dorkbox/util/process/NullOutputStream.java
new file mode 100644
index 0000000..2c6a181
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/process/NullOutputStream.java
@@ -0,0 +1,25 @@
+package dorkbox.util.process;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class NullOutputStream extends OutputStream {
+ @Override
+ public void write(int i) throws IOException {
+ //do nothing
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ //do nothing
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ //do nothing
+ }
+
+ @Override
+ public void flush() throws IOException {
+ //do nothing
+ }
+}
\ No newline at end of file
diff --git a/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java b/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java
new file mode 100644
index 0000000..85b63e7
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java
@@ -0,0 +1,61 @@
+package dorkbox.util.process;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class ProcessProxy extends Thread {
+
+ private final InputStream is;
+ private final OutputStream os;
+
+ // when reading from the stdin and outputting to the process
+ public ProcessProxy(String processName, InputStream inputStreamFromConsole, OutputStream outputStreamToProcess) {
+ is = inputStreamFromConsole;
+ os = outputStreamToProcess;
+
+ setName(processName);
+ setDaemon(true);
+ }
+
+ public void close() {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ // this thread will read until there is no more data to read. (this is generally what you want)
+ // the stream will be closed when the process closes it (usually on exit)
+ int readInt;
+
+ if (os == null) {
+ // just read so it won't block.
+ while ((readInt = is.read()) != -1) {
+ }
+ } else {
+ while ((readInt = is.read()) != -1) {
+ os.write(readInt);
+
+ // flush the output on new line. Works for windows/linux, since \n is always the last char in the sequence.
+ if (readInt == '\n') {
+ os.flush();
+ }
+ }
+ }
+ } catch (IOException ignore) {
+ } catch (IllegalArgumentException e) {
+ } finally {
+ try {
+ if (os != null) {
+ os.flush(); // this goes to the console, so we don't want to close it!
+ }
+ is.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Dorkbox-Util/src/dorkbox/util/process/ShellProcessBuilder.java b/Dorkbox-Util/src/dorkbox/util/process/ShellProcessBuilder.java
new file mode 100644
index 0000000..89adc68
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/process/ShellProcessBuilder.java
@@ -0,0 +1,303 @@
+package dorkbox.util.process;
+
+
+
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import dorkbox.util.OS;
+
+/**
+ * If you want to save off the output from the process, set a PrintStream to the following:
+ * ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
+ * PrintStream outputStream = new PrintStream(byteArrayOutputStream);
+ * ...
+ * String output = byteArrayOutputStream.toString();
+ */
+public class ShellProcessBuilder {
+
+ // TODO: see http://mark.koli.ch/2009/12/uac-prompt-from-java-createprocess-error740-the-requested-operation-requires-elevation.html
+ // for more information on copying files in windows with UAC protections.
+ // maybe we want to hook into our launcher executor so that we can "auto elevate" commands?
+
+ private String workingDirectory = null;
+ private String executableName = null;
+ private String executableDirectory = null;
+
+ protected List arguments = new ArrayList();
+
+ private final PrintStream outputStream;
+ private final PrintStream errorStream;
+ private final InputStream inputStream;
+
+ private Process process = null;
+
+ // true if we want to save off (usually for debugging) the initial output from this
+ private boolean debugInfo = false;
+
+ /**
+ * This will cause the spawned process to pipe it's output to null.
+ */
+ public ShellProcessBuilder() {
+ this(null, null, null);
+ }
+
+ public ShellProcessBuilder(PrintStream out) {
+ this(null, out, out);
+ }
+
+ public ShellProcessBuilder(InputStream in, PrintStream out) {
+ this(in, out, out);
+ }
+
+ public ShellProcessBuilder(InputStream in, PrintStream out, PrintStream err) {
+ outputStream = out;
+ errorStream = err;
+ inputStream = in;
+ }
+
+ /**
+ * When launched from eclipse, the working directory is USUALLY the root of the project folder
+ */
+ public final ShellProcessBuilder setWorkingDirectory(String workingDirectory) {
+ // MUST be absolute path!!
+ this.workingDirectory = new File(workingDirectory).getAbsolutePath();
+ return this;
+ }
+
+ public final ShellProcessBuilder addArgument(String argument) {
+ arguments.add(argument);
+ return this;
+ }
+
+ public final ShellProcessBuilder addArguments(String... paths) {
+ for (String path : paths) {
+ arguments.add(path);
+ }
+ return this;
+ }
+
+ public final ShellProcessBuilder addArguments(List paths) {
+ arguments.addAll(paths);
+ return this;
+ }
+
+ public final ShellProcessBuilder setExecutable(String executableName) {
+ this.executableName = executableName;
+ return this;
+ }
+
+ public ShellProcessBuilder setExecutableDirectory(String executableDirectory) {
+ // MUST be absolute path!!
+ this.executableDirectory = new File(executableDirectory).getAbsolutePath();
+ return this;
+ }
+
+ public ShellProcessBuilder addDebugInfo() {
+ this.debugInfo = true;
+ return this;
+ }
+
+
+ public void start() {
+ // if no executable, then use the command shell
+ if (executableName == null) {
+ if (OS.isWindows()) {
+ // windows
+ executableName = "cmd";
+ arguments.add(0, "/c");
+
+ } else {
+ // *nix
+ executableName = "/bin/bash";
+ // executableName = "/bin/sh";
+ arguments.add(0, "-c");
+ }
+ } else if (workingDirectory != null) {
+ if (!workingDirectory.endsWith("/") && !workingDirectory.endsWith("\\")) {
+ workingDirectory += File.separator;
+ }
+ }
+
+ if (executableDirectory != null) {
+ if (!executableDirectory.endsWith("/") && !executableDirectory.endsWith("\\")) {
+ executableDirectory += File.separator;
+ }
+
+ executableName = executableDirectory + executableName;
+ }
+
+ List argumentsList = new ArrayList();
+ argumentsList.add(executableName);
+
+ for (String arg : arguments) {
+ if (arg.contains(" ")) {
+ // individual arguments MUST be in their own element in order to
+ // be processed properly (this is how it works on the command line!)
+ String[] split = arg.split(" ");
+ for (String s : split) {
+ argumentsList.add(s);
+ }
+ } else {
+ argumentsList.add(arg);
+ }
+ }
+
+
+ // if we don't want output... TODO: i think we want to "exec" (this calls exec -c, which calls our program)
+ // this code as well, since calling it directly won't work
+ boolean pipeToNull = errorStream == null || outputStream == null;
+ if (pipeToNull) {
+ if (OS.isWindows()) {
+ // >NUL on windows
+ argumentsList.add(">NUL");
+ } else {
+ // we will "pipe" it to /dev/null on *nix
+ argumentsList.add(">/dev/null 2>&1");
+ }
+ }
+
+ if (debugInfo) {
+ errorStream.print("Executing: ");
+ Iterator iterator = argumentsList.iterator();
+ while (iterator.hasNext()) {
+ String s = iterator.next();
+ errorStream.print(s);
+ if (iterator.hasNext()) {
+ errorStream.print(" ");
+ }
+ }
+ errorStream.print(OS.LINE_SEPARATOR);
+ }
+
+ ProcessBuilder processBuilder = new ProcessBuilder(argumentsList);
+ if (workingDirectory != null) {
+ processBuilder.directory(new File(workingDirectory));
+ }
+
+ // combine these so output is properly piped to null.
+ if (pipeToNull) {
+ processBuilder.redirectErrorStream(true);
+ }
+
+ try {
+ process = processBuilder.start();
+ } catch (Exception ex) {
+ errorStream.println("There was a problem executing the program. Details:\n");
+ ex.printStackTrace(errorStream);
+
+ if (process != null) {
+ try {
+ process.destroy();
+ process = null;
+ } catch (Exception e) {
+ errorStream.println("Error destroying process: \n");
+ e.printStackTrace(errorStream);
+ }
+ }
+ }
+
+ if (process != null) {
+ ProcessProxy writeToProcess_input;
+ ProcessProxy readFromProcess_output;
+ ProcessProxy readFromProcess_error;
+
+
+ if (pipeToNull) {
+ NullOutputStream nullOutputStream = new NullOutputStream();
+
+ processBuilder.redirectErrorStream(true);
+
+ // readers (read process -> write console)
+ // have to keep the output buffers from filling in the target process.
+ readFromProcess_output = new ProcessProxy("Process Reader: " + executableName, process.getInputStream(), nullOutputStream);
+ readFromProcess_error = null;
+ }
+ // we want to pipe our input/output from process to ourselves
+ else {
+ /**
+ * Proxy the System.out and System.err from the spawned process back
+ * to the user's window. This is important or the spawned process could block.
+ */
+ // readers (read process -> write console)
+ readFromProcess_output = new ProcessProxy("Process Reader: " + executableName, process.getInputStream(), outputStream);
+ if (errorStream != outputStream) {
+ readFromProcess_error = new ProcessProxy("Process Reader: " + executableName, process.getErrorStream(), errorStream);
+ } else {
+ processBuilder.redirectErrorStream(true);
+ readFromProcess_error = null;
+ }
+ }
+
+ if (inputStream != null) {
+ /**
+ * Proxy System.in from the user's window to the spawned process
+ */
+ // writer (read console -> write process)
+ writeToProcess_input = new ProcessProxy("Process Writer: " + executableName, inputStream, process.getOutputStream());
+ } else {
+ writeToProcess_input = null;
+ }
+
+
+ // the process can be killed in two ways
+ // If not in eclipse, by this shutdown hook. (clicking the red square to terminate a process will not run it's shutdown hooks)
+ // Typing "exit" will always terminate the process
+ Thread hook = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ if (debugInfo) {
+ errorStream.println("Terminating process: " + executableName);
+ }
+ process.destroy();
+ }
+ }
+ );
+ // add a shutdown hook to make sure that we properly terminate our spawned processes.
+ Runtime.getRuntime().addShutdownHook(hook);
+
+ if (writeToProcess_input != null) {
+ writeToProcess_input.start();
+ }
+ readFromProcess_output.start();
+ if (readFromProcess_error != null) {
+ readFromProcess_error.start();
+ }
+
+ try {
+ process.waitFor();
+
+ @SuppressWarnings("unused")
+ int exitValue = process.exitValue();
+
+ // wait for the READER threads to die (meaning their streams have closed/EOF'd)
+ if (writeToProcess_input != null) {
+ // the INPUT (from stdin). It should be via the InputConsole, but if it's in eclipse,etc -- then this doesn't do anything
+ // We are done reading input, since our program has closed...
+ writeToProcess_input.close();
+ writeToProcess_input.join();
+ }
+ readFromProcess_output.close();
+ readFromProcess_output.join();
+ if (readFromProcess_error != null) {
+ readFromProcess_error.close();
+ readFromProcess_error.join();
+ }
+
+ // forcibly terminate the process when it's streams have closed.
+ // this is for cleanup ONLY, not to actually do anything.
+ process.destroy();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ // remove the shutdown hook now that we've shutdown.
+ Runtime.getRuntime().removeShutdownHook(hook);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Dorkbox-Util/src/dorkbox/util/properties/PropertiesProvider.java b/Dorkbox-Util/src/dorkbox/util/properties/PropertiesProvider.java
new file mode 100644
index 0000000..110c508
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/properties/PropertiesProvider.java
@@ -0,0 +1,127 @@
+package dorkbox.util.properties;
+
+
+import java.awt.Color;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+public class PropertiesProvider {
+
+ // the basePath for properties based settings. In JAVA proper, this is by default relative to the jar location.
+ // in ANDROID dalvik, this must be specified to be the location of the APK plus some extra info. This must be set by the android app.
+ public static String basePath = "";
+
+ private final Properties properties = new SortedProperties();
+ private final File propertiesFile;
+
+ public PropertiesProvider(File propertiesFile) {
+ propertiesFile = propertiesFile.getAbsoluteFile();
+ // make sure the parent dir exists...
+ File parentFile = propertiesFile.getParentFile();
+ if (parentFile != null) {
+ parentFile.mkdirs();
+ }
+
+ this.propertiesFile = propertiesFile;
+
+ _load();
+ }
+
+ private final void _load() {
+ if (!propertiesFile.canRead() || !propertiesFile.exists()) {
+ // in this case, our properties file doesn't exist yet... create one!
+ _save();
+ }
+
+ try {
+ FileInputStream fis = new FileInputStream(propertiesFile);
+ properties.load(fis);
+ fis.close();
+
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ // oops!
+ System.err.println("Properties cannot load!");
+ e.printStackTrace();
+ }
+ }
+
+
+ private final void _save() {
+ try {
+ FileOutputStream fos = new FileOutputStream(propertiesFile);
+ properties.store(fos, "Settings and configuration file. Strings must be escape formatted!");
+ fos.flush();
+ fos.close();
+
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ System.err.println("Properties cannot save!");
+ } catch (IOException e) {
+ // oops!
+ System.err.println("Properties cannot save!");
+ e.printStackTrace();
+ }
+ }
+
+
+ public synchronized final void remove(final String key) {
+ properties.remove(key);
+ _save();
+ }
+
+ public synchronized final void save(final String key, Object value) {
+ if (key == null || value == null) {
+ return;
+ }
+
+ if (value instanceof Color) {
+ value = ((Color)value).getRGB();
+ }
+
+ properties.setProperty(key, value.toString());
+
+ _save();
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized T get(String key, Class clazz) {
+ if (key == null || clazz == null) {
+ return null;
+ }
+
+ String property = properties.getProperty(key);
+ if (property == null) {
+ return null;
+ }
+
+ // special cases
+ try {
+ if (clazz.equals(Integer.class)) {
+ return (T) new Integer(Integer.parseInt(property));
+ }
+ if (clazz.equals(Long.class)) {
+ return (T) new Long(Long.parseLong(property));
+ }
+ if (clazz.equals(Color.class)) {
+ return (T) new Color(new Integer(Integer.parseInt(property)), true);
+ }
+
+ else {
+ return (T) property;
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Properties Loader for property: " + key + System.getProperty("line.separator") + e.getMessage());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "PropertiesProvider [" + propertiesFile + "]";
+ }
+}
diff --git a/Dorkbox-Util/src/dorkbox/util/properties/SortedProperties.java b/Dorkbox-Util/src/dorkbox/util/properties/SortedProperties.java
new file mode 100644
index 0000000..7298bff
--- /dev/null
+++ b/Dorkbox-Util/src/dorkbox/util/properties/SortedProperties.java
@@ -0,0 +1,32 @@
+package dorkbox.util.properties;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Vector;
+
+public class SortedProperties extends Properties {
+
+ private static final long serialVersionUID = 3988064683926999433L;
+
+ private final Comparator compare = new Comparator() {
+ @Override
+ public int compare(Object o1, Object o2) {
+ return o1.toString().compareTo(o2.toString());
+ }};
+
+ @Override
+ public synchronized Enumeration keys() {
+ Enumeration keysEnum = super.keys();
+
+ Vector vector = new Vector(size());
+ for (;keysEnum.hasMoreElements();) {
+ vector.add(keysEnum.nextElement());
+ }
+
+ Collections.sort(vector, this.compare);
+
+ return vector.elements();
+ }
+}
diff --git a/Dorkbox-Util/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPublicKeyAccessor.java b/Dorkbox-Util/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPublicKeyAccessor.java
new file mode 100644
index 0000000..475d877
--- /dev/null
+++ b/Dorkbox-Util/src/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPublicKeyAccessor.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.jcajce.provider.asymmetric.dsa;
+
+import java.math.BigInteger;
+import java.security.spec.DSAParameterSpec;
+
+public class BCDSAPublicKeyAccessor {
+ public static BCDSAPublicKey newInstance(BigInteger bigInteger, DSAParameterSpec dsaParameterSpec) {
+ return new BCDSAPublicKey(bigInteger, dsaParameterSpec);
+ }
+}
diff --git a/Dorkbox-Util/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPublicKeyAccessor.java b/Dorkbox-Util/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPublicKeyAccessor.java
new file mode 100644
index 0000000..aa8edae
--- /dev/null
+++ b/Dorkbox-Util/src/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPublicKeyAccessor.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.jcajce.provider.asymmetric.rsa;
+
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+
+
+public class BCRSAPublicKeyAccessor {
+ public static BCRSAPublicKey newInstance(RSAKeyParameters publicKey) {
+ return new BCRSAPublicKey(publicKey);
+ }
+}
diff --git a/Dorkbox-Util/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509Accessor.java b/Dorkbox-Util/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509Accessor.java
new file mode 100644
index 0000000..3d76357
--- /dev/null
+++ b/Dorkbox-Util/src/org/bouncycastle/jcajce/provider/asymmetric/x509/X509Accessor.java
@@ -0,0 +1,21 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.util.Date;
+
+public class X509Accessor {
+
+ /**
+ * Verify the TIME/DATE of the certificate
+ * Stupid BC is package private, so this will let us access this method.
+ */
+ public static void verifyDate(java.security.cert.Certificate certificate) throws CertificateExpiredException, CertificateNotYetValidException {
+ // TODO: when checking the validite of the certificate, it is important to use a date from somewhere other than the
+ // host computer! (maybe use google? or something...)
+ // this will validate the DATES of the certificate, to make sure the cert is valid during the correct time period.
+
+ org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject cert = (org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject) certificate;
+ cert.checkValidity(new Date());
+ }
+}
diff --git a/Dorkbox-Util/src/org/bouncycastle/math/ec/ECAccessor.java b/Dorkbox-Util/src/org/bouncycastle/math/ec/ECAccessor.java
new file mode 100644
index 0000000..70ed1dd
--- /dev/null
+++ b/Dorkbox-Util/src/org/bouncycastle/math/ec/ECAccessor.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.math.ec;
+
+public class ECAccessor {
+ public static void setCoordSystem(ECCurve curve, int coordinateSystem) {
+ curve.coord = coordinateSystem;
+
+ }
+}
diff --git a/Dorkbox-Util/test/dorkbox/util/Base64FastTest.java b/Dorkbox-Util/test/dorkbox/util/Base64FastTest.java
new file mode 100644
index 0000000..ac6db29
--- /dev/null
+++ b/Dorkbox-Util/test/dorkbox/util/Base64FastTest.java
@@ -0,0 +1,28 @@
+package dorkbox.util;
+
+
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Random;
+
+import org.junit.Test;
+
+public class Base64FastTest {
+
+ @Test
+ public void base64Test() throws IOException {
+ byte[] randomData = new byte[1000000];
+ new Random().nextBytes(randomData);
+
+ byte[] enc = Base64Fast.encodeToByte(randomData, true);
+ byte[] dec = Base64Fast.decode(enc);
+
+ if (!Arrays.equals(randomData, dec)) {
+ fail("base64 test failed");
+ }
+
+ randomData = null;
+ }
+}
\ No newline at end of file
diff --git a/Dorkbox-Util/test/dorkbox/util/StorageTest.java b/Dorkbox-Util/test/dorkbox/util/StorageTest.java
new file mode 100644
index 0000000..78f6aa1
--- /dev/null
+++ b/Dorkbox-Util/test/dorkbox/util/StorageTest.java
@@ -0,0 +1,221 @@
+package dorkbox.util;
+
+
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class StorageTest {
+
+ @Test
+ public void storageTest() throws IOException {
+ File tempFile = FileUtil.tempFile("storageTest");
+ tempFile.deleteOnExit();
+
+ Data data = new Data();
+ Storage storage = Storage.load(tempFile, data);
+ storage.setSaveDelay(0);
+
+ if (data.bytes != null) {
+ fail("storage has data when it shouldn't");
+ }
+
+ makeData(data);
+ storage.save();
+
+
+ Data data2 = new Data();
+ storage.load(data2);
+
+ if (!data.equals(data2)) {
+ fail("storage test not equal");
+ }
+
+ data.string = "A different string entirely!";
+ storage.setSaveDelay(3000);
+ storage.save();
+
+ data2 = new Data();
+ storage.load(data2);
+ if (!data.equals(data2)) {
+ fail("storage test not copying fields on the fly.");
+ }
+
+ data2 = new Data();
+ storage.load(data2);
+ if (!data.equals(data2)) {
+ fail("storage test not equal");
+ }
+
+
+ try {
+ Storage.load(tempFile, null);
+ fail("storage test allowing null objects");
+ } catch (Exception e) {
+ }
+
+ Storage.shutdown();
+ }
+
+
+ // from kryo unit test.
+ private void makeData(Data data) {
+ StringBuilder buffer = new StringBuilder();
+ for (int i = 0; i < 3; i++) {
+ buffer.append('a');
+ }
+ data.string = buffer.toString();
+
+ data.strings = new String[] {"ab012", "", null, "!@#$", "�����"};
+ data.ints = new int[] {-1234567, 1234567, -1, 0, 1, Integer.MAX_VALUE, Integer.MIN_VALUE};
+ data.shorts = new short[] {-12345, 12345, -1, 0, 1, Short.MAX_VALUE, Short.MIN_VALUE};
+ data.floats = new float[] {0, -0, 1, -1, 123456, -123456, 0.1f, 0.2f, -0.3f, (float)Math.PI, Float.MAX_VALUE,
+ Float.MIN_VALUE};
+ data.doubles = new double[] {0, -0, 1, -1, 123456, -123456, 0.1d, 0.2d, -0.3d, Math.PI, Double.MAX_VALUE, Double.MIN_VALUE};
+ data.longs = new long[] {0, -0, 1, -1, 123456, -123456, 99999999999l, -99999999999l, Long.MAX_VALUE, Long.MIN_VALUE};
+ data.bytes = new byte[] {-123, 123, -1, 0, 1, Byte.MAX_VALUE, Byte.MIN_VALUE};
+ data.chars = new char[] {32345, 12345, 0, 1, 63, Character.MAX_VALUE, Character.MIN_VALUE};
+ data.booleans = new boolean[] {true, false};
+ data.Ints = new Integer[] {-1234567, 1234567, -1, 0, 1, Integer.MAX_VALUE, Integer.MIN_VALUE};
+ data.Shorts = new Short[] {-12345, 12345, -1, 0, 1, Short.MAX_VALUE, Short.MIN_VALUE};
+ data.Floats = new Float[] {0f, -0f, 1f, -1f, 123456f, -123456f, 0.1f, 0.2f, -0.3f, (float)Math.PI, Float.MAX_VALUE,
+ Float.MIN_VALUE};
+ data.Doubles = new Double[] {0d, -0d, 1d, -1d, 123456d, -123456d, 0.1d, 0.2d, -0.3d, Math.PI, Double.MAX_VALUE,
+ Double.MIN_VALUE};
+ data.Longs = new Long[] {0l, -0l, 1l, -1l, 123456l, -123456l, 99999999999l, -99999999999l, Long.MAX_VALUE, Long.MIN_VALUE};
+ data.Bytes = new Byte[] {-123, 123, -1, 0, 1, Byte.MAX_VALUE, Byte.MIN_VALUE};
+ data.Chars = new Character[] {32345, 12345, 0, 1, 63, Character.MAX_VALUE, Character.MIN_VALUE};
+ data.Booleans = new Boolean[] {true, false};
+ }
+
+ public static class Data {
+ public String string;
+ public String[] strings;
+ public int[] ints;
+ public short[] shorts;
+ public float[] floats;
+ public double[] doubles;
+ public long[] longs;
+ public byte[] bytes;
+ public char[] chars;
+ public boolean[] booleans;
+ public Integer[] Ints;
+ public Short[] Shorts;
+ public Float[] Floats;
+ public Double[] Doubles;
+ public Long[] Longs;
+ public Byte[] Bytes;
+ public Character[] Chars;
+ public Boolean[] Booleans;
+
+ public Data() {
+ }
+
+ @Override
+ public int hashCode () {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(this.Booleans);
+ result = prime * result + Arrays.hashCode(this.Bytes);
+ result = prime * result + Arrays.hashCode(this.Chars);
+ result = prime * result + Arrays.hashCode(this.Doubles);
+ result = prime * result + Arrays.hashCode(this.Floats);
+ result = prime * result + Arrays.hashCode(this.Ints);
+ result = prime * result + Arrays.hashCode(this.Longs);
+ result = prime * result + Arrays.hashCode(this.Shorts);
+ result = prime * result + Arrays.hashCode(this.booleans);
+ result = prime * result + Arrays.hashCode(this.bytes);
+ result = prime * result + Arrays.hashCode(this.chars);
+ result = prime * result + Arrays.hashCode(this.doubles);
+ result = prime * result + Arrays.hashCode(this.floats);
+ result = prime * result + Arrays.hashCode(this.ints);
+ result = prime * result + Arrays.hashCode(this.longs);
+ result = prime * result + Arrays.hashCode(this.shorts);
+ result = prime * result + (this.string == null ? 0 : this.string.hashCode());
+ result = prime * result + Arrays.hashCode(this.strings);
+ return result;
+ }
+
+ @Override
+ public boolean equals (Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Data other = (Data)obj;
+ if (!Arrays.equals(this.Booleans, other.Booleans)) {
+ return false;
+ }
+ if (!Arrays.equals(this.Bytes, other.Bytes)) {
+ return false;
+ }
+ if (!Arrays.equals(this.Chars, other.Chars)) {
+ return false;
+ }
+ if (!Arrays.equals(this.Doubles, other.Doubles)) {
+ return false;
+ }
+ if (!Arrays.equals(this.Floats, other.Floats)) {
+ return false;
+ }
+ if (!Arrays.equals(this.Ints, other.Ints)) {
+ return false;
+ }
+ if (!Arrays.equals(this.Longs, other.Longs)) {
+ return false;
+ }
+ if (!Arrays.equals(this.Shorts, other.Shorts)) {
+ return false;
+ }
+ if (!Arrays.equals(this.booleans, other.booleans)) {
+ return false;
+ }
+ if (!Arrays.equals(this.bytes, other.bytes)) {
+ return false;
+ }
+ if (!Arrays.equals(this.chars, other.chars)) {
+ return false;
+ }
+ if (!Arrays.equals(this.doubles, other.doubles)) {
+ return false;
+ }
+ if (!Arrays.equals(this.floats, other.floats)) {
+ return false;
+ }
+ if (!Arrays.equals(this.ints, other.ints)) {
+ return false;
+ }
+ if (!Arrays.equals(this.longs, other.longs)) {
+ return false;
+ }
+ if (!Arrays.equals(this.shorts, other.shorts)) {
+ return false;
+ }
+ if (this.string == null) {
+ if (other.string != null) {
+ return false;
+ }
+ } else if (!this.string.equals(other.string)) {
+ return false;
+ }
+ if (!Arrays.equals(this.strings, other.strings)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString () {
+ return "Data";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Dorkbox-Util/test/dorkbox/util/crypto/AesByteBufTest.java b/Dorkbox-Util/test/dorkbox/util/crypto/AesByteBufTest.java
new file mode 100644
index 0000000..3e1aa08
--- /dev/null
+++ b/Dorkbox-Util/test/dorkbox/util/crypto/AesByteBufTest.java
@@ -0,0 +1,325 @@
+
+package dorkbox.util.crypto;
+
+import static org.junit.Assert.fail;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import org.bouncycastle.crypto.engines.AESFastEngine;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.junit.Test;
+
+import dorkbox.util.crypto.bouncycastle.GCMBlockCipher_ByteBuf;
+
+public class AesByteBufTest {
+
+ private static String entropySeed = "asdjhasdkljalksdfhlaks4356268909087s0dfgkjh255124515hasdg87";
+
+ private String text = "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya." +
+ "hello, my name is inigo montoya. hello, my name is inigo montoya. hello, my name is inigo montoya.";
+
+ // test input smaller than block size
+ private byte[] bytes = "hello!".getBytes();
+
+ // test input larger than block size
+ private byte[] bytesLarge = text.getBytes();
+
+ @Test
+ public void AesGcmEncryptBothA() throws IOException {
+
+ final byte[] SOURCE = bytesLarge;
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ final GCMBlockCipher_ByteBuf aesEngine1 = new GCMBlockCipher_ByteBuf(new AESFastEngine());
+ final GCMBlockCipher aesEngine2 = new GCMBlockCipher(new AESFastEngine());
+
+ final byte[] key = new byte[32];
+ final byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key (32 bytes)
+ rand.nextBytes(iv); // 128bit block size (16 bytes)
+
+ final ParametersWithIV aesIVAndKey = new ParametersWithIV(new KeyParameter(key), iv);
+
+ ByteBuf source = Unpooled.wrappedBuffer(SOURCE);
+ int length = SOURCE.length;
+ ByteBuf encryptAES = Unpooled.buffer(1024);
+ int encryptLength = Crypto.AES.encrypt(aesEngine1, aesIVAndKey, source, encryptAES, length);
+
+ byte[] encrypt = new byte[encryptLength];
+ System.arraycopy(encryptAES.array(), 0, encrypt, 0, encryptLength);
+
+ byte[] encrypt2 = Crypto.AES.encrypt(aesEngine2, key, iv, SOURCE);
+
+
+ if (Arrays.equals(SOURCE, encrypt)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(encrypt, encrypt2)) {
+ fail("bytes not equal");
+ }
+ }
+
+ @Test
+ public void AesGcmEncryptBothB() throws IOException {
+ final byte[] SOURCE = bytes;
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ final GCMBlockCipher_ByteBuf aesEngine1 = new GCMBlockCipher_ByteBuf(new AESFastEngine());
+ final GCMBlockCipher aesEngine2 = new GCMBlockCipher(new AESFastEngine());
+
+ final byte[] key = new byte[32];
+ final byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key (32 bytes)
+ rand.nextBytes(iv); // 128bit block size (16 bytes)
+
+ final ParametersWithIV aesIVAndKey = new ParametersWithIV(new KeyParameter(key), iv);
+
+ ByteBuf source = Unpooled.wrappedBuffer(SOURCE);
+ int length = SOURCE.length;
+ ByteBuf encryptAES = Unpooled.buffer(1024);
+ int encryptLength = Crypto.AES.encrypt(aesEngine1, aesIVAndKey, source, encryptAES, length);
+
+ byte[] encrypt = new byte[encryptLength];
+ System.arraycopy(encryptAES.array(), 0, encrypt, 0, encryptLength);
+
+
+ byte[] encrypt2 = Crypto.AES.encrypt(aesEngine2, key, iv, SOURCE);
+
+
+ if (Arrays.equals(SOURCE, encrypt)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(encrypt, encrypt2)) {
+ fail("bytes not equal");
+ }
+ }
+
+ @Test
+ public void AesGcmEncryptBufOnly() throws IOException {
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ GCMBlockCipher_ByteBuf aesEngine1 = new GCMBlockCipher_ByteBuf(new AESFastEngine());
+ GCMBlockCipher aesEngine2 = new GCMBlockCipher(new AESFastEngine());
+
+ byte[] key = new byte[32];
+ byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key (32 bytes)
+ rand.nextBytes(iv); // 128bit block size (16 bytes)
+
+ ParametersWithIV aesIVAndKey = new ParametersWithIV(new KeyParameter(key), iv);
+ ByteBuf source = Unpooled.wrappedBuffer(bytes);
+ int length = bytes.length;
+ ByteBuf encryptAES = Unpooled.buffer(1024);
+ int encryptLength = Crypto.AES.encrypt(aesEngine1, aesIVAndKey, source, encryptAES, length);
+
+ byte[] encrypt = new byte[encryptLength];
+ System.arraycopy(encryptAES.array(), 0, encrypt, 0, encryptLength);
+
+
+ byte[] decrypt = Crypto.AES.decrypt(aesEngine2, key, iv, encrypt);
+
+
+ if (Arrays.equals(bytes, encrypt)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(bytes, decrypt)) {
+ fail("bytes not equal");
+ }
+ }
+
+ @Test
+ public void AesGcmDecryptBothA() throws IOException {
+
+ final byte[] SOURCE = bytesLarge;
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ final GCMBlockCipher aesEngine1 = new GCMBlockCipher(new AESFastEngine());
+ final GCMBlockCipher_ByteBuf aesEngine2 = new GCMBlockCipher_ByteBuf(new AESFastEngine());
+
+ final byte[] key = new byte[32];
+ final byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key (32 bytes)
+ rand.nextBytes(iv); // 128bit block size (16 bytes)
+
+ final byte[] encrypt = Crypto.AES.encrypt(aesEngine1, key, iv, SOURCE);
+ final ByteBuf encryptAES = Unpooled.wrappedBuffer(encrypt);
+ final int length = encrypt.length;
+
+ byte[] decrypt1 = Crypto.AES.decrypt(aesEngine1, key, iv, encrypt);
+
+
+ ParametersWithIV aesIVAndKey = new ParametersWithIV(new KeyParameter(key), iv);
+ ByteBuf decryptAES = Unpooled.buffer(1024);
+ int decryptLength = Crypto.AES.decrypt(aesEngine2, aesIVAndKey, encryptAES, decryptAES, length);
+ byte[] decrypt2 = new byte[decryptLength];
+ System.arraycopy(decryptAES.array(), 0, decrypt2, 0, decryptLength);
+
+
+ if (Arrays.equals(SOURCE, encrypt)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(decrypt1, decrypt2)) {
+ fail("bytes not equal");
+ }
+
+ if (!Arrays.equals(SOURCE, decrypt1)) {
+ fail("bytes not equal");
+ }
+ }
+
+ @Test
+ public void AesGcmDecryptBothB() throws IOException {
+
+ byte[] SOURCE = bytes;
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ final GCMBlockCipher aesEngine1 = new GCMBlockCipher(new AESFastEngine());
+ final GCMBlockCipher_ByteBuf aesEngine2 = new GCMBlockCipher_ByteBuf(new AESFastEngine());
+
+ final byte[] key = new byte[32];
+ final byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key (32 bytes)
+ rand.nextBytes(iv); // 128bit block size (16 bytes)
+
+ final byte[] encrypt = Crypto.AES.encrypt(aesEngine1, key, iv, SOURCE);
+ final ByteBuf encryptAES = Unpooled.wrappedBuffer(encrypt);
+ final int length = encrypt.length;
+
+
+ byte[] decrypt1 = Crypto.AES.decrypt(aesEngine1, key, iv, encrypt);
+
+
+ ParametersWithIV aesIVAndKey = new ParametersWithIV(new KeyParameter(key), iv);
+ ByteBuf decryptAES = Unpooled.buffer(1024);
+ int decryptLength = Crypto.AES.decrypt(aesEngine2, aesIVAndKey, encryptAES, decryptAES, length);
+ byte[] decrypt2 = new byte[decryptLength];
+ System.arraycopy(decryptAES.array(), 0, decrypt2, 0, decryptLength);
+
+
+ if (Arrays.equals(SOURCE, encrypt)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(decrypt1, decrypt2)) {
+ fail("bytes not equal");
+ }
+
+ if (!Arrays.equals(SOURCE, decrypt1)) {
+ fail("bytes not equal");
+ }
+ }
+
+ @Test
+ public void AesGcmDecryptBufOnlyA() throws IOException {
+ byte[] SOURCE = bytesLarge;
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ GCMBlockCipher aesEngine1 = new GCMBlockCipher(new AESFastEngine());
+ GCMBlockCipher_ByteBuf aesEngine2 = new GCMBlockCipher_ByteBuf(new AESFastEngine());
+
+ byte[] key = new byte[32];
+ byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key (32 bytes)
+ rand.nextBytes(iv); // 128bit block size (16 bytes)
+
+
+ byte[] encrypt = Crypto.AES.encrypt(aesEngine1, key, iv, SOURCE);
+ ByteBuf encryptAES = Unpooled.wrappedBuffer(encrypt);
+ int length = encrypt.length;
+
+
+ ParametersWithIV aesIVAndKey = new ParametersWithIV(new KeyParameter(key), iv);
+ ByteBuf decryptAES = Unpooled.buffer(1024);
+ int decryptLength = Crypto.AES.decrypt(aesEngine2, aesIVAndKey, encryptAES, decryptAES, length);
+ byte[] decrypt = new byte[decryptLength];
+ System.arraycopy(decryptAES.array(), 0, decrypt, 0, decryptLength);
+
+ if (Arrays.equals(SOURCE, encrypt)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(SOURCE, decrypt)) {
+ fail("bytes not equal");
+ }
+ }
+
+ @Test
+ public void AesGcmDecryptBufOnlyB() throws IOException {
+ byte[] SOURCE = bytes;
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ GCMBlockCipher aesEngine1 = new GCMBlockCipher(new AESFastEngine());
+ GCMBlockCipher_ByteBuf aesEngine2 = new GCMBlockCipher_ByteBuf(new AESFastEngine());
+
+ byte[] key = new byte[32];
+ byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key (32 bytes)
+ rand.nextBytes(iv); // 128bit block size (16 bytes)
+
+
+ byte[] encrypt = Crypto.AES.encrypt(aesEngine1, key, iv, SOURCE);
+ ByteBuf encryptAES = Unpooled.wrappedBuffer(encrypt);
+ int length = encrypt.length;
+
+
+ ParametersWithIV aesIVAndKey = new ParametersWithIV(new KeyParameter(key), iv);
+ ByteBuf decryptAES = Unpooled.buffer(1024);
+ int decryptLength = Crypto.AES.decrypt(aesEngine2, aesIVAndKey, encryptAES, decryptAES, length);
+ byte[] decrypt = new byte[decryptLength];
+ System.arraycopy(decryptAES.array(), 0, decrypt, 0, decryptLength);
+
+ if (Arrays.equals(SOURCE, encrypt)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(SOURCE, decrypt)) {
+ fail("bytes not equal");
+ }
+ }
+}
diff --git a/Dorkbox-Util/test/dorkbox/util/crypto/AesTest.java b/Dorkbox-Util/test/dorkbox/util/crypto/AesTest.java
new file mode 100644
index 0000000..5c0a371
--- /dev/null
+++ b/Dorkbox-Util/test/dorkbox/util/crypto/AesTest.java
@@ -0,0 +1,328 @@
+
+package dorkbox.util.crypto;
+
+
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import org.bouncycastle.crypto.engines.AESFastEngine;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.junit.Test;
+
+public class AesTest {
+
+ private static String entropySeed = "asdjhasdkljalksdfhlaks4356268909087s0dfgkjh255124515hasdg87";
+
+ @Test
+ public void AesGcm() throws IOException {
+ byte[] bytes = "hello, my name is inigo montoya".getBytes();
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ GCMBlockCipher aesEngine = new GCMBlockCipher(new AESFastEngine());
+
+ byte[] key = new byte[32];
+ byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key (32 bytes)
+ rand.nextBytes(iv); // 128bit block size (16 bytes)
+
+
+ byte[] encryptAES = Crypto.AES.encrypt(aesEngine, key, iv, bytes);
+ byte[] decryptAES = Crypto.AES.decrypt(aesEngine, key, iv, encryptAES);
+
+ if (Arrays.equals(bytes, encryptAES)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(bytes, decryptAES)) {
+ fail("bytes not equal");
+ }
+ }
+
+ // Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
+ @SuppressWarnings("deprecation")
+ @Test
+ public void AesBlock() throws IOException {
+ byte[] bytes = "hello, my name is inigo montoya".getBytes();
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
+
+ byte[] key = new byte[32];
+ byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key
+ rand.nextBytes(iv); // 16bit block size
+
+
+ byte[] encryptAES = Crypto.AES.encrypt(aesEngine, key, iv, bytes);
+ byte[] decryptAES = Crypto.AES.decrypt(aesEngine, key, iv, encryptAES);
+
+ if (Arrays.equals(bytes, encryptAES)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(bytes, decryptAES)) {
+ fail("bytes not equal");
+ }
+ }
+
+ @Test
+ public void AesGcmStream() throws IOException {
+ byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ GCMBlockCipher aesEngine = new GCMBlockCipher(new AESFastEngine());
+
+ byte[] key = new byte[32];
+ byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key
+ rand.nextBytes(iv); // 128bit block size
+
+
+ boolean success = Crypto.AES.encryptStream(aesEngine, key, iv, inputStream, outputStream);
+
+ if (!success) {
+ fail("crypto was not successful");
+ }
+
+ byte[] encryptBytes = outputStream.toByteArray();
+
+ inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+ outputStream = new ByteArrayOutputStream();
+
+ success = Crypto.AES.decryptStream(aesEngine, key, iv, inputStream, outputStream);
+
+ if (!success) {
+ fail("crypto was not successful");
+ }
+
+ byte[] decryptBytes = outputStream.toByteArray();
+
+ if (Arrays.equals(originalBytes, encryptBytes)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(originalBytes, decryptBytes)) {
+ fail("bytes not equal");
+ }
+ }
+
+ // Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
+ @SuppressWarnings("deprecation")
+ @Test
+ public void AesBlockStream() throws IOException {
+ byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
+
+ byte[] key = new byte[32];
+ byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key
+ rand.nextBytes(iv); // 128bit block size
+
+
+ boolean success = Crypto.AES.encryptStream(aesEngine, key, iv, inputStream, outputStream);
+
+ if (!success) {
+ fail("crypto was not successful");
+ }
+
+ byte[] encryptBytes = outputStream.toByteArray();
+
+ inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+ outputStream = new ByteArrayOutputStream();
+
+ success = Crypto.AES.decryptStream(aesEngine, key, iv, inputStream, outputStream);
+
+
+ if (!success) {
+ fail("crypto was not successful");
+ }
+
+ byte[] decryptBytes = outputStream.toByteArray();
+
+ if (Arrays.equals(originalBytes, encryptBytes)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(originalBytes, decryptBytes)) {
+ fail("bytes not equal");
+ }
+ }
+
+ @Test
+ public void AesWithIVGcm() throws IOException {
+ byte[] bytes = "hello, my name is inigo montoya".getBytes();
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ GCMBlockCipher aesEngine = new GCMBlockCipher(new AESFastEngine());
+
+ byte[] key = new byte[32]; // 256bit key
+ byte[] iv = new byte[aesEngine.getUnderlyingCipher().getBlockSize()];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key);
+ rand.nextBytes(iv);
+
+
+ byte[] encryptAES = Crypto.AES.encryptWithIV(aesEngine, key, iv, bytes);
+ byte[] decryptAES = Crypto.AES.decryptWithIV(aesEngine, key, encryptAES);
+
+ if (Arrays.equals(bytes, encryptAES)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(bytes, decryptAES)) {
+ fail("bytes not equal");
+ }
+ }
+
+ // Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
+ @SuppressWarnings("deprecation")
+ @Test
+ public void AesWithIVBlock() throws IOException {
+ byte[] bytes = "hello, my name is inigo montoya".getBytes();
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
+
+ byte[] key = new byte[32]; // 256bit key
+ byte[] iv = new byte[aesEngine.getUnderlyingCipher().getBlockSize()];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key);
+ rand.nextBytes(iv);
+
+
+ byte[] encryptAES = Crypto.AES.encryptWithIV(aesEngine, key, iv, bytes);
+ byte[] decryptAES = Crypto.AES.decryptWithIV(aesEngine, key, encryptAES);
+
+ if (Arrays.equals(bytes, encryptAES)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(bytes, decryptAES)) {
+ fail("bytes not equal");
+ }
+ }
+
+ @Test
+ public void AesWithIVGcmStream() throws IOException {
+ byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ GCMBlockCipher aesEngine = new GCMBlockCipher(new AESFastEngine());
+
+ byte[] key = new byte[32];
+ byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key
+ rand.nextBytes(iv); // 128bit block size
+
+
+ boolean success = Crypto.AES.encryptStreamWithIV(aesEngine, key, iv, inputStream, outputStream);
+
+ if (!success) {
+ fail("crypto was not successful");
+ }
+
+ byte[] encryptBytes = outputStream.toByteArray();
+
+ inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+ outputStream = new ByteArrayOutputStream();
+
+ success = Crypto.AES.decryptStreamWithIV(aesEngine, key, inputStream, outputStream);
+
+ if (!success) {
+ fail("crypto was not successful");
+ }
+
+ byte[] decryptBytes = outputStream.toByteArray();
+
+ if (Arrays.equals(originalBytes, encryptBytes)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(originalBytes, decryptBytes)) {
+ fail("bytes not equal");
+ }
+ }
+
+ // Note: this is still tested, but DO NOT USE BLOCK MODE as it does NOT provide authentication. GCM does.
+ @SuppressWarnings("deprecation")
+ @Test
+ public void AesWithIVBlockStream() throws IOException {
+ byte[] originalBytes = "hello, my name is inigo montoya".getBytes();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(originalBytes);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ SecureRandom rand = new SecureRandom(entropySeed.getBytes());
+
+ PaddedBufferedBlockCipher aesEngine = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
+
+ byte[] key = new byte[32];
+ byte[] iv = new byte[16];
+
+ // note: the IV needs to be VERY unique!
+ rand.nextBytes(key); // 256bit key
+ rand.nextBytes(iv); // 128bit block size
+
+
+ boolean success = Crypto.AES.encryptStreamWithIV(aesEngine, key, iv, inputStream, outputStream);
+
+ if (!success) {
+ fail("crypto was not successful");
+ }
+
+ byte[] encryptBytes = outputStream.toByteArray();
+
+ inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+ outputStream = new ByteArrayOutputStream();
+
+ success = Crypto.AES.decryptStreamWithIV(aesEngine, key, inputStream, outputStream);
+
+
+ if (!success) {
+ fail("crypto was not successful");
+ }
+
+ byte[] decryptBytes = outputStream.toByteArray();
+
+ if (Arrays.equals(originalBytes, encryptBytes)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(originalBytes, decryptBytes)) {
+ fail("bytes not equal");
+ }
+ }
+}
diff --git a/Dorkbox-Util/test/dorkbox/util/crypto/DsaTest.java b/Dorkbox-Util/test/dorkbox/util/crypto/DsaTest.java
new file mode 100644
index 0000000..67e804d
--- /dev/null
+++ b/Dorkbox-Util/test/dorkbox/util/crypto/DsaTest.java
@@ -0,0 +1,136 @@
+
+package dorkbox.util.crypto;
+
+
+
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DSAParameter;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.junit.Test;
+
+
+@SuppressWarnings("deprecation")
+public class DsaTest {
+ private static String entropySeed = "asdjhaffasttjjhgpx600gn,-356268909087s0dfgkjh255124515hasdg87";
+
+ // Note: this is here just for keeping track of how this is done. This should NOT be used, and instead ECC crypto used instead.
+ @Test
+ public void Dsa() {
+ byte[] bytes = "hello, my name is inigo montoya".getBytes();
+
+ AsymmetricCipherKeyPair generateKeyPair = Crypto.DSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
+ DSAPrivateKeyParameters privateKey = (DSAPrivateKeyParameters) generateKeyPair.getPrivate();
+ DSAPublicKeyParameters publicKey = (DSAPublicKeyParameters) generateKeyPair.getPublic();
+
+
+ BigInteger[] signature = Crypto.DSA.generateSignature(privateKey, new SecureRandom(entropySeed.getBytes()), bytes);
+
+ boolean verify1 = Crypto.DSA.verifySignature(publicKey, bytes, signature);
+
+ if (!verify1) {
+ fail("failed signature verification");
+ }
+
+
+ byte[] bytes2 = "hello, my name is inigo montoya FAILED VERSION".getBytes();
+
+ if (Arrays.equals(bytes, bytes2)) {
+ fail("failed to create different byte arrays for testing bad messages");
+ }
+
+
+
+ boolean verify2 = Crypto.DSA.verifySignature(publicKey, bytes2, signature);
+
+ if (verify2) {
+ fail("failed signature verification with bad message");
+ }
+ }
+
+ @Test
+ public void DsaJceSerializaion() throws IOException {
+
+ AsymmetricCipherKeyPair generateKeyPair = Crypto.DSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
+ DSAPrivateKeyParameters privateKey = (DSAPrivateKeyParameters) generateKeyPair.getPrivate();
+ DSAPublicKeyParameters publicKey = (DSAPublicKeyParameters) generateKeyPair.getPublic();
+
+
+ // public key as bytes.
+ DSAParameters parameters = publicKey.getParameters();
+ byte[] bs = new SubjectPublicKeyInfo(
+ new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa,
+ new DSAParameter(parameters.getP(), parameters.getQ(), parameters.getG()).toASN1Primitive()),
+ new ASN1Integer(publicKey.getY())).getEncoded();
+
+
+
+ parameters = privateKey.getParameters();
+ byte[] bs2 = new PrivateKeyInfo(
+ new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa,
+ new DSAParameter(parameters.getP(), parameters.getQ(), parameters.getG()).toASN1Primitive()),
+ new ASN1Integer(privateKey.getX())).getEncoded();
+
+
+
+
+
+ DSAPrivateKeyParameters privateKey2 = (DSAPrivateKeyParameters) PrivateKeyFactory.createKey(bs2);
+ DSAPublicKeyParameters publicKey2 = (DSAPublicKeyParameters) PublicKeyFactory.createKey(bs);
+
+
+
+ // test via signing
+ byte[] bytes = "hello, my name is inigo montoya".getBytes();
+
+
+ BigInteger[] signature = Crypto.DSA.generateSignature(privateKey, new SecureRandom(entropySeed.getBytes()), bytes);
+
+ boolean verify1 = Crypto.DSA.verifySignature(publicKey, bytes, signature);
+
+ if (!verify1) {
+ fail("failed signature verification");
+ }
+
+
+ boolean verify2 = Crypto.DSA.verifySignature(publicKey2, bytes, signature);
+
+ if (!verify2) {
+ fail("failed signature verification");
+ }
+
+
+
+ // now reverse who signs what.
+ BigInteger[] signatureB = Crypto.DSA.generateSignature(privateKey2, new SecureRandom(entropySeed.getBytes()), bytes);
+
+ boolean verifyB1 = Crypto.DSA.verifySignature(publicKey, bytes, signatureB);
+
+ if (!verifyB1) {
+ fail("failed signature verification");
+ }
+
+
+ boolean verifyB2 = Crypto.DSA.verifySignature(publicKey2, bytes, signatureB);
+
+ if (!verifyB2) {
+ fail("failed signature verification");
+ }
+ }
+
+}
diff --git a/Dorkbox-Util/test/dorkbox/util/crypto/EccTest.java b/Dorkbox-Util/test/dorkbox/util/crypto/EccTest.java
new file mode 100644
index 0000000..715b971
--- /dev/null
+++ b/Dorkbox-Util/test/dorkbox/util/crypto/EccTest.java
@@ -0,0 +1,317 @@
+
+package dorkbox.util.crypto;
+
+
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.BasicAgreement;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
+import org.bouncycastle.crypto.engines.AESFastEngine;
+import org.bouncycastle.crypto.engines.IESEngine;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.IESParameters;
+import org.bouncycastle.crypto.params.IESWithCipherParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.signers.ECDSASigner;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.util.encoders.Hex;
+import org.junit.Test;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+import dorkbox.util.crypto.serialization.EccPrivateKeySerializer;
+import dorkbox.util.crypto.serialization.EccPublicKeySerializer;
+import dorkbox.util.crypto.serialization.IesParametersSerializer;
+import dorkbox.util.crypto.serialization.IesWithCipherParametersSerializer;
+
+
+public class EccTest {
+
+ private static String entropySeed = "asdjhaffasttjasdasdgfgaerym0698768.,./8909087s0dfgkjgb49bmngrSGDSG#";
+
+ @Test
+ public void EccStreamMode() throws IOException {
+ SecureRandom secureRandom = new SecureRandom();
+
+ AsymmetricCipherKeyPair key1 = Crypto.ECC.generateKeyPair(Crypto.ECC.p521_curve, secureRandom);
+ AsymmetricCipherKeyPair key2 = Crypto.ECC.generateKeyPair(Crypto.ECC.p521_curve, secureRandom);
+
+ IESParameters cipherParams = Crypto.ECC.generateSharedParameters(secureRandom);
+
+ IESEngine encrypt = Crypto.ECC.createEngine();
+ IESEngine decrypt = Crypto.ECC.createEngine();
+
+
+ // note: we want an ecc key that is AT LEAST 512 bits! (which is equal to AES 256)
+ // using 521 bits from curve.
+ CipherParameters private1 = key1.getPrivate();
+ CipherParameters public1 = key1.getPublic();
+
+ CipherParameters private2 = key2.getPrivate();
+ CipherParameters public2 = key2.getPublic();
+
+ byte[] message = Hex.decode("123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef");
+
+ // test stream mode
+ byte[] encrypted = Crypto.ECC.encrypt(encrypt, private1, public2, cipherParams, message);
+ byte[] plaintext = Crypto.ECC.decrypt(decrypt, private2, public1, cipherParams, encrypted);
+
+ if (Arrays.equals(encrypted, message)) {
+ fail("stream cipher test failed");
+ }
+
+ if (!Arrays.equals(plaintext, message)) {
+ fail("stream cipher test failed");
+ }
+ }
+
+ @Test
+ public void EccAesMode() throws IOException {
+ // test AES encrypt mode
+ SecureRandom secureRandom = new SecureRandom();
+
+ AsymmetricCipherKeyPair key1 = Crypto.ECC.generateKeyPair(Crypto.ECC.p521_curve, secureRandom);
+ AsymmetricCipherKeyPair key2 = Crypto.ECC.generateKeyPair(Crypto.ECC.p521_curve, secureRandom);
+
+
+ PaddedBufferedBlockCipher aesEngine1 = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
+ PaddedBufferedBlockCipher aesEngine2 = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
+
+ IESWithCipherParameters cipherParams = Crypto.ECC.generateSharedParametersWithCipher(secureRandom);
+
+
+ IESEngine encrypt = Crypto.ECC.createEngine(aesEngine1);
+ IESEngine decrypt = Crypto.ECC.createEngine(aesEngine2);
+
+
+ // note: we want an ecc key that is AT LEAST 512 bits! (which is equal to AES 256)
+ // using 521 bits from curve.
+ CipherParameters private1 = key1.getPrivate();
+ CipherParameters public1 = key1.getPublic();
+
+ CipherParameters private2 = key2.getPrivate();
+ CipherParameters public2 = key2.getPublic();
+
+ byte[] message = Hex.decode("123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef123456784358754934597967249867359283792374987692348750276509765091834790abcdef");
+
+ // test stream mode
+ byte[] encrypted = Crypto.ECC.encrypt(encrypt, private1, public2, cipherParams, message);
+ byte[] plaintext = Crypto.ECC.decrypt(decrypt, private2, public1, cipherParams, encrypted);
+
+ if (Arrays.equals(encrypted, message)) {
+ fail("stream cipher test failed");
+ }
+
+ if (!Arrays.equals(plaintext, message)) {
+ fail("stream cipher test failed");
+ }
+ }
+
+ @Test
+ public void Ecdh() throws IOException {
+ // test DH key exchange
+ SecureRandom secureRandom = new SecureRandom();
+
+ AsymmetricCipherKeyPair key1 = Crypto.ECC.generateKeyPair(Crypto.ECC.p521_curve, secureRandom);
+ AsymmetricCipherKeyPair key2 = Crypto.ECC.generateKeyPair(Crypto.ECC.p521_curve, secureRandom);
+
+ BasicAgreement e1 = new ECDHCBasicAgreement();
+ BasicAgreement e2 = new ECDHCBasicAgreement();
+
+ e1.init(key1.getPrivate());
+ e2.init(key2.getPrivate());
+
+ BigInteger k1 = e1.calculateAgreement(key2.getPublic());
+ BigInteger k2 = e2.calculateAgreement(key1.getPublic());
+
+ if (!k1.equals(k2)) {
+ fail("ECDHC cipher test failed");
+ }
+ }
+
+ @Test
+ public void EccDsa() throws IOException {
+ SecureRandom secureRandom = new SecureRandom();
+
+ AsymmetricCipherKeyPair key1 = Crypto.ECC.generateKeyPair(Crypto.ECC.p521_curve, secureRandom);
+
+ ParametersWithRandom param = new ParametersWithRandom(key1.getPrivate(), new SecureRandom());
+
+ ECDSASigner ecdsa = new ECDSASigner();
+
+ ecdsa.init(true, param);
+
+ byte[] message = new BigInteger("345234598734987394672039478602934578").toByteArray();
+ BigInteger[] sig = ecdsa.generateSignature(message);
+
+
+ ecdsa.init(false, key1.getPublic());
+
+ if (!ecdsa.verifySignature(message, sig[0], sig[1])) {
+ fail("ECDSA signature fails");
+ }
+ }
+
+ @Test
+ public void EccSerialization() {
+ SecureRandom secureRandom = new SecureRandom();
+
+ AsymmetricCipherKeyPair key1 = Crypto.ECC.generateKeyPair(Crypto.ECC.p521_curve, secureRandom);
+
+ IESParameters cipherAParams = Crypto.ECC.generateSharedParameters(secureRandom);
+ IESWithCipherParameters cipherBParams = Crypto.ECC.generateSharedParametersWithCipher(secureRandom);
+
+
+ // note: we want an ecc key that is AT LEAST 512 bits! (which is equal to AES 256)
+ // using 521 bits from curve.
+ ECPrivateKeyParameters private1 = (ECPrivateKeyParameters) key1.getPrivate();
+ ECPublicKeyParameters public1 = (ECPublicKeyParameters) key1.getPublic();
+
+
+ Kryo kryo = new Kryo();
+ kryo.register(IESParameters.class, new IesParametersSerializer());
+ kryo.register(IESWithCipherParameters.class, new IesWithCipherParametersSerializer());
+ kryo.register(ECPublicKeyParameters.class, new EccPublicKeySerializer());
+ kryo.register(ECPrivateKeyParameters.class, new EccPrivateKeySerializer());
+
+
+
+ // Test output to stream, large buffer.
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ Output output = new Output(outStream, 4096);
+ kryo.writeClassAndObject(output, cipherAParams);
+ output.flush();
+
+ // Test input from stream, large buffer.
+ Input input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
+ IESParameters cipherAParams2 = (IESParameters) kryo.readClassAndObject(input);
+
+
+ if (!Crypto.ECC.compare(cipherAParams, cipherAParams2)) {
+ fail("cipher parameters not equal");
+ }
+
+ // Test output to stream, large buffer.
+ outStream = new ByteArrayOutputStream();
+ output = new Output(outStream, 4096);
+ kryo.writeClassAndObject(output, cipherBParams);
+ output.flush();
+
+ // Test input from stream, large buffer.
+ input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
+ IESWithCipherParameters cipherBParams2 = (IESWithCipherParameters) kryo.readClassAndObject(input);
+
+ if (!Crypto.ECC.compare(cipherBParams, cipherBParams2)) {
+ fail("cipher parameters not equal");
+ }
+
+
+ // Test output to stream, large buffer.
+ outStream = new ByteArrayOutputStream();
+ output = new Output(outStream, 4096);
+ kryo.writeClassAndObject(output, private1);
+ output.flush();
+
+ // Test input from stream, large buffer.
+ input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
+ ECPrivateKeyParameters private2 = (ECPrivateKeyParameters) kryo.readClassAndObject(input);
+
+ if (!Crypto.ECC.compare(private1, private2)) {
+ fail("private keys not equal");
+ }
+
+
+ // Test output to stream, large buffer.
+ outStream = new ByteArrayOutputStream();
+ output = new Output(outStream, 4096);
+ kryo.writeClassAndObject(output, public1);
+ output.flush();
+
+ // Test input from stream, large buffer.
+ input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
+ ECPublicKeyParameters public2 = (ECPublicKeyParameters) kryo.readClassAndObject(input);
+
+ if (!Crypto.ECC.compare(public1, public2)) {
+ fail("public keys not equal");
+ }
+ }
+
+
+ @Test
+ public void EccJceSerialization() throws IOException {
+ AsymmetricCipherKeyPair generateKeyPair = Crypto.ECC.generateKeyPair(Crypto.ECC.p521_curve, new SecureRandom());
+ ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) generateKeyPair.getPrivate();
+ ECPublicKeyParameters publicKey = (ECPublicKeyParameters) generateKeyPair.getPublic();
+
+
+ BCECPublicKey bcecPublicKey = new BCECPublicKey("EC", publicKey, (ECParameterSpec) null, BouncyCastleProvider.CONFIGURATION);
+ byte[] publicBytes = bcecPublicKey.getEncoded();
+
+
+
+ // relies on the BC public key.
+ BCECPrivateKey bcecPrivateKey = new BCECPrivateKey("EC", privateKey, bcecPublicKey, (ECParameterSpec) null, BouncyCastleProvider.CONFIGURATION);
+ byte[] privateBytes = bcecPrivateKey.getEncoded();
+
+
+
+ ECPublicKeyParameters publicKey2 = (ECPublicKeyParameters) PublicKeyFactory.createKey(publicBytes);
+ ECPrivateKeyParameters privateKey2 = (ECPrivateKeyParameters) PrivateKeyFactory.createKey(privateBytes);
+
+
+
+ // test via signing
+ byte[] bytes = "hello, my name is inigo montoya".getBytes();
+
+
+ BigInteger[] signature = Crypto.ECC.generateSignature("SHA384", privateKey, new SecureRandom(entropySeed.getBytes()), bytes);
+
+ boolean verify1 = Crypto.ECC.verifySignature("SHA384", publicKey, bytes, signature);
+
+ if (!verify1) {
+ fail("failed signature verification");
+ }
+
+ boolean verify2 = Crypto.ECC.verifySignature("SHA384", publicKey2, bytes, signature);
+
+ if (!verify2) {
+ fail("failed signature verification");
+ }
+
+
+
+ // now reverse who signs what.
+ BigInteger[] signatureB = Crypto.ECC.generateSignature("SHA384", privateKey2, new SecureRandom(entropySeed.getBytes()), bytes);
+
+ boolean verifyB1 = Crypto.ECC.verifySignature("SHA384", publicKey, bytes, signatureB);
+
+ if (!verifyB1) {
+ fail("failed signature verification");
+ }
+
+ boolean verifyB2 = Crypto.ECC.verifySignature("SHA384", publicKey2, bytes, signatureB);
+
+ if (!verifyB2) {
+ fail("failed signature verification");
+ }
+ }
+}
diff --git a/Dorkbox-Util/test/dorkbox/util/crypto/RsaTest.java b/Dorkbox-Util/test/dorkbox/util/crypto/RsaTest.java
new file mode 100644
index 0000000..c590432
--- /dev/null
+++ b/Dorkbox-Util/test/dorkbox/util/crypto/RsaTest.java
@@ -0,0 +1,127 @@
+
+package dorkbox.util.crypto;
+
+
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.encodings.OAEPEncoding;
+import org.bouncycastle.crypto.engines.RSAEngine;
+import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
+import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.crypto.signers.PSSSigner;
+import org.junit.Test;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+import dorkbox.util.crypto.serialization.RsaPrivateKeySerializer;
+import dorkbox.util.crypto.serialization.RsaPublicKeySerializer;
+
+
+public class RsaTest {
+ private static String entropySeed = "asdjhaffasttjjhgpx600gn,-356268909087s0dfgkjh255124515hasdg87";
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void Rsa() {
+ byte[] bytes = "hello, my name is inigo montoya".getBytes();
+
+ AsymmetricCipherKeyPair key = Crypto.RSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
+
+ RSAKeyParameters public1 = (RSAKeyParameters) key.getPublic();
+ RSAPrivateCrtKeyParameters private1 = (RSAPrivateCrtKeyParameters) key.getPrivate();
+
+
+ RSAEngine engine = new RSAEngine();
+ SHA1Digest digest = new SHA1Digest();
+ OAEPEncoding rsaEngine = new OAEPEncoding(engine, digest);
+
+ // test encrypt/decrypt
+ byte[] encryptRSA = Crypto.RSA.encrypt(rsaEngine, public1, bytes);
+ byte[] decryptRSA = Crypto.RSA.decrypt(rsaEngine, private1, encryptRSA);
+
+ if (Arrays.equals(bytes, encryptRSA)) {
+ fail("bytes should not be equal");
+ }
+
+ if (!Arrays.equals(bytes, decryptRSA)) {
+ fail("bytes not equal");
+ }
+
+ // test signing/verification
+ PSSSigner signer = new PSSSigner(engine, digest, digest.getDigestSize());
+
+ byte[] signatureRSA = Crypto.RSA.sign(signer, private1, bytes);
+ boolean verify = Crypto.RSA.verify(signer, public1, signatureRSA, bytes);
+
+ if (!verify) {
+ fail("failed signature verification");
+ }
+ }
+
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void RsaSerialization () throws IOException {
+ RSAKeyPairGenerator keyGen = new RSAKeyPairGenerator();
+ RSAKeyGenerationParameters params = new RSAKeyGenerationParameters(new BigInteger("65537"), // public exponent
+ new SecureRandom(entropySeed.getBytes()), //pnrg
+ 1024, // key length
+ 8); //the number of iterations of the Miller-Rabin primality test.
+ keyGen.init(params);
+
+
+ AsymmetricCipherKeyPair key = keyGen.generateKeyPair();
+
+ RSAKeyParameters public1 = (RSAKeyParameters) key.getPublic();
+ RSAPrivateCrtKeyParameters private1 = (RSAPrivateCrtKeyParameters) key.getPrivate();
+
+
+ Kryo kryo = new Kryo();
+ kryo.register(RSAKeyParameters.class, new RsaPublicKeySerializer());
+ kryo.register(RSAPrivateCrtKeyParameters.class, new RsaPrivateKeySerializer());
+
+ // Test output to stream, large buffer.
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ Output output = new Output(outStream, 4096);
+ kryo.writeClassAndObject(output, public1);
+ output.flush();
+
+ // Test input from stream, large buffer.
+ Input input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
+ RSAKeyParameters public2 = (RSAKeyParameters) kryo.readClassAndObject(input);
+
+
+ if (!Crypto.RSA.compare(public1, public2)) {
+ fail("public keys not equal");
+ }
+
+
+ // Test output to stream, large buffer.
+ outStream = new ByteArrayOutputStream();
+ output = new Output(outStream, 4096);
+ kryo.writeClassAndObject(output, private1);
+ output.flush();
+
+ // Test input from stream, large buffer.
+ input = new Input(new ByteArrayInputStream(outStream.toByteArray()), 4096);
+ RSAPrivateCrtKeyParameters private2 = (RSAPrivateCrtKeyParameters) kryo.readClassAndObject(input);
+
+
+ if (!Crypto.RSA.compare(private1, private2)) {
+ fail("private keys not equal");
+ }
+ }
+}
diff --git a/Dorkbox-Util/test/dorkbox/util/crypto/SCryptTest.java b/Dorkbox-Util/test/dorkbox/util/crypto/SCryptTest.java
new file mode 100644
index 0000000..4bbe8e7
--- /dev/null
+++ b/Dorkbox-Util/test/dorkbox/util/crypto/SCryptTest.java
@@ -0,0 +1,59 @@
+
+package dorkbox.util.crypto;
+
+
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import org.junit.Test;
+
+import dorkbox.util.Sys;
+
+
+public class SCryptTest {
+
+ @Test
+ public void SCrypt() throws IOException, GeneralSecurityException {
+
+ byte[] P, S;
+ int N, r, p, dkLen;
+ String DK;
+
+ // empty key & salt test missing because unsupported by JCE
+
+ P = "password".getBytes("UTF-8");
+ S = "NaCl".getBytes("UTF-8");
+ N = 1024;
+ r = 8;
+ p = 16;
+ dkLen = 64;
+ DK = "FDBABE1C9D3472007856E7190D01E9FE7C6AD7CBC8237830E77376634B3731622EAF30D92E22A3886FF109279D9830DAC727AFB94A83EE6D8360CBDFA2CC0640";
+
+ assertEquals(DK, Sys.bytesToHex(Crypto.SCrypt.encrypt(P, S, N, r, p, dkLen)));
+
+
+ P = "pleaseletmein".getBytes("UTF-8");
+ S = "SodiumChloride".getBytes("UTF-8");
+ N = 16384;
+ r = 8;
+ p = 1;
+ dkLen = 64;
+ DK = "7023BDCB3AFD7348461C06CD81FD38EBFDA8FBBA904F8E3EA9B543F6545DA1F2D5432955613F0FCF62D49705242A9AF9E61E85DC0D651E40DFCF017B45575887";
+
+ assertEquals(DK, Sys.bytesToHex(Crypto.SCrypt.encrypt(P, S, N, r, p, dkLen)));
+
+
+ P = "pleaseletmein".getBytes("UTF-8");
+ S = "SodiumChloride".getBytes("UTF-8");
+ N = 1048576;
+ r = 8;
+ p = 1;
+ dkLen = 64;
+ DK = "2101CB9B6A511AAEADDBBE09CF70F881EC568D574A2FFD4DABE5EE9820ADAA478E56FD8F4BA5D09FFA1C6D927C40F4C337304049E8A952FBCBF45C6FA77A41A4";
+
+ assertEquals(DK, Sys.bytesToHex(Crypto.SCrypt.encrypt(P, S, N, r, p, dkLen)));
+ }
+}
diff --git a/Dorkbox-Util/test/dorkbox/util/crypto/x509Test.java b/Dorkbox-Util/test/dorkbox/util/crypto/x509Test.java
new file mode 100644
index 0000000..f7751f5
--- /dev/null
+++ b/Dorkbox-Util/test/dorkbox/util/crypto/x509Test.java
@@ -0,0 +1,155 @@
+package dorkbox.util.crypto;
+
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.junit.Test;
+
+
+public class x509Test {
+
+ private static String entropySeed = "asdjhaffasdgfaasttjjhgpx600gn,-356268909087s0dfg4-42kjh255124515hasdg87";
+
+ @Test
+ public void EcdsaCertificate() throws IOException {
+ // create the certificate
+ Calendar expiry = Calendar.getInstance();
+ expiry.add(Calendar.DAY_OF_YEAR, 360);
+
+ Date startDate = new Date(); // time from which certificate is valid
+ Date expiryDate = expiry.getTime(); // time after which certificate is not valid
+ BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); // serial number for certificate
+
+
+ AsymmetricCipherKeyPair generateKeyPair = Crypto.ECC.generateKeyPair(Crypto.ECC.p521_curve, new SecureRandom()); // key name from Crypto class
+ ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) generateKeyPair.getPrivate();
+ ECPublicKeyParameters publicKey = (ECPublicKeyParameters) generateKeyPair.getPublic();
+
+
+
+ X509CertificateHolder ECDSAx509Certificate = CryptoX509.ECDSA.createCertHolder("SHA384",
+ startDate, expiryDate,
+ new X500Name("CN=Test"), new X500Name("CN=Test"), serialNumber,
+ privateKey, publicKey);
+ // make sure it's a valid cert.
+ if (ECDSAx509Certificate != null) {
+ boolean valid = CryptoX509.ECDSA.validate(ECDSAx509Certificate);
+
+ if (!valid) {
+ fail("Unable to verify a x509 certificate.");
+ }
+ } else {
+ fail("Unable to create a x509 certificate.");
+ }
+
+ // now sign something, then verify the signature.
+ byte[] data = "My keyboard is awesome".getBytes();
+ byte[] signatureBlock = CryptoX509.createSignature(data, ECDSAx509Certificate, privateKey);
+
+ boolean verifySignature = CryptoX509.ECDSA.verifySignature(signatureBlock, publicKey);
+
+ if (!verifySignature) {
+ fail("Unable to verify a x509 certificate signature.");
+ }
+ }
+
+ @Test
+ public void DsaCertificate() throws IOException {
+ // create the certificate
+ Calendar expiry = Calendar.getInstance();
+ expiry.add(Calendar.DAY_OF_YEAR, 360);
+
+ Date startDate = new Date(); // time from which certificate is valid
+ Date expiryDate = expiry.getTime(); // time after which certificate is not valid
+ BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); // serial number for certificate
+
+
+ @SuppressWarnings("deprecation")
+ AsymmetricCipherKeyPair generateKeyPair = Crypto.DSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
+
+
+ DSAPrivateKeyParameters privateKey = (DSAPrivateKeyParameters) generateKeyPair.getPrivate();
+ DSAPublicKeyParameters publicKey = (DSAPublicKeyParameters) generateKeyPair.getPublic();
+
+
+
+ X509CertificateHolder DSAx509Certificate = CryptoX509.DSA.createCertHolder(startDate, expiryDate,
+ new X500Name("CN=Test"), new X500Name("CN=Test"), serialNumber,
+ privateKey, publicKey);
+ // make sure it's a valid cert.
+ if (DSAx509Certificate != null) {
+ boolean valid = CryptoX509.DSA.validate(DSAx509Certificate);
+
+ if (!valid) {
+ fail("Unable to verify a x509 certificate.");
+ }
+ } else {
+ fail("Unable to create a x509 certificate.");
+ }
+
+ // now sign something, then verify the signature.
+ byte[] data = "My keyboard is awesome".getBytes();
+ byte[] signatureBlock = CryptoX509.createSignature(data, DSAx509Certificate, privateKey);
+
+ boolean verifySignature = CryptoX509.DSA.verifySignature(signatureBlock, publicKey);
+
+ if (!verifySignature) {
+ fail("Unable to verify a x509 certificate signature.");
+ }
+ }
+
+ @Test
+ public void RsaCertificate() throws IOException {
+ // create the certificate
+ Calendar expiry = Calendar.getInstance();
+ expiry.add(Calendar.DAY_OF_YEAR, 360);
+
+ Date startDate = new Date(); // time from which certificate is valid
+ Date expiryDate = expiry.getTime(); // time after which certificate is not valid
+ BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); // serial number for certificate
+
+ @SuppressWarnings("deprecation")
+ AsymmetricCipherKeyPair generateKeyPair = Crypto.RSA.generateKeyPair(new SecureRandom(entropySeed.getBytes()), 1024);
+ RSAPrivateCrtKeyParameters privateKey = (RSAPrivateCrtKeyParameters) generateKeyPair.getPrivate();
+ RSAKeyParameters publicKey = (RSAKeyParameters) generateKeyPair.getPublic();
+
+
+ X509CertificateHolder RSAx509Certificate = CryptoX509.RSA.createCertHolder(startDate, expiryDate,
+ new X500Name("CN=Test"), new X500Name("CN=Test"), serialNumber,
+ privateKey, publicKey);
+ // make sure it's a valid cert.
+ if (RSAx509Certificate != null) {
+ boolean valid = CryptoX509.RSA.validate(RSAx509Certificate);
+
+ if (!valid) {
+ fail("Unable to verify a x509 certificate.");
+ }
+ } else {
+ fail("Unable to create a x509 certificate.");
+ }
+
+ // now sign something, then verify the signature.
+ byte[] data = "My keyboard is awesome".getBytes();
+ byte[] signatureBlock = CryptoX509.createSignature(data, RSAx509Certificate, privateKey);
+
+ boolean verifySignature = CryptoX509.RSA.verifySignature(signatureBlock, publicKey);
+
+ if (!verifySignature) {
+ fail("Unable to verify a x509 certificate signature.");
+ }
+ }
+}