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 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 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 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 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 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."); + } + } +}