001/*
002 * Copyright 2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2018 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util;
022
023
024
025import java.io.IOException;
026import java.io.InputStream;
027import java.security.GeneralSecurityException;
028import java.security.InvalidKeyException;
029import javax.crypto.Cipher;
030import javax.crypto.CipherInputStream;
031
032import com.unboundid.ldap.sdk.LDAPException;
033
034
035
036/**
037 * This class provides an {@code InputStream} implementation that can read
038 * encrypted data written by the {@link PassphraseEncryptedOutputStream}.  It
039 * will use a provided password in conjunction with a
040 * {@link PassphraseEncryptedStreamHeader} that will either be read from the
041 * beginning of the stream or provided in the constructor.
042 */
043@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
044public final class PassphraseEncryptedInputStream
045       extends InputStream
046{
047  // The cipher input stream that will be used to actually read and decrypt the
048  // data.
049  private final CipherInputStream cipherInputStream;
050
051  // A header containing the encoded encryption details.
052  private final PassphraseEncryptedStreamHeader encryptionHeader;
053
054
055
056  /**
057   * Creates a new passphrase-encrypted input stream that will read the
058   * {@link PassphraseEncryptedStreamHeader} from the underlying input stream.
059   *
060   * @param  passphrase          The passphrase used to generate the encryption
061   *                             key when the corresponding
062   *                             {@link PassphraseEncryptedOutputStream} was
063   *                             created.
064   * @param  wrappedInputStream  The input stream from which the encryption
065   *                             header and encrypted data will be read.
066   *
067   * @throws  IOException  If a problem is encountered while trying to read the
068   *                       encryption header from the provided input stream.
069   *
070   * @throws  LDAPException  If s problem is encountered while trying to parse
071   *                         the encryption header read from the provided input
072   *                         stream.
073   *
074   * @throws  InvalidKeyException  If the MAC contained in the header does not
075   *                               match the expected value.
076   *
077   * @throws  GeneralSecurityException  If a problem occurs while attempting to
078   *                                    initialize the decryption.
079   */
080  public PassphraseEncryptedInputStream(final String passphrase,
081                                        final InputStream wrappedInputStream)
082         throws IOException, LDAPException, InvalidKeyException,
083                GeneralSecurityException
084  {
085    this(passphrase.toCharArray(), wrappedInputStream);
086  }
087
088
089
090  /**
091   * Creates a new passphrase-encrypted input stream that will read the
092   * {@link PassphraseEncryptedStreamHeader} from the underlying input stream.
093   *
094   * @param  passphrase          The passphrase used to generate the encryption
095   *                             key when the corresponding
096   *                             {@link PassphraseEncryptedOutputStream} was
097   *                             created.
098   * @param  wrappedInputStream  The input stream from which the encryption
099   *                             header and encrypted data will be read.
100   *
101   * @throws  IOException  If a problem is encountered while trying to read the
102   *                       encryption header from the provided input stream.
103   *
104   * @throws  LDAPException  If s problem is encountered while trying to parse
105   *                         the encryption header read from the provided input
106   *                         stream.
107   *
108   * @throws  InvalidKeyException  If the MAC contained in the header does not
109   *                               match the expected value.
110   *
111   * @throws  GeneralSecurityException  If a problem occurs while attempting to
112   *                                    initialize the decryption.
113   */
114  public PassphraseEncryptedInputStream(final char[] passphrase,
115                                        final InputStream wrappedInputStream)
116         throws IOException, LDAPException, InvalidKeyException,
117                GeneralSecurityException
118  {
119    this(wrappedInputStream,
120         PassphraseEncryptedStreamHeader.readFrom(wrappedInputStream,
121              passphrase));
122  }
123
124
125
126  /**
127   * Creates a new passphrase-encrypted input stream using the provided
128   * information.
129   *
130   * @param  wrappedInputStream  The input stream from which the encrypted data
131   *                             will be read.
132   * @param  encryptionHeader    The encryption header with the information
133   *                             needed (in conjunction with the given
134   *                             passphrase) to decrypt the data read from the
135   *                             provided input stream.
136   *
137   * @throws  GeneralSecurityException  If a problem occurs while attempting to
138   *                                    initialize the decryption.
139   */
140  public PassphraseEncryptedInputStream(final InputStream wrappedInputStream,
141              final PassphraseEncryptedStreamHeader encryptionHeader)
142         throws GeneralSecurityException
143  {
144    this.encryptionHeader = encryptionHeader;
145
146    final Cipher cipher = encryptionHeader.createCipher(Cipher.DECRYPT_MODE);
147    cipherInputStream = new CipherInputStream(wrappedInputStream, cipher);
148  }
149
150
151
152  /**
153   * Retrieves a single byte of decrypted data read from the underlying input
154   * stream.
155   *
156   * @return  A value that is between 0 and 255 representing the byte that was
157   *          read, or -1 to indicate that the end of the input stream has been
158   *          reached.
159   *
160   * @throws  IOException  If a problem is encountered while reading or
161   *                       decrypting the data.
162   */
163  @Override()
164  public int read()
165         throws IOException
166  {
167    return cipherInputStream.read();
168  }
169
170
171
172  /**
173   * Reads decrypted data and writes it into the provided byte array.
174   *
175   * @param  b  The byte array into which the decrypted data will be placed,
176   *            starting with an index of zero.  It must not be {@code null} or
177   *            empty.
178   *
179   * @return  The number of bytes added to the provided buffer, or -1 if the end
180   *          of the input stream has been reached and there is no more data to
181   *          read.
182   *
183   * @throws  IOException  If a problem is encountered while reading or
184   *                       decrypting the data.
185   */
186  @Override()
187  public int read(final byte[] b)
188         throws IOException
189  {
190    return cipherInputStream.read(b);
191  }
192
193
194
195  /**
196   * Reads decrypted data and writes it into the specified portion of the
197   * provided byte array.
198   *
199   * @param  b       The byte array into which the decrypted data will be
200   *                 placed.  It must not be {@code null} or empty.
201   * @param  offset  The position in the provided array at which to begin adding
202   *                 the decrypted data.  It must be greater than or equal to
203   *                 zero and less than the length of the provided array.
204   * @param  length  The maximum number of bytes to be added to the given array.
205   *                 This must be greater than zero, and the sum of the
206   *                 {@code offset} and {@code length} must be less than or
207   *                 equal to the length of the provided array.
208   *
209   * @return  The number of bytes added to the provided buffer, or -1 if the end
210   *          of the input stream has been reached and there is no more data to
211   *          read.
212   *
213   * @throws  IOException  If a problem is encountered while reading or
214   *                       decrypting the data.
215   */
216  @Override()
217  public int read(final byte[] b, final int offset, final int length)
218         throws IOException
219  {
220    return cipherInputStream.read(b, offset, length);
221  }
222
223
224
225  /**
226   * Skips over and discards up to the specified number of bytes of decrypted
227   * data obtained from the underlying input stream.
228   *
229   * @param  maxBytesToSkip  The maximum number of bytes to skip.
230   *
231   * @return  The number of bytes that were actually skipped.
232   *
233   * @throws  IOException  If a problem is encountered while skipping data from
234   *                       the stream.
235   */
236  @Override()
237  public long skip(final long maxBytesToSkip)
238         throws IOException
239  {
240    return cipherInputStream.skip(maxBytesToSkip);
241  }
242
243
244
245  /**
246   * Retrieves an estimate of the number of decrypted byte that are available to
247   * read from the underlying stream without blocking.  Note that some
248   * implementations always return a value of zero, so a return value of zero
249   * does not necessarily mean that there is no data available to read.
250   *
251   * @return  An estimate of the number of decrypted bytes that are available to
252   *          read from the underlying stream without blocking.
253   *
254   * @throws  IOException  If a problem is encountered while attempting to
255   *                       determine the number of bytes available to read.
256   */
257  @Override()
258  public int available()
259         throws IOException
260  {
261    return cipherInputStream.available();
262  }
263
264
265
266  /**
267   * Closes this input stream and the underlying stream.
268   *
269   * @throws  IOException  If a problem is encountered while closing the stream.
270   */
271  @Override()
272  public void close()
273         throws IOException
274  {
275    cipherInputStream.close();
276  }
277
278
279
280  /**
281   * Indicates whether this input stream supports the use of the
282   * {@link #mark(int)} and {@link #reset()} methods.
283   *
284   * @return  {@code true} if this input stream supports the {@code mark} and
285   *          {@code reset} methods, or {@code false} if not.
286   */
287  @Override()
288  public boolean markSupported()
289  {
290    return cipherInputStream.markSupported();
291  }
292
293
294
295  /**
296   * Marks the current position in this input stream so that the caller may
297   * return to that spot (and re-read the data) using the {@link #reset()}
298   * method.  Use the {@link #markSupported()} method to determine whether this
299   * feature is supported for this input stream.
300   *
301   * @param  readLimit  The maximum number of bytes expected to be read between
302   *                    the mark and the call to the {@code reset} method.
303   */
304  @Override()
305  public void mark(final int readLimit)
306  {
307    cipherInputStream.mark(readLimit);
308  }
309
310
311
312  /**
313   * Attempts to reset the position of this input stream to the position of the
314   * last call to {@link #mark(int)}.  Use the {@link #markSupported()} method
315   * to determine whether this feature is supported for ths input stream.
316   *
317   * @throws  IOException  If a problem is encountered while performing the
318   *                       reset (e.g., no mark has been set, if too much data
319   *                       has been read since setting the mark, or if the
320   *                       {@code mark} and {@code reset} methods are not
321   *                       supported).
322   */
323  @Override()
324  public void reset()
325         throws IOException
326  {
327    cipherInputStream.reset();
328  }
329
330
331
332  /**
333   * Retrieves an encryption header with details about the encryption used when
334   * the data was originally written.
335   *
336   * @return  An encryption header with details about the encryption used when
337   *          the data was originally written.
338   */
339  public PassphraseEncryptedStreamHeader getEncryptionHeader()
340  {
341    return encryptionHeader;
342  }
343}