IDEA-160341 secure implementation of joinData

This commit is contained in:
Vladimir Krivosheev
2016-08-26 13:44:03 +02:00
parent 84e58932ff
commit 68a2fd4518
5 changed files with 31 additions and 7 deletions
@@ -17,6 +17,8 @@ package com.intellij.credentialStore
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.io.toByteArray
import java.nio.CharBuffer
import java.security.MessageDigest
import java.util.*
@@ -28,11 +30,23 @@ internal fun toOldKeyAsIdentity(hash: ByteArray) = CredentialAttributes("Intelli
fun toOldKey(requestor: Class<*>, userName: String) = CredentialAttributes("IntelliJ Platform", toOldKey(MessageDigest.getInstance("SHA-256").digest("${requestor.name}/$userName".toByteArray())))
fun joinData(user: String?, password: OneTimeString?): String? {
fun joinData(user: String?, password: OneTimeString?): ByteArray? {
if (user == null && password == null) {
return null
}
return "${StringUtil.escapeChars(user.orEmpty(), '\\', '@')}${if (password == null) "" else "@$password"}"
val builder = StringBuilder(user.orEmpty())
StringUtil.escapeChar(builder, '\\')
StringUtil.escapeChar(builder, '@')
if (password != null) {
builder.append('@')
password.appendTo(builder)
}
val buffer = Charsets.UTF_8.encode(CharBuffer.wrap(builder))
// clear password
builder.setLength(0)
return buffer.toByteArray()
}
fun splitData(data: String?): Credentials? {
@@ -78,4 +92,4 @@ private fun parseString(data: String, delimiter: Char): List<String> {
}
// check isEmpty before
fun Credentials.serialize() = joinData(userName, password)!!.toByteArray()
fun Credentials.serialize() = joinData(userName, password)!!
@@ -13,10 +13,13 @@ private const val SECRET_SCHEMA_NONE = 0
private const val SECRET_SCHEMA_ATTRIBUTE_STRING = 0
// explicitly create pointer to be explicitly dispose it to avoid sensitive data in the memory
internal fun stringPointer(data: ByteArray): DisposableMemory {
internal fun stringPointer(data: ByteArray, clearInput: Boolean = false): DisposableMemory {
val pointer = DisposableMemory(data.size + 1L)
pointer.write(0, data, 0, data.size)
pointer.setByte(data.size.toLong(), 0.toByte())
if (clearInput) {
data.fill(0)
}
return pointer
}
@@ -74,7 +77,7 @@ internal class SecretCredentialStore(schemeName: String) : CredentialStore {
return
}
val passwordPointer = stringPointer(credentials!!.serialize())
val passwordPointer = stringPointer(credentials!!.serialize(), true)
checkError("secret_password_store_sync") { errorRef ->
try {
if (accountName == null) {
@@ -27,7 +27,7 @@ class CredentialSerializeTest {
private fun test(u: String?, p: String?, joined: String) {
val pass = p?.let(::OneTimeString)
assertThat(joinData(u, pass)).isEqualTo(joined)
assertThat(joinData(u, pass)).isEqualTo(joined.toByteArray())
assertThat(splitData(joined)).isEqualTo(Credentials(u, pass))
}
}
@@ -127,4 +127,11 @@ class OneTimeString @JvmOverloads constructor(value: CharArray, offset: Int = 0,
}
return super.equals(other)
}
fun appendTo(builder: StringBuilder) {
if (consumed.get()) {
throw Error("Already consumed")
}
builder.append(myChars, myStart, length)
}
}
@@ -2135,7 +2135,7 @@ public class StringUtil extends StringUtilRt {
return buf.toString();
}
private static void escapeChar(@NotNull final StringBuilder buf, final char character) {
public static void escapeChar(@NotNull final StringBuilder buf, final char character) {
int idx = 0;
while ((idx = indexOf(buf, character, idx)) >= 0) {
buf.insert(idx, "\\");