blob: 008a72830899b404e36f25bacc30f49fceca591f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.internal.processors.cache.persistence.file;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import org.apache.ignite.spi.encryption.EncryptionSpi;
import org.apache.ignite.internal.managers.encryption.GridEncryptionManager;
import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
import org.apache.ignite.internal.processors.cache.persistence.wal.crc.PureJavaCrc32;
import org.apache.ignite.internal.util.typedef.internal.CU;
/**
* Implementation of {@code FileIO} that supports encryption(decryption) of pages written(readed) to(from) file.
*
* @see EncryptedFileIOFactory
*/
public class EncryptedFileIO implements FileIO {
/**
* Underlying file.
*/
private final FileIO plainFileIO;
/**
* Group id.
*/
private final int groupId;
/**
* Size of plain data page in bytes.
*/
private final int pageSize;
/**
* Size of file header in bytes.
*/
private final int headerSize;
/**
* Shared database manager.
*/
private final GridEncryptionManager encMgr;
/**
* Shared database manager.
*/
private final EncryptionSpi encSpi;
/**
* Encryption key.
*/
private Serializable encKey;
/**
* Extra bytes added by encryption.
*/
private final int encryptionOverhead;
/**
* Array of zeroes to fulfill tail of decrypted page.
*/
private final byte[] zeroes;
/**
* @param plainFileIO Underlying file.
* @param groupId Group id.
* @param pageSize Size of plain data page in bytes.
* @param headerSize Size of file header in bytes.
* @param encMgr Encryption manager.
*/
EncryptedFileIO(FileIO plainFileIO, int groupId, int pageSize, int headerSize,
GridEncryptionManager encMgr, EncryptionSpi encSpi) {
this.plainFileIO = plainFileIO;
this.groupId = groupId;
this.pageSize = pageSize;
this.headerSize = headerSize;
this.encMgr = encMgr;
this.encSpi = encSpi;
this.encryptionOverhead = pageSize - CU.encryptedPageSize(pageSize, encSpi);
this.zeroes = new byte[encryptionOverhead];
}
/** {@inheritDoc} */
@Override public long position() throws IOException {
return plainFileIO.position();
}
/** {@inheritDoc} */
@Override public void position(long newPosition) throws IOException {
plainFileIO.position(newPosition);
}
/** {@inheritDoc} */
@Override public int read(ByteBuffer destBuf) throws IOException {
assert position() == 0;
return plainFileIO.read(destBuf);
}
/** {@inheritDoc} */
@Override public int readFully(ByteBuffer destBuf) throws IOException {
return read(destBuf);
}
/** {@inheritDoc} */
@Override public int read(ByteBuffer destBuf, long position) throws IOException {
assert destBuf.remaining() >= pageSize;
assert position() != 0;
ByteBuffer encrypted = ByteBuffer.allocate(pageSize);
int res = plainFileIO.read(encrypted, position);
if (res < 0)
return res;
if (res != pageSize) {
throw new IllegalStateException("Expecting to read whole page[" + pageSize + " bytes], " +
"but read only " + res + " bytes");
}
encrypted.rewind();
decrypt(encrypted, destBuf);
return res;
}
/** {@inheritDoc} */
@Override public int readFully(ByteBuffer destBuf, long position) throws IOException {
assert destBuf.capacity() == pageSize;
assert position() != 0;
ByteBuffer encrypted = ByteBuffer.allocate(pageSize);
int res = plainFileIO.readFully(encrypted, position);
if (res < 0)
return res;
if (res != pageSize) {
throw new IllegalStateException("Expecting to read whole page[" + pageSize + " bytes], " +
"but read only " + res + " bytes");
}
encrypted.rewind();
decrypt(encrypted, destBuf);
return res;
}
/** {@inheritDoc} */
@Override public int read(byte[] buf, int off, int len) throws IOException {
throw new UnsupportedOperationException("Encrypted File doesn't support this operation");
}
/** {@inheritDoc} */
@Override public int readFully(byte[] buf, int off, int len) throws IOException {
return read(buf, off, len);
}
/** {@inheritDoc} */
@Override public int write(ByteBuffer srcBuf) throws IOException {
assert position() == 0;
assert headerSize == srcBuf.capacity();
return plainFileIO.write(srcBuf);
}
/** {@inheritDoc} */
@Override public int writeFully(ByteBuffer srcBuf) throws IOException {
return write(srcBuf);
}
/** {@inheritDoc} */
@Override public int write(ByteBuffer srcBuf, long position) throws IOException {
ByteBuffer encrypted = ByteBuffer.allocate(pageSize);
encrypt(srcBuf, encrypted);
encrypted.rewind();
return plainFileIO.write(encrypted, position);
}
/** {@inheritDoc} */
@Override public int writeFully(ByteBuffer srcBuf, long position) throws IOException {
ByteBuffer encrypted = ByteBuffer.allocate(pageSize);
encrypt(srcBuf, encrypted);
encrypted.rewind();
return plainFileIO.writeFully(encrypted, position);
}
/**
* @param srcBuf Source buffer.
* @param res Destination buffer.
* @throws IOException If failed.
*/
private void encrypt(ByteBuffer srcBuf, ByteBuffer res) throws IOException {
assert position() != 0;
assert srcBuf.remaining() >= pageSize;
assert tailIsEmpty(srcBuf, PageIO.getType(srcBuf));
int srcLimit = srcBuf.limit();
srcBuf.limit(srcBuf.position() + plainDataSize());
encSpi.encryptNoPadding(srcBuf, key(), res);
res.rewind();
storeCRC(res);
srcBuf.limit(srcLimit);
srcBuf.position(srcBuf.position() + encryptionOverhead);
}
/**
* @param encrypted Encrypted buffer.
* @param destBuf Destination buffer.
*/
private void decrypt(ByteBuffer encrypted, ByteBuffer destBuf) throws IOException {
assert encrypted.remaining() >= pageSize;
assert encrypted.limit() >= pageSize;
checkCRC(encrypted);
encrypted.limit(encryptedDataSize());
encSpi.decryptNoPadding(encrypted, key(), destBuf);
destBuf.put(zeroes); //Forcibly purge page buffer tail.
}
/**
* Stores CRC in res.
*
* @param res Destination buffer.
*/
private void storeCRC(ByteBuffer res) {
int crc = PureJavaCrc32.calcCrc32(res, encryptedDataSize());
res.put((byte) (crc >> 24));
res.put((byte) (crc >> 16));
res.put((byte) (crc >> 8));
res.put((byte) crc);
}
/**
* Checks encrypted data integrity.
*
* @param encrypted Encrypted data buffer.
*/
private void checkCRC(ByteBuffer encrypted) throws IOException {
int crc = PureJavaCrc32.calcCrc32(encrypted, encryptedDataSize());
int storedCrc = 0;
storedCrc |= (int)encrypted.get() << 24;
storedCrc |= ((int)encrypted.get() & 0xff) << 16;
storedCrc |= ((int)encrypted.get() & 0xff) << 8;
storedCrc |= encrypted.get() & 0xff;
if(crc != storedCrc) {
throw new IOException("Content of encrypted page is broken. [StoredCrc=" + storedCrc +
", calculatedCrd=" + crc + "]");
}
encrypted.position(encrypted.position() - (encryptedDataSize() + 4 /* CRC size. */));
}
/**
* @return Encrypted data size.
*/
private int encryptedDataSize() {
return pageSize - encSpi.blockSize();
}
/**
* @return Plain data size.
*/
private int plainDataSize() {
return pageSize - encryptionOverhead;
}
/** */
private boolean tailIsEmpty(ByteBuffer src, int pageType) {
int srcPos = src.position();
src.position(srcPos + plainDataSize());
for (int i = 0; i < encryptionOverhead; i++)
assert src.get() == 0 : "Tail of src should be empty [i=" + i + ", pageType=" + pageType + "]";
src.position(srcPos);
return true;
}
/**
* @return Encryption key.
*/
private Serializable key() {
if (encKey == null)
return encKey = encMgr.groupKey(groupId);
return encKey;
}
/** {@inheritDoc} */
@Override public int write(byte[] buf, int off, int len) throws IOException {
throw new UnsupportedOperationException("Encrypted File doesn't support this operation");
}
/** {@inheritDoc} */
@Override public int writeFully(byte[] buf, int off, int len) throws IOException {
return write(buf, off, len);
}
/** {@inheritDoc} */
@Override public MappedByteBuffer map(int sizeBytes) throws IOException {
throw new UnsupportedOperationException("Encrypted File doesn't support this operation");
}
/** {@inheritDoc} */
@Override public void force() throws IOException {
plainFileIO.force();
}
/** {@inheritDoc} */
@Override public void force(boolean withMetadata) throws IOException {
plainFileIO.force(withMetadata);
}
/** {@inheritDoc} */
@Override public long size() throws IOException {
return plainFileIO.size();
}
/** {@inheritDoc} */
@Override public void clear() throws IOException {
plainFileIO.clear();
}
/** {@inheritDoc} */
@Override public void close() throws IOException {
plainFileIO.close();
}
}