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}