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