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