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}