diff --git a/platform/credential-store/src/libraries/macOsKeychainLibrary.kt b/platform/credential-store/src/libraries/macOsKeychainLibrary.kt index abcdb14bc087..f9ba554f49a2 100644 --- a/platform/credential-store/src/libraries/macOsKeychainLibrary.kt +++ b/platform/credential-store/src/libraries/macOsKeychainLibrary.kt @@ -84,7 +84,7 @@ fun findGenericPassword(serviceName: ByteArray, accountName: String?): Credentia checkForError("find", LIBRARY.SecKeychainFindGenericPassword(null, serviceName.size, serviceName, accountNameBytes?.size ?: 0, accountNameBytes, passwordSize, passwordRef, itemRef)) val pointer = passwordRef.value ?: return null - val password = SecureString(pointer.getByteArray(0, passwordSize.get(0))) + val password = OneTimeString(pointer.getByteArray(0, passwordSize.get(0))) LIBRARY.SecKeychainItemFreeContent(null, pointer) var effectiveAccountName = accountName diff --git a/platform/platform-api/src/com/intellij/credentialStore/CredentialAttributes.kt b/platform/platform-api/src/com/intellij/credentialStore/CredentialAttributes.kt index 5fb10d2784a6..82312f090ceb 100644 --- a/platform/platform-api/src/com/intellij/credentialStore/CredentialAttributes.kt +++ b/platform/platform-api/src/com/intellij/credentialStore/CredentialAttributes.kt @@ -59,7 +59,7 @@ fun Credentials?.isEmpty() = this == null || (userName == null && password == nu // input will be cleared @JvmOverloads -fun SecureString(value: ByteArray, offset: Int = 0, length: Int = value.size - offset): OneTimeString { +fun OneTimeString(value: ByteArray, offset: Int = 0, length: Int = value.size - offset): OneTimeString { if (length == 0) { return OneTimeString(ArrayUtil.EMPTY_CHAR_ARRAY) } @@ -82,6 +82,8 @@ fun SecureString(value: ByteArray, offset: Int = 0, length: Int = value.size - o return OneTimeString(charArray, 0, charBuffer.position()) } +private val oneTimeStringEnabled = com.intellij.util.SystemProperties.getBooleanProperty("one.time.string.enabled", false) + @Suppress("EqualsOrHashCode") // todo - eliminate toString class OneTimeString @JvmOverloads constructor(value: CharArray, offset: Int = 0, length: Int = value.size) : CharArrayCharSequence(value, offset, offset + length) { @@ -90,17 +92,22 @@ class OneTimeString @JvmOverloads constructor(value: CharArray, offset: Int = 0, constructor(value: String): this(value.toCharArray()) { } - private fun consume() { - if (!consumed.compareAndSet(null, ExceptionUtil.currentStackTrace())) { + private fun consume(willBeCleared: Boolean) { + if (!oneTimeStringEnabled) { + return + } + + if (!willBeCleared) { + consumed.get()?.let { throw IllegalStateException("Already consumed at $it") } + } + else if (!consumed.compareAndSet(null, ExceptionUtil.currentStackTrace())) { throw IllegalStateException("Already consumed at ${consumed.get()}") } } @JvmOverloads fun toString(clear: Boolean = true): String { - if (clear) { - consume() - } + consume(clear) // todo clear return super.toString() } @@ -108,12 +115,10 @@ class OneTimeString @JvmOverloads constructor(value: CharArray, offset: Int = 0, // string will be cleared and not valid after @JvmOverloads fun toByteArray(clear: Boolean = true): ByteArray { - if (clear) { - consume() - } + consume(clear) val result = Charsets.UTF_8.encode(CharBuffer.wrap(myChars, myStart, length)) - if (clear) { + if (clear && oneTimeStringEnabled) { myChars.fill('\u0000', myStart, myEnd) } return result.toByteArray() @@ -121,9 +126,7 @@ class OneTimeString @JvmOverloads constructor(value: CharArray, offset: Int = 0, @JvmOverloads fun toCharArray(clear: Boolean = true): CharArray { - if (clear) { - consume() - } + consume(clear) // todo clear return chars } @@ -136,7 +139,7 @@ class OneTimeString @JvmOverloads constructor(value: CharArray, offset: Int = 0, } fun appendTo(builder: StringBuilder) { - consumed.get()?.let { throw IllegalStateException("Already consumed at $it") } + consume(false) builder.append(myChars, myStart, length) } } \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/openapi/util/PasswordUtil.java b/platform/platform-api/src/com/intellij/openapi/util/PasswordUtil.java index 7f696a423614..fb95ec4f16a1 100644 --- a/platform/platform-api/src/com/intellij/openapi/util/PasswordUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/util/PasswordUtil.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.util; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -51,15 +52,15 @@ public class PasswordUtil { @NotNull public static char[] decodePasswordAsCharArray(@Nullable String password) throws NumberFormatException { - if (password == null) { + if (StringUtil.isEmpty(password)) { return ArrayUtil.EMPTY_CHAR_ARRAY; } - char[] result = new char[password.length()]; - for (int i = 0; i < password.length(); i += 4) { + char[] result = new char[password.length() / 4]; + for (int i = 0, j = 0; i < password.length(); i += 4, j++) { int c = Integer.parseInt(password.substring(i, i + 4), 16); c ^= 0xdfaa; - result[i] = new Character((char)c).charValue(); + result[j] = new Character((char)c).charValue(); } return result; } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java index 5ffaa7b22103..297786e55522 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java @@ -200,7 +200,7 @@ public class EncryptionUtil { if (len < 0 || len > plain.length - 4) { throw new IllegalStateException("Unmatched password is used"); } - return CredentialAttributesKt.SecureString(plain, 4, len); + return CredentialAttributesKt.OneTimeString(plain, 4, len); } /**