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.OutputStream; 041import java.io.IOException; 042import java.io.InputStream; 043import java.io.Serializable; 044import java.security.GeneralSecurityException; 045import java.security.InvalidKeyException; 046import java.util.ArrayList; 047import java.util.Arrays; 048import java.util.logging.Level; 049 050import javax.crypto.Cipher; 051import javax.crypto.Mac; 052import javax.crypto.SecretKey; 053import javax.crypto.SecretKeyFactory; 054import javax.crypto.spec.IvParameterSpec; 055import javax.crypto.spec.PBEKeySpec; 056import javax.crypto.spec.SecretKeySpec; 057 058import com.unboundid.asn1.ASN1Element; 059import com.unboundid.asn1.ASN1Integer; 060import com.unboundid.asn1.ASN1OctetString; 061import com.unboundid.asn1.ASN1Sequence; 062import com.unboundid.ldap.sdk.LDAPException; 063import com.unboundid.ldap.sdk.ResultCode; 064 065import static com.unboundid.util.UtilityMessages.*; 066 067 068 069/** 070 * This class represents a data structure that will be used to hold information 071 * about the encryption performed by the {@link PassphraseEncryptedOutputStream} 072 * when writing encrypted data, and that will be used by a 073 * {@link PassphraseEncryptedInputStream} to obtain the settings needed to 074 * decrypt the encrypted data. 075 * <BR><BR> 076 * The data associated with this class is completely threadsafe. The methods 077 * used to interact with input and output streams are not threadsafe in that 078 * nothing else should be attempting to read from/write to the stream at the 079 * same time. 080 */ 081@NotMutable() 082@ThreadSafety(level=ThreadSafetyLevel.MOSTLY_THREADSAFE) 083public final class PassphraseEncryptedStreamHeader 084 implements Serializable 085{ 086 /** 087 * The BER type used for the header element that specifies the encoding 088 * version. 089 */ 090 static final byte TYPE_ENCODING_VERSION = (byte) 0x80; 091 092 093 094 /** 095 * The BER type used for the header element containing the key factory 096 * algorithm. 097 */ 098 static final byte TYPE_KEY_FACTORY_ALGORITHM = (byte) 0x81; 099 100 101 102 /** 103 * The BER type used for the header element containing the key factory 104 * iteration count. 105 */ 106 static final byte TYPE_KEY_FACTORY_ITERATION_COUNT = (byte) 0x82; 107 108 109 110 /** 111 * The BER type used for the header element containing the key factory salt. 112 */ 113 static final byte TYPE_KEY_FACTORY_SALT = (byte) 0x83; 114 115 116 117 /** 118 * The BER type used for the header element containing the key length in bits. 119 */ 120 static final byte TYPE_KEY_FACTORY_KEY_LENGTH_BITS = (byte) 0x84; 121 122 123 124 /** 125 * The BER type used for the header element containing the cipher 126 * transformation. 127 */ 128 static final byte TYPE_CIPHER_TRANSFORMATION = (byte) 0x85; 129 130 131 132 /** 133 * The BER type used for the header element containing the cipher 134 * initialization vector. 135 */ 136 static final byte TYPE_CIPHER_INITIALIZATION_VECTOR = (byte) 0x86; 137 138 139 140 /** 141 * The BER type used for the header element containing the key identifier. 142 */ 143 static final byte TYPE_KEY_IDENTIFIER = (byte) 0x87; 144 145 146 147 /** 148 * The BER type used for the header element containing the MAC algorithm name. 149 */ 150 static final byte TYPE_MAC_ALGORITHM = (byte) 0x88; 151 152 153 154 /** 155 * The BER type used for the header element containing the MAC value. 156 */ 157 static final byte TYPE_MAC_VALUE = (byte) 0x89; 158 159 160 161 /** 162 * The "magic" value that will appear at the start of the header. 163 */ 164 @NotNull public static final byte[] MAGIC_BYTES = 165 { 0x50, 0x55, 0x4C, 0x53, 0x50, 0x45, 0x53, 0x48 }; 166 167 168 169 /** 170 * The encoding version for a v1 encoding. 171 */ 172 static final int ENCODING_VERSION_1 = 1; 173 174 175 176 /** 177 * The serial version UID for this serializable class. 178 */ 179 private static final long serialVersionUID = 6756983626170064762L; 180 181 182 183 // The initialization vector used when creating the cipher. 184 @NotNull private final byte[] cipherInitializationVector; 185 186 // An encoded representation of this header. 187 @NotNull private final byte[] encodedHeader; 188 189 // The salt used when generating the encryption key from the passphrase. 190 @NotNull private final byte[] keyFactorySalt; 191 192 // A MAC of the header content. 193 @NotNull private final byte[] macValue; 194 195 // The iteration count used when generating the encryption key from the 196 private final int keyFactoryIterationCount; 197 // passphrase. 198 199 // The length (in bits) of the encryption key generated from the passphrase. 200 private final int keyFactoryKeyLengthBits; 201 202 // The secret key generated from the passphrase. 203 @Nullable private final SecretKey secretKey; 204 205 // The cipher transformation used for the encryption. 206 @NotNull private final String cipherTransformation; 207 208 // The name of the key factory used to generate the encryption key from the 209 // passphrase. 210 @NotNull private final String keyFactoryAlgorithm; 211 212 // An optional identifier that can be used to associate this header with some 213 // other encryption settings object. 214 @Nullable private final String keyIdentifier; 215 216 // The algorithm used to generate a MAC of the header content. 217 @NotNull private final String macAlgorithm; 218 219 220 221 /** 222 * Creates a new passphrase-encrypted stream header with the provided 223 * information. 224 * 225 * @param keyFactoryAlgorithm The key factory algorithm used to 226 * generate the encryption key from the 227 * passphrase. It must not be 228 * {@code null}. 229 * @param keyFactoryIterationCount The iteration count used to generate 230 * the encryption key from the passphrase. 231 * @param keyFactorySalt The salt used to generate the 232 * encryption key from the passphrase. 233 * It must not be {@code null}. 234 * @param keyFactoryKeyLengthBits The length (in bits) of the encryption 235 * key generated from the passphrase. 236 * @param cipherTransformation The cipher transformation used for the 237 * encryption. It must not be 238 * {@code null}. 239 * @param cipherInitializationVector The initialization vector used when 240 * creating the cipher. It must not be 241 * {@code null}. 242 * @param keyIdentifier An optional identifier that can be used 243 * to associate this passphrase-encrypted 244 * stream header with some other 245 * encryption settings object. It may 246 * optionally be {@code null}. 247 * @param secretKey The secret key generated from the 248 * passphrase. 249 * @param macAlgorithm The MAC algorithm to use when 250 * generating a MAC of the header 251 * contents. It must not be {@code null}. 252 * @param macValue A MAC of the header contents. It must 253 * not be {@code null}. 254 * @param encodedHeader An encoded representation of the 255 * header. 256 */ 257 private PassphraseEncryptedStreamHeader( 258 @NotNull final String keyFactoryAlgorithm, 259 final int keyFactoryIterationCount, 260 @NotNull final byte[] keyFactorySalt, 261 final int keyFactoryKeyLengthBits, 262 @NotNull final String cipherTransformation, 263 @NotNull final byte[] cipherInitializationVector, 264 @Nullable final String keyIdentifier, 265 @Nullable final SecretKey secretKey, 266 @NotNull final String macAlgorithm, 267 @NotNull final byte[] macValue, 268 @NotNull final byte[] encodedHeader) 269 { 270 this.keyFactoryAlgorithm = keyFactoryAlgorithm; 271 this.keyFactoryIterationCount = keyFactoryIterationCount; 272 this.keyFactorySalt = Arrays.copyOf(keyFactorySalt, keyFactorySalt.length); 273 this.keyFactoryKeyLengthBits = keyFactoryKeyLengthBits; 274 this.cipherTransformation = cipherTransformation; 275 this.cipherInitializationVector = Arrays.copyOf(cipherInitializationVector, 276 cipherInitializationVector.length); 277 this.keyIdentifier = keyIdentifier; 278 this.secretKey = secretKey; 279 this.macAlgorithm = macAlgorithm; 280 this.macValue = macValue; 281 this.encodedHeader = encodedHeader; 282 } 283 284 285 286 /** 287 * Creates a new passphrase-encrypted stream header with the provided 288 * information. 289 * 290 * @param passphrase The passphrase to use to generate the 291 * encryption key. It must not be 292 * {@code null}. 293 * @param keyFactoryAlgorithm The key factory algorithm used to 294 * generate the encryption key from the 295 * passphrase. It must not be 296 * {@code null}. 297 * @param keyFactoryIterationCount The iteration count used to generate 298 * the encryption key from the passphrase. 299 * @param keyFactorySalt The salt used to generate the 300 * encryption key from the passphrase. 301 * It must not be {@code null}. 302 * @param keyFactoryKeyLengthBits The length (in bits) of the encryption 303 * key generated from the passphrase. 304 * @param cipherTransformation The cipher transformation used for the 305 * encryption. It must not be 306 * {@code null}. 307 * @param cipherInitializationVector The initialization vector used when 308 * creating the cipher. It must not be 309 * {@code null}. 310 * @param keyIdentifier An optional identifier that can be used 311 * to associate this passphrase-encrypted 312 * stream header with some other 313 * encryption settings object. It may 314 * optionally be {@code null}. 315 * @param macAlgorithm The MAC algorithm to use when 316 * generating a MAC of the header 317 * contents. It must not be {@code null}. 318 * 319 * @throws GeneralSecurityException If a problem is encountered while 320 * generating the encryption key or MAC 321 * from the provided passphrase. 322 */ 323 PassphraseEncryptedStreamHeader(@NotNull final char[] passphrase, 324 @NotNull final String keyFactoryAlgorithm, 325 final int keyFactoryIterationCount, 326 @NotNull final byte[] keyFactorySalt, 327 final int keyFactoryKeyLengthBits, 328 @NotNull final String cipherTransformation, 329 @NotNull final byte[] cipherInitializationVector, 330 @Nullable final String keyIdentifier, 331 @NotNull final String macAlgorithm) 332 throws GeneralSecurityException 333 { 334 this.keyFactoryAlgorithm = keyFactoryAlgorithm; 335 this.keyFactoryIterationCount = keyFactoryIterationCount; 336 this.keyFactorySalt = Arrays.copyOf(keyFactorySalt, keyFactorySalt.length); 337 this.keyFactoryKeyLengthBits = keyFactoryKeyLengthBits; 338 this.cipherTransformation = cipherTransformation; 339 this.cipherInitializationVector = Arrays.copyOf(cipherInitializationVector, 340 cipherInitializationVector.length); 341 this.keyIdentifier = keyIdentifier; 342 this.macAlgorithm = macAlgorithm; 343 344 secretKey = generateKeyReliably(keyFactoryAlgorithm, cipherTransformation, 345 passphrase, keyFactorySalt, keyFactoryIterationCount, 346 keyFactoryKeyLengthBits); 347 348 final ObjectPair<byte[],byte[]> headerPair = encode(keyFactoryAlgorithm, 349 keyFactoryIterationCount, this.keyFactorySalt, keyFactoryKeyLengthBits, 350 cipherTransformation, this.cipherInitializationVector, keyIdentifier, 351 secretKey, macAlgorithm); 352 encodedHeader = headerPair.getFirst(); 353 macValue = headerPair.getSecond(); 354 } 355 356 357 358 /** 359 * Generates an encoded representation of the header with the provided 360 * settings. 361 * 362 * @param keyFactoryAlgorithm The key factory algorithm used to 363 * generate the encryption key from the 364 * passphrase. It must not be 365 * {@code null}. 366 * @param keyFactoryIterationCount The iteration count used to generate 367 * the encryption key from the passphrase. 368 * @param keyFactorySalt The salt used to generate the 369 * encryption key from the passphrase. 370 * It must not be {@code null}. 371 * @param keyFactoryKeyLengthBits The length (in bits) of the encryption 372 * key generated from the passphrase. 373 * @param cipherTransformation The cipher transformation used for the 374 * encryption. It must not be 375 * {@code null}. 376 * @param cipherInitializationVector The initialization vector used when 377 * creating the cipher. It must not be 378 * {@code null}. 379 * @param keyIdentifier An optional identifier that can be used 380 * to associate this passphrase-encrypted 381 * stream header with some other 382 * encryption settings object. It may 383 * optionally be {@code null}. 384 * @param secretKey The secret key generated from the 385 * passphrase. 386 * @param macAlgorithm The MAC algorithm to use when 387 * generating a MAC of the header 388 * contents. It must not be {@code null}. 389 * 390 * @return The encoded representation of the header. 391 * 392 * @throws GeneralSecurityException If a problem is encountered while 393 * generating the MAC. 394 */ 395 @NotNull() 396 private static ObjectPair<byte[],byte[]> encode( 397 @NotNull final String keyFactoryAlgorithm, 398 final int keyFactoryIterationCount, 399 @NotNull final byte[] keyFactorySalt, 400 final int keyFactoryKeyLengthBits, 401 @NotNull final String cipherTransformation, 402 @NotNull final byte[] cipherInitializationVector, 403 @Nullable final String keyIdentifier, 404 @Nullable final SecretKey secretKey, 405 @NotNull final String macAlgorithm) 406 throws GeneralSecurityException 407 { 408 // Construct a list of all elements that will go in the header except the 409 // MAC value. 410 final ArrayList<ASN1Element> elements = new ArrayList<>(10); 411 elements.add(new ASN1Integer(TYPE_ENCODING_VERSION, ENCODING_VERSION_1)); 412 elements.add(new ASN1OctetString(TYPE_KEY_FACTORY_ALGORITHM, 413 keyFactoryAlgorithm)); 414 elements.add(new ASN1Integer(TYPE_KEY_FACTORY_ITERATION_COUNT, 415 keyFactoryIterationCount)); 416 elements.add(new ASN1OctetString(TYPE_KEY_FACTORY_SALT, keyFactorySalt)); 417 elements.add(new ASN1Integer(TYPE_KEY_FACTORY_KEY_LENGTH_BITS, 418 keyFactoryKeyLengthBits)); 419 elements.add(new ASN1OctetString(TYPE_CIPHER_TRANSFORMATION, 420 cipherTransformation)); 421 elements.add(new ASN1OctetString(TYPE_CIPHER_INITIALIZATION_VECTOR, 422 cipherInitializationVector)); 423 424 if (keyIdentifier != null) 425 { 426 elements.add(new ASN1OctetString(TYPE_KEY_IDENTIFIER, keyIdentifier)); 427 } 428 429 elements.add(new ASN1OctetString(TYPE_MAC_ALGORITHM, macAlgorithm)); 430 431 432 // Compute the MAC value and add it to the list of elements. 433 final ByteStringBuffer macBuffer = new ByteStringBuffer(); 434 for (final ASN1Element e : elements) 435 { 436 macBuffer.append(e.encode()); 437 } 438 439 final Mac mac = CryptoHelper.getMAC(macAlgorithm); 440 mac.init(secretKey); 441 442 final byte[] macValue = mac.doFinal(macBuffer.toByteArray()); 443 elements.add(new ASN1OctetString(TYPE_MAC_VALUE, macValue)); 444 445 446 // Compute and return the encoded header. 447 final byte[] elementBytes = new ASN1Sequence(elements).encode(); 448 final byte[] headerBytes = 449 new byte[MAGIC_BYTES.length + elementBytes.length]; 450 System.arraycopy(MAGIC_BYTES, 0, headerBytes, 0, MAGIC_BYTES.length); 451 System.arraycopy(elementBytes, 0, headerBytes, MAGIC_BYTES.length, 452 elementBytes.length); 453 return new ObjectPair<>(headerBytes, macValue); 454 } 455 456 457 458 /** 459 * Writes an encoded representation of this passphrase-encrypted stream header 460 * to the provided output stream. The output stream will remain open after 461 * this method completes. 462 * 463 * @param outputStream The output stream to which the header will be 464 * written. 465 * 466 * @throws IOException If a problem is encountered while trying to write to 467 * the provided output stream. 468 */ 469 public void writeTo(@NotNull final OutputStream outputStream) 470 throws IOException 471 { 472 outputStream.write(encodedHeader); 473 } 474 475 476 477 /** 478 * Reads a passphrase-encrypted stream header from the provided input stream. 479 * This method will not close the provided input stream, regardless of whether 480 * it returns successfully or throws an exception. If it returns 481 * successfully, then the position then the header bytes will have been 482 * consumed, so the next data to be read should be the data encrypted with 483 * these settings. If it throws an exception, then some unknown amount of 484 * data may have been read from the stream. 485 * 486 * @param inputStream The input stream from which to read the encoded 487 * passphrase-encrypted stream header. It must not be 488 * {@code null}. 489 * @param passphrase The passphrase to use to generate the encryption key. 490 * If this is {@code null}, then the header will be 491 * read, but no attempt will be made to validate the MAC, 492 * and it will not be possible to use this header to 493 * actually perform encryption or decryption. Providing 494 * a {@code null} value is primarily useful if 495 * information in the header (especially the key 496 * identifier) is needed to determine what passphrase to 497 * use. 498 * 499 * @return The passphrase-encrypted stream header that was read from the 500 * provided input stream. 501 * 502 * @throws IOException If a problem is encountered while attempting to read 503 * data from the provided input stream. 504 * 505 * @throws LDAPException If a problem is encountered while attempting to 506 * decode the data that was read. 507 * 508 * @throws InvalidKeyException If the MAC contained in the header does not 509 * match the expected value. 510 * 511 * @throws GeneralSecurityException If a problem is encountered while trying 512 * to generate the MAC. 513 */ 514 @NotNull() 515 public static PassphraseEncryptedStreamHeader 516 readFrom(@NotNull final InputStream inputStream, 517 @Nullable final char[] passphrase) 518 throws IOException, LDAPException, InvalidKeyException, 519 GeneralSecurityException 520 { 521 // Read the magic from the input stream. 522 for (int i=0; i < MAGIC_BYTES.length; i++) 523 { 524 final int magicByte = inputStream.read(); 525 if (magicByte < 0) 526 { 527 throw new LDAPException(ResultCode.DECODING_ERROR, 528 ERR_PW_ENCRYPTED_STREAM_HEADER_READ_END_OF_STREAM_IN_MAGIC.get()); 529 } 530 else if (magicByte != MAGIC_BYTES[i]) 531 { 532 throw new LDAPException(ResultCode.DECODING_ERROR, 533 ERR_PW_ENCRYPTED_STREAM_HEADER_READ_MAGIC_MISMATCH.get()); 534 } 535 } 536 537 538 // The remainder of the header should be an ASN.1 sequence. Read and 539 // process that sequenced. 540 try 541 { 542 final ASN1Element headerSequenceElement = 543 ASN1Element.readFrom(inputStream); 544 if (headerSequenceElement == null) 545 { 546 throw new LDAPException(ResultCode.DECODING_ERROR, 547 ERR_PW_ENCRYPTED_STREAM_HEADER_READ_END_OF_STREAM_AFTER_MAGIC.get( 548 )); 549 } 550 551 final byte[] encodedHeaderSequence = headerSequenceElement.encode(); 552 final byte[] encodedHeader = 553 new byte[MAGIC_BYTES.length + encodedHeaderSequence.length]; 554 System.arraycopy(MAGIC_BYTES, 0, encodedHeader, 0, MAGIC_BYTES.length); 555 System.arraycopy(encodedHeaderSequence, 0, encodedHeader, 556 MAGIC_BYTES.length, encodedHeaderSequence.length); 557 558 final ASN1Sequence headerSequence = 559 ASN1Sequence.decodeAsSequence(headerSequenceElement); 560 return decodeHeaderSequence(encodedHeader, headerSequence, passphrase); 561 } 562 catch (final IOException | LDAPException | GeneralSecurityException e) 563 { 564 Debug.debugException(e); 565 throw e; 566 } 567 catch (final Exception e) 568 { 569 Debug.debugException(e); 570 throw new LDAPException(ResultCode.DECODING_ERROR, 571 ERR_PW_ENCRYPTED_STREAM_HEADER_READ_ASN1_DECODE_ERROR.get( 572 StaticUtils.getExceptionMessage(e)), 573 e); 574 } 575 } 576 577 578 579 /** 580 * Decodes the contents of the provided byte array as a passphrase-encrypted 581 * stream header. The provided array must contain only the header, with no 582 * additional data before or after. 583 * 584 * @param encodedHeader The bytes that comprise the header to decode. It 585 * must not be {@code null} or empty. 586 * @param passphrase The passphrase to use to generate the encryption 587 * key. If this is {@code null}, then the header will 588 * be read, but no attempt will be made to validate the 589 * MAC, and it will not be possible to use this header 590 * to actually perform encryption or decryption. 591 * Providing a {@code null} value is primarily useful 592 * if information in the header (especially the key 593 * identifier) is needed to determine what passphrase 594 * to use. 595 * 596 * @return The passphrase-encrypted stream header that was decoded from the 597 * provided byte array. 598 * 599 * @throws LDAPException If a problem is encountered while trying to decode 600 * the data as a passphrase-encrypted stream header. 601 * 602 * @throws InvalidKeyException If the MAC contained in the header does not 603 * match the expected value. 604 * 605 * @throws GeneralSecurityException If a problem is encountered while trying 606 * to generate the MAC. 607 */ 608 @NotNull() 609 public static PassphraseEncryptedStreamHeader decode( 610 @NotNull final byte[] encodedHeader, 611 @Nullable final char[] passphrase) 612 throws LDAPException, InvalidKeyException, GeneralSecurityException 613 { 614 // Make sure that the array is long enough to hold a valid header. 615 if (encodedHeader.length <= MAGIC_BYTES.length) 616 { 617 throw new LDAPException(ResultCode.DECODING_ERROR, 618 ERR_PW_ENCRYPTED_STREAM_HEADER_DECODE_TOO_SHORT.get()); 619 } 620 621 622 // Make sure that the array starts with the provided magic value. 623 for (int i=0; i < MAGIC_BYTES.length; i++) 624 { 625 if (encodedHeader[i] != MAGIC_BYTES[i]) 626 { 627 throw new LDAPException(ResultCode.DECODING_ERROR, 628 ERR_PW_ENCRYPTED_STREAM_HEADER_DECODE_MAGIC_MISMATCH.get()); 629 } 630 } 631 632 633 // Decode the remainder of the array as an ASN.1 sequence. 634 final ASN1Sequence headerSequence; 635 try 636 { 637 final byte[] encodedHeaderWithoutMagic = 638 new byte[encodedHeader.length - MAGIC_BYTES.length]; 639 System.arraycopy(encodedHeader, MAGIC_BYTES.length, 640 encodedHeaderWithoutMagic, 0, encodedHeaderWithoutMagic.length); 641 headerSequence = ASN1Sequence.decodeAsSequence(encodedHeaderWithoutMagic); 642 } 643 catch (final Exception e) 644 { 645 Debug.debugException(e); 646 throw new LDAPException(ResultCode.DECODING_ERROR, 647 ERR_PW_ENCRYPTED_STREAM_HEADER_DECODE_ASN1_DECODE_ERROR.get( 648 StaticUtils.getExceptionMessage(e)), 649 e); 650 } 651 652 return decodeHeaderSequence(encodedHeader, headerSequence, passphrase); 653 } 654 655 656 657 /** 658 * Decodes the contents of the provided ASN.1 sequence as the portion of a 659 * passphrase-encrypted stream header that follows the magic bytes. 660 * 661 * @param encodedHeader The bytes that comprise the encoded header. It 662 * must not be {@code null} or empty. 663 * @param headerSequence The header sequence portion of the encoded header. 664 * @param passphrase The passphrase to use to generate the encryption 665 * key. If this is {@code null}, then the header will 666 * be read, but no attempt will be made to validate 667 * the MAC, and it will not be possible to use this 668 * header to actually perform encryption or 669 * decryption. Providing a {@code null} value is 670 * primarily useful if information in the header 671 * (especially the key identifier) is needed to 672 * determine what passphrase to use. 673 * 674 * @return The passphrase-encrypted stream header that was decoded from the 675 * provided ASN.1 sequence. 676 * 677 * @throws LDAPException If a problem is encountered while trying to decode 678 * the data as a passphrase-encrypted stream header. 679 * 680 * @throws InvalidKeyException If the MAC contained in the header does not 681 * match the expected value. 682 * 683 * @throws GeneralSecurityException If a problem is encountered while trying 684 * to generate the MAC. 685 */ 686 @NotNull() 687 private static PassphraseEncryptedStreamHeader decodeHeaderSequence( 688 @NotNull final byte[] encodedHeader, 689 @NotNull final ASN1Sequence headerSequence, 690 @Nullable final char[] passphrase) 691 throws LDAPException, InvalidKeyException, GeneralSecurityException 692 { 693 try 694 { 695 // The first element must be the encoding version, and it must be 1. 696 final ASN1Element[] headerElements = headerSequence.elements(); 697 final ASN1Integer versionElement = 698 ASN1Integer.decodeAsInteger(headerElements[0]); 699 if (versionElement.intValue() != ENCODING_VERSION_1) 700 { 701 throw new LDAPException(ResultCode.DECODING_ERROR, 702 ERR_PW_ENCRYPTED_HEADER_SEQUENCE_UNSUPPORTED_VERSION.get( 703 versionElement.intValue())); 704 } 705 706 // The second element must be the key factory algorithm. 707 final String keyFactoryAlgorithm = 708 ASN1OctetString.decodeAsOctetString(headerElements[1]).stringValue(); 709 710 // The third element must be the key factory iteration count. 711 final int keyFactoryIterationCount = 712 ASN1Integer.decodeAsInteger(headerElements[2]).intValue(); 713 714 // The fourth element must be the key factory salt. 715 final byte[] keyFactorySalt = 716 ASN1OctetString.decodeAsOctetString(headerElements[3]).getValue(); 717 718 // The fifth element must be the key length in bits. 719 final int keyFactoryKeyLengthBits = 720 ASN1Integer.decodeAsInteger(headerElements[4]).intValue(); 721 722 // The sixth element must be the cipher transformation. 723 final String cipherTransformation = 724 ASN1OctetString.decodeAsOctetString(headerElements[5]).stringValue(); 725 726 // The seventh element must be the initialization vector. 727 final byte[] cipherInitializationVector = 728 ASN1OctetString.decodeAsOctetString(headerElements[6]).getValue(); 729 730 // Look through any remaining elements and decode them as appropriate. 731 byte[] macValue = null; 732 int macValuePos = -1; 733 String keyIdentifier = null; 734 String macAlgorithm = null; 735 for (int i=7; i < headerElements.length; i++) 736 { 737 switch (headerElements[i].getType()) 738 { 739 case TYPE_KEY_IDENTIFIER: 740 keyIdentifier = ASN1OctetString.decodeAsOctetString( 741 headerElements[i]).stringValue(); 742 break; 743 case TYPE_MAC_ALGORITHM: 744 macAlgorithm = ASN1OctetString.decodeAsOctetString( 745 headerElements[i]).stringValue(); 746 break; 747 case TYPE_MAC_VALUE: 748 macValuePos = i; 749 macValue = ASN1OctetString.decodeAsOctetString( 750 headerElements[i]).getValue(); 751 break; 752 default: 753 throw new LDAPException(ResultCode.DECODING_ERROR, 754 ERR_PW_ENCRYPTED_HEADER_SEQUENCE_UNRECOGNIZED_ELEMENT_TYPE.get( 755 StaticUtils.toHex(headerElements[i].getType()))); 756 } 757 } 758 759 760 // Compute a MAC of the appropriate header elements and verify that it 761 // matches the value contained in the header. If it doesn't match, then 762 // it means the provided passphrase was invalid. 763 final SecretKey secretKey; 764 if (passphrase == null) 765 { 766 secretKey = null; 767 } 768 else 769 { 770 secretKey = generateKeyReliably(keyFactoryAlgorithm, 771 cipherTransformation, passphrase, keyFactorySalt, 772 keyFactoryIterationCount, keyFactoryKeyLengthBits); 773 774 final ByteStringBuffer macBuffer = new ByteStringBuffer(); 775 for (int i=0; i < headerElements.length; i++) 776 { 777 if (i != macValuePos) 778 { 779 macBuffer.append(headerElements[i].encode()); 780 } 781 } 782 783 final Mac mac = CryptoHelper.getMAC(macAlgorithm); 784 mac.init(secretKey); 785 final byte[] computedMacValue = mac.doFinal(macBuffer.toByteArray()); 786 if (! Arrays.equals(computedMacValue, macValue)) 787 { 788 throw new InvalidKeyException( 789 ERR_PW_ENCRYPTED_HEADER_SEQUENCE_BAD_PW.get()); 790 } 791 } 792 793 return new PassphraseEncryptedStreamHeader(keyFactoryAlgorithm, 794 keyFactoryIterationCount, keyFactorySalt, keyFactoryKeyLengthBits, 795 cipherTransformation, cipherInitializationVector, keyIdentifier, 796 secretKey, macAlgorithm, macValue, encodedHeader); 797 } 798 catch (final LDAPException | GeneralSecurityException e) 799 { 800 Debug.debugException(e); 801 throw e; 802 } 803 catch (final Exception e) 804 { 805 Debug.debugException(e); 806 throw new LDAPException(ResultCode.DECODING_ERROR, 807 ERR_PW_ENCRYPTED_HEADER_SEQUENCE_DECODE_ERROR.get( 808 StaticUtils.getExceptionMessage(e)), 809 e); 810 } 811 } 812 813 814 815 /** 816 * We have seen situations where SecretKeyFactory#generateSecret returns 817 * inconsistent results for the same parameters. This can lead to data being 818 * encrypted or decrypted incorrectly. To avoid this, this method computes the 819 * key multiple times, and only returns the key once an identical key has been 820 * generated three times in a row. 821 * 822 * @param keyFactoryAlgorithm The key factory algorithm to use to 823 * generate the encryption key from the 824 * passphrase. It must not be {@code null}. 825 * @param cipherTransformation The cipher transformation used for the 826 * encryption key. It must not be {@code 827 * null}. 828 * @param passphrase The passphrase to use to generate the 829 * encryption key. It must not be 830 * {@code null}. 831 * @param keyFactorySalt The salt to use to generate the 832 * encryption key from the passphrase. 833 * It must not be {@code null}. 834 * @param keyFactoryIterationCount The iteration count to use to generate 835 * the encryption key from the passphrase. 836 * @param keyFactoryKeyLengthBits The length (in bits) of the encryption 837 * key generated from the passphrase. 838 * 839 * @return A SecretKey that has been consistently generated from the provided 840 * parameters. 841 * 842 * @throws GeneralSecurityException If a problem is encountered while 843 * generating the encryption key including 844 * not being able to generate a consistent 845 * key. 846 */ 847 @NotNull() 848 private static SecretKey generateKeyReliably( 849 @NotNull final String keyFactoryAlgorithm, 850 @NotNull final String cipherTransformation, 851 @NotNull final char[] passphrase, 852 @NotNull final byte[] keyFactorySalt, 853 final int keyFactoryIterationCount, 854 final int keyFactoryKeyLengthBits) 855 throws GeneralSecurityException 856 { 857 // First, see if the key is already in the cache. If so, then we'll use it. 858 final PassphraseEncryptedStreamHeaderCachedKeyIdentifier identifier = 859 new PassphraseEncryptedStreamHeaderCachedKeyIdentifier( 860 keyFactoryAlgorithm, keyFactorySalt, keyFactoryIterationCount, 861 keyFactoryKeyLengthBits, passphrase); 862 SecretKey secretKey = 863 PassphraseEncryptedStreamHeaderSecretKeyCache.get(identifier); 864 if (secretKey != null) 865 { 866 return secretKey; 867 } 868 869 870 // Generate the key. Because we've previously seen cases in which this can 871 // yield inconsistent results, we'll make sure that we repeatedly get the 872 // same answer. 873 byte[] prev = null; 874 byte[] prev2 = null; 875 876 final int iterations = 10; 877 for (int i = 0; i < iterations; i++) 878 { 879 final SecretKeyFactory keyFactory = 880 CryptoHelper.getSecretKeyFactory(keyFactoryAlgorithm); 881 final String cipherAlgorithm = cipherTransformation.substring(0, 882 cipherTransformation.indexOf('/')); 883 final PBEKeySpec pbeKeySpec = new PBEKeySpec(passphrase, keyFactorySalt, 884 keyFactoryIterationCount, keyFactoryKeyLengthBits); 885 final byte[] encoded = keyFactory.generateSecret(pbeKeySpec).getEncoded(); 886 secretKey = new SecretKeySpec(encoded, cipherAlgorithm); 887 888 // If this encoded key is the same as the previous one, and the one before 889 // that, then it was likely computed correctly, so return it. 890 if (Arrays.equals(encoded, prev) && Arrays.equals(encoded, prev2)) 891 { 892 if (i > 2) 893 { 894 Debug.debug(Level.WARNING, DebugType.OTHER, 895 "The secret key was generated inconsistently initially, but " + 896 "after " + i + " iterations, we were able to generate a " + 897 "consistent value."); 898 } 899 900 901 // Put the new key in the cache and return it. 902 PassphraseEncryptedStreamHeaderSecretKeyCache.put(identifier, 903 secretKey); 904 return secretKey; 905 } 906 907 prev2 = prev; 908 prev = encoded; 909 } 910 911 Debug.debug(Level.SEVERE, DebugType.OTHER, 912 "Even after " + iterations + " iterations, the secret key could not " + 913 "be reliably generated."); 914 915 throw new InvalidKeyException( 916 ERR_PW_ENCRYPTED_STREAM_HEADER_CANNOT_GENERATE_KEY.get()); 917 } 918 919 920 921 /** 922 * Creates a {@code Cipher} for the specified purpose. 923 * 924 * @param mode The mode to use for the cipher. It must be one of 925 * {@code Cipher.ENCRYPT_MODE} or {@code Cipher.DECRYPT_MODE}. 926 * 927 * @return The {@code Cipher} instance that was created. 928 * 929 * @throws InvalidKeyException If no passphrase was provided when decoding 930 * this passphrase-encrypted stream header. 931 * 932 * @throws GeneralSecurityException If a problem is encountered while 933 * creating the cipher. 934 */ 935 @NotNull() 936 Cipher createCipher(final int mode) 937 throws InvalidKeyException, GeneralSecurityException 938 { 939 if (secretKey == null) 940 { 941 throw new InvalidKeyException( 942 ERR_PW_ENCRYPTED_HEADER_NO_KEY_AVAILABLE.get()); 943 } 944 945 final Cipher cipher = CryptoHelper.getCipher(cipherTransformation); 946 cipher.init(mode, secretKey, 947 new IvParameterSpec(cipherInitializationVector)); 948 949 return cipher; 950 } 951 952 953 954 /** 955 * Retrieves the key factory algorithm used to generate the encryption key 956 * from the passphrase. 957 * 958 * @return The key factory algorithm used to generate the encryption key from 959 * the passphrase. 960 */ 961 @NotNull() 962 public String getKeyFactoryAlgorithm() 963 { 964 return keyFactoryAlgorithm; 965 } 966 967 968 969 /** 970 * Retrieves the iteration count used to generate the encryption key from the 971 * passphrase. 972 * 973 * @return The iteration count used to generate the encryption key from the 974 * passphrase. 975 */ 976 public int getKeyFactoryIterationCount() 977 { 978 return keyFactoryIterationCount; 979 } 980 981 982 983 /** 984 * Retrieves the salt used to generate the encryption key from the passphrase. 985 * 986 * @return The salt used to generate the encryption key from the passphrase. 987 */ 988 @NotNull() 989 public byte[] getKeyFactorySalt() 990 { 991 return Arrays.copyOf(keyFactorySalt, keyFactorySalt.length); 992 } 993 994 995 996 /** 997 * Retrieves the length (in bits) of the encryption key generated from the 998 * passphrase. 999 * 1000 * @return The length (in bits) of the encryption key generated from the 1001 * passphrase. 1002 */ 1003 public int getKeyFactoryKeyLengthBits() 1004 { 1005 return keyFactoryKeyLengthBits; 1006 } 1007 1008 1009 1010 /** 1011 * Retrieves the cipher transformation used for the encryption. 1012 * 1013 * @return The cipher transformation used for the encryption. 1014 */ 1015 @NotNull() 1016 public String getCipherTransformation() 1017 { 1018 return cipherTransformation; 1019 } 1020 1021 1022 1023 /** 1024 * Retrieves the cipher initialization vector used for the encryption. 1025 * 1026 * @return The cipher initialization vector used for the encryption. 1027 */ 1028 @NotNull() 1029 public byte[] getCipherInitializationVector() 1030 { 1031 return Arrays.copyOf(cipherInitializationVector, 1032 cipherInitializationVector.length); 1033 } 1034 1035 1036 1037 /** 1038 * Retrieves the key identifier used to associate this passphrase-encrypted 1039 * stream header with some other encryption settings object, if defined. 1040 * 1041 * @return The key identifier used to associate this passphrase-encrypted 1042 * stream header with some other encryption settings object, or 1043 * {@code null} if none was provided. 1044 */ 1045 @Nullable() 1046 public String getKeyIdentifier() 1047 { 1048 return keyIdentifier; 1049 } 1050 1051 1052 1053 /** 1054 * Retrieves the algorithm used to generate a MAC of the header content. 1055 * 1056 * @return The algorithm used to generate a MAC of the header content. 1057 */ 1058 @NotNull() 1059 public String getMACAlgorithm() 1060 { 1061 return macAlgorithm; 1062 } 1063 1064 1065 1066 /** 1067 * Retrieves an encoded representation of this passphrase-encrypted stream 1068 * header. 1069 * 1070 * @return An encoded representation of this passphrase-encrypted stream 1071 * header. 1072 */ 1073 @NotNull() 1074 public byte[] getEncodedHeader() 1075 { 1076 return Arrays.copyOf(encodedHeader, encodedHeader.length); 1077 } 1078 1079 1080 1081 /** 1082 * Indicates whether this passphrase-encrypted stream header includes a secret 1083 * key. If this header was read or decoded with no passphrase provided, then 1084 * it will not have a secret key, which means the MAC will not have been 1085 * validated and it cannot be used to encrypt or decrypt data. 1086 * 1087 * @return {@code true} if this passphrase-encrypted stream header includes a 1088 * secret key and can be used to encrypt or decrypt data, or 1089 * {@code false} if not. 1090 */ 1091 public boolean isSecretKeyAvailable() 1092 { 1093 return (secretKey != null); 1094 } 1095 1096 1097 1098 /** 1099 * Creates a copy of this passphrase-encrypted stream header that use a 1100 * newly-computed initialization vector. The new stream header can be used to 1101 * create a new passphrase-encrypted output stream that safely leverages an 1102 * already computed secret key to dramatically reduce the cost of creating a 1103 * new stream from the same underlying passphrase. 1104 * 1105 * @return The new passphrase-encrypted stream header that was created. 1106 * 1107 * @throws GeneralSecurityException If a problem occurs while creating a 1108 * copy of this passphrase-encrypted stream 1109 * header with a new initialization vector. 1110 */ 1111 @NotNull() 1112 PassphraseEncryptedStreamHeader withNewCipherInitializationVector() 1113 throws GeneralSecurityException 1114 { 1115 if (secretKey == null) 1116 { 1117 throw new InvalidKeyException( 1118 ERR_PW_ENCRYPTED_STREAM_HEADER_COPY_WITHOUT_SECRET_KEY.get()); 1119 } 1120 1121 final byte[] newInitializationVector = 1122 new byte[cipherInitializationVector.length]; 1123 ThreadLocalSecureRandom.get().nextBytes(newInitializationVector); 1124 1125 final ObjectPair<byte[],byte[]> headerPair = encode(keyFactoryAlgorithm, 1126 keyFactoryIterationCount, this.keyFactorySalt, keyFactoryKeyLengthBits, 1127 cipherTransformation, newInitializationVector, keyIdentifier, 1128 secretKey, macAlgorithm); 1129 final byte[] newEncodedHeader = headerPair.getFirst(); 1130 final byte[] newMACValue = headerPair.getSecond(); 1131 1132 return new PassphraseEncryptedStreamHeader(keyFactoryAlgorithm, 1133 keyFactoryIterationCount, keyFactorySalt, keyFactoryKeyLengthBits, 1134 cipherTransformation, newInitializationVector, keyIdentifier, 1135 secretKey, macAlgorithm, newMACValue, newEncodedHeader); 1136 } 1137 1138 1139 1140 /** 1141 * Retrieves a string representation of this passphrase-encrypted stream 1142 * header. 1143 * 1144 * @return A string representation of this passphrase-encrypted stream 1145 * header. 1146 */ 1147 @Override() 1148 @NotNull() 1149 public String toString() 1150 { 1151 final StringBuilder buffer = new StringBuilder(); 1152 toString(buffer); 1153 return buffer.toString(); 1154 } 1155 1156 1157 1158 /** 1159 * Appends a string representation of this passphrase-encrypted stream header 1160 * to the provided buffer. 1161 * 1162 * @param buffer The buffer to which the information should be appended. 1163 */ 1164 public void toString(@NotNull final StringBuilder buffer) 1165 { 1166 buffer.append("PassphraseEncryptedStreamHeader(keyFactoryAlgorithm='"); 1167 buffer.append(keyFactoryAlgorithm); 1168 buffer.append("', keyFactoryIterationCount="); 1169 buffer.append(keyFactoryIterationCount); 1170 buffer.append(", keyFactorySaltLengthBytes="); 1171 buffer.append(keyFactorySalt.length); 1172 buffer.append(", keyFactoryKeyLengthBits="); 1173 buffer.append(keyFactoryKeyLengthBits); 1174 buffer.append(", cipherTransformation'="); 1175 buffer.append(cipherTransformation); 1176 buffer.append("', cipherInitializationVectorLengthBytes="); 1177 buffer.append(cipherInitializationVector.length); 1178 buffer.append('\''); 1179 1180 if (keyIdentifier != null) 1181 { 1182 buffer.append(", keyIdentifier='"); 1183 buffer.append(keyIdentifier); 1184 buffer.append('\''); 1185 } 1186 1187 buffer.append(", macAlgorithm='"); 1188 buffer.append(macAlgorithm); 1189 buffer.append("', macValueLengthBytes="); 1190 buffer.append(macValue.length); 1191 buffer.append(", secretKeyAvailable="); 1192 buffer.append(isSecretKeyAvailable()); 1193 buffer.append(", encodedHeaderLengthBytes="); 1194 buffer.append(encodedHeader.length); 1195 buffer.append(')'); 1196 } 1197}