Public-key authorization scheme to restrict use of NO_ET
-Running the GATK with the -et NO_ET or -et STDOUT options now requires a key issued by us. Our reasons for doing this, and the procedure for our users to request keys, are documented here: http://www.broadinstitute.org/gsa/wiki/index.php/Phone_home -A GATK user key is an email address plus a cryptographic signature signed using our private key, all wrapped in a GZIP container. User keys are validated using the public key we now distribute with the GATK. Our private key is kept in a secure location. -Keys are cryptographically secure in that valid keys definitely came from us and keys cannot be fabricated, however keys are not "copy-protected" in any way. -Includes private, standalone utilities to create a new GATK user key (GenerateGATKUserKey) and to create a new master public/private key pair (GenerateKeyPair). Usage of these tools will be documented on the internal wiki shortly. -Comprehensive unit/integration tests, including tests to ensure the continued integrity of the GATK master public/private key pair. -Generation of new user keys and the new unit/integration tests both require access to the GATK private key, which can only be read by members of the group "gsagit".
This commit is contained in:
parent
027843d791
commit
0702ee1587
|
|
@ -47,6 +47,7 @@
|
|||
<property name="R.package.path" value="org/broadinstitute/sting/utils/R" />
|
||||
<property name="resource.file" value="StingText.properties" />
|
||||
<property name="resource.path" value="${java.classes}/StingText.properties" />
|
||||
<property name="key.dir" value="${public.dir}/keys" />
|
||||
|
||||
<property name="scala.public.source.dir" value="${public.dir}/scala/src" />
|
||||
<property name="scala.private.source.dir" value="${private.dir}/scala/src" />
|
||||
|
|
@ -567,6 +568,7 @@
|
|||
</fileset>
|
||||
<fileset dir="${java.classes}" includes="**/commandline/**/*.class"/>
|
||||
<fileset dir="${java.classes}" includes="**/sting/pipeline/**/*.class"/>
|
||||
<fileset dir="${java.classes}" includes="**/sting/tools/**/*.class"/>
|
||||
<fileset dir="${java.classes}" includes="**/sting/jna/**/*.class"/>
|
||||
<fileset dir="${java.classes}" includes="net/sf/picard/**/*.class"/>
|
||||
<fileset dir="${java.classes}" includes="net/sf/samtools/**/*.class"/>
|
||||
|
|
@ -615,6 +617,9 @@
|
|||
<include name="**/gatk/**/*.R"/>
|
||||
<include name="**/alignment/**/*.R"/>
|
||||
</fileset>
|
||||
<fileset dir="${key.dir}">
|
||||
<include name="**/*.key"/>
|
||||
</fileset>
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="org.broadinstitute.sting.gatk.CommandLineGATK" />
|
||||
</manifest>
|
||||
|
|
@ -879,6 +884,7 @@
|
|||
<pathelement location="${R.tar.dir}" />
|
||||
<pathelement location="${R.public.scripts.dir}" />
|
||||
<pathelement location="${R.private.scripts.dir}" />
|
||||
<pathelement location="${key.dir}" />
|
||||
<path refid="external.dependencies" />
|
||||
</path>
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<String> readFilters = new ArrayList<String>();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String> md5s = null;
|
||||
List<String> 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 {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -36,6 +36,8 @@
|
|||
<dir name="org/broadinstitute/sting/utils/R" includes="**/*.tar.gz" />
|
||||
<!-- All R scripts in org.broadinstitute.sting -->
|
||||
<dir name="org/broadinstitute/sting" includes="**/*.R" />
|
||||
<!-- The GATK public key -->
|
||||
<file path="GATK_public.key" />
|
||||
</dependencies>
|
||||
</executable>
|
||||
<resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue