/** Fetches a file from the URL given and returns an input stream to it. */ private static InputStream fetchFile(String url) throws IOException, ClientProtocolException, FileNotFoundException { HttpParams httpClientParams = new BasicHttpParams(); HttpProtocolParams.setUserAgent(httpClientParams, VersionInfoUtils.getUserAgent()); HttpClient httpclient = new DefaultHttpClient(httpClientParams); HttpGet httpget = new HttpGet(url); HttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); if (entity != null) { return entity.getContent(); } return null; }
/** * Client configuration options such as proxy settings, user agent string, max retry attempts, etc. */ @NotThreadSafe public class ClientConfiguration { /** The default timeout for a connected socket. */ public static final int DEFAULT_SOCKET_TIMEOUT = 50 * 1000; /** The default max connection pool size. */ public static final int DEFAULT_MAX_CONNECTIONS = 50; /** The default HTTP user agent header for AWS Java SDK clients. */ public static final String DEFAULT_USER_AGENT = VersionInfoUtils.getUserAgent(); /** The default maximum number of retries for error responses. */ public static final int DEFAULT_MAX_RETRIES = 3; /** * The default on whether to use the {@link IdleConnectionReaper} to manage stale connections * * @see IdleConnectionReaper */ public static final boolean DEFAULT_USE_REAPER = true; /** The HTTP user agent header passed with all HTTP requests. */ private String userAgent = DEFAULT_USER_AGENT; /** * The maximum number of times that a retryable failed request (ex: a 5xx response from a service) * will be retried. */ private int maxErrorRetry = DEFAULT_MAX_RETRIES; /** * The protocol to use when connecting to Amazon Web Services. * * <p>The default configuration is to use HTTPS for all requests for increased security. */ private Protocol protocol = Protocol.HTTPS; /** Optionally specifies the proxy host to connect through. */ private String proxyHost = null; /** Optionally specifies the port on the proxy host to connect through. */ private int proxyPort = -1; /** Optionally specifies the user name to use when connecting through a proxy. */ private String proxyUsername = null; /** Optionally specifies the password to use when connecting through a proxy. */ private String proxyPassword = null; /** Optional Windows domain name for configuring NTLM proxy support. */ private String proxyDomain = null; /** Optional Windows workstation name for configuring NTLM proxy support. */ private String proxyWorkstation = null; /** The maximum number of open HTTP connections. */ private int maxConnections = DEFAULT_MAX_CONNECTIONS; /** * The amount of time to wait (in milliseconds) for data to be transfered over an established, * open connection before the connection is timed out. A value of 0 means infinity, and is not * recommended. */ private int socketTimeout = DEFAULT_SOCKET_TIMEOUT; /** * The amount of time to wait (in milliseconds) when initially establishing a connection before * giving up and timing out. A value of 0 means infinity, and is not recommended. */ private int connectionTimeout = 50 * 1000; /** * Optional size hint (in bytes) for the low level TCP send buffer. This is an advanced option for * advanced users who want to tune low level TCP parameters to try and squeeze out more * performance. */ private int socketSendBufferSizeHint = 0; /** * Optional size hint (in bytes) for the low level TCP receive buffer. This is an advanced option * for advanced users who want to tune low level TCP parameters to try and squeeze out more * performance. */ private int socketReceiveBufferSizeHint = 0; /** * Optional whether to use the {@link IdleConnectionReaper} to manage stale connections. A reason * for not running the {@link IdleConnectionReaper} can be if running in an environment where the * modifyThread and modifyThreadGroup permissions are not allowed. */ private boolean useReaper = DEFAULT_USE_REAPER; public ClientConfiguration() {} public ClientConfiguration(ClientConfiguration other) { this.connectionTimeout = other.connectionTimeout; this.maxConnections = other.maxConnections; this.maxErrorRetry = other.maxErrorRetry; this.protocol = other.protocol; this.proxyDomain = other.proxyDomain; this.proxyHost = other.proxyHost; this.proxyPassword = other.proxyPassword; this.proxyPort = other.proxyPort; this.proxyUsername = other.proxyUsername; this.proxyWorkstation = other.proxyWorkstation; this.socketTimeout = other.socketTimeout; this.userAgent = other.userAgent; this.useReaper = other.useReaper; this.socketReceiveBufferSizeHint = other.socketReceiveBufferSizeHint; this.socketSendBufferSizeHint = other.socketSendBufferSizeHint; } /** * Returns the protocol (HTTP or HTTPS) to use when connecting to Amazon Web Services. * * <p>The default configuration is to use HTTPS for all requests for increased security. * * <p>Individual clients can also override this setting by explicitly including the protocol as * part of the endpoint URL when calling {@link AmazonWebServiceClient#setEndpoint(String)}. * * @return The protocol to use when connecting to Amazon Web Services. */ public Protocol getProtocol() { return protocol; } /** * Sets the protocol (i.e. HTTP or HTTPS) to use when connecting to Amazon Web Services. * * <p>The default configuration is to use HTTPS for all requests for increased security. * * <p>Individual clients can also override this setting by explicitly including the protocol as * part of the endpoint URL when calling {@link AmazonWebServiceClient#setEndpoint(String)}. * * @param protocol The protocol to use when connecting to Amazon Web Services. */ public void setProtocol(Protocol protocol) { this.protocol = protocol; } /** * Sets the protocol (i.e. HTTP or HTTPS) to use when connecting to Amazon Web Services, and * returns the updated ClientConfiguration object so that additional calls may be chained * together. * * <p>The default configuration is to use HTTPS for all requests for increased security. * * <p>Individual clients can also override this setting by explicitly including the protocol as * part of the endpoint URL when calling {@link AmazonWebServiceClient#setEndpoint(String)}. * * @param protocol The protocol to use when connecting to Amazon Web Services. * @return The updated ClientConfiguration object with the new max HTTP connections setting. */ public ClientConfiguration withProtocol(Protocol protocol) { setProtocol(protocol); return this; } /** * Returns the maximum number of allowed open HTTP connections. * * @return The maximum number of allowed open HTTP connections. */ public int getMaxConnections() { return maxConnections; } /** * Sets the maximum number of allowed open HTTP connections. * * @param maxConnections The maximum number of allowed open HTTP connections. */ public void setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; } /** * Sets the maximum number of allowed open HTTP connections and returns the updated * ClientConfiguration object. * * @param maxConnections The maximum number of allowed open HTTP connections. * @return The updated ClientConfiguration object with the new max HTTP connections setting. */ public ClientConfiguration withMaxConnections(int maxConnections) { setMaxConnections(maxConnections); return this; } /** * Returns the HTTP user agent header to send with all requests. * * @return The user agent string to use when sending requests. */ public String getUserAgent() { return userAgent; } /** * Sets the HTTP user agent header to send with all requests. * * @param userAgent The user agent string to use when sending requests. */ public void setUserAgent(String userAgent) { this.userAgent = userAgent; } /** * Sets the HTTP user agent header used in requests and returns the updated ClientConfiguration * object. * * @param userAgent The user agent string to use when sending requests. * @return The updated ClientConfiguration object. */ public ClientConfiguration withUserAgent(String userAgent) { setUserAgent(userAgent); return this; } /** * Returns the optional proxy host the client will connect through. * * @return The proxy host the client will connect through. */ public String getProxyHost() { return proxyHost; } /** * Sets the optional proxy host the client will connect through. * * @param proxyHost The proxy host the client will connect through. */ public void setProxyHost(String proxyHost) { this.proxyHost = proxyHost; } /** * Sets the optional proxy host the client will connect through and returns the updated * ClientConfiguration object. * * @param proxyHost The proxy host the client will connect through. * @return The updated ClientConfiguration object. */ public ClientConfiguration withProxyHost(String proxyHost) { setProxyHost(proxyHost); return this; } /** * Returns the optional proxy port the client will connect through. * * @return The proxy port the client will connect through. */ public int getProxyPort() { return proxyPort; } /** * Sets the optional proxy port the client will connect through. * * @param proxyPort The proxy port the client will connect through. */ public void setProxyPort(int proxyPort) { this.proxyPort = proxyPort; } /** * Sets the optional proxy port the client will connect through and returns the updated * ClientConfiguration object. * * @param proxyPort The proxy port the client will connect through. * @return The updated ClientConfiguration object. */ public ClientConfiguration withProxyPort(int proxyPort) { setProxyPort(proxyPort); return this; } /** * Returns the optional proxy user name to use if connecting through a proxy. * * @return The optional proxy user name the configured client will use if connecting through a * proxy. */ public String getProxyUsername() { return proxyUsername; } /** * Sets the optional proxy user name to use if connecting through a proxy. * * @param proxyUsername The proxy user name to use if connecting through a proxy. */ public void setProxyUsername(String proxyUsername) { this.proxyUsername = proxyUsername; } /** * Sets the optional proxy user name and returns the updated ClientConfiguration object. * * @param proxyUsername The proxy user name to use if connecting through a proxy. * @return The updated ClientConfiguration object. */ public ClientConfiguration withProxyUsername(String proxyUsername) { setProxyUsername(proxyUsername); return this; } /** * Returns the optional proxy password to use when connecting through a proxy. * * @return The password to use when connecting through a proxy. */ public String getProxyPassword() { return proxyPassword; } /** * Sets the optional proxy password to use when connecting through a proxy. * * @param proxyPassword The password to use when connecting through a proxy. */ public void setProxyPassword(String proxyPassword) { this.proxyPassword = proxyPassword; } /** * Sets the optional proxy password to use when connecting through a proxy, and returns the * updated ClientConfiguration object. * * @param proxyPassword The password to use when connecting through a proxy. * @return The updated ClientConfiguration object. */ public ClientConfiguration withProxyPassword(String proxyPassword) { setProxyPassword(proxyPassword); return this; } /** * Returns the optional Windows domain name for configuring an NTLM proxy. If you aren't using a * Windows NTLM proxy, you do not need to set this field. * * @return The optional Windows domain name for configuring an NTLM proxy. */ public String getProxyDomain() { return proxyDomain; } /** * Sets the optional Windows domain name for configuration an NTLM proxy. If you aren't using a * Windows NTLM proxy, you do not need to set this field. * * @param proxyDomain The optional Windows domain name for configuring an NTLM proxy. */ public void setProxyDomain(String proxyDomain) { this.proxyDomain = proxyDomain; } /** * Sets the optional Windows domain name for configuration an NTLM proxy and returns a reference * to this updated ClientConfiguration object so that additional method calls can be chained * together. If you aren't using a Windows NTLM proxy, you do not need to set this field. * * @param proxyDomain The optional Windows domain name for configuring an NTLM proxy. * @return The updated ClientConfiguration object. */ public ClientConfiguration withProxyDomain(String proxyDomain) { setProxyDomain(proxyDomain); return this; } /** * Returns the optional Windows workstation name for configuring NTLM proxy support. If you aren't * using a Windows NTLM proxy, you do not need to set this field. * * @return The optional Windows workstation name for configuring NTLM proxy support. */ public String getProxyWorkstation() { return proxyWorkstation; } /** * Sets the optional Windows workstation name for configuring NTLM proxy support. If you aren't * using a Windows NTLM proxy, you do not need to set this field. * * @param proxyWorkstation The optional Windows workstation name for configuring NTLM proxy * support. */ public void setProxyWorkstation(String proxyWorkstation) { this.proxyWorkstation = proxyWorkstation; } /** * Sets the optional Windows workstation name for configuring NTLM proxy support, and returns the * updated ClientConfiguration object so that additional method calls can be chained together. If * you aren't using a Windows NTLM proxy, you do not need to set this field. * * @param proxyWorkstation The optional Windows workstation name for configuring NTLM proxy * support. * @return The updated ClientConfiguration object. */ public ClientConfiguration withProxyWorkstation(String proxyWorkstation) { setProxyWorkstation(proxyWorkstation); return this; } /** * Returns the maximum number of retry attempts for failed retryable requests (ex: 5xx error * responses from a service). * * @return The maximum number of retry attempts for failed retryable requests. */ public int getMaxErrorRetry() { return maxErrorRetry; } /** * Sets the maximum number of retry attempts for failed retryable requests (ex: 5xx error * responses from services). * * @param maxErrorRetry The maximum number of retry attempts for failed retryable requests. */ public void setMaxErrorRetry(int maxErrorRetry) { this.maxErrorRetry = maxErrorRetry; } /** * Sets the maximum number of retry attempts for failed retryable requests (ex: 5xx error * responses from services), and returns the updated ClientConfiguration object. * * @param maxErrorRetry The maximum number of retry attempts for failed retryable requests. * @return The updated ClientConfiguration object. */ public ClientConfiguration withMaxErrorRetry(int maxErrorRetry) { setMaxErrorRetry(maxErrorRetry); return this; } /** * Returns the amount of time to wait (in milliseconds) for data to be transfered over an * established, open connection before the connection times out and is closed. A value of 0 means * infinity, and isn't recommended. * * @return The amount of time to wait (in milliseconds) for data to be transfered over an * established, open connection before the connection times out and is closed. */ public int getSocketTimeout() { return socketTimeout; } /** * Sets the amount of time to wait (in milliseconds) for data to be transfered over an * established, open connection before the connection times out and is closed. A value of 0 means * infinity, and isn't recommended. * * @param socketTimeout The amount of time to wait (in milliseconds) for data to be transfered * over an established, open connection before the connection is times out and is closed. */ public void setSocketTimeout(int socketTimeout) { this.socketTimeout = socketTimeout; } /** * Sets the amount of time to wait (in milliseconds) for data to be transfered over an * established, open connection before the connection times out and is closed, and returns the * updated ClientConfiguration object so that additional method calls may be chained together. * * @param socketTimeout The amount of time to wait (in milliseconds) for data to be transfered * over an established, open connection before the connection is times out and is closed. * @return The updated ClientConfiguration object. */ public ClientConfiguration withSocketTimeout(int socketTimeout) { setSocketTimeout(socketTimeout); return this; } /** * Returns the amount of time to wait (in milliseconds) when initially establishing a connection * before giving up and timing out. A value of 0 means infinity, and is not recommended. * * @return The amount of time to wait (in milliseconds) when initially establishing a connection * before giving up and timing out. */ public int getConnectionTimeout() { return connectionTimeout; } /** * Sets the amount of time to wait (in milliseconds) when initially establishing a connection * before giving up and timing out. A value of 0 means infinity, and is not recommended. * * @param connectionTimeout The amount of time to wait (in milliseconds) when initially * establishing a connection before giving up and timing out. */ public void setConnectionTimeout(int connectionTimeout) { this.connectionTimeout = connectionTimeout; } /** * Sets the amount of time to wait (in milliseconds) when initially establishing a connection * before giving up and timing out, and returns the updated ClientConfiguration object so that * additional method calls may be chained together. * * @param connectionTimeout the amount of time to wait (in milliseconds) when initially * establishing a connection before giving up and timing out. * @return The updated ClientConfiguration object. */ public ClientConfiguration withConnectionTimeout(int connectionTimeout) { setConnectionTimeout(connectionTimeout); return this; } /** * Checks if the {@link IdleConnectionReaper} is to be started * * @return if the {@link IdleConnectionReaper} is to be started */ public boolean useReaper() { return useReaper; } /** * Sets whether the {@link IdleConnectionReaper} is to be started as a daemon thread * * @param use whether the {@link IdleConnectionReaper} is to be started as a daemon thread * @see IdleConnectionReaper */ public void setUseReaper(boolean use) { this.useReaper = use; } /** * Sets whether the {@link IdleConnectionReaper} is to be started as a daemon thread * * @param use the {@link IdleConnectionReaper} is to be started as a daemon thread * @return The updated ClientConfiguration object. */ public ClientConfiguration withReaper(boolean use) { setUseReaper(use); return this; } /** * Returns the optional size hints (in bytes) for the low level TCP send and receive buffers. This * is an advanced option for advanced users who want to tune low level TCP parameters to try and * squeeze out more performance. * * <p>The optimal TCP buffer sizes for a particular application are highly dependent on network * configuration and operating system configuration and capabilities. For example, most modern * operating systems provide auto-tuning functionality for TCP buffer sizes, which can have a big * impact on performance for TCP connections that are held open long enough for the auto-tuning to * optimize buffer sizes. * * <p>Large buffer sizes (ex: 2MB) will allow the operating system to buffer more data in memory * without requiring the remote server to acknowledge receipt of that information, so can be * particularly useful when the network has high latency. * * <p>This is only a <b>hint</b>, and the operating system may choose not to honor it. When using * this option, users should <b>always</b> check the operating system's configured limits and * defaults. Most OS's have a maximum TCP buffer size limit configured, and won't let you go * beyond that limit unless you explicitly raise the max TCP buffer size limit. * * <p>There are many resources available online to help with configuring TCP buffer sizes and * operating system specific TCP settings, including: * * <ul> * <li>http://onlamp.com/pub/a/onlamp/2005/11/17/tcp_tuning.html * <li>http://fasterdata.es.net/TCP-tuning/ * </ul> * * @return A two element array containing first the TCP send buffer size hint and then the TCP * receive buffer size hint. */ public int[] getSocketBufferSizeHints() { return new int[] {socketSendBufferSizeHint, socketReceiveBufferSizeHint}; } /** * Sets the optional size hints (in bytes) for the low level TCP send and receive buffers. This is * an advanced option for advanced users who want to tune low level TCP parameters to try and * squeeze out more performance. * * <p>The optimal TCP buffer sizes for a particular application are highly dependent on network * configuration and operating system configuration and capabilities. For example, most modern * operating systems provide auto-tuning functionality for TCP buffer sizes, which can have a big * impact on performance for TCP connections that are held open long enough for the auto-tuning to * optimize buffer sizes. * * <p>Large buffer sizes (ex: 2MB) will allow the operating system to buffer more data in memory * without requiring the remote server to acknowledge receipt of that information, so can be * particularly useful when the network has high latency. * * <p>This is only a <b>hint</b>, and the operating system may choose not to honor it. When using * this option, users should <b>always</b> check the operating system's configured limits and * defaults. Most OS's have a maximum TCP buffer size limit configured, and won't let you go * beyond that limit unless you explicitly raise the max TCP buffer size limit. * * <p>There are many resources available online to help with configuring TCP buffer sizes and * operating system specific TCP settings, including: * * <ul> * <li>http://onlamp.com/pub/a/onlamp/2005/11/17/tcp_tuning.html * <li>http://fasterdata.es.net/TCP-tuning/ * </ul> * * @param socketSendBufferSizeHint The size hint (in bytes) for the low level TCP send buffer. * @param socketReceiveBufferSizeHint The size hint (in bytes) for the low level TCP receive * buffer. */ public void setSocketBufferSizeHints( int socketSendBufferSizeHint, int socketReceiveBufferSizeHint) { this.socketSendBufferSizeHint = socketSendBufferSizeHint; this.socketReceiveBufferSizeHint = socketReceiveBufferSizeHint; } /** * Sets the optional size hints (in bytes) for the low level TCP send and receive buffers, and * returns the updated ClientConfiguration object so that additional method calls may be chained * together. * * <p>This is an advanced option for advanced users who want to tune low level TCP parameters to * try and squeeze out more performance. * * <p>The optimal TCP buffer sizes for a particular application are highly dependent on network * configuration and operating system configuration and capabilities. For example, most modern * operating systems provide auto-tuning functionality for TCP buffer sizes, which can have a big * impact on performance for TCP connections that are held open long enough for the auto-tuning to * optimize buffer sizes. * * <p>Large buffer sizes (ex: 2MB) will allow the operating system to buffer more data in memory * without requiring the remote server to acknowledge receipt of that information, so can be * particularly useful when the network has high latency. * * <p>This is only a <b>hint</b>, and the operating system may choose not to honor it. When using * this option, users should <b>always</b> check the operating system's configured limits and * defaults. Most OS's have a maximum TCP buffer size limit configured, and won't let you go * beyond that limit unless you explicitly raise the max TCP buffer size limit. * * <p>There are many resources available online to help with configuring TCP buffer sizes and * operating system specific TCP settings, including: * * <ul> * <li>http://onlamp.com/pub/a/onlamp/2005/11/17/tcp_tuning.html * <li>http://fasterdata.es.net/TCP-tuning/ * </ul> * * @param socketSendBufferSizeHint The size hint (in bytes) for the low level TCP send buffer. * @param socketReceiveBufferSizeHint The size hint (in bytes) for the low level TCP receive * buffer. * @return The updated ClientConfiguration object. */ public ClientConfiguration withSocketBufferSizeHints( int socketSendBufferSizeHint, int socketReceiveBufferSizeHint) { setSocketBufferSizeHints(socketSendBufferSizeHint, socketReceiveBufferSizeHint); return this; } }
/** * The AmazonS3Encryption class extends the Amazon S3 Client, allowing you to store data securely in * S3. * * <p>The encryption materials specified in the constructor will be used to encrypt and decrypt * data. */ public class AmazonS3EncryptionClient extends AmazonS3Client { private EncryptionMaterialsProvider encryptionMaterialsProvider; private CryptoConfiguration cryptoConfig; private static final String USER_AGENT = AmazonS3EncryptionClient.class.getName() + "/" + VersionInfoUtils.getVersion(); /** Shared logger for encryption client events */ private static Log log = LogFactory.getLog(AmazonS3EncryptionClient.class); /** Map of data about in progress encrypted multipart uploads. */ private Map<String, EncryptedUploadContext> currentMultipartUploadSecretKeys = Collections.synchronizedMap(new HashMap<String, EncryptedUploadContext>()); /** * Constructs a new Amazon S3 Encryption client that will make <b>anonymous</b> requests to Amazon * S3. If {@link #getObject(String, String)} is called, the object contents will be decrypted with * the encryption materials provided. * * <p>Only a subset of the Amazon S3 API will work with anonymous <i>(i.e. unsigned)</i> requests, * but this can prove useful in some situations. For example: * * <ul> * <li>If an Amazon S3 bucket has {@link Permission#Read} permission for the {@link * GroupGrantee#AllUsers} group, anonymous clients can call {@link #listObjects(String)} to * see what objects are stored in a bucket. * <li>If an object has {@link Permission#Read} permission for the {@link GroupGrantee#AllUsers} * group, anonymous clients can call {@link #getObject(String, String)} and {@link * #getObjectMetadata(String, String)} to pull object content and metadata. * <li>If a bucket has {@link Permission#Write} permission for the {@link GroupGrantee#AllUsers} * group, anonymous clients can upload objects to the bucket. * </ul> * * @param encryptionMaterials The encryption materials to be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient(EncryptionMaterials encryptionMaterials) { this(new StaticEncryptionMaterialsProvider(encryptionMaterials)); } /** * Constructs a new Amazon S3 Encryption client that will make <b>anonymous</b> requests to Amazon * S3. If {@link #getObject(String, String)} is called, the object contents will be decrypted with * the encryption materials provided. * * <p>Only a subset of the Amazon S3 API will work with anonymous <i>(i.e. unsigned)</i> requests, * but this can prove useful in some situations. For example: * * <ul> * <li>If an Amazon S3 bucket has {@link Permission#Read} permission for the {@link * GroupGrantee#AllUsers} group, anonymous clients can call {@link #listObjects(String)} to * see what objects are stored in a bucket. * <li>If an object has {@link Permission#Read} permission for the {@link GroupGrantee#AllUsers} * group, anonymous clients can call {@link #getObject(String, String)} and {@link * #getObjectMetadata(String, String)} to pull object content and metadata. * <li>If a bucket has {@link Permission#Write} permission for the {@link GroupGrantee#AllUsers} * group, anonymous clients can upload objects to the bucket. * </ul> * * @param encryptionMaterialsProvider A provider for the encryption materials to be used to * encrypt and decrypt data. */ public AmazonS3EncryptionClient(EncryptionMaterialsProvider encryptionMaterialsProvider) { this( (AWSCredentialsProvider) null, encryptionMaterialsProvider, new ClientConfiguration(), new CryptoConfiguration()); } /** * Constructs a new Amazon S3 Encryption client that will make <b>anonymous</b> requests to Amazon * S3. If {@link #getObject(String, String)} is called, the object contents will be decrypted with * the encryption materials provided. The encryption implementation of the provided crypto * provider will be used to encrypt and decrypt data. * * <p>Only a subset of the Amazon S3 API will work with anonymous <i>(i.e. unsigned)</i> requests, * but this can prove useful in some situations. For example: * * <ul> * <li>If an Amazon S3 bucket has {@link Permission#Read} permission for the {@link * GroupGrantee#AllUsers} group, anonymous clients can call {@link #listObjects(String)} to * see what objects are stored in a bucket. * <li>If an object has {@link Permission#Read} permission for the {@link GroupGrantee#AllUsers} * group, anonymous clients can call {@link #getObject(String, String)} and {@link * #getObjectMetadata(String, String)} to pull object content and metadata. * <li>If a bucket has {@link Permission#Write} permission for the {@link GroupGrantee#AllUsers} * group, anonymous clients can upload objects to the bucket. * </ul> * * @param encryptionMaterials The encryption materials to be used to encrypt and decrypt data. * @param cryptoConfig The crypto configuration whose parameters will be used to encrypt and * decrypt data. */ public AmazonS3EncryptionClient( EncryptionMaterials encryptionMaterials, CryptoConfiguration cryptoConfig) { this(new StaticEncryptionMaterialsProvider(encryptionMaterials), cryptoConfig); } /** * Constructs a new Amazon S3 Encryption client that will make <b>anonymous</b> requests to Amazon * S3. If {@link #getObject(String, String)} is called, the object contents will be decrypted with * the encryption materials provided. The encryption implementation of the provided crypto * provider will be used to encrypt and decrypt data. * * <p>Only a subset of the Amazon S3 API will work with anonymous <i>(i.e. unsigned)</i> requests, * but this can prove useful in some situations. For example: * * <ul> * <li>If an Amazon S3 bucket has {@link Permission#Read} permission for the {@link * GroupGrantee#AllUsers} group, anonymous clients can call {@link #listObjects(String)} to * see what objects are stored in a bucket. * <li>If an object has {@link Permission#Read} permission for the {@link GroupGrantee#AllUsers} * group, anonymous clients can call {@link #getObject(String, String)} and {@link * #getObjectMetadata(String, String)} to pull object content and metadata. * <li>If a bucket has {@link Permission#Write} permission for the {@link GroupGrantee#AllUsers} * group, anonymous clients can upload objects to the bucket. * </ul> * * @param encryptionMaterialsProvider A provider for the encryption materials to be used to * encrypt and decrypt data. * @param cryptoConfig The crypto configuration whose parameters will be used to encrypt and * decrypt data. */ public AmazonS3EncryptionClient( EncryptionMaterialsProvider encryptionMaterialsProvider, CryptoConfiguration cryptoConfig) { this( (AWSCredentialsProvider) null, encryptionMaterialsProvider, new ClientConfiguration(), cryptoConfig); } /** * Constructs a new Amazon S3 Encryption client using the specified AWS credentials to access * Amazon S3. Object contents will be encrypted and decrypted with the encryption materials * provided. * * @param credentials The AWS credentials to use when making requests to Amazon S3 with this * client. * @param encryptionMaterials The encryption materials to be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient( AWSCredentials credentials, EncryptionMaterials encryptionMaterials) { this(credentials, new StaticEncryptionMaterialsProvider(encryptionMaterials)); } /** * Constructs a new Amazon S3 Encryption client using the specified AWS credentials to access * Amazon S3. Object contents will be encrypted and decrypted with the encryption materials * provided. * * @param credentials The AWS credentials to use when making requests to Amazon S3 with this * client. * @param encryptionMaterialsProvider A provider for the encryption materials to be used to * encrypt and decrypt data. */ public AmazonS3EncryptionClient( AWSCredentials credentials, EncryptionMaterialsProvider encryptionMaterialsProvider) { this( credentials, encryptionMaterialsProvider, new ClientConfiguration(), new CryptoConfiguration()); } /** * Constructs a new Amazon S3 Encryption client using the specified AWS credentials to access * Amazon S3. Object contents will be encrypted and decrypted with the encryption materials * provided. * * @param credentialsProvider The AWS credentials provider which will provide credentials to * authenticate requests with AWS services. * @param encryptionMaterialsProvider A provider for the encryption materials to be used to * encrypt and decrypt data. */ public AmazonS3EncryptionClient( AWSCredentialsProvider credentialsProvider, EncryptionMaterialsProvider encryptionMaterialsProvider) { this( credentialsProvider, encryptionMaterialsProvider, new ClientConfiguration(), new CryptoConfiguration()); } /** * Constructs a new Amazon S3 Encryption client using the specified AWS credentials to access * Amazon S3. Object contents will be encrypted and decrypted with the encryption materials * provided. The encryption implementation of the provided crypto provider will be used to encrypt * and decrypt data. * * @param credentials The AWS credentials to use when making requests to Amazon S3 with this * client. * @param encryptionMaterials The encryption materials to be used to encrypt and decrypt data. * @param cryptoConfig The crypto configuration whose parameters will be used to encrypt and * decrypt data. */ public AmazonS3EncryptionClient( AWSCredentials credentials, EncryptionMaterials encryptionMaterials, CryptoConfiguration cryptoConfig) { this(credentials, new StaticEncryptionMaterialsProvider(encryptionMaterials), cryptoConfig); } /** * Constructs a new Amazon S3 Encryption client using the specified AWS credentials to access * Amazon S3. Object contents will be encrypted and decrypted with the encryption materials * provided. The encryption implementation of the provided crypto provider will be used to encrypt * and decrypt data. * * @param credentials The AWS credentials to use when making requests to Amazon S3 with this * client. * @param encryptionMaterialsProvider A provider for the encryption materials to be used to * encrypt and decrypt data. * @param cryptoConfig The crypto configuration whose parameters will be used to encrypt and * decrypt data. */ public AmazonS3EncryptionClient( AWSCredentials credentials, EncryptionMaterialsProvider encryptionMaterialsProvider, CryptoConfiguration cryptoConfig) { this(credentials, encryptionMaterialsProvider, new ClientConfiguration(), cryptoConfig); } /** * Constructs a new Amazon S3 Encryption client using the specified AWS credentials to access * Amazon S3. Object contents will be encrypted and decrypted with the encryption materials * provided. The encryption implementation of the provided crypto provider will be used to encrypt * and decrypt data. * * @param credentialsProvider The AWS credentials provider which will provide credentials to * authenticate requests with AWS services. * @param encryptionMaterialsProvider A provider for the encryption materials to be used to * encrypt and decrypt data. * @param cryptoConfig The crypto configuration whose parameters will be used to encrypt and * decrypt data. */ public AmazonS3EncryptionClient( AWSCredentialsProvider credentialsProvider, EncryptionMaterialsProvider encryptionMaterialsProvider, CryptoConfiguration cryptoConfig) { this(credentialsProvider, encryptionMaterialsProvider, new ClientConfiguration(), cryptoConfig); } /** * Constructs a new Amazon S3 Encryption client using the specified AWS credentials and client * configuration to access Amazon S3. Object contents will be encrypted and decrypted with the * encryption materials provided. The crypto provider and storage mode denoted in the specified * crypto configuration will be used to encrypt and decrypt data. * * @param credentials The AWS credentials to use when making requests to Amazon S3 with this * client. * @param encryptionMaterials The encryption materials to be used to encrypt and decrypt data. * @param clientConfiguration The client configuration options controlling how this client * connects to Amazon S3 (ex: proxy settings, retry counts, etc). * @param cryptoConfig The crypto configuration whose parameters will be used to encrypt and * decrypt data. * @throws IllegalArgumentException If either of the encryption materials or crypto configuration * parameters are null. */ public AmazonS3EncryptionClient( AWSCredentials credentials, EncryptionMaterials encryptionMaterials, ClientConfiguration clientConfig, CryptoConfiguration cryptoConfig) { this( credentials, new StaticEncryptionMaterialsProvider(encryptionMaterials), clientConfig, cryptoConfig); } public AmazonS3EncryptionClient( AWSCredentials credentials, EncryptionMaterialsProvider encryptionMaterialsProvider, ClientConfiguration clientConfig, CryptoConfiguration cryptoConfig) { super(credentials, clientConfig); assertParameterNotNull( encryptionMaterialsProvider, "EncryptionMaterialsProvider parameter must not be null."); assertParameterNotNull(cryptoConfig, "CryptoConfiguration parameter must not be null."); this.encryptionMaterialsProvider = encryptionMaterialsProvider; this.cryptoConfig = cryptoConfig; } public AmazonS3EncryptionClient( AWSCredentialsProvider credentialsProvider, EncryptionMaterialsProvider encryptionMaterialsProvider, ClientConfiguration clientConfig, CryptoConfiguration cryptoConfig) { super(credentialsProvider, clientConfig); assertParameterNotNull( encryptionMaterialsProvider, "EncryptionMaterialsProvider parameter must not be null."); assertParameterNotNull(cryptoConfig, "CryptoConfiguration parameter must not be null."); this.encryptionMaterialsProvider = encryptionMaterialsProvider; this.cryptoConfig = cryptoConfig; } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3#putObject(com.amazonaws.services.s3.model.PutObjectRequest) */ @Override public PutObjectResult putObject(PutObjectRequest putObjectRequest) throws AmazonClientException, AmazonServiceException { appendUserAgent(putObjectRequest, USER_AGENT); if (this.cryptoConfig.getStorageMode() == CryptoStorageMode.InstructionFile) { return putObjectUsingInstructionFile(putObjectRequest); } else { return putObjectUsingMetadata(putObjectRequest); } } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3#getObject(com.amazonaws.services.s3.model.GetObjectRequest) */ @Override public S3Object getObject(GetObjectRequest getObjectRequest) throws AmazonClientException, AmazonServiceException { appendUserAgent(getObjectRequest, USER_AGENT); // Adjust the crypto range to retrieve all of the cipher blocks needed to contain the user's // desired // range of bytes. long[] desiredRange = getObjectRequest.getRange(); long[] adjustedCryptoRange = EncryptionUtils.getAdjustedCryptoRange(desiredRange); if (adjustedCryptoRange != null) { getObjectRequest.setRange(adjustedCryptoRange[0], adjustedCryptoRange[1]); } // Get the object from S3 S3Object retrievedObject = super.getObject(getObjectRequest); // If the caller has specified constraints, it's possible that super.getObject(...) // would return null, so we simply return null as well. if (retrievedObject == null) return null; S3Object objectToBeReturned; try { // Check if encryption info is in object metadata if (EncryptionUtils.isEncryptionInfoInMetadata(retrievedObject)) { objectToBeReturned = decryptObjectUsingMetadata(retrievedObject); } else { // Check if encrypted info is in an instruction file S3Object instructionFile = null; try { instructionFile = getInstructionFile(getObjectRequest); if (EncryptionUtils.isEncryptionInfoInInstructionFile(instructionFile)) { objectToBeReturned = decryptObjectUsingInstructionFile(retrievedObject, instructionFile); } else { // The object was not encrypted to begin with. Return the object without decrypting it. log.warn( String.format( "Unable to detect encryption information for object '%s' in bucket '%s'. " + "Returning object without decryption.", retrievedObject.getKey(), retrievedObject.getBucketName())); objectToBeReturned = retrievedObject; } } finally { if (instructionFile != null) { try { instructionFile.getObjectContent().close(); } catch (Exception e) { } } } } } catch (AmazonClientException ace) { // If we're unable to set up the decryption, make sure we close the HTTP connection try { retrievedObject.getObjectContent().close(); } catch (Exception e) { } throw ace; } // Adjust the output to the desired range of bytes. return EncryptionUtils.adjustOutputToDesiredRange(objectToBeReturned, desiredRange); } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3#getObject(com.amazonaws.services.s3.model.GetObjectRequest, java.io.File) */ @Override public ObjectMetadata getObject(GetObjectRequest getObjectRequest, File destinationFile) throws AmazonClientException, AmazonServiceException { assertParameterNotNull( destinationFile, "The destination file parameter must be specified when downloading an object directly to a file"); S3Object s3Object = getObject(getObjectRequest); // getObject can return null if constraints were specified but not met if (s3Object == null) return null; OutputStream outputStream = null; try { outputStream = new BufferedOutputStream(new FileOutputStream(destinationFile)); byte[] buffer = new byte[1024 * 10]; int bytesRead; while ((bytesRead = s3Object.getObjectContent().read(buffer)) > -1) { outputStream.write(buffer, 0, bytesRead); } } catch (IOException e) { throw new AmazonClientException( "Unable to store object contents to disk: " + e.getMessage(), e); } finally { try { outputStream.close(); } catch (Exception e) { } try { s3Object.getObjectContent().close(); } catch (Exception e) { } } /* * Unlike the standard Amazon S3 Client, the Amazon S3 Encryption Client does not do an MD5 check * here because the contents stored in S3 and the contents we just retrieved are different. In * S3, the stored contents are encrypted, and locally, the retrieved contents are decrypted. */ return s3Object.getObjectMetadata(); } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3Client#deleteObject(com.amazonaws.services.s3.model.DeleteObjectRequest) */ @Override public void deleteObject(DeleteObjectRequest deleteObjectRequest) { appendUserAgent(deleteObjectRequest, USER_AGENT); // Delete the object super.deleteObject(deleteObjectRequest); // If it exists, delete the instruction file. DeleteObjectRequest instructionDeleteRequest = EncryptionUtils.createInstructionDeleteObjectRequest(deleteObjectRequest); super.deleteObject(instructionDeleteRequest); } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3Client#completeMultipartUpload(com.amazonaws.services.s3.model.CompleteMultipartUploadRequest) */ @Override public CompleteMultipartUploadResult completeMultipartUpload( CompleteMultipartUploadRequest completeMultipartUploadRequest) throws AmazonClientException, AmazonServiceException { appendUserAgent(completeMultipartUploadRequest, USER_AGENT); String uploadId = completeMultipartUploadRequest.getUploadId(); EncryptedUploadContext encryptedUploadContext = currentMultipartUploadSecretKeys.get(uploadId); if (encryptedUploadContext.hasFinalPartBeenSeen() == false) { throw new AmazonClientException( "Unable to complete an encrypted multipart upload without being told which part was the last. " + "Without knowing which part was the last, the encrypted data in Amazon S3 is incomplete and corrupt."); } CompleteMultipartUploadResult result = super.completeMultipartUpload(completeMultipartUploadRequest); // In InstructionFile mode, we want to write the instruction file only after the whole upload // has completed correctly. if (cryptoConfig.getStorageMode() == CryptoStorageMode.InstructionFile) { Cipher symmetricCipher = EncryptionUtils.createSymmetricCipher( encryptedUploadContext.getEnvelopeEncryptionKey(), Cipher.ENCRYPT_MODE, cryptoConfig.getCryptoProvider(), encryptedUploadContext.getFirstInitializationVector()); EncryptionMaterials encryptionMaterials = encryptionMaterialsProvider.getEncryptionMaterials(); // Encrypt the envelope symmetric key byte[] encryptedEnvelopeSymmetricKey = EncryptionUtils.getEncryptedSymmetricKey( encryptedUploadContext.getEnvelopeEncryptionKey(), encryptionMaterials, cryptoConfig.getCryptoProvider()); EncryptionInstruction instruction = new EncryptionInstruction( encryptionMaterials.getMaterialsDescription(), encryptedEnvelopeSymmetricKey, encryptedUploadContext.getEnvelopeEncryptionKey(), symmetricCipher); // Put the instruction file into S3 super.putObject( EncryptionUtils.createInstructionPutRequest( encryptedUploadContext.getBucketName(), encryptedUploadContext.getKey(), instruction)); } currentMultipartUploadSecretKeys.remove(uploadId); return result; } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3Client#initiateMultipartUpload(com.amazonaws.services.s3.model.InitiateMultipartUploadRequest) */ @Override public InitiateMultipartUploadResult initiateMultipartUpload( InitiateMultipartUploadRequest initiateMultipartUploadRequest) throws AmazonClientException, AmazonServiceException { appendUserAgent(initiateMultipartUploadRequest, USER_AGENT); // Generate a one-time use symmetric key and initialize a cipher to encrypt object data SecretKey envelopeSymmetricKey = EncryptionUtils.generateOneTimeUseSymmetricKey(); Cipher symmetricCipher = EncryptionUtils.createSymmetricCipher( envelopeSymmetricKey, Cipher.ENCRYPT_MODE, cryptoConfig.getCryptoProvider(), null); if (cryptoConfig.getStorageMode() == CryptoStorageMode.ObjectMetadata) { EncryptionMaterials encryptionMaterials = encryptionMaterialsProvider.getEncryptionMaterials(); // Encrypt the envelope symmetric key byte[] encryptedEnvelopeSymmetricKey = EncryptionUtils.getEncryptedSymmetricKey( envelopeSymmetricKey, encryptionMaterials, cryptoConfig.getCryptoProvider()); // Store encryption info in metadata ObjectMetadata metadata = EncryptionUtils.updateMetadataWithEncryptionInfo( initiateMultipartUploadRequest, encryptedEnvelopeSymmetricKey, symmetricCipher, encryptionMaterials.getMaterialsDescription()); // Update the request's metadata to the updated metadata initiateMultipartUploadRequest.setObjectMetadata(metadata); } InitiateMultipartUploadResult result = super.initiateMultipartUpload(initiateMultipartUploadRequest); EncryptedUploadContext encryptedUploadContext = new EncryptedUploadContext( initiateMultipartUploadRequest.getBucketName(), initiateMultipartUploadRequest.getKey(), envelopeSymmetricKey); encryptedUploadContext.setNextInitializationVector(symmetricCipher.getIV()); encryptedUploadContext.setFirstInitializationVector(symmetricCipher.getIV()); currentMultipartUploadSecretKeys.put(result.getUploadId(), encryptedUploadContext); return result; } /** * {@inheritDoc} * * <p><b>NOTE:</b> Because the encryption process requires context from block N-1 in order to * encrypt block N, parts uploaded with the AmazonS3EncryptionClient (as opposed to the normal * AmazonS3Client) must be uploaded serially, and in order. Otherwise, the previous encryption * context isn't available to use when encrypting the current part. */ @Override public UploadPartResult uploadPart(UploadPartRequest uploadPartRequest) throws AmazonClientException, AmazonServiceException { appendUserAgent(uploadPartRequest, USER_AGENT); boolean isLastPart = uploadPartRequest.isLastPart(); String uploadId = uploadPartRequest.getUploadId(); boolean partSizeMultipleOfCipherBlockSize = uploadPartRequest.getPartSize() % JceEncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE == 0; if (!isLastPart && !partSizeMultipleOfCipherBlockSize) { throw new AmazonClientException( "Invalid part size: part sizes for encrypted multipart uploads must be multiples " + "of the cipher block size (" + JceEncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE + ") with the exception of the last part. " + "Otherwise encryption adds extra padding that will corrupt the final object."); } // Generate the envelope symmetric key and initialize a cipher to encrypt the object's data EncryptedUploadContext encryptedUploadContext = currentMultipartUploadSecretKeys.get(uploadId); if (encryptedUploadContext == null) throw new AmazonClientException( "No client-side information available on upload ID " + uploadId); SecretKey envelopeSymmetricKey = encryptedUploadContext.getEnvelopeEncryptionKey(); byte[] iv = encryptedUploadContext.getNextInitializationVector(); CipherFactory cipherFactory = new CipherFactory( envelopeSymmetricKey, Cipher.ENCRYPT_MODE, iv, this.cryptoConfig.getCryptoProvider()); // Create encrypted input stream InputStream encryptedInputStream = EncryptionUtils.getEncryptedInputStream(uploadPartRequest, cipherFactory); uploadPartRequest.setInputStream(encryptedInputStream); // The last part of the multipart upload will contain extra padding from the encryption process, // which // changes the if (uploadPartRequest.isLastPart()) { // We only change the size of the last part long cryptoContentLength = EncryptionUtils.calculateCryptoContentLength( cipherFactory.createCipher(), uploadPartRequest); if (cryptoContentLength > 0) uploadPartRequest.setPartSize(cryptoContentLength); if (encryptedUploadContext.hasFinalPartBeenSeen()) { throw new AmazonClientException( "This part was specified as the last part in a multipart upload, but a previous part was already marked as the last part. " + "Only the last part of the upload should be marked as the last part, otherwise it will cause the encrypted data to be corrupted."); } encryptedUploadContext.setHasFinalPartBeenSeen(true); } // Treat all encryption requests as input stream upload requests, not as file upload requests. uploadPartRequest.setFile(null); uploadPartRequest.setFileOffset(0); UploadPartResult result = super.uploadPart(uploadPartRequest); if (encryptedInputStream instanceof ByteRangeCapturingInputStream) { ByteRangeCapturingInputStream bris = (ByteRangeCapturingInputStream) encryptedInputStream; encryptedUploadContext.setNextInitializationVector(bris.getBlock()); } else { throw new AmazonClientException("Unable to access last block of encrypted data"); } return result; } @Override public CopyPartResult copyPart(CopyPartRequest copyPartRequest) { String uploadId = copyPartRequest.getUploadId(); EncryptedUploadContext encryptedUploadContext = currentMultipartUploadSecretKeys.get(uploadId); if (!encryptedUploadContext.hasFinalPartBeenSeen()) { encryptedUploadContext.setHasFinalPartBeenSeen(true); } return super.copyPart(copyPartRequest); } /* * Private helper methods */ /** * Puts an encrypted object into S3 and stores encryption info in the object metadata. * * @param putObjectRequest The request object containing all the parameters to upload a new object * to Amazon S3. * @return A {@link PutObjectResult} object containing the information returned by Amazon S3 for * the new, created object. * @throws AmazonClientException If any errors are encountered on the client while making the * request or handling the response. * @throws AmazonServiceException If any errors occurred in Amazon S3 while processing the * request. */ private PutObjectResult putObjectUsingMetadata(PutObjectRequest putObjectRequest) throws AmazonClientException, AmazonServiceException { // Create instruction EncryptionInstruction instruction = EncryptionUtils.generateInstruction( this.encryptionMaterialsProvider, this.cryptoConfig.getCryptoProvider()); // Encrypt the object data with the instruction PutObjectRequest encryptedObjectRequest = EncryptionUtils.encryptRequestUsingInstruction(putObjectRequest, instruction); // Update the metadata EncryptionUtils.updateMetadataWithEncryptionInstruction(putObjectRequest, instruction); // Put the encrypted object into S3 return super.putObject(encryptedObjectRequest); } /** * Puts an encrypted object into S3, and puts an instruction file into S3. Encryption info is * stored in the instruction file. * * @param putObjectRequest The request object containing all the parameters to upload a new object * to Amazon S3. * @return A {@link PutObjectResult} object containing the information returned by Amazon S3 for * the new, created object. * @throws AmazonClientException If any errors are encountered on the client while making the * request or handling the response. * @throws AmazonServiceException If any errors occurred in Amazon S3 while processing the * request. */ private PutObjectResult putObjectUsingInstructionFile(PutObjectRequest putObjectRequest) throws AmazonClientException, AmazonServiceException { // Create instruction EncryptionInstruction instruction = EncryptionUtils.generateInstruction( this.encryptionMaterialsProvider, this.cryptoConfig.getCryptoProvider()); // Encrypt the object data with the instruction PutObjectRequest encryptedObjectRequest = EncryptionUtils.encryptRequestUsingInstruction(putObjectRequest, instruction); // Put the encrypted object into S3 PutObjectResult encryptedObjectResult = super.putObject(encryptedObjectRequest); // Put the instruction file into S3 PutObjectRequest instructionRequest = EncryptionUtils.createInstructionPutRequest(putObjectRequest, instruction); super.putObject(instructionRequest); // Return the result of the encrypted object PUT. return encryptedObjectResult; } /** * Decrypts an object using information retrieved from metadata. If decryption is not possible, * returns null. * * @param object The S3Object to be decrypted. * @return An S3Object with decrypted object contents. If decryption is not possible, returns * null. */ private S3Object decryptObjectUsingMetadata(S3Object object) { // Create an instruction object from the object headers EncryptionInstruction instruction = EncryptionUtils.buildInstructionFromObjectMetadata( object, this.encryptionMaterialsProvider, this.cryptoConfig.getCryptoProvider()); // Decrypt the object file with the instruction return EncryptionUtils.decryptObjectUsingInstruction(object, instruction); } /** * Decrypts an object using information retrieved from an instruction file. * * @param object The S3Object to be decrypted. * @param instructionFile The S3Object instruction file to be used to decrypt the object. * @return An S3Object with decrypted object contents. */ private S3Object decryptObjectUsingInstructionFile(S3Object object, S3Object instructionFile) { // Create an instruction object from the retrieved instruction file EncryptionInstruction instruction = EncryptionUtils.buildInstructionFromInstructionFile( instructionFile, this.encryptionMaterialsProvider, this.cryptoConfig.getCryptoProvider()); // Decrypt the object file with the instruction return EncryptionUtils.decryptObjectUsingInstruction(object, instruction); } /** * Retrieves an instruction file from S3. If no instruction file is found, returns null. * * @param getObjectRequest A GET request for an object in S3. The parameters from this request * will be used to retrieve the corresponding instruction file. * @return An instruction file, or null if no instruction file was found. */ private S3Object getInstructionFile(GetObjectRequest getObjectRequest) { try { GetObjectRequest instructionFileRequest = EncryptionUtils.createInstructionGetRequest(getObjectRequest); return super.getObject(instructionFileRequest); } catch (AmazonServiceException e) { // If no instruction file is found, log a debug message, and return null. log.debug("Unable to retrieve instruction file : " + e.getMessage()); return null; } } /** * Asserts that the specified parameter value is not null and if it is, throws an * IllegalArgumentException with the specified error message. * * @param parameterValue The parameter value being checked. * @param errorMessage The error message to include in the IllegalArgumentException if the * specified parameter is null. */ private void assertParameterNotNull(Object parameterValue, String errorMessage) { if (parameterValue == null) throw new IllegalArgumentException(errorMessage); } public <X extends AmazonWebServiceRequest> X appendUserAgent(X request, String userAgent) { request.getRequestClientOptions().appendUserAgent(userAgent); return request; } }