/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is part of dcm4che, an implementation of DICOM(TM) in
* Java(TM), hosted at https://github.com/gunterze/dcm4che.
*
* The Initial Developer of the Original Code is
* Agfa Healthcare.
* Portions created by the Initial Developer are Copyright (C) 2012-2014
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* See @authors listed below
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.dcm4chee.storage.test.unit.encrypt;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.SecretKey;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.inject.Named;
import org.dcm4che3.net.Device;
import org.dcm4che3.util.StreamUtils;
import org.dcm4chee.storage.RetrieveContext;
import org.dcm4chee.storage.StorageContext;
import org.dcm4chee.storage.conf.StorageDevice;
import org.dcm4chee.storage.conf.StorageDeviceExtension;
import org.dcm4chee.storage.conf.StorageSystem;
import org.dcm4chee.storage.conf.StorageSystemGroup;
import org.dcm4chee.storage.conf.StorageSystemStatus;
import org.dcm4chee.storage.encrypt.BlockCipherInputStream;
import org.dcm4chee.storage.encrypt.BlockCipherOutputStream;
import org.dcm4chee.storage.encrypt.StorageSystemProviderEncryptDecorator;
import org.dcm4chee.storage.filesystem.FileSystemStorageSystemProvider;
import org.dcm4chee.storage.spi.StorageSystemProvider;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* @author Steve Kroetsch<stevekroetsch@hotmail.com>
*
*/
@RunWith(Arquillian.class)
public class StorageSystemProviderEncryptDecoratorTest {
private static final String ID1 = "a/b/c";
private static final String ID2 = "x/y/z";
private static final String FS_PATH = "target/test-storage/encrypt";
private static final Path DIR = Paths.get(FS_PATH);
private static final Path FILE1 = DIR.resolve(ID1);
private static final Path FILE2 = DIR.resolve(ID2);
private static final String KEYSTORE_URL = Paths.get("src/test/data/key.jks").toUri()
.toString();
private static final String KEYSTORE_PASSWORD = "secret";
private static final String KEYSTORE_TYPE = "jceks";
private static final String KEY_ALIAS = "test";
private static final byte[] TEST_DATA = { 't', 'e', 's', 't' };
private static SecretKey secretKey;
@Deployment
public static JavaArchive createDeployment() {
return ShrinkWrap
.create(JavaArchive.class)
.addClass(FileSystemStorageSystemProvider.class)
.addClass(StorageSystemProviderEncryptDecorator.class)
.addAsManifestResource(
new StringAsset(
"<decorators><class>org.dcm4chee.storage.encrypt.StorageSystemProviderEncryptDecorator</class></decorators>"),
"beans.xml");
}
@Inject
@Named("org.dcm4chee.storage.filesystem")
StorageSystemProvider provider;
@Produces @StorageDevice
static Device device = new Device("test");
StorageDeviceExtension ext;
StorageSystemGroup fsGroup;
StorageSystem fs;
StorageContext storageCtx;
RetrieveContext retrieveCtx;
@Before
public void setup() throws IOException, InterruptedException {
ext = new StorageDeviceExtension();
device.addDeviceExtension(ext);
device.setKeyStoreURL(KEYSTORE_URL);
device.setKeyStorePin(KEYSTORE_PASSWORD);
device.setKeyStoreKeyPin(KEYSTORE_PASSWORD);
device.setKeyStoreType(KEYSTORE_TYPE);
fsGroup = new StorageSystemGroup();
fsGroup.setGroupID("fs");
ext.addStorageSystemGroup(fsGroup);
fs = new StorageSystem();
fs.setStorageSystemID("fs");
fs.setStorageSystemPath(FS_PATH);
fs.setStorageSystemStatus(StorageSystemStatus.OK);
fs.setEncryptionKeyAlias(KEY_ALIAS);
fs.setStorageSystemGroup(fsGroup);
provider.init(fs);
storageCtx = new StorageContext();
storageCtx.setStorageSystemProvider(provider);
storageCtx.setStorageSystem(fs);
retrieveCtx = new RetrieveContext();
retrieveCtx.setStorageSystemProvider(provider);
retrieveCtx.setStorageSystem(fs);
Files.deleteIfExists(FILE2);
if (!Files.exists(FILE1)) {
Files.createDirectories(FILE1.getParent());
try (OutputStream out = Files.newOutputStream(FILE1,
StandardOpenOption.CREATE)) {
out.write(TEST_DATA);
}
}
}
@After
public void teardown() {
device.removeDeviceExtension(ext);
ext = null;
fsGroup = null;
fs = null;
}
@BeforeClass
public static void readSecretKey() throws KeyStoreException, IOException,
NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE);
try (InputStream in = StreamUtils.openFileOrURL(KEYSTORE_URL)) {
ks.load(in, KEYSTORE_PASSWORD.toCharArray());
secretKey = (SecretKey) ks.getKey(KEY_ALIAS, KEYSTORE_PASSWORD.toCharArray());
}
}
@Test
public void testOpenOutputStream() throws Exception {
Assert.assertFalse(Files.exists(FILE2));
try (OutputStream out = provider.openOutputStream(storageCtx, ID2)) {
Files.copy(FILE1, out);
}
Assert.assertTrue(Files.exists(FILE2));
Assert.assertArrayEquals(TEST_DATA, decryptToByteArray(FILE2));
}
@Test
public void testStoreFile() throws Exception {
Assert.assertFalse(Files.exists(FILE2));
provider.storeFile(storageCtx, FILE1, ID2);
Assert.assertTrue(Files.exists(FILE2));
Assert.assertArrayEquals(TEST_DATA, decryptToByteArray(FILE2));
}
@Test
public void testMoveFile() throws Exception {
Assert.assertTrue(Files.exists(FILE1));
Assert.assertFalse(Files.exists(FILE2));
provider.moveFile(storageCtx, FILE1, ID2);
Assert.assertTrue(Files.exists(FILE2));
Assert.assertFalse(Files.exists(FILE1));
Assert.assertArrayEquals(TEST_DATA, decryptToByteArray(FILE2));
}
@Test
public void testOpenInputStream() throws Exception {
Assert.assertFalse(Files.exists(FILE2));
Files.createDirectories(FILE2.getParent());
try (OutputStream encrypt = new BlockCipherOutputStream(Files.newOutputStream(
FILE2, StandardOpenOption.CREATE), secretKey)) {
Files.copy(FILE1, encrypt);
}
try (InputStream decrypt = provider.openInputStream(retrieveCtx, ID2)) {
Assert.assertArrayEquals(TEST_DATA, copyToByteArray(decrypt));
}
}
@Test(expected = UnsupportedOperationException.class)
public void testGetFile() throws Exception {
provider.getFile(retrieveCtx, ID1);
}
@Test
public void testDeleteObject() throws Exception {
Assert.assertTrue(Files.exists(FILE1));
provider.deleteObject(storageCtx, ID1);
Assert.assertFalse(Files.exists(FILE1));
}
private byte[] decryptToByteArray(Path encryptedFile) throws IOException {
try (InputStream in = new BlockCipherInputStream(
Files.newInputStream(encryptedFile), secretKey)) {
return copyToByteArray(in);
}
}
private byte[] copyToByteArray(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamUtils.copy(in, out);
return out.toByteArray();
}
}