diff --git a/build.xml b/build.xml
index 1df75cd1d..d3e25d424 100644
--- a/build.xml
+++ b/build.xml
@@ -47,6 +47,7 @@
+
@@ -567,6 +568,7 @@
+
@@ -615,6 +617,9 @@
+
+
+
@@ -879,6 +884,7 @@
+
diff --git a/public/java/src/org/broadinstitute/sting/gatk/CommandLineExecutable.java b/public/java/src/org/broadinstitute/sting/gatk/CommandLineExecutable.java
index 32002e093..e5aaf2338 100644
--- a/public/java/src/org/broadinstitute/sting/gatk/CommandLineExecutable.java
+++ b/public/java/src/org/broadinstitute/sting/gatk/CommandLineExecutable.java
@@ -35,9 +35,12 @@ import org.broadinstitute.sting.gatk.io.stubs.VCFWriterArgumentTypeDescriptor;
import org.broadinstitute.sting.gatk.phonehome.GATKRunReport;
import org.broadinstitute.sting.gatk.refdata.utils.RMDTriplet;
import org.broadinstitute.sting.gatk.walkers.Walker;
-import org.broadinstitute.sting.utils.classloader.JVMUtils;
+import org.broadinstitute.sting.utils.crypt.CryptUtils;
+import org.broadinstitute.sting.utils.crypt.GATKKey;
+import org.broadinstitute.sting.utils.exceptions.UserException;
import org.broadinstitute.sting.utils.text.ListFileUtils;
+import java.security.PublicKey;
import java.util.*;
/**
@@ -78,6 +81,9 @@ public abstract class CommandLineExecutable extends CommandLineProgram {
Walker,?> walker = engine.getWalkerByName(getAnalysisName());
try {
+ // Make sure a valid GATK user key is present, if required.
+ authorizeGATKRun();
+
engine.setArguments(getArgumentCollection());
// File lists can require a bit of additional expansion. Set these explicitly by the engine.
@@ -130,6 +136,28 @@ public abstract class CommandLineExecutable extends CommandLineProgram {
return 0;
}
+ /**
+ * Authorizes this run of the GATK by checking for a valid GATK user key, if required.
+ * Currently, a key is required only if running with the -et NO_ET or -et STDOUT options.
+ */
+ private void authorizeGATKRun() {
+ if ( getArgumentCollection().phoneHomeType == GATKRunReport.PhoneHomeOption.NO_ET ||
+ getArgumentCollection().phoneHomeType == GATKRunReport.PhoneHomeOption.STDOUT ) {
+ if ( getArgumentCollection().gatkKeyFile == null ) {
+ throw new UserException("Running with the -et NO_ET or -et STDOUT option requires a GATK Key file. " +
+ "Please see http://www.broadinstitute.org/gsa/wiki/index.php/Phone_home " +
+ "for more information and instructions on how to obtain a key.");
+ }
+ else {
+ PublicKey gatkPublicKey = CryptUtils.loadGATKDistributedPublicKey();
+ GATKKey gatkUserKey = new GATKKey(gatkPublicKey, getArgumentCollection().gatkKeyFile);
+
+ if ( ! gatkUserKey.isValid() ) {
+ throw new UserException.KeySignatureVerificationException(getArgumentCollection().gatkKeyFile);
+ }
+ }
+ }
+ }
/**
* Generate the GATK run report for this walker using the current GATKEngine, if -et is enabled.
diff --git a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java
index 8ec707801..02d211a0c 100755
--- a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java
+++ b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java
@@ -65,9 +65,12 @@ public class GATKArgumentCollection {
@Argument(fullName = "read_buffer_size", shortName = "rbs", doc="Number of reads per SAM file to buffer in memory", required = false)
public Integer readBufferSize = null;
- @Argument(fullName = "phone_home", shortName = "et", doc="What kind of GATK run report should we generate? Standard is the default, can be verbose or NO_ET so nothing is posted to the run repository", required = false)
+ @Argument(fullName = "phone_home", shortName = "et", doc="What kind of GATK run report should we generate? STANDARD is the default, can be NO_ET so nothing is posted to the run repository. Please see http://www.broadinstitute.org/gsa/wiki/index.php/Phone_home for details.", required = false)
public GATKRunReport.PhoneHomeOption phoneHomeType = GATKRunReport.PhoneHomeOption.STANDARD;
+ @Argument(fullName = "gatk_key", shortName = "K", doc="GATK Key file. Required if running with -et NO_ET. Please see http://www.broadinstitute.org/gsa/wiki/index.php/Phone_home for details.", required = false)
+ public File gatkKeyFile = null;
+
@Argument(fullName = "read_filter", shortName = "rf", doc = "Specify filtration criteria to apply to each read individually", required = false)
public List readFilters = new ArrayList();
diff --git a/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java b/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java
index e8627ef4c..f1f74069f 100644
--- a/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java
+++ b/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java
@@ -154,9 +154,7 @@ public class GATKRunReport {
/** Standard option. Writes to local repository if it can be found, or S3 otherwise */
STANDARD,
/** Force output to STDOUT. For debugging only */
- STDOUT,
- /** Force output to S3. For debugging only */
- AWS_S3 // todo -- remove me -- really just for testing purposes
+ STDOUT
}
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH.mm.ss");
@@ -239,11 +237,8 @@ public class GATKRunReport {
case STDOUT:
postReportToStream(System.out);
break;
- case AWS_S3:
- postReportToAWSS3();
- break;
default:
- exceptDuringRunReport("BUG: unexcepted PhoneHomeOption ");
+ exceptDuringRunReport("BUG: unexpected PhoneHomeOption ");
break;
}
}
diff --git a/public/java/src/org/broadinstitute/sting/utils/crypt/CryptUtils.java b/public/java/src/org/broadinstitute/sting/utils/crypt/CryptUtils.java
new file mode 100644
index 000000000..e84b1432e
--- /dev/null
+++ b/public/java/src/org/broadinstitute/sting/utils/crypt/CryptUtils.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (c) 2012, The Broad Institute
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.broadinstitute.sting.utils.crypt;
+
+import org.broadinstitute.sting.utils.exceptions.ReviewedStingException;
+import org.broadinstitute.sting.utils.io.IOUtils;
+
+import javax.crypto.Cipher;
+import java.io.File;
+import java.io.InputStream;
+import java.security.*;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+
+/**
+ * A set of cryptographic utility methods and constants.
+ *
+ * Contains methods to:
+ *
+ * -Create a public/private key pair
+ * -Read and write public/private keys to/from files/streams
+ * -Load the GATK master private/public keys
+ * -Encrypt/decrypt data
+ *
+ * Also contains constants that control the cryptographic defaults
+ * throughout the GATK.
+ *
+ * @author David Roazen
+ */
+public class CryptUtils {
+
+ // ---------------------------------------------------------------------------------
+ // Constants (these control the default cryptographic settings throughout the GATK):
+ // ---------------------------------------------------------------------------------
+
+ /**
+ * Default key length in bits of newly-created keys. 2048 bits provides a good balance between
+ * security and speed.
+ */
+ public static final int DEFAULT_KEY_LENGTH = 2048;
+
+ /**
+ * Default encryption algorithm to use, when none is specified.
+ */
+ public static final String DEFAULT_ENCRYPTION_ALGORITHM = "RSA";
+
+ /**
+ * Default random-number generation algorithm to use, when none is specified.
+ */
+ public static final String DEFAULT_RANDOM_NUMBER_GENERATION_ALGORITHM = "SHA1PRNG";
+
+ /**
+ * Name of the public key file distributed with the GATK. This file is packaged
+ * into the GATK jar, and we use the system ClassLoader to find it.
+ */
+ public static final String GATK_DISTRIBUTED_PUBLIC_KEY_FILE_NAME = "GATK_public.key";
+
+ /**
+ * Location of the master copy of the GATK private key.
+ */
+ public static final String GATK_MASTER_PRIVATE_KEY_FILE = "/humgen/gsa-hpprojects/GATK/data/gatk_master_keys/GATK_private.key";
+
+ /**
+ * Location of the master copy of the GATK public key. This file should always be the same as
+ * the public key file distributed with the GATK (and there are automated tests to ensure that it is).
+ */
+ public static final String GATK_MASTER_PUBLIC_KEY_FILE = "/humgen/gsa-hpprojects/GATK/data/gatk_master_keys/GATK_public.key";
+
+ /**
+ * Directory where generated GATK user keys are stored. See the GATKKey class for more information.
+ */
+ public static final String GATK_USER_KEY_DIRECTORY = "/humgen/gsa-hpprojects/GATK/data/gatk_user_keys/";
+
+
+ // -----------------------
+ // Utility Methods:
+ // -----------------------
+
+ /**
+ * Generate a new public/private key pair using the default encryption settings defined above.
+ *
+ * @return A new public/private key pair created using the default settings
+ */
+ public static KeyPair generateKeyPair() {
+ return generateKeyPair(DEFAULT_KEY_LENGTH, DEFAULT_ENCRYPTION_ALGORITHM, DEFAULT_RANDOM_NUMBER_GENERATION_ALGORITHM);
+ }
+
+ /**
+ * Generate a new public/private key pair using custom encryption settings.
+ *
+ * @param keyLength Length of the key in bits
+ * @param encryptionAlgorithm Encryption algorithm to use
+ * @param randNumberAlgorithm Random-number generation algorithm to use
+ * @return A new public/private key pair, created according to the specified parameters
+ */
+ public static KeyPair generateKeyPair( int keyLength, String encryptionAlgorithm, String randNumberAlgorithm ) {
+ try {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance(encryptionAlgorithm);
+ SecureRandom randomnessSource = createRandomnessSource(randNumberAlgorithm);
+
+ keyGen.initialize(keyLength, randomnessSource);
+ return keyGen.generateKeyPair();
+ }
+ catch ( NoSuchAlgorithmException e ) {
+ throw new ReviewedStingException(String.format("Could not find an implementation of the requested encryption algorithm %s", encryptionAlgorithm), e);
+ }
+ catch ( Exception e ) {
+ throw new ReviewedStingException("Error while generating key pair", e);
+ }
+ }
+
+ /**
+ * Create a source of randomness using the default random-number generation algorithm.
+ *
+ * @return A randomness source that uses the default algorithm
+ */
+ public static SecureRandom createRandomnessSource() {
+ return createRandomnessSource(DEFAULT_RANDOM_NUMBER_GENERATION_ALGORITHM);
+ }
+
+ /**
+ * Create a source of randomness using a custom random-number generation algorithm.
+ *
+ * @param randAlgorithm The random-number generation algorithm to use
+ * @return A randomness sources that uses the specified algorithm
+ */
+ public static SecureRandom createRandomnessSource ( String randAlgorithm ) {
+ try {
+ return SecureRandom.getInstance(randAlgorithm);
+ }
+ catch ( NoSuchAlgorithmException e ) {
+ throw new ReviewedStingException(String.format("Could not find an implementation of the requested random-number generation algorithm %s", randAlgorithm), e);
+ }
+ }
+
+ /**
+ * Writes a public/private key pair to disk
+ *
+ * @param keyPair The key pair we're writing to disk
+ * @param privateKeyFile Location to write the private key
+ * @param publicKeyFile Location to write the public key
+ */
+ public static void writeKeyPair ( KeyPair keyPair, File privateKeyFile, File publicKeyFile ) {
+ writeKey(keyPair.getPrivate(), privateKeyFile);
+ writeKey(keyPair.getPublic(), publicKeyFile);
+ }
+
+ /**
+ * Writes an arbitrary key to disk
+ *
+ * @param key The key to write
+ * @param destination Location to write the key to
+ */
+ public static void writeKey ( Key key, File destination ) {
+ IOUtils.writeByteArrayToFile(key.getEncoded(), destination);
+ }
+
+ /**
+ * Reads in a public key created using the default encryption algorithm from a file.
+ *
+ * @param source File containing the public key
+ * @return The public key read
+ */
+ public static PublicKey readPublicKey ( File source ) {
+ return decodePublicKey(IOUtils.readFileIntoByteArray(source), DEFAULT_ENCRYPTION_ALGORITHM);
+ }
+
+ /**
+ * Reads in a public key created using the default encryption algorithm from a stream.
+ *
+ * @param source Stream attached to the public key
+ * @return The public key read
+ */
+ public static PublicKey readPublicKey ( InputStream source ) {
+ return decodePublicKey(IOUtils.readStreamIntoByteArray(source), DEFAULT_ENCRYPTION_ALGORITHM);
+ }
+
+ /**
+ * Decodes the raw bytes of a public key into a usable object.
+ *
+ * @param rawKey The encoded bytes of a public key as read from, eg., a file. The
+ * key must be in the standard X.509 format for a public key.
+ * @param encryptionAlgorithm The encryption algorithm used to create the public key
+ * @return The public key as a usable object
+ */
+ public static PublicKey decodePublicKey ( byte[] rawKey, String encryptionAlgorithm ) {
+ try {
+ KeySpec keySpec = new X509EncodedKeySpec(rawKey);
+ KeyFactory keyFactory = KeyFactory.getInstance(encryptionAlgorithm);
+ return keyFactory.generatePublic(keySpec);
+ }
+ catch ( NoSuchAlgorithmException e ) {
+ throw new ReviewedStingException(String.format("Could not find an implementation of the requested encryption algorithm %s", encryptionAlgorithm), e);
+ }
+ catch ( InvalidKeySpecException e ) {
+ throw new ReviewedStingException("Unable to use X.509 key specification to decode the given key", e);
+ }
+ }
+
+ /**
+ * Reads in a private key created using the default encryption algorithm from a file.
+ *
+ * @param source File containing the private key
+ * @return The private key read
+ */
+ public static PrivateKey readPrivateKey ( File source ) {
+ return decodePrivateKey(IOUtils.readFileIntoByteArray(source), DEFAULT_ENCRYPTION_ALGORITHM);
+ }
+
+ /**
+ * Reads in a private key created using the default encryption algorithm from a stream.
+ *
+ * @param source Stream attached to the private key
+ * @return The private key read
+ */
+ public static PrivateKey readPrivateKey ( InputStream source ) {
+ return decodePrivateKey(IOUtils.readStreamIntoByteArray(source), DEFAULT_ENCRYPTION_ALGORITHM);
+ }
+
+ /**
+ * Decodes the raw bytes of a private key into a usable object.
+ *
+ * @param rawKey The encoded bytes of a private key as read from, eg., a file. The
+ * key must be in the standard PKCS #8 format for a private key.
+ * @param encryptionAlgorithm The encryption algorithm used to create the private key
+ * @return The private key as a usable object
+ */
+ public static PrivateKey decodePrivateKey ( byte[] rawKey, String encryptionAlgorithm ) {
+ try {
+ KeySpec keySpec = new PKCS8EncodedKeySpec(rawKey);
+ KeyFactory keyFactory = KeyFactory.getInstance(encryptionAlgorithm);
+ return keyFactory.generatePrivate(keySpec);
+ }
+ catch ( NoSuchAlgorithmException e ) {
+ throw new ReviewedStingException(String.format("Could not find an implementation of the requested encryption algorithm %s", encryptionAlgorithm), e);
+ }
+ catch ( InvalidKeySpecException e ) {
+ throw new ReviewedStingException("Unable to use the PKCS #8 key specification to decode the given key", e);
+ }
+ }
+
+ /**
+ * Loads the copy of the GATK public key that is distributed with the GATK. Uses the system
+ * ClassLoader to locate the public key file, which should be stored at the root of the GATK
+ * jar file.
+ *
+ * @return The GATK public key as a usable object
+ */
+ public static PublicKey loadGATKDistributedPublicKey() {
+ InputStream publicKeyInputStream = ClassLoader.getSystemResourceAsStream(GATK_DISTRIBUTED_PUBLIC_KEY_FILE_NAME);
+
+ if ( publicKeyInputStream == null ) {
+ throw new ReviewedStingException(String.format("Could not locate the GATK public key %s in the classpath",
+ GATK_DISTRIBUTED_PUBLIC_KEY_FILE_NAME));
+ }
+
+ return readPublicKey(publicKeyInputStream);
+ }
+
+ /**
+ * Loads the master copy of the GATK private key. You must have the appropriate UNIX permissions
+ * to do this!
+ *
+ * @return The GATK master private key as a usable object
+ */
+ public static PrivateKey loadGATKMasterPrivateKey() {
+ return readPrivateKey(new File(GATK_MASTER_PRIVATE_KEY_FILE));
+ }
+
+ /**
+ * Loads the master copy of the GATK public key. This should always be the same as the
+ * public key distributed with the GATK returned by loadGATKDistributedPublicKey().
+ *
+ * @return The GATK master public key as a usable object
+ */
+ public static PublicKey loadGATKMasterPublicKey() {
+ return readPublicKey(new File(GATK_MASTER_PUBLIC_KEY_FILE));
+ }
+
+ /**
+ * Encrypts the given data using the key provided.
+ *
+ * @param data The data to encrypt, as a byte array
+ * @param encryptKey The key with which to encrypt the data
+ * @return The encrypted version of the provided data
+ */
+ public static byte[] encryptData ( byte[] data, Key encryptKey ) {
+ return transformDataUsingCipher(data, encryptKey, Cipher.ENCRYPT_MODE);
+ }
+
+ /**
+ * Decrypts the given data using the key provided.
+ *
+ * @param encryptedData Data to decrypt, as a byte array
+ * @param decryptKey The key with which to decrypt the data
+ * @return The decrypted version of the provided data
+ */
+ public static byte[] decryptData ( byte[] encryptedData, Key decryptKey ) {
+ return transformDataUsingCipher(encryptedData, decryptKey, Cipher.DECRYPT_MODE);
+ }
+
+ /**
+ * Helper method for encryption/decryption that takes data and processes it using
+ * the given key
+ *
+ * @param data Data to encrypt/decrypt
+ * @param key Key to use to encrypt/decrypt the data
+ * @param cipherMode Specifies whether we are encrypting or decrypting
+ * @return The encrypted/decrypted data
+ */
+ private static byte[] transformDataUsingCipher ( byte[] data, Key key, int cipherMode ) {
+ try {
+ Cipher cipher = Cipher.getInstance(key.getAlgorithm());
+ cipher.init(cipherMode, key);
+ return cipher.doFinal(data);
+ }
+ catch ( NoSuchAlgorithmException e ) {
+ throw new ReviewedStingException(String.format("Could not find an implementation of the requested algorithm %s",
+ key.getAlgorithm()), e);
+ }
+ catch ( InvalidKeyException e ) {
+ throw new ReviewedStingException("Key is invalid", e);
+ }
+ catch ( GeneralSecurityException e ) {
+ throw new ReviewedStingException("Error during encryption", e);
+ }
+ }
+
+ /**
+ * Tests whether the public/private keys provided can each decrypt data encrypted by
+ * the other key -- ie., tests whether these two keys are part of the same public/private
+ * key pair.
+ *
+ * @param privateKey The private key to test
+ * @param publicKey The public key to test
+ * @return True if the keys are part of the same key pair and can decrypt each other's
+ * encrypted data, otherwise false.
+ */
+ public static boolean keysDecryptEachOther ( PrivateKey privateKey, PublicKey publicKey ) {
+ byte[] plainText = "Test PlainText".getBytes();
+
+ byte[] dataEncryptedUsingPrivateKey = CryptUtils.encryptData(plainText, privateKey);
+ byte[] dataEncryptedUsingPublicKey = CryptUtils.encryptData(plainText, publicKey);
+
+ byte[] privateKeyDataDecryptedWithPublicKey = CryptUtils.decryptData(dataEncryptedUsingPrivateKey, publicKey);
+ byte[] publicKeyDataDecryptedWithPrivateKey = CryptUtils.decryptData(dataEncryptedUsingPublicKey, privateKey);
+
+ // Make sure we actually transformed the data during encryption:
+ if ( Arrays.equals(plainText, dataEncryptedUsingPrivateKey) ||
+ Arrays.equals(plainText, dataEncryptedUsingPublicKey) ||
+ Arrays.equals(dataEncryptedUsingPrivateKey, dataEncryptedUsingPublicKey) ) {
+ return false;
+ }
+
+ // Make sure that we were able to recreate the original plaintext using
+ // both the public key on the private-key-encrypted data and the private
+ // key on the public-key-encrypted data:
+ if ( ! Arrays.equals(plainText, privateKeyDataDecryptedWithPublicKey) ||
+ ! Arrays.equals(plainText, publicKeyDataDecryptedWithPrivateKey) ) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/public/java/src/org/broadinstitute/sting/utils/crypt/GATKKey.java b/public/java/src/org/broadinstitute/sting/utils/crypt/GATKKey.java
new file mode 100644
index 000000000..408cb56aa
--- /dev/null
+++ b/public/java/src/org/broadinstitute/sting/utils/crypt/GATKKey.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2012, The Broad Institute
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.broadinstitute.sting.utils.crypt;
+
+import org.broadinstitute.sting.utils.exceptions.ReviewedStingException;
+import org.broadinstitute.sting.utils.exceptions.UserException;
+import org.broadinstitute.sting.utils.io.IOUtils;
+
+import java.io.*;
+import java.security.*;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Class to represent a GATK user key.
+ *
+ * A GATK user key contains an email address and a cryptographic signature.
+ * The signature is the SHA-1 hash of the email address encrypted using
+ * the GATK master private key. The GATK master public key (distributed
+ * with the GATK) is used to decrypt the signature and validate the key
+ * at the start of each GATK run that requires a key.
+ *
+ * Keys are cryptographically secure in that valid keys definitely come
+ * from us and cannot be fabricated, however nothing prevents keys from
+ * being shared between users.
+ *
+ * GATK user keys have the following on-disk format:
+ *
+ * GZIP Container:
+ * Email address
+ * NUL byte (delimiter)
+ * Cryptographic Signature (encrypted SHA-1 hash of email address)
+ *
+ * The key data is wrapped within a GZIP container to placate over-zealous
+ * email filters (since keys must often be emailed) and also to provide an
+ * additional integrity check via the built-in GZIP CRC.
+ *
+ * @author David Roazen
+ */
+public class GATKKey {
+
+ /**
+ * Private key used to sign the GATK key. Required only when creating a new
+ * key from scratch, not when loading an existing key from disk.
+ */
+ private PrivateKey privateKey;
+
+ /**
+ * Public key used to validate the GATK key.
+ */
+ private PublicKey publicKey;
+
+ /**
+ * The user's email address, stored within the key and signed.
+ */
+ private String emailAddress;
+
+ /**
+ * The cryptographic signature of the email address. By default, this is
+ * the SHA-1 hash of the email address encrypted using the RSA algorithm.
+ */
+ private byte[] signature;
+
+ /**
+ * The combination of hash/encryption algorithms to use to generate the signature.
+ * By default this is "SHA1withRSA"
+ */
+ private String signingAlgorithm;
+
+ /**
+ * Default hash/encryption algorithms to use to sign the key.
+ */
+ public static final String DEFAULT_SIGNING_ALGORITHM = "SHA1withRSA";
+
+ /**
+ * Byte value used to separate the email address from its signature in the key file.
+ */
+ public static final byte GATK_KEY_SECTIONAL_DELIMITER = 0;
+
+
+ // -----------------------
+ // Constructors:
+ // -----------------------
+
+ /**
+ * Constructor to create a new GATK key from scratch using an email address
+ * and public/private key pair. The private key is used for signing, and the
+ * public key is used to validate the newly-created key.
+ *
+ * @param privateKey Private key used to sign the new GATK key
+ * @param publicKey Public key used to validate the new GATK key
+ * @param emailAddress The user's email address, which we will store in the key and sign
+ */
+ public GATKKey ( PrivateKey privateKey, PublicKey publicKey, String emailAddress ) {
+ this(privateKey, publicKey, emailAddress, DEFAULT_SIGNING_ALGORITHM);
+ }
+
+ /**
+ * Constructor to create a new GATK key from scratch using an email address
+ * and public/private key pair, and additionally specify the signing algorithm
+ * to use. The private key is used for signing, and the public key is used to
+ * validate the newly-created key.
+ *
+ * @param privateKey Private key used to sign the new GATK key
+ * @param publicKey Public key used to validate the new GATK key
+ * @param emailAddress The user's email address, which we will store in the key and sign
+ * @param signingAlgorithm The combination of hash and encryption algorithms to use to sign the key
+ */
+ public GATKKey ( PrivateKey privateKey, PublicKey publicKey, String emailAddress, String signingAlgorithm ) {
+ if ( privateKey == null || publicKey == null || emailAddress == null || emailAddress.length() == 0 || signingAlgorithm == null ) {
+ throw new ReviewedStingException("Cannot construct GATKKey using null/empty arguments");
+ }
+
+ this.privateKey = privateKey;
+ this.publicKey = publicKey;
+ this.emailAddress = emailAddress;
+ this.signingAlgorithm = signingAlgorithm;
+
+ validateEmailAddress();
+ generateSignature();
+
+ if ( ! isValid() ) {
+ throw new ReviewedStingException("Newly-generated GATK key fails validation -- this should never happen!");
+ }
+ }
+
+ /**
+ * Constructor to load an existing GATK key from a file.
+ *
+ * During loading, the key file is checked for integrity, but not cryptographic
+ * validity (which must be done through a subsequent call to isValid()).
+ *
+ * @param publicKey Public key that will be used to validate the loaded GATK key
+ * in subsequent calls to isValid()
+ * @param keyFile File containing the GATK key to load
+ */
+ public GATKKey ( PublicKey publicKey, File keyFile ) {
+ this(publicKey, keyFile, DEFAULT_SIGNING_ALGORITHM);
+ }
+
+ /**
+ * Constructor to load an existing GATK key from a file, and additionally specify
+ * the signing algorithm used to sign the key being loaded.
+ *
+ * During loading, the key file is checked for integrity, but not cryptographic
+ * validity (which must be done through a subsequent call to isValid()).
+ *
+ * @param publicKey Public key that will be used to validate the loaded GATK key
+ * in subsequent calls to isValid()
+ * @param keyFile File containing the GATK key to load
+ * @param signingAlgorithm The combination of hash and encryption algorithms used to sign the key
+ */
+ public GATKKey ( PublicKey publicKey, File keyFile, String signingAlgorithm ) {
+ if ( publicKey == null || keyFile == null || signingAlgorithm == null ) {
+ throw new ReviewedStingException("Cannot construct GATKKey using null arguments");
+ }
+
+ this.publicKey = publicKey;
+ this.signingAlgorithm = signingAlgorithm;
+
+ readKey(keyFile);
+ }
+
+ // -----------------------
+ // Public API Methods:
+ // -----------------------
+
+ /**
+ * Writes out this key to a file in the format described at the top of this class,
+ * encapsulating the key within a GZIP container.
+ *
+ * @param destination File to write the key to
+ */
+ public void writeKey ( File destination ) {
+ try {
+ byte[] keyBytes = marshalKeyData();
+ IOUtils.writeByteArrayToStream(keyBytes, new GZIPOutputStream(new FileOutputStream(destination)));
+ }
+ catch ( IOException e ) {
+ throw new UserException.CouldNotCreateOutputFile(destination, e);
+ }
+ }
+
+ /**
+ * Checks whether the signature of this key is cryptographically valid (ie., can be
+ * decrypted by the public key to produce a valid SHA-1 hash of the email address
+ * in the key).
+ *
+ * @return True if the key's signature passes validation, otherwise false
+ */
+ public boolean isValid() {
+ try {
+ Signature sig = Signature.getInstance(signingAlgorithm);
+ sig.initVerify(publicKey);
+ sig.update(emailAddress.getBytes());
+ return sig.verify(signature);
+ }
+ catch ( NoSuchAlgorithmException e ) {
+ throw new ReviewedStingException(String.format("Signing algorithm %s not found", signingAlgorithm), e);
+ }
+ catch ( InvalidKeyException e ) {
+ // If the GATK public key is invalid, it's likely our problem, not the user's:
+ throw new ReviewedStingException(String.format("Public key %s is invalid", publicKey), e);
+ }
+ catch ( SignatureException e ) {
+ throw new UserException.UnreadableKeyException("Signature is invalid or signing algorithm was unable to process the input data", e);
+ }
+ }
+
+ // -----------------------
+ // Private Helper Methods:
+ // -----------------------
+
+ /**
+ * Helper method that creates a signature for this key using the combination of
+ * hash/encryption algorithms specified at construction time.
+ */
+ private void generateSignature() {
+ try {
+ Signature sig = Signature.getInstance(signingAlgorithm);
+ sig.initSign(privateKey, CryptUtils.createRandomnessSource());
+ sig.update(emailAddress.getBytes());
+ signature = sig.sign();
+ }
+ catch ( NoSuchAlgorithmException e ) {
+ throw new ReviewedStingException(String.format("Signing algorithm %s not found", signingAlgorithm), e);
+ }
+ catch ( InvalidKeyException e ) {
+ throw new ReviewedStingException(String.format("Private key %s is invalid", privateKey), e);
+ }
+ catch ( SignatureException e ) {
+ throw new ReviewedStingException(String.format("Error creating signature for email address %s", emailAddress), e);
+ }
+ }
+
+ /**
+ * Helper method that reads in a GATK key from a file. Should not be called directly --
+ * use the appropriate constructor above.
+ *
+ * @param source File to read the key from
+ */
+ private void readKey ( File source ) {
+ try {
+ byte[] keyBytes = IOUtils.readStreamIntoByteArray(new GZIPInputStream(new FileInputStream(source)));
+
+ // As a sanity check, compare the number of bytes read to the uncompressed file size
+ // stored in the GZIP ISIZE field. If they don't match, the key must be corrupt:
+ if ( keyBytes.length != IOUtils.getGZIPFileUncompressedSize(source) ) {
+ throw new UserException.UnreadableKeyException("Number of bytes read does not match the uncompressed size specified in the GZIP ISIZE field");
+ }
+
+ unmarshalKeyData(keyBytes);
+ }
+ catch ( FileNotFoundException e ) {
+ throw new UserException.CouldNotReadInputFile(source, e);
+ }
+ catch ( IOException e ) {
+ throw new UserException.UnreadableKeyException(source, e);
+ }
+ catch ( UserException.CouldNotReadInputFile e ) {
+ throw new UserException.UnreadableKeyException(source, e);
+ }
+ }
+
+ /**
+ * Helper method that assembles the email address and signature into a format
+ * suitable for writing to disk.
+ *
+ * @return The aggregated key data, ready to be written to disk
+ */
+ private byte[] marshalKeyData() {
+ byte[] emailAddressBytes = emailAddress.getBytes();
+ byte[] assembledKey = new byte[emailAddressBytes.length + 1 + signature.length];
+
+ System.arraycopy(emailAddressBytes, 0, assembledKey, 0, emailAddressBytes.length);
+ assembledKey[emailAddressBytes.length] = GATK_KEY_SECTIONAL_DELIMITER;
+ System.arraycopy(signature, 0, assembledKey, emailAddressBytes.length + 1, signature.length);
+
+ return assembledKey;
+ }
+
+ /**
+ * Helper method that parses the raw key data from disk into its component
+ * email address and signature. Performs some basic validation in the process.
+ *
+ * @param keyBytes The raw, uncompressed key data read from disk
+ */
+ private void unmarshalKeyData ( byte[] keyBytes ) {
+ int delimiterPosition = -1;
+
+ for ( int i = 0; i < keyBytes.length; i++ ) {
+ if ( keyBytes[i] == GATK_KEY_SECTIONAL_DELIMITER ) {
+ delimiterPosition = i;
+ break;
+ }
+ }
+
+ if ( delimiterPosition == -1 ) {
+ throw new UserException.UnreadableKeyException("Malformed GATK key contains no sectional delimiter");
+ }
+ else if ( delimiterPosition == 0 ) {
+ throw new UserException.UnreadableKeyException("Malformed GATK key contains no email address");
+ }
+ else if ( delimiterPosition == keyBytes.length - 1 ) {
+ throw new UserException.UnreadableKeyException("Malformed GATK key contains no signature");
+ }
+
+ byte[] emailAddressBytes = new byte[delimiterPosition];
+ System.arraycopy(keyBytes, 0, emailAddressBytes, 0, delimiterPosition);
+ emailAddress = new String(emailAddressBytes);
+
+ signature = new byte[keyBytes.length - delimiterPosition - 1];
+ System.arraycopy(keyBytes, delimiterPosition + 1, signature, 0, keyBytes.length - delimiterPosition - 1);
+ }
+
+ /**
+ * Helper method that ensures that the user's email address does not contain the NUL byte, which we
+ * reserve as a delimiter within each key file.
+ */
+ private void validateEmailAddress() {
+ for ( byte b : emailAddress.getBytes() ) {
+ if ( b == GATK_KEY_SECTIONAL_DELIMITER ) {
+ throw new UserException(String.format("Email address must not contain a byte with value %d", GATK_KEY_SECTIONAL_DELIMITER));
+ }
+ }
+ }
+}
diff --git a/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java b/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java
index 2ece3b077..6cc8008d2 100755
--- a/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java
+++ b/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java
@@ -132,6 +132,10 @@ public class UserException extends ReviewedStingException {
public CouldNotReadInputFile(File file, Exception e) {
this(file, e.getMessage());
}
+
+ public CouldNotReadInputFile(String message) {
+ super(message);
+ }
}
@@ -151,6 +155,10 @@ public class UserException extends ReviewedStingException {
public CouldNotCreateOutputFile(File file, Exception e) {
super(String.format("Couldn't write file %s because exception %s", file.getAbsolutePath(), e.getMessage()));
}
+
+ public CouldNotCreateOutputFile(String message, Exception e) {
+ super(message, e);
+ }
}
public static class MissortedBAM extends UserException {
@@ -319,4 +327,32 @@ public class UserException extends ReviewedStingException {
"and try again.", null);
}
}
+
+ public static class UnreadableKeyException extends UserException {
+ public UnreadableKeyException ( File f, Exception e ) {
+ super(String.format("Key file %s cannot be read (possibly the key file is corrupt?). Error was: %s. " +
+ "Please see http://www.broadinstitute.org/gsa/wiki/index.php/Phone_home for help.",
+ f.getAbsolutePath(), e.getMessage()));
+ }
+
+ public UnreadableKeyException ( String message, Exception e ) {
+ this(String.format("%s. Error was: %s", message, e.getMessage()));
+ }
+
+ public UnreadableKeyException ( String message ) {
+ super(String.format("Key file cannot be read (possibly the key file is corrupt?): %s. " +
+ "Please see http://www.broadinstitute.org/gsa/wiki/index.php/Phone_home for help.",
+ message));
+ }
+ }
+
+ public static class KeySignatureVerificationException extends UserException {
+ public KeySignatureVerificationException ( File f ) {
+ super(String.format("The signature in key file %s failed cryptographic verification. " +
+ "If this key was valid in the past, it's likely been revoked. " +
+ "Please see http://www.broadinstitute.org/gsa/wiki/index.php/Phone_home " +
+ "for help.",
+ f.getAbsolutePath()));
+ }
+ }
}
diff --git a/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java b/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java
index a5ba857ef..160df0e51 100644
--- a/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java
+++ b/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java
@@ -29,10 +29,13 @@ import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
+import org.broadinstitute.sting.utils.exceptions.ReviewedStingException;
import org.broadinstitute.sting.utils.exceptions.StingException;
import org.broadinstitute.sting.utils.exceptions.UserException;
import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.*;
public class IOUtils {
@@ -400,4 +403,173 @@ public class IOUtils {
public static boolean isSpecialFile(File file) {
return file != null && (file.getAbsolutePath().startsWith("/dev/") || file.equals(DEV_DIR));
}
+
+ /**
+ * Reads the entirety of the given file into a byte array. Uses a read buffer size of 4096 bytes.
+ *
+ * @param source File to read
+ * @return The contents of the file as a byte array
+ */
+ public static byte[] readFileIntoByteArray ( File source ) {
+ return readFileIntoByteArray(source, 4096);
+ }
+
+ /**
+ * Reads the entirety of the given file into a byte array using the requested read buffer size.
+ *
+ * @param source File to read
+ * @param readBufferSize Number of bytes to read in at one time
+ * @return The contents of the file as a byte array
+ */
+ public static byte[] readFileIntoByteArray ( File source, int readBufferSize ) {
+ if ( source == null ) {
+ throw new ReviewedStingException("Source file was null");
+ }
+
+ byte[] fileContents;
+
+ try {
+ fileContents = readStreamIntoByteArray(new FileInputStream(source), readBufferSize);
+ }
+ catch ( FileNotFoundException e ) {
+ throw new UserException.CouldNotReadInputFile(source, e);
+ }
+
+ if ( fileContents.length != source.length() ) {
+ throw new UserException.CouldNotReadInputFile(String.format("Unable to completely read file %s: read only %d/%d bytes",
+ source.getAbsolutePath(), fileContents.length, source.length()));
+ }
+
+ return fileContents;
+ }
+
+ /**
+ * Reads all data from the given stream into a byte array. Uses a read buffer size of 4096 bytes.
+ *
+ * @param in Stream to read data from
+ * @return The contents of the stream as a byte array
+ */
+ public static byte[] readStreamIntoByteArray ( InputStream in ) {
+ return readStreamIntoByteArray(in, 4096);
+ }
+
+ /**
+ * Reads all data from the given stream into a byte array using the requested read buffer size.
+ *
+ * @param in Stream to read data from
+ * @param readBufferSize Number of bytes to read in at one time
+ * @return The contents of the stream as a byte array
+ */
+ public static byte[] readStreamIntoByteArray ( InputStream in, int readBufferSize ) {
+ if ( in == null ) {
+ throw new ReviewedStingException("Input stream was null");
+ }
+ else if ( readBufferSize <= 0 ) {
+ throw new ReviewedStingException("Read buffer size must be > 0");
+ }
+
+ // Use a fixed-size buffer for each read, but a dynamically-growing buffer
+ // to hold the accumulated contents of the file/stream:
+ byte[] readBuffer = new byte[readBufferSize];
+ ByteArrayOutputStream fileBuffer = new ByteArrayOutputStream(readBufferSize * 4);
+
+ try {
+ try {
+ int currentBytesRead;
+
+ while ( (currentBytesRead = in.read(readBuffer, 0, readBuffer.length)) >= 0 ) {
+ fileBuffer.write(readBuffer, 0, currentBytesRead);
+ }
+ }
+ finally {
+ in.close();
+ }
+ }
+ catch ( IOException e ) {
+ throw new UserException.CouldNotReadInputFile("I/O error reading from input stream", e);
+ }
+
+ return fileBuffer.toByteArray();
+ }
+
+ /**
+ * Writes the given array of bytes to a file
+ *
+ * @param bytes Data to write
+ * @param destination File to write the data to
+ */
+ public static void writeByteArrayToFile ( byte[] bytes, File destination ) {
+ if ( destination == null ) {
+ throw new ReviewedStingException("Destination file was null");
+ }
+
+ try {
+ writeByteArrayToStream(bytes, new FileOutputStream(destination));
+ }
+ catch ( FileNotFoundException e ) {
+ throw new UserException.CouldNotCreateOutputFile(destination, e);
+ }
+ }
+
+ /**
+ * Writes the given array of bytes to a stream
+ *
+ * @param bytes Data to write
+ * @param out Stream to write the data to
+ */
+ public static void writeByteArrayToStream ( byte[] bytes, OutputStream out ) {
+ if ( bytes == null || out == null ) {
+ throw new ReviewedStingException("Data to write or output stream was null");
+ }
+
+ try {
+ try {
+ out.write(bytes);
+ }
+ finally {
+ out.close();
+ }
+ }
+ catch ( IOException e ) {
+ throw new UserException.CouldNotCreateOutputFile("I/O error writing to output stream", e);
+ }
+ }
+
+ /**
+ * Determines the uncompressed size of a GZIP file. Uses the GZIP ISIZE field in the last
+ * 4 bytes of the file to get this information.
+ *
+ * @param gzipFile GZIP-format file whose uncompressed size to determine
+ * @return The uncompressed size (in bytes) of the GZIP file
+ */
+ public static int getGZIPFileUncompressedSize ( File gzipFile ) {
+ if ( gzipFile == null ) {
+ throw new ReviewedStingException("GZIP file to examine was null");
+ }
+
+ try {
+ // The GZIP ISIZE field holds the uncompressed size of the compressed data.
+ // It occupies the last 4 bytes of any GZIP file:
+ RandomAccessFile in = new RandomAccessFile(gzipFile, "r");
+ in.seek(gzipFile.length() - 4);
+ byte[] sizeBytes = new byte[4];
+ in.read(sizeBytes, 0, 4);
+
+ ByteBuffer byteBuf = ByteBuffer.wrap(sizeBytes);
+ byteBuf.order(ByteOrder.LITTLE_ENDIAN); // The GZIP spec mandates little-endian byte order
+ int uncompressedSize = byteBuf.getInt();
+
+ // If the size read in is negative, we've overflowed our signed integer:
+ if ( uncompressedSize < 0 ) {
+ throw new UserException.CouldNotReadInputFile(String.format("Cannot accurately determine the uncompressed size of file %s " +
+ "because it's either larger than %d bytes or the GZIP ISIZE field is corrupt",
+ gzipFile.getAbsolutePath(), Integer.MAX_VALUE));
+ }
+
+ return uncompressedSize;
+ }
+ catch ( IOException e ) {
+ throw new UserException.CouldNotReadInputFile(gzipFile, e);
+ }
+ }
}
diff --git a/public/java/test/org/broadinstitute/sting/BaseTest.java b/public/java/test/org/broadinstitute/sting/BaseTest.java
index ac3a970f9..bc4ce098b 100755
--- a/public/java/test/org/broadinstitute/sting/BaseTest.java
+++ b/public/java/test/org/broadinstitute/sting/BaseTest.java
@@ -6,6 +6,7 @@ import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.spi.LoggingEvent;
import org.broadinstitute.sting.commandline.CommandLineUtils;
+import org.broadinstitute.sting.utils.crypt.CryptUtils;
import org.broadinstitute.sting.utils.exceptions.ReviewedStingException;
import org.broadinstitute.sting.utils.io.IOUtils;
@@ -87,6 +88,9 @@ public abstract class BaseTest {
public static final File testDirFile = new File("public/testdata/");
public static final String testDir = testDirFile.getAbsolutePath() + "/";
+ public static final String keysDataLocation = validationDataLocation + "keys/";
+ public static final String gatkKeyFile = CryptUtils.GATK_USER_KEY_DIRECTORY + "gsamembers_broadinstitute.org.key";
+
/** before the class starts up */
static {
// setup a basic log configuration
diff --git a/public/java/test/org/broadinstitute/sting/WalkerTest.java b/public/java/test/org/broadinstitute/sting/WalkerTest.java
index ca7653b58..c9e3b6b1b 100755
--- a/public/java/test/org/broadinstitute/sting/WalkerTest.java
+++ b/public/java/test/org/broadinstitute/sting/WalkerTest.java
@@ -30,6 +30,7 @@ import org.broad.tribble.FeatureCodec;
import org.broad.tribble.Tribble;
import org.broad.tribble.index.Index;
import org.broad.tribble.index.IndexFactory;
+import org.broadinstitute.sting.gatk.phonehome.GATKRunReport;
import org.broadinstitute.sting.utils.codecs.vcf.VCFCodec;
import org.broadinstitute.sting.gatk.CommandLineExecutable;
import org.broadinstitute.sting.gatk.CommandLineGATK;
@@ -45,7 +46,7 @@ import java.io.File;
import java.util.*;
public class WalkerTest extends BaseTest {
- private static final boolean ENABLE_REPORTING = false;
+ private static final boolean ENABLE_PHONE_HOME_FOR_TESTS = false;
@BeforeMethod
public void initializeRandomGenerator() {
@@ -121,11 +122,19 @@ public class WalkerTest extends BaseTest {
}
public class WalkerTestSpec {
+
+ // Arguments implicitly included in all Walker command lines, unless explicitly
+ // disabled using the disableImplicitArgs() method below.
+ final String IMPLICIT_ARGS = ENABLE_PHONE_HOME_FOR_TESTS ?
+ String.format("-et %s", GATKRunReport.PhoneHomeOption.STANDARD) :
+ String.format("-et %s -K %s", GATKRunReport.PhoneHomeOption.NO_ET, gatkKeyFile);
+
String args = "";
int nOutputFiles = -1;
List md5s = null;
List exts = null;
Class expectedException = null;
+ boolean includeImplicitArgs = true;
// the default output path for the integration test
private File outputFileLocation = null;
@@ -159,6 +168,10 @@ public class WalkerTest extends BaseTest {
this.expectedException = expectedException;
}
+ public String getArgsWithImplicitArgs() {
+ return args + (includeImplicitArgs ? " " + IMPLICIT_ARGS : "");
+ }
+
public void setOutputFileLocation(File outputFileLocation) {
this.outputFileLocation = outputFileLocation;
}
@@ -180,6 +193,9 @@ public class WalkerTest extends BaseTest {
auxillaryFiles.put(expectededMD5sum, outputfile);
}
+ public void disableImplicitArgs() {
+ includeImplicitArgs = false;
+ }
}
protected boolean parameterize() {
@@ -213,7 +229,7 @@ public class WalkerTest extends BaseTest {
tmpFiles.add(fl);
}
- final String args = String.format(spec.args, tmpFiles.toArray());
+ final String args = String.format(spec.getArgsWithImplicitArgs(), tmpFiles.toArray());
System.out.println(Utils.dupString('-', 80));
if ( spec.expectsException() ) {
@@ -277,13 +293,10 @@ public class WalkerTest extends BaseTest {
* @param args the argument list
* @param expectedException the expected exception or null
*/
- public static void executeTest(String name, String args, Class expectedException) {
+ private void executeTest(String name, String args, Class expectedException) {
CommandLineGATK instance = new CommandLineGATK();
String[] command = Utils.escapeExpressions(args);
- // add the logging level to each of the integration test commands
- command = Utils.appendArray(command, "-et", ENABLE_REPORTING ? "STANDARD" : "NO_ET");
-
// run the executable
boolean gotAnException = false;
try {
diff --git a/public/java/test/org/broadinstitute/sting/utils/crypt/CryptUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/crypt/CryptUtilsUnitTest.java
new file mode 100644
index 000000000..eae4486c6
--- /dev/null
+++ b/public/java/test/org/broadinstitute/sting/utils/crypt/CryptUtilsUnitTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2012, The Broad Institute
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.broadinstitute.sting.utils.crypt;
+
+import org.broadinstitute.sting.BaseTest;
+import org.broadinstitute.sting.utils.exceptions.ReviewedStingException;
+import org.broadinstitute.sting.utils.exceptions.UserException;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.Assert;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Arrays;
+
+public class CryptUtilsUnitTest extends BaseTest {
+
+ @Test
+ public void testGenerateValidKeyPairWithDefaultSettings() {
+ KeyPair keyPair = CryptUtils.generateKeyPair();
+ Assert.assertTrue(CryptUtils.keysDecryptEachOther(keyPair.getPrivate(), keyPair.getPublic()));
+ }
+
+ @DataProvider( name = "InvalidKeyPairSettings" )
+ public Object[][] invalidKeyPairSettingsDataProvider() {
+ return new Object[][] {
+ { -1, CryptUtils.DEFAULT_ENCRYPTION_ALGORITHM, CryptUtils.DEFAULT_RANDOM_NUMBER_GENERATION_ALGORITHM},
+ { CryptUtils.DEFAULT_KEY_LENGTH, "Made-up algorithm", CryptUtils.DEFAULT_RANDOM_NUMBER_GENERATION_ALGORITHM},
+ { CryptUtils.DEFAULT_KEY_LENGTH, CryptUtils.DEFAULT_ENCRYPTION_ALGORITHM, "Made-up algorithm"}
+ };
+ }
+
+ @Test( dataProvider = "InvalidKeyPairSettings", expectedExceptions = ReviewedStingException.class )
+ public void testGenerateKeyPairWithInvalidSettings( int keyLength, String encryptionAlgorithm, String randomNumberGenerationAlgorithm ) {
+ KeyPair keyPair = CryptUtils.generateKeyPair(keyLength, encryptionAlgorithm, randomNumberGenerationAlgorithm);
+ }
+
+ @Test
+ public void testGATKMasterKeyPairMutualDecryption() {
+ Assert.assertTrue(CryptUtils.keysDecryptEachOther(CryptUtils.loadGATKMasterPrivateKey(), CryptUtils.loadGATKMasterPublicKey()));
+ }
+
+ @Test
+ public void testGATKMasterPrivateKeyWithDistributedPublicKeyMutualDecryption() {
+ Assert.assertTrue(CryptUtils.keysDecryptEachOther(CryptUtils.loadGATKMasterPrivateKey(), CryptUtils.loadGATKDistributedPublicKey()));
+ }
+
+ @Test
+ public void testKeyPairWriteThenRead() {
+ KeyPair keyPair = CryptUtils.generateKeyPair();
+ File privateKeyFile = createTempFile("testKeyPairWriteThenRead_private", "key");
+ File publicKeyFile = createTempFile("testKeyPairWriteThenRead_public", "key");
+
+ CryptUtils.writeKeyPair(keyPair, privateKeyFile, publicKeyFile);
+
+ assertKeysAreEqual(keyPair.getPrivate(), CryptUtils.readPrivateKey(privateKeyFile));
+ assertKeysAreEqual(keyPair.getPublic(), CryptUtils.readPublicKey(publicKeyFile));
+ }
+
+ @Test
+ public void testPublicKeyWriteThenReadFromFile() {
+ File keyFile = createTempFile("testPublicKeyWriteThenReadFromFile", "key");
+ PublicKey publicKey = CryptUtils.generateKeyPair().getPublic();
+
+ CryptUtils.writeKey(publicKey, keyFile);
+
+ assertKeysAreEqual(publicKey, CryptUtils.readPublicKey(keyFile));
+ }
+
+ @Test
+ public void testPublicKeyWriteThenReadFromStream() throws IOException {
+ File keyFile = createTempFile("testPublicKeyWriteThenReadFromStream", "key");
+ PublicKey publicKey = CryptUtils.generateKeyPair().getPublic();
+
+ CryptUtils.writeKey(publicKey, keyFile);
+
+ assertKeysAreEqual(publicKey, CryptUtils.readPublicKey(new FileInputStream(keyFile)));
+ }
+
+ @Test
+ public void testPrivateKeyWriteThenReadFromFile() {
+ File keyFile = createTempFile("testPrivateKeyWriteThenReadFromFile", "key");
+ PrivateKey privateKey = CryptUtils.generateKeyPair().getPrivate();
+
+ CryptUtils.writeKey(privateKey, keyFile);
+
+ assertKeysAreEqual(privateKey, CryptUtils.readPrivateKey(keyFile));
+ }
+
+ @Test
+ public void testPrivateKeyWriteThenReadFromStream() throws IOException {
+ File keyFile = createTempFile("testPrivateKeyWriteThenReadFromStream", "key");
+ PrivateKey privateKey = CryptUtils.generateKeyPair().getPrivate();
+
+ CryptUtils.writeKey(privateKey, keyFile);
+
+ assertKeysAreEqual(privateKey, CryptUtils.readPrivateKey(new FileInputStream(keyFile)));
+ }
+
+ @Test( expectedExceptions = UserException.CouldNotReadInputFile.class )
+ public void testReadNonExistentPublicKey() {
+ File nonExistentFile = new File("jdshgkdfhg.key");
+ Assert.assertFalse(nonExistentFile.exists());
+
+ CryptUtils.readPublicKey(nonExistentFile);
+ }
+
+ @Test( expectedExceptions = UserException.CouldNotReadInputFile.class )
+ public void testReadNonExistentPrivateKey() {
+ File nonExistentFile = new File("jdshgkdfhg.key");
+ Assert.assertFalse(nonExistentFile.exists());
+
+ CryptUtils.readPrivateKey(nonExistentFile);
+ }
+
+ @Test
+ public void testDecodePublicKey() {
+ PublicKey originalKey = CryptUtils.generateKeyPair().getPublic();
+ PublicKey decodedKey = CryptUtils.decodePublicKey(originalKey.getEncoded(), CryptUtils.DEFAULT_ENCRYPTION_ALGORITHM);
+ assertKeysAreEqual(originalKey, decodedKey);
+ }
+
+ @Test
+ public void testDecodePrivateKey() {
+ PrivateKey originalKey = CryptUtils.generateKeyPair().getPrivate();
+ PrivateKey decodedKey = CryptUtils.decodePrivateKey(originalKey.getEncoded(), CryptUtils.DEFAULT_ENCRYPTION_ALGORITHM);
+ assertKeysAreEqual(originalKey, decodedKey);
+ }
+
+ @Test
+ public void testLoadGATKMasterPrivateKey() {
+ PrivateKey gatkMasterPrivateKey = CryptUtils.loadGATKMasterPrivateKey();
+ }
+
+ @Test
+ public void testLoadGATKMasterPublicKey() {
+ PublicKey gatkMasterPublicKey = CryptUtils.loadGATKMasterPublicKey();
+ }
+
+ @Test
+ public void testLoadGATKDistributedPublicKey() {
+ PublicKey gatkDistributedPublicKey = CryptUtils.loadGATKDistributedPublicKey();
+ }
+
+ private void assertKeysAreEqual( Key originalKey, Key keyFromDisk ) {
+ Assert.assertTrue(Arrays.equals(originalKey.getEncoded(), keyFromDisk.getEncoded()));
+ Assert.assertEquals(originalKey.getAlgorithm(), keyFromDisk.getAlgorithm());
+ Assert.assertEquals(originalKey.getFormat(), keyFromDisk.getFormat());
+ }
+}
diff --git a/public/java/test/org/broadinstitute/sting/utils/crypt/GATKKeyIntegrationTest.java b/public/java/test/org/broadinstitute/sting/utils/crypt/GATKKeyIntegrationTest.java
new file mode 100644
index 000000000..8fb75ef38
--- /dev/null
+++ b/public/java/test/org/broadinstitute/sting/utils/crypt/GATKKeyIntegrationTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2012, The Broad Institute
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.broadinstitute.sting.utils.crypt;
+
+import org.broadinstitute.sting.WalkerTest;
+import org.broadinstitute.sting.gatk.phonehome.GATKRunReport;
+import org.broadinstitute.sting.utils.exceptions.UserException;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+
+public class GATKKeyIntegrationTest extends WalkerTest {
+
+ public static final String BASE_COMMAND = String.format("-T PrintReads -R %s -I %s -o %%s",
+ testDir + "exampleFASTA.fasta",
+ testDir + "exampleBAM.bam");
+ public static final String MD5_UPON_SUCCESSFUL_RUN = "b9dc5bf6753ca2819e70b056eaf61258";
+
+
+ private void runGATKKeyTest ( String testName, String etArg, String keyArg, Class expectedException, String md5 ) {
+ String command = BASE_COMMAND + String.format(" %s %s", etArg, keyArg);
+
+ WalkerTestSpec spec = expectedException != null ?
+ new WalkerTestSpec(command, 1, expectedException) :
+ new WalkerTestSpec(command, 1, Arrays.asList(md5));
+
+ spec.disableImplicitArgs(); // Turn off automatic inclusion of -et/-K args by WalkerTest
+ executeTest(testName, spec);
+ }
+
+ @Test
+ public void testValidKeyNoET() {
+ runGATKKeyTest("testValidKeyNoET",
+ "-et " + GATKRunReport.PhoneHomeOption.NO_ET,
+ "-K " + keysDataLocation + "valid.key",
+ null,
+ MD5_UPON_SUCCESSFUL_RUN);
+ }
+
+ @Test
+ public void testValidKeyETStdout() {
+ runGATKKeyTest("testValidKeyETStdout",
+ "-et " + GATKRunReport.PhoneHomeOption.STDOUT,
+ "-K " + keysDataLocation + "valid.key",
+ null,
+ MD5_UPON_SUCCESSFUL_RUN);
+ }
+
+ @Test
+ public void testValidKeyETStandard() {
+ runGATKKeyTest("testValidKeyETStandard",
+ "",
+ "-K " + keysDataLocation + "valid.key",
+ null,
+ MD5_UPON_SUCCESSFUL_RUN);
+ }
+
+ @Test
+ public void testNoKeyNoET() {
+ runGATKKeyTest("testNoKeyNoET",
+ "-et " + GATKRunReport.PhoneHomeOption.NO_ET,
+ "",
+ UserException.class,
+ null);
+ }
+
+ @Test
+ public void testNoKeyETStdout() {
+ runGATKKeyTest("testNoKeyETStdout",
+ "-et " + GATKRunReport.PhoneHomeOption.STDOUT,
+ "",
+ UserException.class,
+ null);
+ }
+
+ @Test
+ public void testNoKeyETStandard() {
+ runGATKKeyTest("testNoKeyETStandard",
+ "",
+ "",
+ null,
+ MD5_UPON_SUCCESSFUL_RUN);
+ }
+
+ @Test
+ public void testRevokedKey() {
+ runGATKKeyTest("testRevokedKey",
+ "-et " + GATKRunReport.PhoneHomeOption.NO_ET,
+ "-K " + keysDataLocation + "revoked.key",
+ UserException.KeySignatureVerificationException.class,
+ null);
+ }
+
+ @DataProvider(name = "CorruptKeyTestData")
+ public Object[][] corruptKeyDataProvider() {
+ return new Object[][] {
+ { "corrupt_empty.key", UserException.UnreadableKeyException.class },
+ { "corrupt_single_byte_file.key", UserException.UnreadableKeyException.class },
+ { "corrupt_random_contents.key", UserException.UnreadableKeyException.class },
+ { "corrupt_single_byte_deletion.key", UserException.UnreadableKeyException.class },
+ { "corrupt_single_byte_insertion.key", UserException.UnreadableKeyException.class },
+ { "corrupt_single_byte_change.key", UserException.UnreadableKeyException.class },
+ { "corrupt_multi_byte_deletion.key", UserException.UnreadableKeyException.class },
+ { "corrupt_multi_byte_insertion.key", UserException.UnreadableKeyException.class },
+ { "corrupt_multi_byte_change.key", UserException.UnreadableKeyException.class },
+ { "corrupt_bad_isize_field.key", UserException.UnreadableKeyException.class },
+ { "corrupt_bad_crc.key", UserException.UnreadableKeyException.class },
+ { "corrupt_no_email_address.key", UserException.UnreadableKeyException.class },
+ { "corrupt_no_sectional_delimiter.key", UserException.KeySignatureVerificationException.class },
+ { "corrupt_no_signature.key", UserException.UnreadableKeyException.class },
+ { "corrupt_bad_signature.key", UserException.KeySignatureVerificationException.class },
+ { "corrupt_non_gzipped_valid_key.key", UserException.UnreadableKeyException.class }
+ };
+ }
+
+ @Test(dataProvider = "CorruptKeyTestData")
+ public void testCorruptKey ( String corruptKeyName, Class expectedException ) {
+ runGATKKeyTest(String.format("testCorruptKey (%s)", corruptKeyName),
+ "-et " + GATKRunReport.PhoneHomeOption.NO_ET,
+ "-K " + keysDataLocation + corruptKeyName,
+ expectedException,
+ null);
+ }
+
+ @Test
+ public void testCorruptButNonRequiredKey() {
+ runGATKKeyTest("testCorruptButNonRequiredKey",
+ "",
+ "-K " + keysDataLocation + "corrupt_random_contents.key",
+ null,
+ MD5_UPON_SUCCESSFUL_RUN);
+ }
+}
diff --git a/public/java/test/org/broadinstitute/sting/utils/crypt/GATKKeyUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/crypt/GATKKeyUnitTest.java
new file mode 100644
index 000000000..5e7b07a1e
--- /dev/null
+++ b/public/java/test/org/broadinstitute/sting/utils/crypt/GATKKeyUnitTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2012, The Broad Institute
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.broadinstitute.sting.utils.crypt;
+
+import org.broadinstitute.sting.BaseTest;
+import org.broadinstitute.sting.utils.exceptions.ReviewedStingException;
+import org.broadinstitute.sting.utils.exceptions.UserException;
+import org.testng.annotations.Test;
+import org.testng.Assert;
+
+import java.io.File;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+public class GATKKeyUnitTest extends BaseTest {
+
+ @Test
+ public void testCreateGATKKeyUsingMasterKeyPair() {
+ PrivateKey masterPrivateKey = CryptUtils.loadGATKMasterPrivateKey();
+ PublicKey masterPublicKey = CryptUtils.loadGATKMasterPublicKey();
+
+ // We should be able to create a valid GATKKey using our master key pair:
+ GATKKey key = new GATKKey(masterPrivateKey, masterPublicKey, "foo@bar.com");
+ Assert.assertTrue(key.isValid());
+ }
+
+ @Test
+ public void testCreateGATKKeyUsingMasterPrivateKeyAndDistributedPublicKey() {
+ PrivateKey masterPrivateKey = CryptUtils.loadGATKMasterPrivateKey();
+ PublicKey distributedPublicKey = CryptUtils.loadGATKDistributedPublicKey();
+
+ // We should also be able to create a valid GATKKey using our master private
+ // key and the public key we distribute with the GATK:
+ GATKKey key = new GATKKey(masterPrivateKey, distributedPublicKey, "foo@bar.com");
+ Assert.assertTrue(key.isValid());
+ }
+
+ @Test( expectedExceptions = ReviewedStingException.class )
+ public void testKeyPairMismatch() {
+ KeyPair firstKeyPair = CryptUtils.generateKeyPair();
+ KeyPair secondKeyPair = CryptUtils.generateKeyPair();
+
+ // Attempting to create a GATK Key with private and public keys that aren't part of the
+ // same key pair should immediately trigger a validation failure:
+ GATKKey key = new GATKKey(firstKeyPair.getPrivate(), secondKeyPair.getPublic(), "foo@bar.com");
+ }
+
+ @Test( expectedExceptions = ReviewedStingException.class )
+ public void testEncryptionAlgorithmMismatch() {
+ KeyPair keyPair = CryptUtils.generateKeyPair(CryptUtils.DEFAULT_KEY_LENGTH, "DSA", CryptUtils.DEFAULT_RANDOM_NUMBER_GENERATION_ALGORITHM);
+
+ // Attempting to use a DSA private key to create an RSA signature should throw an error:
+ GATKKey key = new GATKKey(keyPair.getPrivate(), keyPair.getPublic(), "foo@bar.com", "SHA1withRSA");
+ }
+
+ @Test( expectedExceptions = UserException.class )
+ public void testInvalidEmailAddress() {
+ String emailAddressWithNulByte = new String(new byte[] { 0 });
+ KeyPair keyPair = CryptUtils.generateKeyPair();
+
+ // Email addresses cannot contain the NUL byte, since it's used as a sectional delimiter in the key file:
+ GATKKey key = new GATKKey(CryptUtils.loadGATKMasterPrivateKey(), CryptUtils.loadGATKDistributedPublicKey(),
+ emailAddressWithNulByte);
+ }
+
+ @Test
+ public void testCreateGATKKeyFromValidKeyFile() {
+ GATKKey key = new GATKKey(CryptUtils.loadGATKDistributedPublicKey(), new File(keysDataLocation + "valid.key"));
+ Assert.assertTrue(key.isValid());
+ }
+
+ @Test( expectedExceptions = UserException.UnreadableKeyException.class )
+ public void testCreateGATKKeyFromCorruptKeyFile() {
+ GATKKey key = new GATKKey(CryptUtils.loadGATKDistributedPublicKey(), new File(keysDataLocation + "corrupt_random_contents.key"));
+ }
+
+ @Test
+ public void testCreateGATKKeyFromRevokedKeyFile() {
+ GATKKey key = new GATKKey(CryptUtils.loadGATKDistributedPublicKey(), new File(keysDataLocation + "revoked.key"));
+ Assert.assertFalse(key.isValid());
+ }
+
+ @Test( expectedExceptions = UserException.CouldNotReadInputFile.class )
+ public void testCreateGATKKeyFromNonExistentFile() {
+ File nonExistentFile = new File("ghfdkgsdhg.key");
+ Assert.assertFalse(nonExistentFile.exists());
+
+ GATKKey key = new GATKKey(CryptUtils.loadGATKDistributedPublicKey(), nonExistentFile);
+ }
+}
diff --git a/public/java/test/org/broadinstitute/sting/utils/io/IOUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/io/IOUtilsUnitTest.java
index 757e6efdf..941d2b14c 100644
--- a/public/java/test/org/broadinstitute/sting/utils/io/IOUtilsUnitTest.java
+++ b/public/java/test/org/broadinstitute/sting/utils/io/IOUtilsUnitTest.java
@@ -27,12 +27,18 @@ package org.broadinstitute.sting.utils.io;
import org.apache.commons.io.FileUtils;
import org.broadinstitute.sting.BaseTest;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
+import java.util.Random;
+import org.broadinstitute.sting.gatk.GenomeAnalysisEngine;
+import org.broadinstitute.sting.utils.exceptions.ReviewedStingException;
import org.broadinstitute.sting.utils.exceptions.UserException;
import org.testng.Assert;
+import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class IOUtilsUnitTest extends BaseTest {
@@ -230,4 +236,90 @@ public class IOUtilsUnitTest extends BaseTest {
Assert.assertFalse(IOUtils.isSpecialFile(new File("/home/user/my.file")));
Assert.assertFalse(IOUtils.isSpecialFile(new File("/devfake/null")));
}
+
+ @DataProvider( name = "ByteArrayIOTestData")
+ public Object[][] byteArrayIOTestDataProvider() {
+ return new Object[][] {
+ // file size, read buffer size
+ { 0, 4096 },
+ { 1, 4096 },
+ { 2000, 4096 },
+ { 4095, 4096 },
+ { 4096, 4096 },
+ { 4097, 4096 },
+ { 6000, 4096 },
+ { 8191, 4096 },
+ { 8192, 4096 },
+ { 8193, 4096 },
+ { 10000, 4096 }
+ };
+ }
+
+ @Test( dataProvider = "ByteArrayIOTestData" )
+ public void testWriteThenReadFileIntoByteArray ( int fileSize, int readBufferSize ) throws Exception {
+ File tempFile = createTempFile(String.format("testWriteThenReadFileIntoByteArray_%d_%d", fileSize, readBufferSize), "tmp");
+
+ byte[] dataWritten = getDeterministicRandomData(fileSize);
+ IOUtils.writeByteArrayToFile(dataWritten, tempFile);
+ byte[] dataRead = IOUtils.readFileIntoByteArray(tempFile, readBufferSize);
+
+ Assert.assertEquals(dataRead.length, dataWritten.length);
+ Assert.assertTrue(Arrays.equals(dataRead, dataWritten));
+ }
+
+ @Test( dataProvider = "ByteArrayIOTestData" )
+ public void testWriteThenReadStreamIntoByteArray ( int fileSize, int readBufferSize ) throws Exception {
+ File tempFile = createTempFile(String.format("testWriteThenReadStreamIntoByteArray_%d_%d", fileSize, readBufferSize), "tmp");
+
+ byte[] dataWritten = getDeterministicRandomData(fileSize);
+ IOUtils.writeByteArrayToStream(dataWritten, new FileOutputStream(tempFile));
+ byte[] dataRead = IOUtils.readStreamIntoByteArray(new FileInputStream(tempFile), readBufferSize);
+
+ Assert.assertEquals(dataRead.length, dataWritten.length);
+ Assert.assertTrue(Arrays.equals(dataRead, dataWritten));
+ }
+
+ @Test( expectedExceptions = UserException.CouldNotReadInputFile.class )
+ public void testReadNonExistentFileIntoByteArray() {
+ File nonExistentFile = new File("djfhsdkjghdfk");
+ Assert.assertFalse(nonExistentFile.exists());
+
+ IOUtils.readFileIntoByteArray(nonExistentFile);
+ }
+
+ @Test( expectedExceptions = ReviewedStingException.class )
+ public void testReadNullStreamIntoByteArray() {
+ IOUtils.readStreamIntoByteArray(null);
+ }
+
+ @Test( expectedExceptions = ReviewedStingException.class )
+ public void testReadStreamIntoByteArrayInvalidBufferSize() throws Exception {
+ IOUtils.readStreamIntoByteArray(new FileInputStream(createTempFile("testReadStreamIntoByteArrayInvalidBufferSize", "tmp")),
+ -1);
+ }
+
+ @Test( expectedExceptions = UserException.CouldNotCreateOutputFile.class )
+ public void testWriteByteArrayToUncreatableFile() {
+ IOUtils.writeByteArrayToFile(new byte[]{0}, new File("/dev/foo/bar"));
+ }
+
+ @Test( expectedExceptions = ReviewedStingException.class )
+ public void testWriteNullByteArrayToFile() {
+ IOUtils.writeByteArrayToFile(null, createTempFile("testWriteNullByteArrayToFile", "tmp"));
+ }
+
+ @Test( expectedExceptions = ReviewedStingException.class )
+ public void testWriteByteArrayToNullStream() {
+ IOUtils.writeByteArrayToStream(new byte[]{0}, null);
+ }
+
+ private byte[] getDeterministicRandomData ( int size ) {
+ GenomeAnalysisEngine.resetRandomGenerator();
+ Random rand = GenomeAnalysisEngine.getRandomGenerator();
+
+ byte[] randomData = new byte[size];
+ rand.nextBytes(randomData);
+
+ return randomData;
+ }
}
diff --git a/public/keys/GATK_public.key b/public/keys/GATK_public.key
new file mode 100644
index 000000000..05cdde1c2
Binary files /dev/null and b/public/keys/GATK_public.key differ
diff --git a/public/packages/GATKEngine.xml b/public/packages/GATKEngine.xml
index 283b5eabf..68459f6d2 100644
--- a/public/packages/GATKEngine.xml
+++ b/public/packages/GATKEngine.xml
@@ -36,6 +36,8 @@
+
+