|
9 | 9 |
|
10 | 10 | import com.joyent.manta.client.MantaObjectInputStream; |
11 | 11 | import com.joyent.manta.client.MantaObjectResponse; |
| 12 | +import com.joyent.manta.config.DefaultsConfigContext; |
12 | 13 | import com.joyent.manta.exception.MantaClientEncryptionException; |
13 | 14 | import com.joyent.manta.http.MantaHttpHeaders; |
14 | 15 | import com.joyent.manta.http.entity.ExposedStringEntity; |
|
17 | 18 | import org.apache.commons.io.FileUtils; |
18 | 19 | import org.apache.commons.io.IOUtils; |
19 | 20 | import org.apache.commons.io.input.BoundedInputStream; |
| 21 | +import org.apache.commons.io.input.BrokenInputStream; |
20 | 22 | import org.apache.commons.lang3.RandomStringUtils; |
21 | 23 | import org.apache.commons.lang3.RandomUtils; |
22 | 24 | import org.apache.http.HttpEntity; |
23 | 25 | import org.apache.http.client.methods.CloseableHttpResponse; |
24 | 26 | import org.apache.http.conn.EofSensorInputStream; |
| 27 | +import org.apache.http.entity.InputStreamEntity; |
25 | 28 | import org.bouncycastle.jcajce.io.CipherInputStream; |
26 | 29 | import org.mockito.Mockito; |
27 | 30 | import org.testng.Assert; |
|
34 | 37 | import java.io.FileInputStream; |
35 | 38 | import java.io.FileOutputStream; |
36 | 39 | import java.io.IOException; |
| 40 | +import java.io.OutputStream; |
37 | 41 | import java.net.URL; |
38 | 42 | import java.nio.charset.Charset; |
39 | 43 | import java.nio.charset.StandardCharsets; |
@@ -116,6 +120,89 @@ public void canSurviveNetworkFailuresInAesCbc() throws Exception { |
116 | 120 | canBeWrittenIdempotently(AesCbcCipherDetails.INSTANCE_128_BIT); |
117 | 121 | } |
118 | 122 |
|
| 123 | + /* Cipher-generic tests */ |
| 124 | + |
| 125 | + private void canBeWrittenIdempotently(final SupportedCipherDetails cipherDetails) throws Exception { |
| 126 | + final SecretKey secretKey = SecretKeyUtils.generate(cipherDetails); |
| 127 | + final String content = RandomStringUtils.randomAlphanumeric(RandomUtils.nextInt(500, 1500)); |
| 128 | + final ExposedStringEntity contentEntity = new ExposedStringEntity( |
| 129 | + content, |
| 130 | + StandardCharsets.UTF_8); |
| 131 | + |
| 132 | + final ByteArrayOutputStream referenceEncrypted = new ByteArrayOutputStream(content.length()); |
| 133 | + { |
| 134 | + |
| 135 | + final EncryptingEntity referenceEntity = new EncryptingEntity( |
| 136 | + secretKey, |
| 137 | + cipherDetails, |
| 138 | + contentEntity); |
| 139 | + |
| 140 | + referenceEntity.writeTo(referenceEncrypted); |
| 141 | + validateCiphertext( |
| 142 | + cipherDetails, |
| 143 | + secretKey, |
| 144 | + contentEntity.getBackingBuffer().array(), |
| 145 | + referenceEntity.getCipher().getIV(), |
| 146 | + referenceEncrypted.toByteArray()); |
| 147 | + } |
| 148 | + |
| 149 | + final ByteArrayOutputStream retryEncrypted = new ByteArrayOutputStream(content.length()); |
| 150 | + final FailingOutputStream output = new FailingOutputStream(retryEncrypted, content.length() / 2); |
| 151 | + { |
| 152 | + final EncryptingEntity retryingEntity = new EncryptingEntity( |
| 153 | + secretKey, |
| 154 | + cipherDetails, |
| 155 | + contentEntity); |
| 156 | + |
| 157 | + Assert.assertThrows(IOException.class, () -> { |
| 158 | + retryingEntity.writeTo(output); |
| 159 | + }); |
| 160 | + |
| 161 | + // clear the data written so we only see what makes it into the second request |
| 162 | + retryEncrypted.reset(); |
| 163 | + output.setMinimumBytes(FailingOutputStream.NO_FAILURE); |
| 164 | + |
| 165 | + retryingEntity.writeTo(output); |
| 166 | + validateCiphertext( |
| 167 | + cipherDetails, |
| 168 | + secretKey, |
| 169 | + contentEntity.getBackingBuffer().array(), |
| 170 | + retryingEntity.getCipher().getIV(), |
| 171 | + retryEncrypted.toByteArray()); |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + public void doesNotCloseSuppliedOutputStreamWhenWrittenSuccessfully() throws Exception { |
| 176 | + final SupportedCipherDetails cipherDetails = DefaultsConfigContext.DEFAULT_CIPHER; |
| 177 | + final SecretKey secretKey = SecretKeyUtils.generate(cipherDetails); |
| 178 | + final String content = RandomStringUtils.randomAlphanumeric(RandomUtils.nextInt(500, 1500)); |
| 179 | + final ExposedStringEntity contentEntity = new ExposedStringEntity( |
| 180 | + content, |
| 181 | + StandardCharsets.UTF_8); |
| 182 | + |
| 183 | + final EncryptingEntity encryptingEntity = new EncryptingEntity( |
| 184 | + secretKey, |
| 185 | + cipherDetails, |
| 186 | + contentEntity); |
| 187 | + |
| 188 | + final OutputStream output = Mockito.mock(OutputStream.class); |
| 189 | + encryptingEntity.writeTo(output); |
| 190 | + Mockito.verify(output, Mockito.never()).close(); |
| 191 | + } |
| 192 | + |
| 193 | + public void doesNotCloseSuppliedOutputStreamWhenFailureOccurs() throws Exception { |
| 194 | + final SupportedCipherDetails cipherDetails = DefaultsConfigContext.DEFAULT_CIPHER; |
| 195 | + final SecretKey secretKey = SecretKeyUtils.generate(cipherDetails); |
| 196 | + final EncryptingEntity encryptingEntity = new EncryptingEntity( |
| 197 | + secretKey, |
| 198 | + cipherDetails, |
| 199 | + new InputStreamEntity(new BrokenInputStream(new IOException("bad input")))); |
| 200 | + |
| 201 | + final OutputStream output = Mockito.mock(OutputStream.class); |
| 202 | + Assert.assertThrows(IOException.class, () -> encryptingEntity.writeTo(output)); |
| 203 | + Mockito.verify(output, Mockito.never()).close(); |
| 204 | + } |
| 205 | + |
119 | 206 | /* Test helper methods */ |
120 | 207 |
|
121 | 208 | private void canCountBytesFromStreamWithUnknownLength(SupportedCipherDetails cipherDetails) |
@@ -228,56 +315,6 @@ private static void verifyEncryptionWorksRoundTrip(byte[] keyBytes, |
228 | 315 | } |
229 | 316 | } |
230 | 317 |
|
231 | | - private void canBeWrittenIdempotently(final SupportedCipherDetails cipherDetails) throws Exception { |
232 | | - final SecretKey secretKey = SecretKeyUtils.generate(cipherDetails); |
233 | | - final String content = RandomStringUtils.randomAlphanumeric(RandomUtils.nextInt(500, 1500)); |
234 | | - final ExposedStringEntity contentEntity = new ExposedStringEntity( |
235 | | - content, |
236 | | - StandardCharsets.UTF_8); |
237 | | - |
238 | | - final ByteArrayOutputStream referenceEncrypted = new ByteArrayOutputStream(content.length()); |
239 | | - { |
240 | | - |
241 | | - final EncryptingEntity referenceEntity = new EncryptingEntity( |
242 | | - secretKey, |
243 | | - cipherDetails, |
244 | | - contentEntity); |
245 | | - |
246 | | - referenceEntity.writeTo(referenceEncrypted); |
247 | | - validateCiphertext( |
248 | | - cipherDetails, |
249 | | - secretKey, |
250 | | - contentEntity.getBackingBuffer().array(), |
251 | | - referenceEntity.getCipher().getIV(), |
252 | | - referenceEncrypted.toByteArray()); |
253 | | - } |
254 | | - |
255 | | - final ByteArrayOutputStream retryEncrypted = new ByteArrayOutputStream(content.length()); |
256 | | - final FailingOutputStream output = new FailingOutputStream(retryEncrypted, content.length() / 2); |
257 | | - { |
258 | | - final EncryptingEntity retryingEntity = new EncryptingEntity( |
259 | | - secretKey, |
260 | | - cipherDetails, |
261 | | - contentEntity); |
262 | | - |
263 | | - Assert.assertThrows(IOException.class, () -> { |
264 | | - retryingEntity.writeTo(output); |
265 | | - }); |
266 | | - |
267 | | - // clear the data written so we only see what makes it into the second request |
268 | | - retryEncrypted.reset(); |
269 | | - output.setMinimumBytes(FailingOutputStream.NO_FAILURE); |
270 | | - |
271 | | - retryingEntity.writeTo(output); |
272 | | - validateCiphertext( |
273 | | - cipherDetails, |
274 | | - secretKey, |
275 | | - contentEntity.getBackingBuffer().array(), |
276 | | - retryingEntity.getCipher().getIV(), |
277 | | - retryEncrypted.toByteArray()); |
278 | | - } |
279 | | - } |
280 | | - |
281 | 318 | private void validateCiphertext(SupportedCipherDetails cipherDetails, |
282 | 319 | SecretKey secretKey, |
283 | 320 | byte[] plaintext, |
|
0 commit comments