mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

david_page
15.37.2007 4adb907371925d48ff3cd42cedbd644080b064c2
no issue
CryptoManager
Add a single-byte version number prefix to the ciphertext prologue to allow for configurable options (e.g., signed hash in the backup stream). The current version produced and accepted is 0x01.
3 files modified
123 ■■■■ changed files
opends/src/messages/messages/core.properties 8 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/crypto/CryptoManagerImpl.java 103 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/crypto/CryptoManagerTestCase.java 12 ●●●● patch | view | raw | blame | history
opends/src/messages/messages/core.properties
@@ -1724,7 +1724,7 @@
 failed to write the stream prologue:  %s
MILD_ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_KEY_IDENTIFIER_683=CryptoManager \
 failed to decrypt the supplied data because it could not read the symmetric \
 key identifier in the data prologue
 key identifier in the data prologue:  %s
MILD_ERR_CRYPTOMGR_DECRYPT_UNKNOWN_KEY_IDENTIFIER_684=CryptoManager failed to \
 decrypt the supplied data because the symmetric key identifier in the data \
 prologue does not match any known key entries
@@ -1755,3 +1755,9 @@
SEVERE_ERR_CRYPTOMGR_FULL_KEY_WRAPPING_TRANSFORMATION_REQUIRED_694=CryptoManager \
 cipher transformation specification "%s" is invalid: it must be of the form \
 "algorithm/mode/padding"
MILD_ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_PROLOGUE_VERSION_695=CryptoManager \
 failed to decrypt the supplied data because it could not read the version \
 number in the data prologue:  %s
MILD_ERR_CRYPTOMGR_DECRYPT_UNKNOWN_PROLOGUE_VERSION_696=CryptoManager failed to \
 decrypt the supplied data because the version "%d" in the data prologue is \
 unknown
opends/src/server/org/opends/server/crypto/CryptoManagerImpl.java
@@ -159,6 +159,15 @@
  // The preferred message digest algorithm for the Directory Server.
  private String preferredDigestAlgorithm;
  // The first byte in any ciphertext produced by CryptoManager is the
  // prologue version. At present, this constant is both the version written
  // and the expected version. If a new version is introduced (e.g., to allow
  // embedding the HMAC key identifier and signature in a signed backup) the
  // prologue version will likely need to be configurable at the granularity
  // of the CryptoManager client (e.g., password encryption might use version 1,
  // while signed backups might use version 2.
  private static final int CIPHERTEXT_PROLOGUE_VERSION = 1 ;
  // The map from encryption key ID to MacKeyEntry (cache). The cache
  // is accessed by methods that request, publish, and import keys.
  private final Map<KeyEntryID, MacKeyEntry> macKeyEntryCache
@@ -2802,25 +2811,28 @@
  {
    Validator.ensureNotNull(cipherTransformation, data);
    CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(
            this, cipherTransformation, keyLengthBits);
    if (null == keyEntry) {
      keyEntry = CipherKeyEntry.generateKeyEntry(this,
    CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(this,
              cipherTransformation, keyLengthBits);
    if (null == keyEntry) {
      keyEntry = CipherKeyEntry.generateKeyEntry(this, cipherTransformation,
              keyLengthBits);
    }
    final Cipher cipher
            = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
    final Cipher cipher = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
    final byte[] keyID = keyEntry.getKeyID().getByteValue();
    final byte[] iv = cipher.getIV();
    final int prologueLength
            = keyID.length + ((null == iv) ? 0 : iv.length);
            = /* version */ 1 + keyID.length + ((null == iv) ? 0 : iv.length);
    final int dataLength = cipher.getOutputSize(data.length);
    final byte[] cipherText = new byte[prologueLength + dataLength];
    System.arraycopy(keyID, 0, cipherText, 0, keyID.length);
    int writeIndex = 0;
    cipherText[writeIndex++] = CIPHERTEXT_PROLOGUE_VERSION;
    System.arraycopy(keyID, 0, cipherText, writeIndex, keyID.length);
    writeIndex += keyID.length;
    if (null != iv) {
      System.arraycopy(iv, 0, cipherText, keyID.length, iv.length);
      System.arraycopy(iv, 0, cipherText, writeIndex, iv.length);
      writeIndex += iv.length;
    }
    System.arraycopy(cipher.doFinal(data), 0, cipherText,
                     prologueLength, dataLength);
@@ -2848,14 +2860,14 @@
    CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(
            this, cipherTransformation, keyLengthBits);
    if (null == keyEntry) {
      keyEntry = CipherKeyEntry.generateKeyEntry(this,
              cipherTransformation, keyLengthBits);
      keyEntry = CipherKeyEntry.generateKeyEntry(this, cipherTransformation,
              keyLengthBits);
    }
    final Cipher cipher
            = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
    final Cipher cipher = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
    final byte[] keyID = keyEntry.getKeyID().getByteValue();
    try {
      outputStream.write((byte)CIPHERTEXT_PROLOGUE_VERSION);
      outputStream.write(keyID);
      if (null != cipher.getIV()) {
        outputStream.write(cipher.getIV());
@@ -2879,11 +2891,37 @@
         throws GeneralSecurityException,
                CryptoManagerException
  {
    int readIndex = 0;
    int version;
    try {
      version = data[readIndex++];
    }
    catch (Exception ex) {
      // IndexOutOfBoundsException, ArrayStoreException, ...
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, ex);
      }
      throw new CryptoManagerException(
              ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_PROLOGUE_VERSION.get(
                      ex.getMessage()), ex);
    }
    switch (version) {
      case CIPHERTEXT_PROLOGUE_VERSION:
        // Encryption key identifier only in the data prologue.
        break;
      default:
        throw new CryptoManagerException(
                ERR_CRYPTOMGR_DECRYPT_UNKNOWN_PROLOGUE_VERSION.get(version));
    }
    KeyEntryID keyID;
    try {
      final byte[] keyIDBytes
              = new byte[KeyEntryID.getByteValueLength()];
      System.arraycopy(data, 0, keyIDBytes, 0, keyIDBytes.length);
      System.arraycopy(data, readIndex, keyIDBytes, 0, keyIDBytes.length);
      readIndex += keyIDBytes.length;
      keyID = new KeyEntryID(keyIDBytes);
    }
    catch (Exception ex) {
@@ -2892,8 +2930,8 @@
        TRACER.debugCaught(DebugLogLevel.ERROR, ex);
      }
      throw new CryptoManagerException(
           ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_KEY_IDENTIFIER.get(),
              ex);
           ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_KEY_IDENTIFIER.get(
                   ex.getMessage()), ex);
    }
    CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(this, keyID);
@@ -2906,8 +2944,8 @@
    if (0 < keyEntry.getIVLengthBits()) {
      iv = new byte[keyEntry.getIVLengthBits()/Byte.SIZE];
      try {
        System.arraycopy(data, KeyEntryID.getByteValueLength(), iv, 0,
                iv.length);
        System.arraycopy(data, readIndex, iv, 0, iv.length);
        readIndex += iv.length;
      }
      catch (Exception ex) {
        // IndexOutOfBoundsException, ArrayStoreException, ...
@@ -2919,12 +2957,8 @@
      }
    }
    final Cipher cipher = getCipher(keyEntry, Cipher.DECRYPT_MODE,
            iv);
    final int prologueLength = KeyEntryID.getByteValueLength()
                                     + ((null == iv) ? 0 : iv.length);
    return cipher.doFinal(data, prologueLength,
                          data.length - prologueLength);
    final Cipher cipher = getCipher(keyEntry, Cipher.DECRYPT_MODE, iv);
    return cipher.doFinal(data, readIndex, data.length - readIndex);
  }
@@ -2932,13 +2966,32 @@
  public CipherInputStream getCipherInputStream(
          InputStream inputStream) throws CryptoManagerException
  {
    int version;
    CipherKeyEntry keyEntry;
    byte[] iv = null;
    try {
      final byte[] rawVersion = new byte[1];
      if (rawVersion.length != inputStream.read(rawVersion)) {
        throw new CryptoManagerException(
                ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_PROLOGUE_VERSION.get(
                      "stream underflow"));
      }
      version = rawVersion[0];
      switch (version) {
        case CIPHERTEXT_PROLOGUE_VERSION:
          // Encryption key identifier only in the data prologue.
          break;
        default:
          throw new CryptoManagerException(
                  ERR_CRYPTOMGR_DECRYPT_UNKNOWN_PROLOGUE_VERSION.get(version));
      }
      final byte[] keyID = new byte[KeyEntryID.getByteValueLength()];
      if (keyID.length != inputStream.read(keyID)){
        throw new CryptoManagerException(
           ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_KEY_IDENTIFIER.get());
           ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_KEY_IDENTIFIER.get(
                   "stream underflow"));
      }
      keyEntry = CipherKeyEntry.getKeyEntry(this,
              new KeyEntryID(keyID));
opends/tests/unit-tests-testng/src/server/org/opends/server/crypto/CryptoManagerTestCase.java
@@ -202,6 +202,7 @@
    // default (preferred) AES/CBC/PKCS5Padding 128bit key.
    paramList.add(new CipherParameters(null, null, null, 128, 128));
    // custom
// TODO: https://opends.dev.java.net/issues/show_bug.cgi?id=2448
// TODO: paramList.add(new CipherParameters("Blowfish", "CFB", "NoPadding", 448, 64));
    paramList.add(new CipherParameters("Blowfish", "CFB", "NoPadding", 128, 64));
    paramList.add(new CipherParameters("RC4", null, null, 104, 0));
@@ -306,8 +307,8 @@
    try {
      Method m = Arrays.class.getMethod("copyOfRange", (new byte[16]).getClass(),
              Integer.TYPE, Integer.TYPE);
      final byte[] keyID = (byte[])m.invoke(null, cipherText, 0, 16);
      final byte[] keyID2 = (byte[])m.invoke(null, cipherText2, 0, 16);
      final byte[] keyID = (byte[])m.invoke(null, cipherText, 1, 16);
      final byte[] keyID2 = (byte[])m.invoke(null, cipherText2, 1, 16);
      assertEquals(keyID, keyID2);
    }
    catch (NoSuchMethodException ex) {
@@ -427,8 +428,8 @@
    // 1. Test for distinct keys.
    final byte[] keyID = new byte[16];
    final byte[] keyID2 = new byte[16];
    System.arraycopy(cipherText, 0, keyID, 0, 16);
    System.arraycopy(cipherText2, 0, keyID2, 0, 16);
    System.arraycopy(cipherText, 1, keyID, 0, 16);
    System.arraycopy(cipherText2, 1, keyID2, 0, 16);
    assertTrue(! Arrays.equals(keyID, keyID2));
    // 2. Confirm ciphertext produced using the compromised key can still be
@@ -440,8 +441,7 @@
    // using a compromised key can no longer be decrypted.
    for (Entry e : searchOp.getSearchEntries()) {
      TestCaseUtils.applyModifications(
        "dn: " + e.getDN().toNormalizedString(),
        "changetype: delete");
        "dn: " + e.getDN().toNormalizedString(), "changetype: delete");
    }
    Thread.sleep(1000); // Clearing the cache is asynchronous.
    try {