/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.keys; import java.io.IOException; import java.util.Arrays; import freenet.crypt.PCFBMode; import freenet.crypt.UnsupportedCipherException; import freenet.crypt.ciphers.Rijndael; import freenet.support.Logger; import freenet.support.api.Bucket; import freenet.support.api.BucketFactory; import freenet.support.io.ArrayBucket; import freenet.support.io.ArrayBucketFactory; import freenet.support.io.BucketTools; public class ClientSSKBlock implements ClientKeyBlock { static final int DATA_DECRYPT_KEY_LENGTH = 32; static public final int MAX_DECOMPRESSED_DATA_LENGTH = 32768; private final SSKBlock block; /** Is metadata. Set on decode. */ private boolean isMetadata; /** Has decoded? */ private boolean decoded; /** Client-key. This contains the decryption key etc. */ private final ClientSSK key; /** Compression algorithm from last time tried to decompress. */ private short compressionAlgorithm = -1; public ClientSSKBlock(byte[] data, byte[] headers, ClientSSK key, boolean dontVerify) throws SSKVerifyException { block = new SSKBlock(data, headers, (NodeSSK) key.getNodeKey(true), dontVerify); this.key = key; } public static ClientSSKBlock construct(SSKBlock block, ClientSSK key) throws SSKVerifyException { // Constructor expects clientkey to have the pubkey. // In the case of binary blobs, the block may have it instead. if(key.getPubKey() == null) key.setPublicKey(block.getPubKey()); return new ClientSSKBlock(block.data, block.headers, key, false); } /** * Decode the data. */ @Override public Bucket decode(BucketFactory factory, int maxLength, boolean dontDecompress) throws KeyDecodeException, IOException { /* We know the signature is valid because it is checked in the constructor. */ /* We also know e(h(docname)) is valid */ byte[] decryptedHeaders = new byte[SSKBlock.ENCRYPTED_HEADERS_LENGTH]; System.arraycopy(block.headers, block.headersOffset, decryptedHeaders, 0, SSKBlock.ENCRYPTED_HEADERS_LENGTH); Rijndael aes; try { Logger.minor(this, "cryptoAlgorithm="+key.cryptoAlgorithm+" for "+getClientKey().getURI()); aes = new Rijndael(256,256); } catch (UnsupportedCipherException e) { throw new Error(e); } aes.initialize(key.cryptoKey); // ECB-encrypted E(H(docname)) serves as IV. PCFBMode pcfb = PCFBMode.create(aes, key.ehDocname); pcfb.blockDecipher(decryptedHeaders, 0, decryptedHeaders.length); // First 32 bytes are the key byte[] dataDecryptKey = Arrays.copyOf(decryptedHeaders, DATA_DECRYPT_KEY_LENGTH); aes.initialize(dataDecryptKey); byte[] dataOutput = block.data.clone(); // Data decrypt key should be unique, so use it as IV pcfb.reset(dataDecryptKey); pcfb.blockDecipher(dataOutput, 0, dataOutput.length); // 2 bytes - data length int dataLength = ((decryptedHeaders[DATA_DECRYPT_KEY_LENGTH] & 0xff) << 8) + (decryptedHeaders[DATA_DECRYPT_KEY_LENGTH+1] & 0xff); // Metadata flag is top bit if((dataLength & 32768) != 0) { dataLength = dataLength & ~32768; isMetadata = true; } if(dataLength > dataOutput.length) { throw new SSKDecodeException("Data length: "+dataLength+" but data.length="+dataOutput.length); } compressionAlgorithm = (short)(((decryptedHeaders[DATA_DECRYPT_KEY_LENGTH+2] & 0xff) << 8) + (decryptedHeaders[DATA_DECRYPT_KEY_LENGTH+3] & 0xff)); decoded = true; if(dontDecompress) { if(compressionAlgorithm == (short)-1) return BucketTools.makeImmutableBucket(factory, dataOutput, dataLength); else if(dataLength < 2) throw new SSKDecodeException("Data length is less than 2 yet compressed!"); else return BucketTools.makeImmutableBucket(factory, dataOutput, 2, dataLength - 2); } Bucket b = Key.decompress(compressionAlgorithm >= 0, dataOutput, dataLength, factory, Math.min(MAX_DECOMPRESSED_DATA_LENGTH, maxLength), compressionAlgorithm, true); return b; } @Override public boolean isMetadata() { if(!decoded) throw new IllegalStateException("Cannot read isMetadata before decoded"); return isMetadata; } @Override public ClientSSK getClientKey() { return key; } public short getCompressionCodec() { return compressionAlgorithm; } @Override public byte[] memoryDecode() throws KeyDecodeException { return memoryDecode(false); } /** * Decode into RAM, if short. * @throws KeyDecodeException */ public byte[] memoryDecode(boolean dontDecompress) throws KeyDecodeException { try { ArrayBucket a = (ArrayBucket) decode(new ArrayBucketFactory(), 32*1024, dontDecompress); return BucketTools.toByteArray(a); // FIXME } catch (IOException e) { throw new Error(e); } } @Override public int hashCode() { return block.hashCode() ^ key.hashCode(); } /** Return true if this is the same block as the other ClientSSKBlock, *and* it is the same key */ @Override public boolean equals(Object o) { if(!(o instanceof ClientSSKBlock)) return false; ClientSSKBlock block = (ClientSSKBlock) o; if(!key.equals(block.key)) return false; return this.block.equals(block.block); } @Override public KeyBlock getBlock() { return block; } @Override public Key getKey() { return block.getKey(); } }