diff --git a/README.md b/README.md index 4c822e6..26a1069 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,9 @@ Books without Borders has got your back! - Easily add a title page or chapter page (for an unsigned book, you can add a blank page as well) with `/addBookTitlePage`. - Remove extra blank pages or unneeded chapter pages with `/deleteBookPage` +- If the necessary options are enabled, books can be truly encrypted with the AES cipher, making them impossible to + decrypt without knowing the password. Even admin decryption can be made useless. Only enable if you are aware of the + potential loss caused by forgotten passwords! #### Group encryption @@ -101,38 +104,39 @@ An in-game description of available commands is available through the /bwb comma #### Single permissions -| Node | Description | -|--------------------------------------------|---------------------------------------------------------------------------------| -| bookswithoutborders.addtitlepage | Allows player to add a blank title page to a book | -| bookswithoutborders.bypassauthoronlycopy | Allows player to ignore Author_Only_Copy config setting | -| bookswithoutborders.bypassauthoronlyunsign | Allows player to ignore Author_Only_Unsign config setting | -| bookswithoutborders.bypassauthoronlysave | Allows player to ignore Author_Only_Save config setting | -| bookswithoutborders.bypassbookprice | Allows player to ignore Price_to_create_book config setting | -| bookswithoutborders.clear | Allows player to clear the contents of the held writable book | -| bookswithoutborders.copy | Allows player to copy books | -| bookswithoutborders.decrypt | Allows player to decrypt books | -| bookswithoutborders.decrypt.agroup | Allows player to decrypt books group-encrypted for group "agroup" | -| bookswithoutborders.delete | Allows player to delete books from their personal directory | -| bookswithoutborders.deletepage | Allows player to delete a page from a book | -| bookswithoutborders.editbookshelf | Allows player to set name/lore for bookshelves, used for peeking | -| bookswithoutborders.encrypt | Allows player to encrypt books | -| bookswithoutborders.format | Allows a player to format a book | -| bookswithoutborders.give | Allows player to give another player one of their privately saved books | -| bookswithoutborders.givepublic | Allows a player to give another player a book from the public directory | -| bookswithoutborders.groupencrypt | Allows player to use group-based encryption | -| bookswithoutborders.load | Allows player to load books from their personal directory | -| bookswithoutborders.loadpublic | Allows player to load from the public directory | -| bookswithoutborders.peekbookshelf | Allows player to left-click a bookshelf to see the contents of the shelf | -| bookswithoutborders.reload | Allows player to reload this plugin | -| bookswithoutborders.save | Allows a player to save books to their personal directory | -| bookswithoutborders.savepublic | Allows player to save to the public directory | -| bookswithoutborders.setauthor | Allows player to set the author of the currently held book | -| bookswithoutborders.setbookprice | Allows player to set the cost of creating a book | -| bookswithoutborders.setgeneration | Allows player to change the generation of a book (Original, Copy, Copy of Copy) | -| bookswithoutborders.settitle | Allows player to set the title of the currently held book | -| bookswithoutborders.signs | Allows player to create signs that give/encrypt/decrypt books | -| bookswithoutborders.unsign | Allows player to un-sign books | -| bookswithoutborders.setlore | Allows player to set the lore of the currently held item | +| Node | Description | +|--------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------| +| bookswithoutborders.addtitlepage | Allows player to add a blank title page to a book | +| bookswithoutborders.bypassauthoronlycopy | Allows player to ignore Author_Only_Copy config setting | +| bookswithoutborders.bypassauthoronlyunsign | Allows player to ignore Author_Only_Unsign config setting | +| bookswithoutborders.bypassauthoronlysave | Allows player to ignore Author_Only_Save config setting | +| bookswithoutborders.bypassbookprice | Allows player to ignore Price_to_create_book config setting | +| bookswithoutborders.clear | Allows player to clear the contents of the held writable book | +| bookswithoutborders.copy | Allows player to copy books | +| bookswithoutborders.decrypt | Allows player to decrypt books | +| bookswithoutborders.decrypt.agroup | Allows player to decrypt books group-encrypted for group "agroup" | +| bookswithoutborders.delete | Allows player to delete books from their personal directory | +| bookswithoutborders.deletepage | Allows player to delete a page from a book | +| bookswithoutborders.editbookshelf | Allows player to set name/lore for bookshelves, used for peeking | +| bookswithoutborders.encrypt | Allows player to encrypt books | +| bookswithoutborders.format | Allows a player to format a book | +| bookswithoutborders.give | Allows player to give another player one of their privately saved books | +| bookswithoutborders.givepublic | Allows a player to give another player a book from the public directory | +| bookswithoutborders.groupencrypt | Allows player to use group-based encryption | +| bookswithoutborders.load | Allows player to load books from their personal directory | +| bookswithoutborders.loadpublic | Allows player to load from the public directory | +| bookswithoutborders.peekbookshelf | Allows player to left-click a bookshelf to see the contents of the shelf | +| bookswithoutborders.reload | Allows player to reload this plugin | +| bookswithoutborders.save | Allows a player to save books to their personal directory | +| bookswithoutborders.savepublic | Allows player to save to the public directory | +| bookswithoutborders.setauthor | Allows player to set the author of the currently held book | +| bookswithoutborders.setbookprice | Allows player to set the cost of creating a book | +| bookswithoutborders.setgeneration | Allows player to change the generation of a book (Original, Copy, Copy of Copy) | +| bookswithoutborders.settitle | Allows player to set the title of the currently held book | +| bookswithoutborders.signs | Allows player to create signs that give/encrypt/decrypt books | +| bookswithoutborders.unsign | Allows player to un-sign books | +| bookswithoutborders.setlore | Allows player to set the lore of the currently held item | +| bookswithoutborders.preventadmindecryption | If use real encryption and prevent admin decryption options are enabled, allows player to disable admin decryption for a book | ### Signs @@ -172,4 +176,5 @@ The **_decrypt_** sign must have **\[Decrypt]** on its second line. The third li | Format_Book_After_Signing | Whether to automatically format every book when it's signed | | Change_Generation_On_Copy | Whether to display "COPY" or "COPY_OF_COPY" instead of "ORIGINAL" when a book is copied. This also uses the vanilla behavior where a copy of a copy or tattered book cannot be copied further. | | Enable_Book_Peeking | Whether to enable hitting a chiseled bookshelf while sneaking to see the shelf's contents. | -| Use_Real_Encryption | Enables true AES encryption instead of the very fake legacy encryption. The encryption key is stored in the book file to allow admin decryption, but looking at the encrypted book in the file system, only reveals the encrypted pages. Note that real encryption might alter, corrupt or lose a book's contents, so don't use real encryption with books that have no backup in in-game book form or saved book form. | \ No newline at end of file +| Use_Real_Encryption | Enables true AES encryption instead of the very fake legacy encryption. The encryption key is stored in the book file to allow admin decryption, but looking at the encrypted book in the file system, only reveals the encrypted pages. Note that real encryption might alter, corrupt or lose a book's contents, so don't use real encryption with books that have no backup in in-game book form or saved book form. | +| Allow_Prevent_Admin_Decryption | Allows players to disable storing the encryption key for an encrypted book. This is only usable for real encryption. This effectively disable admin decryption for the book. Providing the correct password is the only way to decrypt a book without a stored key. THIS IS A DANGEROUS OPTION! | \ No newline at end of file diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandEncrypt.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandEncrypt.java index eb0a578..d4b0dfe 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandEncrypt.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandEncrypt.java @@ -1,6 +1,8 @@ package net.knarcraft.bookswithoutborders.command; import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.config.BwBConfig; +import net.knarcraft.bookswithoutborders.config.Permission; import net.knarcraft.bookswithoutborders.config.translation.Translatable; import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle; import net.knarcraft.bookswithoutborders.state.ItemSlot; @@ -29,7 +31,7 @@ public class CommandEncrypt implements TabExecutor { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] arguments) { StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter(); - if (performPreChecks(sender, arguments, 1, + if (performPreChecks(sender, arguments, 1, 2, stringFormatter.getUnFormattedColoredMessage(Translatable.ERROR_ENCRYPT_NO_KEY)) == null) { return false; } @@ -37,11 +39,16 @@ public class CommandEncrypt implements TabExecutor { EncryptionStyle encryptionStyle = arguments.length == 2 ? EncryptionStyle.getFromString(arguments[1]) : EncryptionStyle.AES; // AES is the only reliable method for retaining the plaintext - if (BooksWithoutBorders.getConfiguration().useRealEncryption() && !encryptionStyle.isRealEncryptionSupported()) { + BwBConfig config = BooksWithoutBorders.getConfiguration(); + boolean realEncryption = config.useRealEncryption(); + if (realEncryption && !encryptionStyle.isRealEncryptionSupported()) { encryptionStyle = EncryptionStyle.AES; } - return encryptBook(encryptionStyle, (Player) sender, arguments[0], ""); + boolean preventAdminDecryption = realEncryption && config.allowPreventAdminDecryption() && + sender.hasPermission(Permission.PREVENT_ADMIN_DECRYPTION.toString()); + + return encryptBook(encryptionStyle, (Player) sender, arguments[0], "", preventAdminDecryption); } /** @@ -55,7 +62,7 @@ public class CommandEncrypt implements TabExecutor { */ @Nullable protected BookMeta performPreChecks(@NotNull CommandSender sender, @NotNull String[] arguments, - int necessaryArguments, @NotNull String missingArgumentsError) { + int necessaryArguments, int optionalArguments, @NotNull String missingArgumentsError) { StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter(); if (!(sender instanceof Player player)) { stringFormatter.displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY); @@ -75,7 +82,7 @@ public class CommandEncrypt implements TabExecutor { stringFormatter.displayErrorMessage(player, missingArgumentsError); return null; } - if (argumentCount > necessaryArguments + 1) { + if (argumentCount > necessaryArguments + optionalArguments) { stringFormatter.displayErrorMessage(player, Translatable.ERROR_TOO_MANY_ARGUMENTS_COMMAND); return null; } @@ -96,16 +103,18 @@ public class CommandEncrypt implements TabExecutor { /** * Encrypts the given book * - * @param encryptionStyle

The encryption style to use

- * @param player

The player encrypting the book

- * @param key

The encryption key to use

- * @param group

The group to encrypt for

+ * @param encryptionStyle

The encryption style to use

+ * @param player

The player encrypting the book

+ * @param key

The encryption key to use

+ * @param group

The group to encrypt for

+ * @param preventAdminDecryption

Whether to prevent storage of a key that can be used for admin decryption

* @return

True if the book was encrypted successfully

*/ protected boolean encryptBook(@NotNull EncryptionStyle encryptionStyle, @NotNull Player player, @NotNull String key, - @NotNull String group) { + @NotNull String group, boolean preventAdminDecryption) { ItemSlot heldSlot = InventoryHelper.getHeldSlotBook(player, false, false, true, true); - ItemStack encryptedBook = EncryptionHelper.encryptBook(player, heldSlot == ItemSlot.MAIN_HAND, key, encryptionStyle, group); + ItemStack encryptedBook = EncryptionHelper.encryptBook(player, heldSlot == ItemSlot.MAIN_HAND, key, + encryptionStyle, group, preventAdminDecryption); if (encryptedBook != null) { InventoryHelper.setHeldWrittenBook(player, encryptedBook); @@ -119,19 +128,20 @@ public class CommandEncrypt implements TabExecutor { @NotNull public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { - return doTabCompletion(arguments, false); + return doTabCompletion(sender, arguments, false); } /** * Gets a list of string for tab completions * - * @param args

The arguments given

+ * @param sender

The command sender executing this command

+ * @param arguments

The arguments given

* @param groupEncrypt

Whether to auto-complete for group encryption

* @return

The strings to auto-complete

*/ @NotNull - protected List doTabCompletion(@NotNull String[] args, boolean groupEncrypt) { - int argumentsCount = args.length; + protected List doTabCompletion(@NotNull CommandSender sender, @NotNull String[] arguments, boolean groupEncrypt) { + int argumentsCount = arguments.length; boolean useRealEncryption = BooksWithoutBorders.getConfiguration().useRealEncryption(); List encryptionStyles = new ArrayList<>(); @@ -147,13 +157,17 @@ public class CommandEncrypt implements TabExecutor { } else if (argumentsCount == 2) { return List.of(""); } else if (argumentsCount == 3) { - return TabCompletionHelper.filterMatchingStartsWith(encryptionStyles, args[2]); + return TabCompletionHelper.filterMatchingStartsWith(encryptionStyles, arguments[2]); } } else { + BwBConfig config = BooksWithoutBorders.getConfiguration(); if (argumentsCount == 1) { return List.of(""); } else if (argumentsCount == 2) { - return TabCompletionHelper.filterMatchingStartsWith(encryptionStyles, args[1]); + return TabCompletionHelper.filterMatchingStartsWith(encryptionStyles, arguments[1]); + } else if (argumentsCount == 3 && (config.useRealEncryption() && config.allowPreventAdminDecryption()) && + sender.hasPermission(Permission.PREVENT_ADMIN_DECRYPTION.toString())) { + return TabCompletionHelper.filterMatchingStartsWith(List.of("true", "false"), arguments[2]); } } return List.of(); diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandGroupEncrypt.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandGroupEncrypt.java index 25a2146..a0b3219 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandGroupEncrypt.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandGroupEncrypt.java @@ -27,7 +27,7 @@ public class CommandGroupEncrypt extends CommandEncrypt implements TabExecutor { return false; } - BookMeta bookMetadata = performPreChecks(sender, arguments, 2, + BookMeta bookMetadata = performPreChecks(sender, arguments, 2, 1, stringFormatter.getUnFormattedColoredMessage(Translatable.ERROR_GROUP_ENCRYPT_ARGUMENTS_MISSING)); if (bookMetadata == null) { @@ -42,13 +42,13 @@ public class CommandGroupEncrypt extends CommandEncrypt implements TabExecutor { } EncryptionStyle encryptionStyle = arguments.length == 3 ? EncryptionStyle.getFromString(arguments[2]) : EncryptionStyle.SUBSTITUTION; - return encryptBook(encryptionStyle, player, arguments[1], arguments[0]); + return encryptBook(encryptionStyle, player, arguments[1], arguments[0], false); } @Override public @NotNull List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { - return doTabCompletion(arguments, true); + return doTabCompletion(sender, arguments, true); } } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/BwBConfig.java b/src/main/java/net/knarcraft/bookswithoutborders/config/BwBConfig.java index c2ba81f..d886a15 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/BwBConfig.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/BwBConfig.java @@ -42,6 +42,7 @@ public class BwBConfig { private boolean changeGenerationOnCopy; private boolean enableBookshelfPeeking; private boolean useRealEncryption; + private boolean allowPreventAdminDecryption; private final Translator translator; private EconomyManager economyManager; @@ -275,6 +276,15 @@ public class BwBConfig { return this.useRealEncryption; } + /** + * Checks whether to allow removing the admin decrypt ability + * + * @return

True if admin decrypt removal is allowed

+ */ + public boolean allowPreventAdminDecryption() { + return this.allowPreventAdminDecryption; + } + /** * Gets the path used to store encrypted books * @@ -316,6 +326,7 @@ public class BwBConfig { config.set(ConfigOption.AUTHOR_ONLY_UNSIGN.getConfigNode(), this.authorOnlyUnsign); config.set(ConfigOption.AUTHOR_ONLY_SAVE.getConfigNode(), this.authorOnlySave); config.set(ConfigOption.CHANGE_GENERATION_ON_COPY.getConfigNode(), this.changeGenerationOnCopy); + config.set(ConfigOption.ALLOW_PREVENT_ADMIN_DECRYPTION.getConfigNode(), this.allowPreventAdminDecryption); BooksWithoutBorders.getInstance().saveConfig(); } @@ -343,6 +354,7 @@ public class BwBConfig { this.changeGenerationOnCopy = getBoolean(config, ConfigOption.CHANGE_GENERATION_ON_COPY); this.enableBookshelfPeeking = getBoolean(config, ConfigOption.ENABLE_BOOKSHELF_PEEKING); this.useRealEncryption = getBoolean(config, ConfigOption.USE_REAL_ENCRYPTION); + this.allowPreventAdminDecryption = getBoolean(config, ConfigOption.ALLOW_PREVENT_ADMIN_DECRYPTION); String language = config.getString("language", "en"); this.translator.loadLanguages(BooksWithoutBorders.getInstance().getDataFolder(), "en", language); diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/ConfigOption.java b/src/main/java/net/knarcraft/bookswithoutborders/config/ConfigOption.java index 1f79a5b..4e9b368 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/ConfigOption.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/ConfigOption.java @@ -81,6 +81,11 @@ public enum ConfigOption { * Whether to use real AES encryption instead of storing garbled book text, while the full plaintext is stored in a file */ USE_REAL_ENCRYPTION("Options.Use_Real_Encryption", false), + + /** + * Whether to allow disabling admin encryption for a real encrypted book + */ + ALLOW_PREVENT_ADMIN_DECRYPTION("Options.Allow_Prevent_Admin_Decryption", false), ; private final String configNode; diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/Permission.java b/src/main/java/net/knarcraft/bookswithoutborders/config/Permission.java index dadb981..1b73e37 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/Permission.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/Permission.java @@ -50,7 +50,13 @@ public enum Permission { /** * The permission for using special signs */ - SIGNS("signs"); + SIGNS("signs"), + + /** + * The permission for preventing a real encrypted book to be decrypted by an admin + */ + PREVENT_ADMIN_DECRYPTION("preventAdminDecryption"), + ; private final @NotNull String node; diff --git a/src/main/java/net/knarcraft/bookswithoutborders/container/EncryptedBook.java b/src/main/java/net/knarcraft/bookswithoutborders/container/EncryptedBook.java index b38a01c..85ba1d1 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/container/EncryptedBook.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/container/EncryptedBook.java @@ -11,13 +11,14 @@ 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

+ * @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

+ * @param preventAdminDecrypt

Whether this book should not support admin decryption

*/ public record EncryptedBook(@NotNull BookMeta bookMeta, @NotNull EncryptionStyle encryptionStyle, @NotNull String encryptionKey, @NotNull List data, - @Nullable AESConfiguration aesConfiguration) { + @Nullable AESConfiguration aesConfiguration, boolean preventAdminDecrypt) { } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/listener/SignEventListener.java b/src/main/java/net/knarcraft/bookswithoutborders/listener/SignEventListener.java index 3409969..d301036 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/listener/SignEventListener.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/listener/SignEventListener.java @@ -304,7 +304,7 @@ public class SignEventListener implements Listener { if (heldItemType == Material.WRITTEN_BOOK) { player.closeInventory(); eBook = EncryptionHelper.encryptBook(player, mainHand, BookFormatter.stripColor(lines[2]), - EncryptionStyle.getFromString(BookFormatter.stripColor(lines[3]))); + EncryptionStyle.getFromString(BookFormatter.stripColor(lines[3])), false); if (eBook != null) { player.getInventory().setItem(hand, eBook); } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java index 3f6715d..56b3b42 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java @@ -76,7 +76,9 @@ public final class BookToFromTextHelper { FileConfiguration bookYml = getBookConfiguration(encryptedBook.bookMeta()); bookYml.set("Encryption.Style", encryptedBook.encryptionStyle().toString()); - bookYml.set("Encryption.Key", encryptedBook.encryptionKey()); + if (!encryptedBook.preventAdminDecrypt()) { + 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!"); @@ -155,7 +157,8 @@ public final class BookToFromTextHelper { } // If the plaintext is stored in the file, don't bother with real decryption - EncryptedBook encryptedBook = new EncryptedBook(meta, encryptionStyle, userKey, data, aesConfiguration); + EncryptedBook encryptedBook = new EncryptedBook(meta, encryptionStyle, userKey, data, aesConfiguration, + realKey.isBlank()); if (!meta.getPages().isEmpty()) { return encryptedBook; } diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/EncryptionHelper.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/EncryptionHelper.java index b4d9d07..03daa15 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/EncryptionHelper.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/EncryptionHelper.java @@ -127,31 +127,34 @@ public final class EncryptionHelper { /** * Encrypts a book * - * @param player

The player encrypting the book

- * @param mainHand

Whether the player is holding the book in its main hand

- * @param key

The key/password to use for encryption

- * @param style

The encryption style to use

+ * @param player

The player encrypting the book

+ * @param mainHand

Whether the player is holding the book in its main hand

+ * @param key

The key/password to use for encryption

+ * @param style

The encryption style to use

+ * @param preventAdminDecrypt

Whether to prevent storage of a key that can be used for admin decryption

* @return

An encrypted version of the book

*/ @Nullable public static ItemStack encryptBook(@NotNull Player player, boolean mainHand, @NotNull String key, - @NotNull EncryptionStyle style) { - return encryptBook(player, mainHand, key, style, ""); + @NotNull EncryptionStyle style, boolean preventAdminDecrypt) { + return encryptBook(player, mainHand, key, style, "", preventAdminDecrypt); } /** * Encrypts a book * - * @param player

The player encrypting the book

- * @param mainHand

Whether the player is holding the book in its main hand

- * @param key

The key/password to use for encryption

- * @param style

The encryption style to use

- * @param groupName

The name of the group to encrypt for, or "" otherwise

+ * @param player

The player encrypting the book

+ * @param mainHand

Whether the player is holding the book in its main hand

+ * @param key

The key/password to use for encryption

+ * @param style

The encryption style to use

+ * @param groupName

The name of the group to encrypt for, or "" otherwise

+ * @param preventAdminDecrypt

Whether to prevent storage of a key that can be used for admin decryption

* @return

An encrypted version of the book

*/ @Nullable public static ItemStack encryptBook(Player player, boolean mainHand, @NotNull String key, - @NotNull EncryptionStyle style, @NotNull String groupName) { + @NotNull EncryptionStyle style, @NotNull String groupName, + boolean preventAdminDecrypt) { BookMeta book = InventoryHelper.getHeldBookMetadata(player, mainHand); if (book == null) { BooksWithoutBorders.sendErrorMessage(player, "Unable to get metadata from the held book!"); @@ -167,7 +170,8 @@ public final class EncryptionHelper { AESConfiguration configuration = AESConfiguration.getNewConfiguration(hashedKey); //Save the book's un-encrypted contents to a file - BookMeta newMetadata = saveBookPlaintext(groupName, player, book, style, hashedKey, configuration); + BookMeta newMetadata = saveEncryptedBook(groupName, player, + new EncryptedBook(book, style, hashedKey, new ArrayList<>(), configuration, preventAdminDecrypt)); if (newMetadata == null) { return null; } @@ -216,26 +220,22 @@ public final class EncryptionHelper { } /** - * Saves a book's plain text to a file + * Saves an encrypted book to a file * - * @param groupName

The group who's allowed to decrypt the book, or ""

- * @param player

The player trying to encrypt the book

- * @param book

The book to encrypt

- * @param encryptionStyle

The encryption style used for the book

- * @param key

The key used to encrypt the book

- * @param aesConfiguration

The AES configuration to use, if encrypting using AES

+ * @param groupName

The group who's allowed to decrypt the book, or ""

+ * @param player

The player trying to encrypt the book

+ * @param encryptedBook

The book to encrypt

* @return

The new metadata for the book, or null if it could not be saved

*/ @Nullable - private static BookMeta saveBookPlaintext(@NotNull String groupName, @NotNull Player player, - @NotNull BookMeta book, @NotNull EncryptionStyle encryptionStyle, - @NotNull String key, @NotNull AESConfiguration aesConfiguration) { - BookMeta newMetadata = book; + private static BookMeta saveEncryptedBook(@NotNull String groupName, @NotNull Player player, + @NotNull EncryptedBook encryptedBook) { + BookMeta newMetadata = encryptedBook.bookMeta(); boolean wasSaved; if (groupName.trim().isEmpty()) { - wasSaved = saveEncryptedBook(player, book, encryptionStyle, key, aesConfiguration); + wasSaved = saveEncryptedBook(player, encryptedBook); } else { - newMetadata = saveEncryptedBookForGroup(player, book, groupName); + newMetadata = saveEncryptedBookForGroup(player, encryptedBook.bookMeta(), groupName); wasSaved = newMetadata != null; } if (wasSaved) { @@ -327,7 +327,6 @@ public final class EncryptionHelper { File file = new File(path + fileName + ".yml"); if (!file.isFile()) { file = new File(path + fileName + ".txt"); - if (!file.isFile()) { BooksWithoutBorders.sendErrorMessage(player, "Incorrect decryption key!"); return null; @@ -476,20 +475,15 @@ public final class EncryptionHelper { /** * Saves an encrypted book to be decryptable for the given user * - * @param player

The player encrypting the book

- * @param bookMetaData

Metadata for the book to encrypt

- * @param encryptionStyle

The style of encryption used

- * @param key

The key to use for encryption

- * @param aesConfiguration

The AES configuration to use if encrypting with AES

+ * @param player

The player encrypting the book

+ * @param encryptedBook

The book to save

* @return

The new encrypted metadata for the book, or null if encryption failed

*/ @NotNull - private static Boolean saveEncryptedBook(@NotNull Player player, @NotNull BookMeta bookMetaData, - @NotNull EncryptionStyle encryptionStyle, @NotNull String key, - @Nullable AESConfiguration aesConfiguration) { + private static Boolean saveEncryptedBook(@NotNull Player player, @NotNull EncryptedBook encryptedBook) { String path = BooksWithoutBorders.getConfiguration().getEncryptedBookPath(); - String fileName = BookHelper.getBookFile(bookMetaData, player, true); + String fileName = BookHelper.getBookFile(encryptedBook.bookMeta(), player, true); fileName = cleanString(fileName); //cancels saving if file is already encrypted @@ -500,8 +494,7 @@ public final class EncryptionHelper { } try { - BookToFromTextHelper.encryptedBookToYml(path, fileName, - new EncryptedBook(bookMetaData, encryptionStyle, key, new ArrayList<>(), aesConfiguration)); + BookToFromTextHelper.encryptedBookToYml(path, fileName, encryptedBook); } catch (IOException exception) { BooksWithoutBorders.sendErrorMessage(player, "Encryption failed!"); return false; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 5eeb612..71cf0d4 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -38,4 +38,8 @@ Options: # peek at books, if an admin gets a hold of a book with the same title and author, but only the encrypted AES cypher text is stored in the book. # Note that real encryption might alter, corrupt or lose a book's contents, so don't use real encryption with books # that have no backup in in-game book form or saved book form. - Use_Real_Encryption: false \ No newline at end of file + Use_Real_Encryption: false + # Whether to allow players to specifically disable admin decryption for a real encrypted book. This is only available + # when real encryption is enabled. It allows a player to prevent the storage of the encryption key in the plugin + # folder, meaning that the only way to decrypt the book is to provide the correct key. THIS IS A DANGEROUS OPTION! + Allow_Prevent_Admin_Decryption: false \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 441e472..ac2b36e 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -102,9 +102,11 @@ commands: Encrypts the book the player is holding. "key" is required and can be any phrase or number excluding spaces. "style" is not required. Possible values are "dna", "substitution", "aes", "onetimepad" and "magic". If real encryption is enabled, possible methods are restricted. + If real encryption and prevent admin decryption are enabled, the third argument prevents the key from being + stored in the server files, preventing admin decryption. If the password is lost, decryption is impossible. aliases: - bwbencrypt - usage: / [encryption style] + usage: / [encryption style] [prevent admin decrypt] permission: bookswithoutborders.encrypt setbookgeneration: description: Sets the generation of your held book @@ -265,6 +267,7 @@ permissions: bookswithoutborders.reload: true bookswithoutborders.setgeneration: true bookswithoutborders.editbookshelf: true + bookswithoutborders.preventadmindecryption: true bookswithoutborders.use: description: Allows player to use commands to save/load/delete in their personal directory, and peeking at bookshelves if enabled children: @@ -341,4 +344,6 @@ permissions: bookswithoutborders.addtitlepage: description: Allows player to add a blank title page to a book bookswithoutborders.deletepage: - description: Allows player to delete a page from a book \ No newline at end of file + description: Allows player to delete a page from a book + bookswithoutborders.preventadmindecryption: + description: If use real encryption and prevent admin decryption options are enabled, allows player to disable admin decryption for a book \ No newline at end of file