diff options
Diffstat (limited to 'core/src/main/java/org/spongycastle/crypto/io')
10 files changed, 1076 insertions, 0 deletions
diff --git a/core/src/main/java/org/spongycastle/crypto/io/CipherIOException.java b/core/src/main/java/org/spongycastle/crypto/io/CipherIOException.java new file mode 100644 index 00000000..c621c6b1 --- /dev/null +++ b/core/src/main/java/org/spongycastle/crypto/io/CipherIOException.java @@ -0,0 +1,26 @@ +package org.spongycastle.crypto.io; + +import java.io.IOException; + +/** + * {@link IOException} wrapper around an exception indicating a problem with the use of a cipher. + */ +public class CipherIOException + extends IOException +{ + private static final long serialVersionUID = 1L; + + private final Throwable cause; + + public CipherIOException(String message, Throwable cause) + { + super(message); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/crypto/io/CipherInputStream.java b/core/src/main/java/org/spongycastle/crypto/io/CipherInputStream.java new file mode 100644 index 00000000..625e6659 --- /dev/null +++ b/core/src/main/java/org/spongycastle/crypto/io/CipherInputStream.java @@ -0,0 +1,477 @@ +package org.spongycastle.crypto.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.SkippingCipher; +import org.spongycastle.crypto.StreamCipher; +import org.spongycastle.crypto.modes.AEADBlockCipher; +import org.spongycastle.util.Arrays; + +/** + * A CipherInputStream is composed of an InputStream and a cipher so that read() methods return data + * that are read in from the underlying InputStream but have been additionally processed by the + * Cipher. The cipher must be fully initialized before being used by a CipherInputStream. + * <p> + * For example, if the Cipher is initialized for decryption, the + * CipherInputStream will attempt to read in data and decrypt them, + * before returning the decrypted data. + */ +public class CipherInputStream + extends FilterInputStream +{ + private static final int INPUT_BUF_SIZE = 2048; + + private SkippingCipher skippingCipher; + private byte[] inBuf; + + private BufferedBlockCipher bufferedBlockCipher; + private StreamCipher streamCipher; + private AEADBlockCipher aeadBlockCipher; + + private byte[] buf; + private byte[] markBuf; + + + private int bufOff; + private int maxBuf; + private boolean finalized; + private long markPosition; + private int markBufOff; + + /** + * Constructs a CipherInputStream from an InputStream and a + * BufferedBlockCipher. + */ + public CipherInputStream( + InputStream is, + BufferedBlockCipher cipher) + { + this(is, cipher, INPUT_BUF_SIZE); + } + + /** + * Constructs a CipherInputStream from an InputStream and a StreamCipher. + */ + public CipherInputStream( + InputStream is, + StreamCipher cipher) + { + this(is, cipher, INPUT_BUF_SIZE); + } + + /** + * Constructs a CipherInputStream from an InputStream and an AEADBlockCipher. + */ + public CipherInputStream( + InputStream is, + AEADBlockCipher cipher) + { + this(is, cipher, INPUT_BUF_SIZE); + } + + /** + * Constructs a CipherInputStream from an InputStream, a + * BufferedBlockCipher, and a specified internal buffer size. + */ + public CipherInputStream( + InputStream is, + BufferedBlockCipher cipher, + int bufSize) + { + super(is); + + this.bufferedBlockCipher = cipher; + this.inBuf = new byte[bufSize]; + this.skippingCipher = (cipher instanceof SkippingCipher) ? (SkippingCipher)cipher : null; + } + + /** + * Constructs a CipherInputStream from an InputStream, a StreamCipher, and a specified internal buffer size. + */ + public CipherInputStream( + InputStream is, + StreamCipher cipher, + int bufSize) + { + super(is); + + this.streamCipher = cipher; + this.inBuf = new byte[bufSize]; + this.skippingCipher = (cipher instanceof SkippingCipher) ? (SkippingCipher)cipher : null; + } + + /** + * Constructs a CipherInputStream from an InputStream, an AEADBlockCipher, and a specified internal buffer size. + */ + public CipherInputStream( + InputStream is, + AEADBlockCipher cipher, + int bufSize) + { + super(is); + + this.aeadBlockCipher = cipher; + this.inBuf = new byte[bufSize]; + this.skippingCipher = (cipher instanceof SkippingCipher) ? (SkippingCipher)cipher : null; + } + + /** + * Read data from underlying stream and process with cipher until end of stream or some data is + * available after cipher processing. + * + * @return -1 to indicate end of stream, or the number of bytes (> 0) available. + */ + private int nextChunk() + throws IOException + { + if (finalized) + { + return -1; + } + + bufOff = 0; + maxBuf = 0; + + // Keep reading until EOF or cipher processing produces data + while (maxBuf == 0) + { + int read = in.read(inBuf); + if (read == -1) + { + finaliseCipher(); + if (maxBuf == 0) + { + return -1; + } + return maxBuf; + } + + try + { + ensureCapacity(read, false); + if (bufferedBlockCipher != null) + { + maxBuf = bufferedBlockCipher.processBytes(inBuf, 0, read, buf, 0); + } + else if (aeadBlockCipher != null) + { + maxBuf = aeadBlockCipher.processBytes(inBuf, 0, read, buf, 0); + } + else + { + streamCipher.processBytes(inBuf, 0, read, buf, 0); + maxBuf = read; + } + } + catch (Exception e) + { + throw new CipherIOException("Error processing stream ", e); + } + } + return maxBuf; + } + + private void finaliseCipher() + throws IOException + { + try + { + finalized = true; + ensureCapacity(0, true); + if (bufferedBlockCipher != null) + { + maxBuf = bufferedBlockCipher.doFinal(buf, 0); + } + else if (aeadBlockCipher != null) + { + maxBuf = aeadBlockCipher.doFinal(buf, 0); + } + else + { + maxBuf = 0; // a stream cipher + } + } + catch (final InvalidCipherTextException e) + { + throw new InvalidCipherTextIOException("Error finalising cipher", e); + } + catch (Exception e) + { + throw new IOException("Error finalising cipher " + e); + } + } + + /** + * Reads data from the underlying stream and processes it with the cipher until the cipher + * outputs data, and returns the next available byte. + * <p> + * If the underlying stream is exhausted by this call, the cipher will be finalised. + * </p> + * @throws IOException if there was an error closing the input stream. + * @throws InvalidCipherTextIOException if the data read from the stream was invalid ciphertext + * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). + */ + public int read() + throws IOException + { + if (bufOff >= maxBuf) + { + if (nextChunk() < 0) + { + return -1; + } + } + + return buf[bufOff++] & 0xff; + } + + /** + * Reads data from the underlying stream and processes it with the cipher until the cipher + * outputs data, and then returns up to <code>b.length</code> bytes in the provided array. + * <p> + * If the underlying stream is exhausted by this call, the cipher will be finalised. + * </p> + * @param b the buffer into which the data is read. + * @return the total number of bytes read into the buffer, or <code>-1</code> if there is no + * more data because the end of the stream has been reached. + * @throws IOException if there was an error closing the input stream. + * @throws InvalidCipherTextIOException if the data read from the stream was invalid ciphertext + * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). + */ + public int read( + byte[] b) + throws IOException + { + return read(b, 0, b.length); + } + + /** + * Reads data from the underlying stream and processes it with the cipher until the cipher + * outputs data, and then returns up to <code>len</code> bytes in the provided array. + * <p> + * If the underlying stream is exhausted by this call, the cipher will be finalised. + * </p> + * @param b the buffer into which the data is read. + * @param off the start offset in the destination array <code>b</code> + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or <code>-1</code> if there is no + * more data because the end of the stream has been reached. + * @throws IOException if there was an error closing the input stream. + * @throws InvalidCipherTextIOException if the data read from the stream was invalid ciphertext + * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). + */ + public int read( + byte[] b, + int off, + int len) + throws IOException + { + if (bufOff >= maxBuf) + { + if (nextChunk() < 0) + { + return -1; + } + } + + int toSupply = Math.min(len, available()); + System.arraycopy(buf, bufOff, b, off, toSupply); + bufOff += toSupply; + return toSupply; + } + + public long skip( + long n) + throws IOException + { + if (n <= 0) + { + return 0; + } + + if (skippingCipher != null) + { + int avail = available(); + if (n <= avail) + { + bufOff += n; + + return n; + } + + bufOff = maxBuf; + + long skip = in.skip(n - avail); + + long cSkip = skippingCipher.skip(skip); + + if (skip != cSkip) + { + throw new IOException("Unable to skip cipher " + skip + " bytes."); + } + + return skip + avail; + } + else + { + int skip = (int)Math.min(n, available()); + bufOff += skip; + + return skip; + } + } + + public int available() + throws IOException + { + return maxBuf - bufOff; + } + + /** + * Ensure the cipher text buffer has space sufficient to accept an upcoming output. + * + * @param updateSize the size of the pending update. + * @param finalOutput <code>true</code> iff this the cipher is to be finalised. + */ + private void ensureCapacity(int updateSize, boolean finalOutput) + { + int bufLen = updateSize; + if (finalOutput) + { + if (bufferedBlockCipher != null) + { + bufLen = bufferedBlockCipher.getOutputSize(updateSize); + } + else if (aeadBlockCipher != null) + { + bufLen = aeadBlockCipher.getOutputSize(updateSize); + } + } + else + { + if (bufferedBlockCipher != null) + { + bufLen = bufferedBlockCipher.getUpdateOutputSize(updateSize); + } + else if (aeadBlockCipher != null) + { + bufLen = aeadBlockCipher.getUpdateOutputSize(updateSize); + } + } + + if ((buf == null) || (buf.length < bufLen)) + { + buf = new byte[bufLen]; + } + } + + /** + * Closes the underlying input stream and finalises the processing of the data by the cipher. + * + * @throws IOException if there was an error closing the input stream. + * @throws InvalidCipherTextIOException if the data read from the stream was invalid ciphertext + * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). + */ + public void close() + throws IOException + { + try + { + in.close(); + } + finally + { + if (!finalized) + { + // Reset the cipher, discarding any data buffered in it + // Errors in cipher finalisation trump I/O error closing input + finaliseCipher(); + } + } + maxBuf = bufOff = 0; + markBufOff = 0; + markPosition = 0; + if (markBuf != null) + { + Arrays.fill(markBuf, (byte)0); + markBuf = null; + } + if (buf != null) + { + Arrays.fill(buf, (byte)0); + buf = null; + } + Arrays.fill(inBuf, (byte)0); + } + + /** + * Mark the current position. + * <p> + * This method only works if markSupported() returns true - which means the underlying stream supports marking, and the cipher passed + * in to this stream's constructor is a SkippingCipher (so capable of being reset to an arbitrary point easily). + * </p> + * @param readlimit the maximum read ahead required before a reset() may be called. + */ + public void mark(int readlimit) + { + in.mark(readlimit); + if (skippingCipher != null) + { + markPosition = skippingCipher.getPosition(); + } + + if (buf != null) + { + markBuf = new byte[buf.length]; + System.arraycopy(buf, 0, markBuf, 0, buf.length); + } + + markBufOff = bufOff; + } + + /** + * Reset to the last marked position, if supported. + * + * @throws IOException if marking not supported by the cipher used, or the underlying stream. + */ + public void reset() + throws IOException + { + if (skippingCipher == null) + { + throw new IOException("cipher must implement SkippingCipher to be used with reset()"); + } + + in.reset(); + + skippingCipher.seekTo(markPosition); + + if (markBuf != null) + { + buf = markBuf; + } + + bufOff = markBufOff; + } + + /** + * Return true if mark(readlimit) is supported. This will be true if the underlying stream supports marking and the + * cipher used is a SkippingCipher, + * + * @return true if mark(readlimit) supported, false otherwise. + */ + public boolean markSupported() + { + if (skippingCipher != null) + { + return in.markSupported(); + } + + return false; + } + +} diff --git a/core/src/main/java/org/spongycastle/crypto/io/CipherOutputStream.java b/core/src/main/java/org/spongycastle/crypto/io/CipherOutputStream.java new file mode 100644 index 00000000..e45962f3 --- /dev/null +++ b/core/src/main/java/org/spongycastle/crypto/io/CipherOutputStream.java @@ -0,0 +1,276 @@ +package org.spongycastle.crypto.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.StreamCipher; +import org.spongycastle.crypto.modes.AEADBlockCipher; + +/** + * A CipherOutputStream is composed of an OutputStream and a cipher so that write() methods process + * the written data with the cipher, and the output of the cipher is in turn written to the + * underlying OutputStream. The cipher must be fully initialized before being used by a + * CipherInputStream. + * <p> + * For example, if the cipher is initialized for encryption, the CipherOutputStream will encrypt the + * data before writing the encrypted data to the underlying stream. + */ +public class CipherOutputStream + extends FilterOutputStream +{ + private BufferedBlockCipher bufferedBlockCipher; + private StreamCipher streamCipher; + private AEADBlockCipher aeadBlockCipher; + + private final byte[] oneByte = new byte[1]; + private byte[] buf; + + /** + * Constructs a CipherOutputStream from an OutputStream and a + * BufferedBlockCipher. + */ + public CipherOutputStream( + OutputStream os, + BufferedBlockCipher cipher) + { + super(os); + this.bufferedBlockCipher = cipher; + } + + /** + * Constructs a CipherOutputStream from an OutputStream and a + * BufferedBlockCipher. + */ + public CipherOutputStream( + OutputStream os, + StreamCipher cipher) + { + super(os); + this.streamCipher = cipher; + } + + /** + * Constructs a CipherOutputStream from an OutputStream and a AEADBlockCipher. + */ + public CipherOutputStream(OutputStream os, AEADBlockCipher cipher) + { + super(os); + this.aeadBlockCipher = cipher; + } + + /** + * Writes the specified byte to this output stream. + * + * @param b the <code>byte</code>. + * @throws java.io.IOException if an I/O error occurs. + */ + public void write( + int b) + throws IOException + { + oneByte[0] = (byte)b; + + if (streamCipher != null) + { + out.write(streamCipher.returnByte((byte)b)); + } + else + { + write(oneByte, 0, 1); + } + } + + /** + * Writes <code>b.length</code> bytes from the specified byte array + * to this output stream. + * <p> + * The <code>write</code> method of + * <code>CipherOutputStream</code> calls the <code>write</code> + * method of three arguments with the three arguments + * <code>b</code>, <code>0</code>, and <code>b.length</code>. + * + * @param b the data. + * @throws java.io.IOException if an I/O error occurs. + * @see #write(byte[], int, int) + */ + public void write( + byte[] b) + throws IOException + { + write(b, 0, b.length); + } + + /** + * Writes <code>len</code> bytes from the specified byte array + * starting at offset <code>off</code> to this output stream. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @throws java.io.IOException if an I/O error occurs. + */ + public void write( + byte[] b, + int off, + int len) + throws IOException + { + ensureCapacity(len, false); + + if (bufferedBlockCipher != null) + { + int outLen = bufferedBlockCipher.processBytes(b, off, len, buf, 0); + + if (outLen != 0) + { + out.write(buf, 0, outLen); + } + } + else if (aeadBlockCipher != null) + { + int outLen = aeadBlockCipher.processBytes(b, off, len, buf, 0); + + if (outLen != 0) + { + out.write(buf, 0, outLen); + } + } + else + { + streamCipher.processBytes(b, off, len, buf, 0); + + out.write(buf, 0, len); + } + } + + /** + * Ensure the ciphertext buffer has space sufficient to accept an upcoming output. + * + * @param updateSize the size of the pending update. + * @param finalOutput <code>true</code> iff this the cipher is to be finalised. + */ + private void ensureCapacity(int updateSize, boolean finalOutput) + { + int bufLen = updateSize; + if (finalOutput) + { + if (bufferedBlockCipher != null) + { + bufLen = bufferedBlockCipher.getOutputSize(updateSize); + } + else if (aeadBlockCipher != null) + { + bufLen = aeadBlockCipher.getOutputSize(updateSize); + } + } + else + { + if (bufferedBlockCipher != null) + { + bufLen = bufferedBlockCipher.getUpdateOutputSize(updateSize); + } + else if (aeadBlockCipher != null) + { + bufLen = aeadBlockCipher.getUpdateOutputSize(updateSize); + } + } + + if ((buf == null) || (buf.length < bufLen)) + { + buf = new byte[bufLen]; + } + } + + /** + * Flushes this output stream by forcing any buffered output bytes + * that have already been processed by the encapsulated cipher object + * to be written out. + * <p> + * Any bytes buffered by the encapsulated cipher + * and waiting to be processed by it will not be written out. For example, + * if the encapsulated cipher is a block cipher, and the total number of + * bytes written using one of the <code>write</code> methods is less than + * the cipher's block size, no bytes will be written out. + * + * @throws java.io.IOException if an I/O error occurs. + */ + public void flush() + throws IOException + { + out.flush(); + } + + /** + * Closes this output stream and releases any system resources + * associated with this stream. + * <p> + * This method invokes the <code>doFinal</code> method of the encapsulated + * cipher object, which causes any bytes buffered by the encapsulated + * cipher to be processed. The result is written out by calling the + * <code>flush</code> method of this output stream. + * <p> + * This method resets the encapsulated cipher object to its initial state + * and calls the <code>close</code> method of the underlying output + * stream. + * + * @throws java.io.IOException if an I/O error occurs. + * @throws InvalidCipherTextIOException if the data written to this stream was invalid ciphertext + * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). + */ + public void close() + throws IOException + { + ensureCapacity(0, true); + IOException error = null; + try + { + if (bufferedBlockCipher != null) + { + int outLen = bufferedBlockCipher.doFinal(buf, 0); + + if (outLen != 0) + { + out.write(buf, 0, outLen); + } + } + else if (aeadBlockCipher != null) + { + int outLen = aeadBlockCipher.doFinal(buf, 0); + + if (outLen != 0) + { + out.write(buf, 0, outLen); + } + } + } + catch (final InvalidCipherTextException e) + { + error = new InvalidCipherTextIOException("Error finalising cipher data", e); + } + catch (Exception e) + { + error = new CipherIOException("Error closing stream: ", e); + } + + try + { + flush(); + out.close(); + } + catch (IOException e) + { + // Invalid ciphertext takes precedence over close error + if (error == null) + { + error = e; + } + } + if (error != null) + { + throw error; + } + } +} diff --git a/core/src/main/java/org/spongycastle/crypto/io/DigestInputStream.java b/core/src/main/java/org/spongycastle/crypto/io/DigestInputStream.java new file mode 100644 index 00000000..23a71964 --- /dev/null +++ b/core/src/main/java/org/spongycastle/crypto/io/DigestInputStream.java @@ -0,0 +1,52 @@ +package org.spongycastle.crypto.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.crypto.Digest; + +public class DigestInputStream + extends FilterInputStream +{ + protected Digest digest; + + public DigestInputStream( + InputStream stream, + Digest digest) + { + super(stream); + this.digest = digest; + } + + public int read() + throws IOException + { + int b = in.read(); + + if (b >= 0) + { + digest.update((byte)b); + } + return b; + } + + public int read( + byte[] b, + int off, + int len) + throws IOException + { + int n = in.read(b, off, len); + if (n > 0) + { + digest.update(b, off, n); + } + return n; + } + + public Digest getDigest() + { + return digest; + } +} diff --git a/core/src/main/java/org/spongycastle/crypto/io/DigestOutputStream.java b/core/src/main/java/org/spongycastle/crypto/io/DigestOutputStream.java new file mode 100644 index 00000000..d280b514 --- /dev/null +++ b/core/src/main/java/org/spongycastle/crypto/io/DigestOutputStream.java @@ -0,0 +1,42 @@ +package org.spongycastle.crypto.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.crypto.Digest; + +public class DigestOutputStream + extends OutputStream +{ + protected Digest digest; + + public DigestOutputStream( + Digest Digest) + { + this.digest = Digest; + } + + public void write(int b) + throws IOException + { + digest.update((byte)b); + } + + public void write( + byte[] b, + int off, + int len) + throws IOException + { + digest.update(b, off, len); + } + + public byte[] getDigest() + { + byte[] res = new byte[digest.getDigestSize()]; + + digest.doFinal(res, 0); + + return res; + } +} diff --git a/core/src/main/java/org/spongycastle/crypto/io/InvalidCipherTextIOException.java b/core/src/main/java/org/spongycastle/crypto/io/InvalidCipherTextIOException.java new file mode 100644 index 00000000..fd488a97 --- /dev/null +++ b/core/src/main/java/org/spongycastle/crypto/io/InvalidCipherTextIOException.java @@ -0,0 +1,19 @@ +package org.spongycastle.crypto.io; + +import java.io.IOException; + +/** + * {@link IOException} wrapper around an exception indicating an invalid ciphertext, such as in + * authentication failure during finalisation of an AEAD cipher. For use in streams that need to + * expose invalid ciphertext errors. + */ +public class InvalidCipherTextIOException + extends CipherIOException +{ + private static final long serialVersionUID = 1L; + + public InvalidCipherTextIOException(String message, Throwable cause) + { + super(message, cause); + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/crypto/io/MacInputStream.java b/core/src/main/java/org/spongycastle/crypto/io/MacInputStream.java new file mode 100644 index 00000000..35e0e9c2 --- /dev/null +++ b/core/src/main/java/org/spongycastle/crypto/io/MacInputStream.java @@ -0,0 +1,52 @@ +package org.spongycastle.crypto.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.crypto.Mac; + +public class MacInputStream + extends FilterInputStream +{ + protected Mac mac; + + public MacInputStream( + InputStream stream, + Mac mac) + { + super(stream); + this.mac = mac; + } + + public int read() + throws IOException + { + int b = in.read(); + + if (b >= 0) + { + mac.update((byte)b); + } + return b; + } + + public int read( + byte[] b, + int off, + int len) + throws IOException + { + int n = in.read(b, off, len); + if (n >= 0) + { + mac.update(b, off, n); + } + return n; + } + + public Mac getMac() + { + return mac; + } +} diff --git a/core/src/main/java/org/spongycastle/crypto/io/MacOutputStream.java b/core/src/main/java/org/spongycastle/crypto/io/MacOutputStream.java new file mode 100644 index 00000000..340b994e --- /dev/null +++ b/core/src/main/java/org/spongycastle/crypto/io/MacOutputStream.java @@ -0,0 +1,42 @@ +package org.spongycastle.crypto.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.crypto.Mac; + +public class MacOutputStream + extends OutputStream +{ + protected Mac mac; + + public MacOutputStream( + Mac mac) + { + this.mac = mac; + } + + public void write(int b) + throws IOException + { + mac.update((byte)b); + } + + public void write( + byte[] b, + int off, + int len) + throws IOException + { + mac.update(b, off, len); + } + + public byte[] getMac() + { + byte[] res = new byte[mac.getMacSize()]; + + mac.doFinal(res, 0); + + return res; + } +} diff --git a/core/src/main/java/org/spongycastle/crypto/io/SignerInputStream.java b/core/src/main/java/org/spongycastle/crypto/io/SignerInputStream.java new file mode 100644 index 00000000..f7f6935f --- /dev/null +++ b/core/src/main/java/org/spongycastle/crypto/io/SignerInputStream.java @@ -0,0 +1,52 @@ +package org.spongycastle.crypto.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.crypto.Signer; + +public class SignerInputStream + extends FilterInputStream +{ + protected Signer signer; + + public SignerInputStream( + InputStream stream, + Signer signer) + { + super(stream); + this.signer = signer; + } + + public int read() + throws IOException + { + int b = in.read(); + + if (b >= 0) + { + signer.update((byte)b); + } + return b; + } + + public int read( + byte[] b, + int off, + int len) + throws IOException + { + int n = in.read(b, off, len); + if (n > 0) + { + signer.update(b, off, n); + } + return n; + } + + public Signer getSigner() + { + return signer; + } +} diff --git a/core/src/main/java/org/spongycastle/crypto/io/SignerOutputStream.java b/core/src/main/java/org/spongycastle/crypto/io/SignerOutputStream.java new file mode 100644 index 00000000..6e6c55b1 --- /dev/null +++ b/core/src/main/java/org/spongycastle/crypto/io/SignerOutputStream.java @@ -0,0 +1,38 @@ +package org.spongycastle.crypto.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.crypto.Signer; + +public class SignerOutputStream + extends OutputStream +{ + protected Signer signer; + + public SignerOutputStream( + Signer Signer) + { + this.signer = Signer; + } + + public void write(int b) + throws IOException + { + signer.update((byte)b); + } + + public void write( + byte[] b, + int off, + int len) + throws IOException + { + signer.update(b, off, len); + } + + public Signer getSigner() + { + return signer; + } +} |