Package freenet.support.io

Source Code of freenet.support.io.PaddedEphemerallyEncryptedBucket$PaddedEphemerallyEncryptedInputStream

/* 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.support.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Random;

import freenet.client.async.ClientContext;
import freenet.crypt.MasterSecret;
import freenet.crypt.PCFBMode;
import freenet.crypt.RandomSource;
import freenet.crypt.UnsupportedCipherException;
import freenet.crypt.ciphers.Rijndael;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.api.Bucket;
import freenet.support.math.MersenneTwister;

/**
* A proxy Bucket which adds:
* - Encryption with the supplied cipher, and a random, ephemeral key.
* - Padding to the next PO2 size.
*
* CRYPTO WARNING: This uses PCFB with no IV. That means it is only safe if the key is unique!
*/
public class PaddedEphemerallyEncryptedBucket implements Bucket, Serializable {

    private static final long serialVersionUID = 1L;
    private final Bucket bucket;
  private final int minPaddedSize;
  /** The decryption key. */
  private final byte[] key;
  private final byte[] iv;
  private transient byte[] randomSeed;
  private long dataLength;
  private boolean readOnly;
  private transient int lastOutputStream;
  
        private static volatile boolean logMINOR;
  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
      }
    });
  }

  /**
   * Create a padded encrypted proxy bucket.
   * @param bucket The bucket which we are proxying to. Must be empty.
   * @param pcfb The encryption mode with which to encipher/decipher the data.
   * @param minSize The minimum padded size of the file (after it has been closed).
   * @param strongPRNG a strong prng we will key from.
   * @param weakPRNG a week prng we will padd from.
   * Serialization: Note that it is not our responsibility to free the random number generators,
   * but we WILL free the underlying bucket.
   * @throws UnsupportedCipherException
   */
  public PaddedEphemerallyEncryptedBucket(Bucket bucket, int minSize, RandomSource strongPRNG, Random weakPRNG) {
    this.bucket = bucket;
    if(bucket.size() != 0) throw new IllegalArgumentException("Bucket must be empty");
    byte[] tempKey = new byte[32];
    randomSeed = new byte[32];
    weakPRNG.nextBytes(randomSeed);
    strongPRNG.nextBytes(tempKey);
    this.key = tempKey;
    this.iv = new byte[32];
    strongPRNG.nextBytes(iv);
    this.minPaddedSize = minSize;
    readOnly = false;
    lastOutputStream = 0;
    dataLength = 0;
  }

  public PaddedEphemerallyEncryptedBucket(PaddedEphemerallyEncryptedBucket orig, Bucket newBucket) {
    this.dataLength = orig.dataLength;
    this.key = orig.key.clone();
    this.randomSeed = null; // Will be read-only
    setReadOnly();
    this.bucket = newBucket;
    this.minPaddedSize = orig.minPaddedSize;
    if(orig.iv != null) {
      iv = Arrays.copyOf(orig.iv, 32);
    } else {
      iv = null;
    }
  }
 
  protected PaddedEphemerallyEncryptedBucket() {
      // For serialization.
      bucket = null;
      minPaddedSize = 0;
      key = null;
      iv = null;
      randomSeed = null;
  }

    @Override
  public OutputStream getOutputStream() throws IOException {
        return new BufferedOutputStream(getOutputStreamUnbuffered());
  }

    public OutputStream getOutputStreamUnbuffered() throws IOException {
        if(readOnly) throw new IOException("Read only");
        OutputStream os = bucket.getOutputStreamUnbuffered();
        synchronized(this) {
            dataLength = 0;
        }
        return new PaddedEphemerallyEncryptedOutputStream(os, ++lastOutputStream);
    }

  private class PaddedEphemerallyEncryptedOutputStream extends OutputStream {

    final PCFBMode pcfb;
    final OutputStream out;
    final int streamNumber;
    private boolean closed;
   
    public PaddedEphemerallyEncryptedOutputStream(OutputStream out, int streamNumber) {
      this.out = out;
      dataLength = 0;
      this.streamNumber = streamNumber;
      pcfb = getPCFB();
    }
   
    @Override
    public void write(int b) throws IOException {
        synchronized(PaddedEphemerallyEncryptedBucket.this) {
            if(closed) throw new IOException("Already closed!");
            if(streamNumber != lastOutputStream)
                throw new IllegalStateException("Writing to old stream in "+getName());
        }
      //if((b < 0) || (b > 255))
      //  throw new IllegalArgumentException();
      int toWrite = pcfb.encipher(b);
      synchronized(PaddedEphemerallyEncryptedBucket.this) {
        out.write(toWrite);
        dataLength++;
      }
    }
   
    // Override this or FOS will use write(int)
    @Override
    public void write(byte[] buf) throws IOException {
            synchronized(PaddedEphemerallyEncryptedBucket.this) {
                if(closed)
                    throw new IOException("Already closed!");
                if(streamNumber != lastOutputStream)
                    throw new IllegalStateException("Writing to old stream in "+getName());
            }
      write(buf, 0, buf.length);
    }
   
    @Override
    public void write(byte[] buf, int offset, int length) throws IOException {
      synchronized(PaddedEphemerallyEncryptedBucket.this) {
              if(closed) throw new IOException("Already closed!");
          if(streamNumber != lastOutputStream)
              throw new IllegalStateException("Writing to old stream in "+getName());
      }
      if(length == 0) return;
      byte[] enc = Arrays.copyOfRange(buf, offset, offset + length);
      pcfb.blockEncipher(enc, 0, enc.length);
      synchronized(PaddedEphemerallyEncryptedBucket.this) {
        out.write(enc, 0, enc.length);
        dataLength += enc.length;
      }
    }
   
        @Override
    @SuppressWarnings("cast")
    public void close() throws IOException {
      try {
        Random random = new MersenneTwister(randomSeed);
        synchronized(PaddedEphemerallyEncryptedBucket.this) {
                if(closed) return;
                  if(streamNumber != lastOutputStream) {
                      Logger.normal(this, "Not padding out to length because have been superceded: "+getName());
                      return;
                  }
          long finalLength = paddedLength();
          long padding = finalLength - dataLength;
          int sz = 65536;
          if(padding < (long)sz)
            sz = (int)padding;
          byte[] buf = new byte[sz];
          long writtenPadding = 0;
          while(writtenPadding < padding) {
            int left = (int) Math.min((long) (padding - writtenPadding), (long) buf.length);
            random.nextBytes(buf);
            out.write(buf, 0, left);
            writtenPadding += left;
          }
        }
      } finally {
        closed = true;
        out.flush();
        out.close();
      }
    }
  }

  @Override
  public InputStream getInputStream() throws IOException {
    return new BufferedInputStream(getInputStreamUnbuffered());
  }

    @Override
    public InputStream getInputStreamUnbuffered() throws IOException {
        return new PaddedEphemerallyEncryptedInputStream(bucket.getInputStreamUnbuffered());
    }

  private class PaddedEphemerallyEncryptedInputStream extends InputStream {

    final InputStream in;
    final PCFBMode pcfb;
    long ptr;
   
    public PaddedEphemerallyEncryptedInputStream(InputStream in) {
      this.in = in;
      pcfb = getPCFB();
      ptr = 0;
    }
   
    @Override
    public int read() throws IOException {
      if(ptr >= dataLength) return -1;
      int x = in.read();
      if(x == -1) return x;
      ptr++;
      return pcfb.decipher(x);
    }
   
    @Override
    public final int available() {
      int x = (int)Math.min(dataLength - ptr, Integer.MAX_VALUE);
      return (x < 0) ? 0 : x;
    }
   
    @Override
    public int read(byte[] buf, int offset, int length) throws IOException {
      // FIXME remove debugging
      if((length+offset > buf.length) || (offset < 0) || (length < 0))
        throw new ArrayIndexOutOfBoundsException("a="+offset+", b="+length+", length "+buf.length);
      int x = available();
      if(x <= 0) return -1;
      length = Math.min(length, x);
      int readBytes = in.read(buf, offset, length);
      if(readBytes <= 0) return readBytes;
      ptr += readBytes;
      pcfb.blockDecipher(buf, offset, readBytes);
      return readBytes;
    }

    @Override
    public int read(byte[] buf) throws IOException {
      return read(buf, 0, buf.length);
    }
   
    @Override
    public long skip(long bytes) throws IOException {
      byte[] buf = new byte[(int)Math.min(4096, bytes)];
      long skipped = 0;
      while(skipped < bytes) {
        int x = read(buf, 0, (int)Math.min(bytes-skipped, buf.length));
        if(x <= 0) return skipped;
        skipped += x;
      }
      return skipped;
    }
   
    @Override
    public void close() throws IOException {
      in.close();
    }
  }
 
  public synchronized long paddedLength() {
      return paddedLength(dataLength, minPaddedSize);
  }

  public static final int MIN_PADDED_SIZE = 1024;
 
  /**
   * Return the length of the data in the proxied bucket, after padding.
   */
  public static long paddedLength(long dataLength, long minPaddedSize) {
    long size = dataLength;
    if(size < minPaddedSize) size = minPaddedSize;
    if(size == minPaddedSize) return size;
    long min = minPaddedSize;
    long max = (long)minPaddedSize << 1;
    while(true) {
      if(max < 0)
        throw new Error("Impossible size: "+size+" - min="+min+", max="+max);
      if(size < min)
        throw new IllegalStateException("???");
      if((size >= min) && (size <= max)) {
        if(logMINOR)
          Logger.minor(PaddedEphemerallyEncryptedBucket.class, "Padded: "+max+" was: "+dataLength);
        return max;
      }
      min = max;
      max = max << 1;
    }
  }

  private synchronized Rijndael getRijndael() {
    Rijndael aes;
    try {
      aes = new Rijndael(256, 256);
    } catch (UnsupportedCipherException e) {
      throw new Error(e);
    }
    aes.initialize(key);
    return aes;
  }

  @SuppressWarnings("deprecation")
  public PCFBMode getPCFB() {
    Rijndael aes = getRijndael();
    if(iv != null)
      return PCFBMode.create(aes, iv);
    else
      // FIXME CRYPTO We should probably migrate all old buckets automatically so we can get rid of this?
      // Since the key is unique it is actually almost safe to use all zeros IV, but it's better to use a real IV.
      return PCFBMode.create(aes);
  }

  @Override
  public String getName() {
    return "Encrypted:"+bucket.getName();
  }

  @Override
  public String toString() {
    return super.toString()+ ':' +bucket;
  }
 
  @Override
  public synchronized long size() {
    return dataLength;
  }

  @Override
  public boolean isReadOnly() {
    return readOnly;
  }
 
  @Override
  public void setReadOnly() {
    readOnly = true;
  }

  /**
   * @return The underlying Bucket.
   */
  public Bucket getUnderlying() {
    return bucket;
  }

  @Override
  public void free() {
    bucket.free();
  }

  /**
   * Get the decryption key.
   */
  public byte[] getKey() {
    return key;
  }

  @Override
  public Bucket createShadow() {
    Bucket newUnderlying = bucket.createShadow();
    if(newUnderlying == null) return null;
    return new PaddedEphemerallyEncryptedBucket(this, newUnderlying);
  }

    @Override
    public void onResume(ClientContext context) throws ResumeFailedException {
        randomSeed = new byte[32];
        context.fastWeakRandom.nextBytes(randomSeed);
        bucket.onResume(context);
    }
   
    public static final int MAGIC = 0x66c71fc9;
    static final int VERSION = 1;

    @Override
    public void storeTo(DataOutputStream dos) throws IOException {
        dos.writeInt(MAGIC);
        dos.writeInt(VERSION);
        dos.writeInt(minPaddedSize);
        dos.write(key);
        if(iv != null) {
            dos.writeBoolean(true);
            dos.write(iv);
        } else {
            dos.writeBoolean(false);
        }
        // randomSeed should be recovered in onResume().
        dos.writeLong(dataLength);
        dos.writeBoolean(readOnly);
        bucket.storeTo(dos);
    }
   
    protected PaddedEphemerallyEncryptedBucket(DataInputStream dis, FilenameGenerator fg,
            PersistentFileTracker persistentFileTracker, MasterSecret masterKey)
    throws StorageFormatException, IOException, ResumeFailedException {
        int version = dis.readInt();
        if(version != VERSION) throw new StorageFormatException("Bad version");
        minPaddedSize = dis.readInt();
        key = new byte[32];
        dis.readFully(key);
        if(dis.readBoolean()) {
            iv = new byte[32];
            dis.readFully(iv);
        } else {
            iv = null;
        }
        dataLength = dis.readLong();
        readOnly = dis.readBoolean();
        bucket = BucketTools.restoreFrom(dis, fg, persistentFileTracker, masterKey);
    }

}
TOP

Related Classes of freenet.support.io.PaddedEphemerallyEncryptedBucket$PaddedEphemerallyEncryptedInputStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.