--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.jdbc.EncryptedLOBFile\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.jdbc;\r
+\r
+import java.io.EOFException;\r
+import java.io.FileNotFoundException;\r
+import java.io.IOException;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.i18n.MessageService;\r
+import org.apache.derby.iapi.store.raw.data.DataFactory;\r
+import org.apache.derby.io.StorageFile;\r
+\r
+/**\r
+ * This class is a wrapper class on top of StorageRandomAccess to provide common\r
+ * methods to write in encrypted file.\r
+ * This class is NOT thread safe. The user class should take care\r
+ * of synchronization if being used in multi threaded environment.\r
+ */\r
+class EncryptedLOBFile extends LOBFile {\r
+ /** Block size for encryption. */\r
+ private final int blockSize;\r
+ /** Leftover bytes. Stored in memory until they fill one block .*/\r
+ private final byte [] tail;\r
+ /** Number of actual bytes in tail array. */\r
+ private int tailSize;\r
+ /** Current file position. */\r
+ private long currentPos;\r
+ /** Factory object used for encryption and decryption. */\r
+ private final DataFactory df;\r
+\r
+ /**\r
+ * Constructs the EncryptedLOBFile object with encryption support.\r
+ *\r
+ * @param lobFile StorageFile Object for which file will be created\r
+ * @param df data factory for encryption and decription\r
+ * @throws FileNotFoundException if the file exists but is a directory or\r
+ * cannot be opened\r
+ */\r
+ EncryptedLOBFile(StorageFile lobFile, DataFactory df)\r
+ throws FileNotFoundException {\r
+ super(lobFile);\r
+ this.df = df;\r
+ blockSize = df.getEncryptionBlockSize();\r
+ tail = new byte [blockSize];\r
+ tailSize = 0;\r
+ }\r
+\r
+ /**\r
+ * Find the blocks containing the data we are interested in.\r
+ *\r
+ * @param pos first position we are interested in\r
+ * @param len number of bytes of interest\r
+ * @return byte array containing all the blocks of data the specified\r
+ * region spans over\r
+ */\r
+ private byte [] getBlocks (long pos, int len)\r
+ throws IOException, StandardException {\r
+ if (len < 0)\r
+ throw new IndexOutOfBoundsException (\r
+ MessageService.getTextMessage (\r
+ SQLState.BLOB_NONPOSITIVE_LENGTH, new Integer (len)));\r
+ //starting position of the 1st block\r
+ long startPos = pos - pos % blockSize;\r
+ //end position of last block\r
+ long endPos = (pos + len + blockSize - 1) / blockSize * blockSize;\r
+\r
+ byte [] data = new byte [(int) (endPos - startPos)];\r
+ super.seek (startPos);\r
+ super.read (data, 0, data.length);\r
+ return data;\r
+ }\r
+\r
+ /**\r
+ * Returns file length.\r
+ * @return file length\r
+ * @throws IOException if an I/O error occurs\r
+ */\r
+ long length() throws IOException {\r
+ return super.length() + tailSize;\r
+ }\r
+\r
+ /**\r
+ * Returns the currrent position in the file.\r
+ * @return current position of file pointer\r
+ */\r
+ long getFilePointer() {\r
+ return currentPos;\r
+ }\r
+\r
+ /**\r
+ * Sets the current file pointer to specific location.\r
+ * @param pos new position\r
+ * @throws IOException\r
+ */\r
+ void seek (long pos) throws IOException {\r
+ long fileLength = super.length();\r
+ if (pos > fileLength + tailSize) {\r
+ //this should never happen\r
+ //this exception will mean internal error most\r
+ //probably in LOBStreamControl\r
+ throw new IllegalArgumentException ("Internal Error");\r
+ }\r
+ if (pos < fileLength) {\r
+ super.seek (pos);\r
+ }\r
+ currentPos = pos;\r
+ }\r
+\r
+ /**\r
+ * Writes one byte into the file.\r
+ * @param b byte value\r
+ * @throws IOException if disk operation fails\r
+ * @throws StandardException if error occured during encryption/decryption\r
+ */\r
+ void write (int b) throws IOException, StandardException {\r
+ long length = super.length();\r
+ if (currentPos >= length) {\r
+ //current position is in memory\r
+ int pos = (int) (currentPos - length);\r
+ tail [pos] = (byte) b;\r
+ if (pos >= tailSize) {\r
+ tailSize = pos + 1;\r
+ }\r
+ if (tailSize == blockSize) {\r
+ //we have enough data to fill one block encrypt and write\r
+ //in file\r
+ byte [] cypherText = new byte [blockSize];\r
+ df.encrypt (tail, 0, tailSize, cypherText, 0, false);\r
+ super.seek (length);\r
+ super.write (cypherText);\r
+ tailSize = 0;\r
+ }\r
+ }\r
+ else {\r
+ //write position is in the middle of the file\r
+ //get the complete block in which the destination byte falls into\r
+ byte [] cypherText = getBlocks (currentPos, 1);\r
+ byte [] clearText = new byte [blockSize];\r
+ //decrypt the block before updating\r
+ df.decrypt(cypherText, 0, blockSize, clearText, 0);\r
+ clearText [(int) (currentPos%blockSize)] = (byte) b;\r
+ //encrypt and write back\r
+ df.encrypt (clearText, 0, blockSize, cypherText, 0, false);\r
+ super.seek (currentPos - currentPos % blockSize);\r
+ super.write (cypherText);\r
+ }\r
+ currentPos++;\r
+ }\r
+\r
+ /**\r
+ * Writes length number of bytes from buffer starting from off position.\r
+ * @param b byte array containing bytes to be written\r
+ * @param off starting offset of the byte array from where the\r
+ * data should be written to the file\r
+ * @param len number of bytes to be written\r
+ * @throws IOException if disk operation fails\r
+ * @throws StandardException if error occured during encryption/decryption\r
+ */\r
+ void write(byte[] b, int off, int len)\r
+ throws IOException, StandardException {\r
+ long fileLength = super.length();\r
+ if (currentPos < fileLength) {\r
+ //starting position for write is in file\r
+ //find out if we need to update memory\r
+ int overFlow = (int) Math.max(0L, currentPos + len - fileLength);\r
+ long oldPos = currentPos;\r
+ //get the block containing bytes we are going to overwrite\r
+ byte [] cypherText = getBlocks (currentPos, len - overFlow);\r
+ byte [] clearText = new byte [cypherText.length];\r
+ //decrypt the data before updating\r
+ for (int i = 0; i < cypherText.length / blockSize; i++)\r
+ df.decrypt (cypherText, i * blockSize, blockSize, clearText,\r
+ i * blockSize);\r
+ //update the data\r
+ System.arraycopy (b, off, clearText, (int) (currentPos%blockSize),\r
+ len - overFlow);\r
+ //encrypt and write back\r
+ for (int i = 0; i < cypherText.length / blockSize; i++)\r
+ df.encrypt (clearText, i * blockSize, blockSize,\r
+ cypherText, i * blockSize, false);\r
+ super.seek (oldPos - oldPos % blockSize);\r
+ super.write (cypherText);\r
+ currentPos = oldPos + cypherText.length;\r
+ //nothing to keep in memory.\r
+ if (overFlow == 0)\r
+ return;\r
+ //adjust the value to perform rest of the writes in tail buffer\r
+ off = off + len - overFlow;\r
+ len = overFlow;\r
+ //write rest of the data in memory\r
+ currentPos = fileLength;\r
+ }\r
+ //starting position in array\r
+ int pos = (int) (currentPos - fileLength);\r
+ int finalPos = pos + len;\r
+ if (finalPos < blockSize) {\r
+ //updated size won't be enough to perform encryption\r
+ System.arraycopy (b, off, tail, pos, len);\r
+ tailSize = Math.max(tailSize, pos + len);\r
+ currentPos += len;\r
+ return;\r
+ }\r
+ //number of bytes which can be encrypted\r
+ int encLength = finalPos - finalPos % blockSize;\r
+ int leftOver = finalPos % blockSize;\r
+ byte [] clearText = new byte [encLength];\r
+ //create array to encrypt\r
+ //copy the bytes from tail which won't be overwritten\r
+ System.arraycopy (tail, 0, clearText, 0, pos);\r
+ //copy remaining data into array\r
+ System.arraycopy (b, off, clearText, pos, encLength - pos);\r
+ byte [] cypherText = new byte [clearText.length];\r
+ //encrypt and write\r
+ for (int offset = 0; offset < cypherText.length ; offset += blockSize)\r
+ df.encrypt (clearText, offset, blockSize, cypherText,\r
+ offset, false);\r
+ super.seek (fileLength);\r
+ super.write (cypherText);\r
+ //copy rest of it in tail\r
+ System.arraycopy (b, off + len - leftOver, tail, 0, leftOver);\r
+ tailSize = leftOver;\r
+ currentPos = tailSize + fileLength + cypherText.length;\r
+ }\r
+\r
+ /**\r
+ * Write the buffer into file at current position. It overwrites the\r
+ * data if current position is in the middle of the file and appends into\r
+ * the file if the total length exceeds the file size.\r
+ * @param b byte array to be written\r
+ * @throws IOException if disk operation fails\r
+ * @throws StandardException if error occured during encryption/decryption\r
+ */\r
+ void write(byte[] b) throws IOException, StandardException {\r
+ write (b, 0, b.length);\r
+ }\r
+\r
+ /**\r
+ * closes the file.\r
+ * @throws IOException\r
+ */\r
+ void close() throws IOException {\r
+ super.close();\r
+ }\r
+\r
+ /**\r
+ * Reads one byte from file.\r
+ * @return byte\r
+ * @throws IOException if disk operation fails\r
+ * @throws StandardException if error occured during encryption/decryption\r
+ */\r
+ int readByte() throws IOException, StandardException {\r
+ long fileLength = super.length();\r
+ if (currentPos >= fileLength + tailSize)\r
+ throw new EOFException ();\r
+ if (currentPos >= fileLength)\r
+ return tail [(int) (currentPos++ - fileLength)] & 0xff;\r
+ //get the block containing the byte we are interested in\r
+ byte cypherText [] = getBlocks (currentPos, 1);\r
+ byte [] clearText = new byte [cypherText.length];\r
+ df.decrypt (cypherText, 0, cypherText.length, clearText, 0);\r
+ return clearText [(int) (currentPos++ % blockSize)] & 0xff;\r
+ }\r
+\r
+ /**\r
+ * Reads len or remaining bytes in the file (whichever is lower) bytes\r
+ * into buff starting from off position of the buffer.\r
+ * @param buff byte array to fill read bytes\r
+ * @param off offset of buff where the byte will be written\r
+ * @param len number of bytes to be read\r
+ * @return number of bytes read\r
+ * @throws IOException if disk operation fails\r
+ * @throws StandardException if error occured during encryption/decryption\r
+ */\r
+ int read(byte[] buff, int off, int len)\r
+ throws IOException, StandardException {\r
+ long fileLength = super.length();\r
+ if (currentPos < fileLength) {\r
+ //starting position is in file\r
+ //find number of bytes spilling out of file\r
+ int overFlow = (int) Math.max(0L, currentPos + len - fileLength);\r
+ //get all the blocks\r
+ byte [] cypherText = getBlocks (currentPos, len - overFlow);\r
+ byte [] tmpByte = new byte [cypherText.length];\r
+ //decrypt\r
+ for (int offset = 0; offset < cypherText.length; offset += blockSize) {\r
+ df.decrypt (cypherText, offset, blockSize, tmpByte,\r
+ offset);\r
+ }\r
+ //copy the bytes we are interested in\r
+ System.arraycopy (tmpByte, (int) (currentPos%blockSize), buff,\r
+ off, len - overFlow);\r
+ if (overFlow == 0) {\r
+ currentPos += len;\r
+ return len;\r
+ }\r
+ //find out total number of bytes we can read\r
+ int newLen = Math.min(overFlow, tailSize);\r
+ //fill the buffer from tail\r
+ System.arraycopy (tail, 0, buff, off + len - overFlow, newLen);\r
+ currentPos += len - overFlow + newLen;\r
+ return len - overFlow + newLen;\r
+ }\r
+ int newLen = (int) Math.min (\r
+ tailSize - currentPos + fileLength, len);\r
+ if (newLen == 0 && len != 0)\r
+ return -1;\r
+\r
+ System.arraycopy (tail, (int) (currentPos - fileLength),\r
+ buff, off, newLen);\r
+ currentPos += newLen;\r
+ return newLen;\r
+ }\r
+\r
+ /**\r
+ * Sets the file length to a given size. If the new size is smaller than the\r
+ * file length the file is truncated.\r
+ *\r
+ * @param size new file size. Must be lower than file length.\r
+ * @throws IOException if file i/o fails\r
+ * @throws StandardException if error occured during encryption/decryption\r
+ */\r
+ void setLength(long size) throws IOException, StandardException {\r
+ long fileLength = super.length();\r
+ if (size > fileLength + tailSize) {\r
+ //this should never happen\r
+ //this exception will mean internal error most\r
+ //probably in LOBStreamControl\r
+ throw new IllegalArgumentException ("Internal Error");\r
+ }\r
+ if (size < fileLength) {\r
+ byte [] block = getBlocks (size, 1);\r
+ super.setLength (size - size % blockSize);\r
+ df.decrypt (block, 0, blockSize, tail, 0);\r
+ tailSize = (int) (size % blockSize);\r
+ }\r
+ else {\r
+ tailSize = (int) (size - fileLength);\r
+ }\r
+ }\r
+}\r