diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java b/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java deleted file mode 100644 index 9576fb3e..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.util; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.Arrays; - -public class Passphrase { - - public final Object lock = new Object(); - - private final char[] chars; - private boolean valid = true; - - /** - * Passphrase for keys etc. - * - * @param chars may be null for empty passwords. - */ - public Passphrase(@Nullable char[] chars) { - if (chars == null) { - this.chars = null; - } else { - char[] trimmed = removeTrailingAndLeadingWhitespace(chars); - if (trimmed.length == 0) { - this.chars = null; - } else { - this.chars = trimmed; - } - } - } - - /** - * Return a copy of the passed in char array, with leading and trailing whitespace characters removed. - * - * @param chars char array - * @return copy of char array with leading and trailing whitespace characters removed - */ - private static char[] removeTrailingAndLeadingWhitespace(char[] chars) { - int i = 0; - while (i < chars.length && isWhitespace(chars[i])) { - i++; - } - int j = chars.length - 1; - while (j >= i && isWhitespace(chars[j])) { - j--; - } - - char[] trimmed = new char[chars.length - i - (chars.length - 1 - j)]; - System.arraycopy(chars, i, trimmed, 0, trimmed.length); - - return trimmed; - } - - /** - * Return true, if the passed in char is a whitespace symbol (space, newline, tab). - * - * @param xar char - * @return true if whitespace - */ - private static boolean isWhitespace(char xar) { - return xar == ' ' || xar == '\n' || xar == '\t'; - } - - /** - * Create a {@link Passphrase} from a {@link String}. - * - * @param password password - * @return passphrase - */ - public static Passphrase fromPassword(@Nonnull String password) { - return new Passphrase(password.toCharArray()); - } - - /** - * Overwrite the char array with spaces and mark the {@link Passphrase} as invalidated. - */ - public void clear() { - synchronized (lock) { - if (chars != null) { - Arrays.fill(chars, ' '); - } - valid = false; - } - } - - /** - * Return a copy of the underlying char array. - * A return value of {@code null} represents no password. - * - * @return passphrase chars. - * - * @throws IllegalStateException in case the password has been cleared at this point. - */ - public @Nullable char[] getChars() { - synchronized (lock) { - if (!valid) { - throw new IllegalStateException("Passphrase has been cleared."); - } - - if (chars == null) { - return null; - } - - char[] copy = new char[chars.length]; - System.arraycopy(chars, 0, copy, 0, chars.length); - return copy; - } - } - - /** - * Return true if the passphrase has not yet been cleared. - * - * @return valid - */ - public boolean isValid() { - synchronized (lock) { - return valid; - } - } - - /** - * Return true if the passphrase represents no password. - * - * @return empty - */ - public boolean isEmpty() { - synchronized (lock) { - return valid && chars == null; - } - } - - /** - * Represents a {@link Passphrase} instance that represents no password. - * - * @return empty passphrase - */ - public static Passphrase emptyPassphrase() { - return new Passphrase(null); - } - - @Override - public int hashCode() { - if (getChars() == null) { - return 0; - } - return new String(getChars()).hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - if (!(obj instanceof Passphrase)) { - return false; - } - Passphrase other = (Passphrase) obj; - return (getChars() == null && other.getChars() == null) || - org.bouncycastle.util.Arrays.constantTimeAreEqual(getChars(), other.getChars()); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt new file mode 100644 index 00000000..a64fda53 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.util + +import org.bouncycastle.util.Arrays + +/** + * Passphrase for keys or messages. + * + * @param chars may be null for empty passwords. + */ +class Passphrase( + chars: CharArray? +) { + private val lock = Any() + private var valid = true + private val chars: CharArray? + + init { + this.chars = trimWhitespace(chars) + } + + /** + * Return a copy of the underlying char array. + * A return value of null represents an empty password. + * + * @return passphrase chars. + * + * @throws IllegalStateException in case the password has been cleared at this point. + */ + fun getChars(): CharArray? = synchronized(lock) { + check(valid) { "Passphrase has been cleared." } + chars?.copyOf() + } + + /** + * Return true if the passphrase has not yet been cleared. + * + * @return valid + */ + val isValid: Boolean + get() = synchronized(lock) { valid } + + /** + * Return true if the passphrase represents no password. + * + * @return empty + */ + val isEmpty: Boolean + get() = synchronized(lock) { valid && chars == null } + + /** + * Overwrite the char array with spaces and mark the [Passphrase] as invalidated. + */ + fun clear() = synchronized(lock) { + chars?.fill(' ') + valid = false + } + + override fun equals(other: Any?): Boolean { + return if (other == null) + false + else if (this === other) + true + else if (other !is Passphrase) + false + else + getChars() == null && other.getChars() == null || Arrays.constantTimeAreEqual(getChars(), other.getChars()) + } + + override fun hashCode(): Int = getChars()?.let { String(it) }.hashCode() + + companion object { + + /** + * Create a [Passphrase] from a [CharSequence]. + * + * @param password password + * @return passphrase + */ + @JvmStatic + fun fromPassword(password: CharSequence) = Passphrase(password.toString().toCharArray()) + + @JvmStatic + fun emptyPassphrase() = Passphrase(null) + + /** + * Return a copy of the passed in char array, with leading and trailing whitespace characters removed. + * If the passed in char array is null, return null. + * If the resulting char array is empty, return null as well. + * + * @param chars char array + * @return copy of char array with leading and trailing whitespace characters removed + */ + @JvmStatic + private fun trimWhitespace(chars: CharArray?): CharArray? { + return chars?.dropWhile { it.isWhitespace() } + ?.dropLastWhile { it.isWhitespace() } + ?.toCharArray() + ?.let { if (it.isEmpty()) null else it } + } + } +} \ No newline at end of file