001/*
002 * Copyright 2018-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2018-2024 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-2024 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  @NotNull private final CipherInputStream cipherInputStream;
065
066  // A header containing the encoded encryption details.
067  @NotNull 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(@NotNull final String passphrase,
096              @NotNull 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(@NotNull final char[] passphrase,
130              @NotNull 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(
156              @NotNull final InputStream wrappedInputStream,
157              @NotNull final PassphraseEncryptedStreamHeader encryptionHeader)
158         throws GeneralSecurityException
159  {
160    this.encryptionHeader = encryptionHeader;
161
162    final Cipher cipher = encryptionHeader.createCipher(Cipher.DECRYPT_MODE);
163    cipherInputStream = new CipherInputStream(wrappedInputStream, cipher);
164  }
165
166
167
168  /**
169   * Retrieves a single byte of decrypted data read from the underlying input
170   * stream.
171   *
172   * @return  A value that is between 0 and 255 representing the byte that was
173   *          read, or -1 to indicate that the end of the input stream has been
174   *          reached.
175   *
176   * @throws  IOException  If a problem is encountered while reading or
177   *                       decrypting the data.
178   */
179  @Override()
180  public int read()
181         throws IOException
182  {
183    return cipherInputStream.read();
184  }
185
186
187
188  /**
189   * Reads decrypted data and writes it into the provided byte array.
190   *
191   * @param  b  The byte array into which the decrypted data will be placed,
192   *            starting with an index of zero.  It must not be {@code null} or
193   *            empty.
194   *
195   * @return  The number of bytes added to the provided buffer, or -1 if the end
196   *          of the input stream has been reached and there is no more data to
197   *          read.
198   *
199   * @throws  IOException  If a problem is encountered while reading or
200   *                       decrypting the data.
201   */
202  @Override()
203  public int read(@NotNull final byte[] b)
204         throws IOException
205  {
206    return cipherInputStream.read(b);
207  }
208
209
210
211  /**
212   * Reads decrypted data and writes it into the specified portion of the
213   * provided byte array.
214   *
215   * @param  b       The byte array into which the decrypted data will be
216   *                 placed.  It must not be {@code null} or empty.
217   * @param  offset  The position in the provided array at which to begin adding
218   *                 the decrypted data.  It must be greater than or equal to
219   *                 zero and less than the length of the provided array.
220   * @param  length  The maximum number of bytes to be added to the given array.
221   *                 This must be greater than zero, and the sum of the
222   *                 {@code offset} and {@code length} must be less than or
223   *                 equal to the length of the provided array.
224   *
225   * @return  The number of bytes added to the provided buffer, or -1 if the end
226   *          of the input stream has been reached and there is no more data to
227   *          read.
228   *
229   * @throws  IOException  If a problem is encountered while reading or
230   *                       decrypting the data.
231   */
232  @Override()
233  public int read(@NotNull final byte[] b, final int offset, final int length)
234         throws IOException
235  {
236    return cipherInputStream.read(b, offset, length);
237  }
238
239
240
241  /**
242   * Skips over and discards up to the specified number of bytes of decrypted
243   * data obtained from the underlying input stream.
244   *
245   * @param  maxBytesToSkip  The maximum number of bytes to skip.
246   *
247   * @return  The number of bytes that were actually skipped.
248   *
249   * @throws  IOException  If a problem is encountered while skipping data from
250   *                       the stream.
251   */
252  @Override()
253  public long skip(final long maxBytesToSkip)
254         throws IOException
255  {
256    return cipherInputStream.skip(maxBytesToSkip);
257  }
258
259
260
261  /**
262   * Retrieves an estimate of the number of decrypted byte that are available to
263   * read from the underlying stream without blocking.  Note that some
264   * implementations always return a value of zero, so a return value of zero
265   * does not necessarily mean that there is no data available to read.
266   *
267   * @return  An estimate of the number of decrypted bytes that are available to
268   *          read from the underlying stream without blocking.
269   *
270   * @throws  IOException  If a problem is encountered while attempting to
271   *                       determine the number of bytes available to read.
272   */
273  @Override()
274  public int available()
275         throws IOException
276  {
277    return cipherInputStream.available();
278  }
279
280
281
282  /**
283   * Closes this input stream and the underlying stream.
284   *
285   * @throws  IOException  If a problem is encountered while closing the stream.
286   */
287  @Override()
288  public void close()
289         throws IOException
290  {
291    cipherInputStream.close();
292  }
293
294
295
296  /**
297   * Indicates whether this input stream supports the use of the
298   * {@link #mark(int)} and {@link #reset()} methods.
299   *
300   * @return  {@code true} if this input stream supports the {@code mark} and
301   *          {@code reset} methods, or {@code false} if not.
302   */
303  @Override()
304  public boolean markSupported()
305  {
306    return cipherInputStream.markSupported();
307  }
308
309
310
311  /**
312   * Marks the current position in this input stream so that the caller may
313   * return to that spot (and re-read the data) using the {@link #reset()}
314   * method.  Use the {@link #markSupported()} method to determine whether this
315   * feature is supported for this input stream.
316   *
317   * @param  readLimit  The maximum number of bytes expected to be read between
318   *                    the mark and the call to the {@code reset} method.
319   */
320  @Override()
321  public void mark(final int readLimit)
322  {
323    cipherInputStream.mark(readLimit);
324  }
325
326
327
328  /**
329   * Attempts to reset the position of this input stream to the position of the
330   * last call to {@link #mark(int)}.  Use the {@link #markSupported()} method
331   * to determine whether this feature is supported for ths input stream.
332   *
333   * @throws  IOException  If a problem is encountered while performing the
334   *                       reset (e.g., no mark has been set, if too much data
335   *                       has been read since setting the mark, or if the
336   *                       {@code mark} and {@code reset} methods are not
337   *                       supported).
338   */
339  @Override()
340  public void reset()
341         throws IOException
342  {
343    cipherInputStream.reset();
344  }
345
346
347
348  /**
349   * Retrieves an encryption header with details about the encryption used when
350   * the data was originally written.
351   *
352   * @return  An encryption header with details about the encryption used when
353   *          the data was originally written.
354   */
355  @NotNull()
356  public PassphraseEncryptedStreamHeader getEncryptionHeader()
357  {
358    return encryptionHeader;
359  }
360}