From 5604638b84e0708ba31aedc56864d335f48fe446 Mon Sep 17 00:00:00 2001 From: Marcus Sorensen Date: Wed, 27 Sep 2023 13:11:07 +0530 Subject: [PATCH] Apple base416 passphrase enc (#240) * Move PassphraseVO to use String instead of byte[] to support Encrypt annotation * Check for unencrypted passphrases before migrating passphrase table --------- Co-authored-by: Marcus Sorensen Fixes #239 This PR moves PassphraseVO passphrase to String type. Since the GenericDaoBase manipulates encrypted fields as Strings we don't improve anything by handling as byte arrays. We still use byte arrays to pass these values down to the agents and we can get some security gains there. This PR also handles cases where the passphrase field may be previously unencrypted, and upgrades them to encrypted fields using the old encryption during cloudstack-migrate-databases. Then the process can upgrade to new encryption normally. --- .../crypt/EncryptionSecretKeyChanger.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java b/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java index 88830b3e3f9..6017dca58c1 100644 --- a/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java +++ b/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java @@ -486,6 +486,8 @@ public class EncryptionSecretKeyChanger { migrateImageStoreUrlForCifs(conn); migrateStoragePoolPathForSMB(conn); + preparePassphraseTableForMigration(conn); + // migrate columns with annotation @Encrypt migrateEncryptedTableColumns(conn); @@ -665,6 +667,56 @@ public class EncryptionSecretKeyChanger { System.out.println("End migrate user vm deploy_as_is details"); } + // encrypt any unencrypted passphrases using old style encryptor before we migrate + private void preparePassphraseTableForMigration(Connection conn) throws SQLException { + System.out.println("Preparing passphrase table by checking for unencrypted passphrases"); + + try(PreparedStatement selectPstmt = conn.prepareStatement("SELECT id, passphrase FROM passphrase"); + ResultSet rs = selectPstmt.executeQuery(); + PreparedStatement updatePstmt = conn.prepareStatement("UPDATE passphrase SET passphrase=? WHERE id=?") + ) { + while(rs.next()) { + long id = rs.getLong(1); + String value = rs.getString(2); + if (StringUtils.isBlank(value)) { + continue; + } + + // passphrases are 64 bytes long when unencrypted, longer when encrypted + if (value.length() == 64) { + // just confirm it won't decrypt, to be safe, before assuming raw value and encrypting + try { + oldEncryptor.decrypt(value); + System.out.printf("Passphrase table entry db id %d was already encrypted with old encryption\n", id); + } catch(EncryptionException | CloudRuntimeException ex) { + String message = null; + if (ex instanceof CloudRuntimeException && ex.getCause() != null) { + if ((ex.getCause() instanceof EncryptionException)) { + message = ex.getCause().getMessage(); + } + } else if (ex instanceof EncryptionException) { + message = ex.getMessage(); + } + + if (message != null && message.contains("Failed to decrypt")) { + System.out.printf("Encrypting unencrypted passphrase table entry db id %d before migration using old encryption\n", id); + String encrypted = oldEncryptor.encrypt(value); + updatePstmt.setBytes(1, encrypted.getBytes(StandardCharsets.UTF_8)); + updatePstmt.setLong(2, id); + updatePstmt.executeUpdate(); + } else { + throwCloudRuntimeException("Unhandled EncryptionException", ex); + } + } + } + } + } catch (SQLException e) { + throwCloudRuntimeException("Unable to prepare passphrase table", e); + } + + System.out.println("End preparing passphrase table"); + } + private void migrateImageStoreUrlForCifs(Connection conn) { System.out.println("Begin migrate image store url if protocol is cifs");