/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.common.buffer.impl;
import java.io.IOException;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import org.teiid.common.buffer.FileStore;
import org.teiid.common.buffer.StorageManager;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.core.crypto.CryptoException;
import org.teiid.core.crypto.SymmetricCryptor;
/**
* Implements a block AES cipher over a regular filestore.
* <br>
* With ECB mode and no padding, we just replace the
* the bytes 1 block at a time with some special handling
* for when not block aligned.
* <br>
* A great deal of the security comes from the encryption
* key only being used on a temporary basis. We also xor
* by the block to add a very simple CTR like mode so that
* identical blocks don't result in the same storage bytes.
* <br>
* TODO: use masking for division
*/
public class EncryptedStorageManager implements StorageManager {
private static final String DEFAULT_ALGORITHM = "AES/ECB/NoPadding"; //$NON-NLS-1$
static final class EncryptedFileStore extends FileStore {
private final FileStore file;
private volatile long len;
private Cipher encrypt;
private Cipher decrypt;
private int blockSize;
private EncryptedFileStore(FileStore file, SecretKey key) throws GeneralSecurityException {
this.file = file;
decrypt = Cipher.getInstance( DEFAULT_ALGORITHM);
decrypt.init( Cipher.DECRYPT_MODE, key );
encrypt = Cipher.getInstance( DEFAULT_ALGORITHM);
blockSize = encrypt.getBlockSize();
encrypt.init( Cipher.ENCRYPT_MODE, key );
}
@Override
public synchronized void setLength(long length) throws IOException {
this.len = length;
if (length % blockSize == 0) {
file.setLength(length);
} else {
file.setLength((length/blockSize + 1)*blockSize);
}
}
@Override
protected void removeDirect() {
file.remove();
}
@Override
protected synchronized int readWrite(long fileOffset, byte[] b, int offSet, int length,
boolean write) throws IOException {
if (length == 0) {
return 0;
}
long block = fileOffset/blockSize;
int remainder = (int)(fileOffset%blockSize);
if (!write) {
if (fileOffset > len) {
throw new IOException("Invalid file position " + fileOffset + " length " + length); //$NON-NLS-1$ //$NON-NLS-2$
}
length = (int)Math.min(Integer.MAX_VALUE, Math.min(len - fileOffset, length));
}
long adjustedfileOffset = fileOffset;
int adjustedLength = length;
byte[] buffer = b;
int bufferOffset = offSet;
int remainingLength = (int) ((length+fileOffset)%blockSize);
if (remainder!=0||remainingLength!=0) {
adjustedfileOffset -= remainder;
adjustedLength += remainder;
if (adjustedLength%blockSize!=0) {
//round to the next block
adjustedLength=(adjustedLength/blockSize + 1)*blockSize;
}
//create a new fully aligned buffer
buffer = new byte[adjustedLength];
bufferOffset = 0;
}
int blocks = adjustedLength/blockSize;
int blockOffset = 0;
if (!write) {
file.readFully(adjustedfileOffset, buffer, bufferOffset, adjustedLength);
for (int i = 0; i < blocks; i++) {
try {
//TODO: directly copy into the output buffer when not block aligned
decrypt.doFinal(buffer, blockOffset, blockSize, buffer, blockOffset);
xorByBlock(buffer, 0, block + i, blockOffset);
} catch (GeneralSecurityException e) {
throw new IOException(e);
}
blockOffset += blockSize;
}
if (adjustedLength != length) {
//copy back the proper subset into the output buffer
System.arraycopy(buffer, remainder, b, offSet, length);
}
return length;
}
if (remainder!=0) {
//need to read the partial value and re-encrypt the whole block
readFully(fileOffset - remainder, buffer, 0, remainder);
} else if (buffer == b) {
//create a temp buffer to do the encryption in
buffer = new byte[adjustedLength];
}
System.arraycopy(b, offSet, buffer, remainder, length);
for (int i = 0; i < blocks; i++) {
try {
if (i + 1 == blocks && remainingLength != 0 && fileOffset + length < len) {
//TODO: combine with read above when working with a small enough length
readFully(fileOffset + length, buffer, blockOffset + remainingLength, (int)Math.min(blockSize - remainingLength, len - fileOffset - length));
}
xorByBlock(buffer, offSet, block + i, blockOffset);
encrypt.doFinal(buffer, blockOffset, blockSize, buffer, blockOffset);
} catch (GeneralSecurityException e) {
throw new IOException(e);
}
blockOffset += blockSize;
}
file.write(adjustedfileOffset, buffer, 0, adjustedLength);
len = Math.max(len, fileOffset + length);
return length;
}
private void xorByBlock(byte[] b, int offSet, long blockMask,
int blockOffset) {
for (int j = 0; j < blockSize && blockMask > 0; j++) {
b[offSet + blockOffset + j] ^= blockMask;
blockMask >>= 8;
}
}
@Override
public long getLength() {
return len;
}
FileStore getFile() {
return file;
}
}
private StorageManager manager;
private SecretKey key;
public EncryptedStorageManager(StorageManager manager) {
this.manager = manager;
}
@Override
public void initialize() throws TeiidComponentException {
manager.initialize();
try {
key = SymmetricCryptor.generateKey();
} catch (CryptoException e) {
throw new TeiidComponentException(e);
}
}
@Override
public EncryptedFileStore createFileStore(String name) {
final FileStore file = manager.createFileStore(name);
try {
return new EncryptedFileStore(file, key);
} catch (GeneralSecurityException e) {
throw new TeiidRuntimeException(e);
}
}
@Override
public long getMaxStorageSpace() {
return manager.getMaxStorageSpace();
}
}