/***************************************************************** * 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.cayenne.crypto.transformer.bytes; import java.io.UnsupportedEncodingException; import org.apache.cayenne.crypto.CayenneCryptoException; /** * Represents a header with metadata about the encrypted data. A header is * prependend to each encrypted value, and itself is not encrypted. Header * format is the following: * <ul> * <li>byte 0..2: "magic" number identifying the format as Cayenne-crypto * encrypted sequence. * <li>byte 3: header length N, i.e. how many bytes the header contains, * including magic number and the length indicator. N can be 0..127. * <li>byte 4: a bit String representing various flags, such as compression. * <li>byte 5..N: UTF8-encoded symbolic name of the encryption key. * </ul> * * @since 4.0 */ public class Header { private static final String KEY_NAME_CHARSET = "UTF-8"; // "CC1" is a "magic number" identifying Cayenne-crypto version 1 value private static final byte[] MAGIC_NUMBER = { 'C', 'C', '1' }; /** * Position of the "flags" byte in the header. */ private static final int MAGIC_NUMBER_POSITION = 0; /** * Position of the header size byte in the header. */ private static final int SIZE_POSITION = 3; /** * Position of the "flags" byte in the header. */ private static final int FLAGS_POSITION = 4; /** * Position of the key name within the header block. */ private static final int KEY_NAME_OFFSET = 5; /** * Max size of a key name within a header. */ private static final int KEY_NAME_MAX_SIZE = Byte.MAX_VALUE - KEY_NAME_OFFSET; /** * A position of the compress bit. */ private static final int COMPRESS_BIT = 0; /** * A position if the HMAC bit */ private static final int HMAC_BIT = 1; private byte[] data; private int offset; public static Header create(String keyName, boolean compressed, boolean withHMAC) { byte[] keyNameBytes; try { keyNameBytes = keyName.getBytes(KEY_NAME_CHARSET); } catch (UnsupportedEncodingException e) { throw new CayenneCryptoException("Can't encode in " + KEY_NAME_CHARSET, e); } if (keyNameBytes.length > KEY_NAME_MAX_SIZE) { throw new CayenneCryptoException("Key name '" + keyName + "' is too long. Its UTF8-encoded form should not exceed " + KEY_NAME_MAX_SIZE + " bytes"); } int n = MAGIC_NUMBER.length + 1 + 1 + keyNameBytes.length; byte[] data = new byte[n]; System.arraycopy(MAGIC_NUMBER, 0, data, MAGIC_NUMBER_POSITION, MAGIC_NUMBER.length); // total header size data[SIZE_POSITION] = (byte) n; // flags if (compressed) { data[FLAGS_POSITION] = bitOn(data[FLAGS_POSITION], COMPRESS_BIT); } if (withHMAC) { data[FLAGS_POSITION] = bitOn(data[FLAGS_POSITION], HMAC_BIT); } // key name System.arraycopy(keyNameBytes, 0, data, KEY_NAME_OFFSET, keyNameBytes.length); return create(data, 0); } public static Header create(byte[] data, int offset) { return new Header(data, offset); } public static byte setCompressed(byte bits, boolean compressed) { return compressed ? bitOn(bits, COMPRESS_BIT) : bitOff(bits, COMPRESS_BIT); } public static byte setHaveHMAC(byte bits, boolean haveHMAC) { return haveHMAC ? bitOn(bits, HMAC_BIT) : bitOff(bits, HMAC_BIT); } private static byte bitOn(byte bits, int position) { return (byte) (bits | (1 << position)); } private static byte bitOff(byte bits, int position) { return (byte) (bits & ~(1 << position)); } private static boolean isBitOn(byte bits, int position) { return ((bits >> position) & 1) == 1; } // private constructor... construction is done via factory methods... private Header(byte[] data, int offset) { this.data = data; this.offset = offset; } public int size() { return data[offset + SIZE_POSITION]; } public boolean isCompressed() { return isBitOn(getFlags(), COMPRESS_BIT); } public boolean haveHMAC() { return isBitOn(getFlags(), HMAC_BIT); } public byte getFlags() { return data[offset + FLAGS_POSITION]; } /** * Saves the header bytes in the provided buffer at specified offset. */ public void store(byte[] output, int outputOffset, byte flags) { System.arraycopy(data, offset, output, outputOffset, size()); output[outputOffset + FLAGS_POSITION] = flags; } public String getKeyName() { try { return new String(data, offset + KEY_NAME_OFFSET, size() - KEY_NAME_OFFSET, KEY_NAME_CHARSET); } catch (UnsupportedEncodingException e) { throw new CayenneCryptoException("Can't decode with " + KEY_NAME_CHARSET, e); } } }