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}