From 93ce915a30c84f97db6cb0ff52f6678f1a305573 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Thu, 21 Aug 2025 16:36:28 +0200 Subject: [PATCH] Stops migration from altering encrypted books --- README.md | 2 - .../config/translation/Translatable.java | 5 ++ .../container/EncryptedBook.java | 23 +++++ .../thread/MigrationQueueThread.java | 49 +++++----- .../utility/BookFileHelper.java | 4 + .../utility/BookLoader.java | 1 - .../utility/BookToFromTextHelper.java | 90 +++++++++++-------- .../utility/EncryptionHelper.java | 16 ++-- src/main/resources/plugin.yml | 1 - src/main/resources/strings.yml | 3 + 10 files changed, 125 insertions(+), 69 deletions(-) create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/container/EncryptedBook.java diff --git a/README.md b/README.md index 9e2a13c..4c822e6 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,6 @@ Books without Borders has got your back! - The `/migrateBooks` command allows for easy fixing of old book naming, changing the title author separator (the default changed from `,` to `ยค`, as a comma is a natural character to use in a title), or updating books saved as txt to yml. -- Note that if real encryption is enabled, migrating the books will store them unencrypted (like they used to before - real encryption was implemented) afterward. ### Book formatting diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Translatable.java b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Translatable.java index ff33580..396a098 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Translatable.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/translation/Translatable.java @@ -307,6 +307,11 @@ public enum Translatable implements TranslatableMessage { * The error displayed when attempting to change a book's title with a title that's too long */ ERROR_TITLE_LENGTH, + + /** + * The error displayed when attempting to encrypt a book that has an existing encrypted file + */ + ERROR_ENCRYPT_ALREADY_SAVED, ; @Override diff --git a/src/main/java/net/knarcraft/bookswithoutborders/container/EncryptedBook.java b/src/main/java/net/knarcraft/bookswithoutborders/container/EncryptedBook.java new file mode 100644 index 0000000..b38a01c --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/container/EncryptedBook.java @@ -0,0 +1,23 @@ +package net.knarcraft.bookswithoutborders.container; + +import net.knarcraft.bookswithoutborders.encryption.AESConfiguration; +import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle; +import org.bukkit.inventory.meta.BookMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * A representation of an encrypted book + * + * @param bookMeta

The book's book meta

+ * @param encryptionStyle

The book's encryption style

+ * @param encryptionKey

The book's encryption key, or null if admin decrypt is forbidden

+ * @param data

The encrypted pages of the book

+ * @param aesConfiguration

The AES configuration for the book, or null if not AES encrypted

+ */ +public record EncryptedBook(@NotNull BookMeta bookMeta, @NotNull EncryptionStyle encryptionStyle, + @NotNull String encryptionKey, @NotNull List data, + @Nullable AESConfiguration aesConfiguration) { +} diff --git a/src/main/java/net/knarcraft/bookswithoutborders/thread/MigrationQueueThread.java b/src/main/java/net/knarcraft/bookswithoutborders/thread/MigrationQueueThread.java index 4fef364..9291640 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/thread/MigrationQueueThread.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/thread/MigrationQueueThread.java @@ -1,6 +1,7 @@ package net.knarcraft.bookswithoutborders.thread; import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.container.EncryptedBook; import net.knarcraft.bookswithoutborders.container.MigrationRequest; import net.knarcraft.bookswithoutborders.utility.BookFileHelper; import net.knarcraft.bookswithoutborders.utility.BookHelper; @@ -89,12 +90,17 @@ public class MigrationQueueThread implements Runnable { if (bookMeta == null) { return false; } + String slash = BooksWithoutBorders.getConfiguration().getSlash(); - BookMeta loadedBook; + EncryptedBook encryptedBook = null; + BookMeta loadedBook = null; String extension = BookFileHelper.getExtensionFromPath(file.getName()); - if (extension.equalsIgnoreCase("yml")) { - loadedBook = BookToFromTextHelper.encryptedBookFromYml(file, bookMeta, "", true); - } else if (extension.equalsIgnoreCase("txt")) { + if (file.getAbsolutePath().contains("Books" + slash + "Encrypted")) { + encryptedBook = BookToFromTextHelper.encryptedBookFromYml(file, bookMeta, "", true); + if (encryptedBook != null) { + loadedBook = encryptedBook.bookMeta(); + } + } else if (extension.equalsIgnoreCase("txt") || extension.equalsIgnoreCase("yml")) { loadedBook = BookToFromTextHelper.bookFromFile(file, bookMeta); } else { BooksWithoutBorders.log(Level.WARNING, "File with unexpected extension " + extension + " encountered!"); @@ -102,7 +108,7 @@ public class MigrationQueueThread implements Runnable { } if (loadedBook == null) { - BooksWithoutBorders.log(Level.SEVERE, "Unable to load book: " + file.getName()); + BooksWithoutBorders.log(Level.SEVERE, "Unable to load book: " + file.getAbsolutePath()); return false; } @@ -125,8 +131,13 @@ public class MigrationQueueThread implements Runnable { newName = "[" + key + "]" + newName; } - return saveBook(file.getParentFile(), newName, loadedBook, file); - } catch (IllegalArgumentException exception) { + if (encryptedBook != null) { + BookToFromTextHelper.encryptedBookToYml(file.getParentFile().getAbsolutePath(), newName, encryptedBook); + } else { + BookToFromTextHelper.bookToYml(file.getParentFile().getAbsolutePath(), newName, bookMeta); + } + return deleteBook(file.getParentFile(), newName, file); + } catch (IllegalArgumentException | IOException exception) { BooksWithoutBorders.sendErrorMessage(player, "Failed to migrate book: " + file.getName() + " Cause:"); BooksWithoutBorders.sendErrorMessage(player, exception.getMessage()); return false; @@ -134,25 +145,19 @@ public class MigrationQueueThread implements Runnable { } /** - * Saves a migrated book + * Deletes a migrated book, if the file path changed * - * @param parent

The parent folder the file belongs to

- * @param newName

The new name of the file

- * @param bookMeta

The metadata of the book to migrate

- * @param oldFile

The old file path, in case it should be deleted

+ * @param parent

The parent folder the file belongs to

+ * @param newName

The new name of the file

+ * @param oldFile

The old file path, in case it should be deleted

* @return

True if successfully saved

*/ - private boolean saveBook(@NotNull File parent, @NotNull String newName, @NotNull BookMeta bookMeta, - @NotNull File oldFile) { - try { - BookToFromTextHelper.bookToYml(parent.getAbsolutePath(), newName, bookMeta); - if (!oldFile.getAbsolutePath().equalsIgnoreCase(new File(parent, newName + ".yml").getAbsolutePath())) { - return oldFile.delete(); - } + private boolean deleteBook(@NotNull File parent, @NotNull String newName, + @NotNull File oldFile) { + if (!oldFile.getAbsolutePath().equalsIgnoreCase(new File(parent, newName + ".yml").getAbsolutePath())) { + return oldFile.delete(); + } else { return true; - } catch (IOException exception) { - BooksWithoutBorders.log(Level.SEVERE, "Failed to save migrated book: " + newName); - return false; } } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFileHelper.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFileHelper.java index cf1cb39..c429588 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFileHelper.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFileHelper.java @@ -198,6 +198,8 @@ public final class BookFileHelper { String stripped = stripExtensionFromPath(path); if (stripped.contains(separator)) { return stripped.split(separator)[0]; + } else if (stripped.contains(",")) { + return stripped.split(",")[0]; } else { return stripped; } @@ -215,6 +217,8 @@ public final class BookFileHelper { String stripped = stripExtensionFromPath(path); if (stripped.contains(separator)) { return stripped.split(separator)[1]; + } else if (stripped.contains(",")) { + return stripped.split(",")[1]; } else { return BooksWithoutBorders.getStringFormatter().getUnFormattedColoredMessage(Formatting.NEUTRAL_UNKNOWN_AUTHOR); } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookLoader.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookLoader.java index 54ea3d6..f36d510 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookLoader.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookLoader.java @@ -140,7 +140,6 @@ public final class BookLoader { BookHelper.increaseGeneration(book); book.setAmount(numCopies); - if (!isSigned && book.getItemMeta() != null) { return BookHelper.unsignBook((BookMeta) book.getItemMeta(), book.getAmount()); } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java index e86388b..3f6715d 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java @@ -3,6 +3,7 @@ package net.knarcraft.bookswithoutborders.utility; import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.config.StaticMessage; import net.knarcraft.bookswithoutborders.config.translation.Formatting; +import net.knarcraft.bookswithoutborders.container.EncryptedBook; import net.knarcraft.bookswithoutborders.encryption.AESConfiguration; import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle; import net.knarcraft.knarlib.formatting.StringFormatter; @@ -65,39 +66,43 @@ public final class BookToFromTextHelper { /** * Saves an encrypted book's contents to a .yml file * - * @param path

The path of the folder to save to. Must end with a slash

- * @param fileName

The name of the file to load to

- * @param bookMetadata

Metadata about the book to save

+ * @param path

The path of the folder to save to

+ * @param fileName

The name of the file to load to

+ * @param encryptedBook

The encrypted book to save

* @throws IOException

If unable to save the book

*/ - public static void encryptedBookToYml(@NotNull String path, @NotNull String fileName, @NotNull BookMeta bookMetadata, - @NotNull EncryptionStyle encryptionStyle, @NotNull String encryptionKey, - @Nullable AESConfiguration aesConfiguration) throws IOException { - FileConfiguration bookYml = getBookConfiguration(bookMetadata); + public static void encryptedBookToYml(@NotNull String path, @NotNull String fileName, + @NotNull EncryptedBook encryptedBook) throws IOException { + FileConfiguration bookYml = getBookConfiguration(encryptedBook.bookMeta()); - bookYml.set("Encryption.Style", encryptionStyle.toString()); - bookYml.set("Encryption.Key", encryptionKey); - if (encryptionStyle == EncryptionStyle.AES) { - if (aesConfiguration == null) { + bookYml.set("Encryption.Style", encryptedBook.encryptionStyle().toString()); + bookYml.set("Encryption.Key", encryptedBook.encryptionKey()); + if (encryptedBook.encryptionStyle() == EncryptionStyle.AES) { + if (encryptedBook.aesConfiguration() == null) { throw new IOException("Attempted to save AES encrypted book without supplying a configuration!"); } - bookYml.set("Encryption.AES.IV", EncryptionHelper.bytesToHex(aesConfiguration.iv())); - bookYml.set("Encryption.AES.Salt", EncryptionHelper.bytesToHex(aesConfiguration.salt())); + bookYml.set("Encryption.AES.IV", EncryptionHelper.bytesToHex(encryptedBook.aesConfiguration().iv())); + bookYml.set("Encryption.AES.Salt", EncryptionHelper.bytesToHex(encryptedBook.aesConfiguration().salt())); } - List encryptedPages = EncryptionHelper.encryptDecryptBookPages(bookMetadata, encryptionStyle, - aesConfiguration, encryptionKey, true); - if (encryptedPages == null || encryptedPages.isEmpty()) { - throw new IOException("Book encryption failed!"); + // If data is non-empty, that means the book is already encrypted + if (!encryptedBook.data().isEmpty()) { + bookYml.set("Encryption.Data", encryptedBook.data()); + } else { + List encryptedPages = EncryptionHelper.encryptDecryptBookPages(encryptedBook.bookMeta(), + encryptedBook.encryptionStyle(), encryptedBook.aesConfiguration(), encryptedBook.encryptionKey(), true); + if (encryptedPages == null || encryptedPages.isEmpty()) { + throw new IOException("Book encryption failed!"); + } + bookYml.set("Encryption.Data", encryptedPages); } - bookYml.set("Encryption.Data", encryptedPages); // Make sure the plaintext cannot simply be seen in the file if (BooksWithoutBorders.getConfiguration().useRealEncryption()) { bookYml.set("Pages", null); } - bookYml.save(path + fileName + ".yml"); + bookYml.save(new File(path, fileName + ".yml")); } /** @@ -110,8 +115,8 @@ public final class BookToFromTextHelper { * @return

Metadata for the loaded book

*/ @Nullable - public static BookMeta encryptedBookFromYml(@NotNull File file, @NotNull BookMeta bookMetadata, - @NotNull String userKey, boolean forceDecrypt) { + public static EncryptedBook encryptedBookFromYml(@NotNull File file, @NotNull BookMeta bookMetadata, + @NotNull String userKey, boolean forceDecrypt) { BookMeta meta; try { @@ -123,25 +128,21 @@ public final class BookToFromTextHelper { return null; } - // If the plaintext is stored in the file, don't bother with real decryption - if (!meta.getPages().isEmpty()) { - return meta; - } - FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file); + + // If key is blank, it's either not real encrypted, or admin decryption is disabled for the book userKey = EncryptionHelper.sha256(userKey); String realKey = bookYml.getString("Encryption.Key", ""); - if (forceDecrypt) { - userKey = realKey; - } - if (!userKey.equals(realKey)) { - return null; + if (!realKey.isBlank()) { + if (forceDecrypt) { + userKey = realKey; + } + if (!userKey.equals(realKey)) { + return null; + } } List data = bookYml.getStringList("Encryption.Data"); - if (data.isEmpty()) { - return null; - } EncryptionStyle encryptionStyle = EncryptionStyle.getFromString(bookYml.getString("Encryption.Style", EncryptionStyle.SUBSTITUTION.toString())); @@ -153,15 +154,28 @@ public final class BookToFromTextHelper { aesConfiguration = new AESConfiguration(iv, salt, userKey); } + // If the plaintext is stored in the file, don't bother with real decryption + EncryptedBook encryptedBook = new EncryptedBook(meta, encryptionStyle, userKey, data, aesConfiguration); + if (!meta.getPages().isEmpty()) { + return encryptedBook; + } + + // Neither plaintext nor cipher text is stored + if (data.isEmpty()) { + return null; + } + + // Perform the actual AES decryption meta.setPages(data); - List decryptedPages = EncryptionHelper.encryptDecryptBookPages(meta, encryptionStyle, - aesConfiguration, userKey, false); + List decryptedPages = EncryptionHelper.encryptDecryptBookPages(meta, encryptionStyle, aesConfiguration, + userKey, false); if (decryptedPages != null && !decryptedPages.isEmpty()) { meta.setPages(decryptedPages); } else { return null; } - return meta; + + return encryptedBook; } /** @@ -260,7 +274,7 @@ public final class BookToFromTextHelper { //Update the metadata of the book with its new values bookMetadata.setAuthor(authorFromUUID(author)); - bookMetadata.setTitle(title.substring(0, 32)); + bookMetadata.setTitle(title.substring(0, Math.min(title.length(), 32))); bookMetadata.setPages(pages); return bookMetadata; diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/EncryptionHelper.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/EncryptionHelper.java index 59da41a..b4d9d07 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/EncryptionHelper.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/EncryptionHelper.java @@ -3,6 +3,8 @@ package net.knarcraft.bookswithoutborders.utility; import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.config.BwBConfig; import net.knarcraft.bookswithoutborders.config.StaticMessage; +import net.knarcraft.bookswithoutborders.config.translation.Translatable; +import net.knarcraft.bookswithoutborders.container.EncryptedBook; import net.knarcraft.bookswithoutborders.encryption.AES; import net.knarcraft.bookswithoutborders.encryption.AESConfiguration; import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle; @@ -273,9 +275,11 @@ public final class EncryptionHelper { return null; } else { try { - bookMetadata = BookToFromTextHelper.encryptedBookFromYml(file, bookMetadata, key, forceDecrypt); - if (bookMetadata == null) { + EncryptedBook book = BookToFromTextHelper.encryptedBookFromYml(file, bookMetadata, key, forceDecrypt); + if (book == null) { throw new IllegalArgumentException(); + } else { + bookMetadata = book.bookMeta(); } } catch (Exception e) { BooksWithoutBorders.sendErrorMessage(player, "Decryption failed!"); @@ -489,13 +493,15 @@ public final class EncryptionHelper { fileName = cleanString(fileName); //cancels saving if file is already encrypted - File file = new File(path + fileName + ".yml"); + File file = new File(path, fileName + ".yml"); if (file.isFile()) { - return true; + BooksWithoutBorders.getStringFormatter().displayErrorMessage(player, Translatable.ERROR_ENCRYPT_ALREADY_SAVED); + return false; } try { - BookToFromTextHelper.encryptedBookToYml(path, fileName, bookMetaData, encryptionStyle, key, aesConfiguration); + BookToFromTextHelper.encryptedBookToYml(path, fileName, + new EncryptedBook(bookMetaData, encryptionStyle, key, new ArrayList<>(), aesConfiguration)); } catch (IOException exception) { BooksWithoutBorders.sendErrorMessage(player, "Encryption failed!"); return false; diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index f5d3e18..441e472 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -232,7 +232,6 @@ commands: All txt files will be converted to yml. All books will be re-saved, fixing any incorrect file-names, a changed title-author separator and files using underscores "_" instead of spaces. - Books encrypted using real encryption will have their decrypted contents show up in the file system. aliases: - bwbmigrate usage: / diff --git a/src/main/resources/strings.yml b/src/main/resources/strings.yml index 5cb2569..5c8baa5 100644 --- a/src/main/resources/strings.yml +++ b/src/main/resources/strings.yml @@ -90,6 +90,9 @@ en: ERROR_NO_ITEM: "You must be holding an item to use this command!" ERROR_TITLE_EMPTY: "You must specify the new title/display name to set!" ERROR_TITLE_LENGTH: "Book titles are capped at 32 characters!" + ERROR_ENCRYPT_ALREADY_SAVED: | + An encrypted version of this book is already saved. + Please decrypt your previously encrypted copy first. # ---------------------------------- # # Custom formatting for some output # # ---------------------------------- #