should be the only credentials per service name — fixed and tested for macOS

This commit is contained in:
Vladimir Krivosheev
2016-09-14 19:29:59 +02:00
parent 60d529baab
commit a44fc62d02
4 changed files with 48 additions and 11 deletions
@@ -135,8 +135,8 @@ internal class KeePassCredentialStore(keyToValue: Map<CredentialAttributes, Cred
else {
val group = db.rootGroup.getOrCreateGroup(GROUP_NAME)
// should be the only credentials per service name — find without user name
var entry = group.getEntry(attributes.serviceName, null)
val userName = attributes.userName ?: credentials.userName
var entry = group.getEntry(attributes.serviceName, if (attributes.serviceName == SERVICE_NAME_PREFIX) userName else null)
if (entry == null) {
entry = group.getOrCreateEntry(attributes.serviceName, userName)
}
@@ -27,11 +27,15 @@ import javax.crypto.spec.SecretKeySpec
internal val LOG = Logger.getInstance(CredentialStore::class.java)
internal val SERVICE_NAME_PREFIX = "IntelliJ Platform"
private fun toOldKey(hash: ByteArray) = "old-hashed-key|" + Base64.getEncoder().encodeToString(hash)
internal fun toOldKeyAsIdentity(hash: ByteArray) = CredentialAttributes("IntelliJ Platform", toOldKey(hash))
internal fun toOldKeyAsIdentity(hash: ByteArray) = CredentialAttributes(SERVICE_NAME_PREFIX, toOldKey(hash))
fun toOldKey(requestor: Class<*>, userName: String) = CredentialAttributes("IntelliJ Platform", toOldKey(MessageDigest.getInstance("SHA-256").digest("${requestor.name}/$userName".toByteArray())))
fun toOldKey(requestor: Class<*>, userName: String): CredentialAttributes {
return CredentialAttributes(SERVICE_NAME_PREFIX, toOldKey(MessageDigest.getInstance("SHA-256").digest("${requestor.name}/$userName".toByteArray())))
}
fun joinData(user: String?, password: OneTimeString?): ByteArray? {
if (user == null && password == null) {
@@ -59,15 +59,29 @@ internal class KeyChainCredentialStore() : CredentialStore {
val password = credentials!!.password?.toByteArray(false)
val userName = (attributes.userName ?: credentials.userName)?.toByteArray()
val searchUserName = if (attributes.serviceName == SERVICE_NAME_PREFIX) userName else null
val itemRef = PointerByReference()
val library = LIBRARY
checkForError("find (for save)", library.SecKeychainFindGenericPassword(null, serviceName.size, serviceName, userName?.size ?: 0, userName, null, null, itemRef))
checkForError("find (for save)", library.SecKeychainFindGenericPassword(null, serviceName.size, serviceName, searchUserName?.size ?: 0, searchUserName, null, null, itemRef))
val pointer = itemRef.value
if (pointer == null) {
checkForError("save (new)", library.SecKeychainAddGenericPassword(null, serviceName.size, serviceName, userName?.size ?: 0, userName, password?.size ?: 0, password))
}
else {
checkForError("save (update)", library.SecKeychainItemModifyContent(pointer, null, password?.size ?: 0, password))
val attribute = SecKeychainAttribute()
attribute.tag = kSecAccountItemAttr
attribute.length = userName?.size ?: 0
if (userName != null) {
val userNamePointer = Memory(userName.size.toLong())
userNamePointer.write(0, userName, 0, userName.size)
attribute.data = userNamePointer
}
val attributeList = SecKeychainAttributeList()
attributeList.count = 1
attribute.write()
attributeList.attr = attribute.pointer
checkForError("save (update)", library.SecKeychainItemModifyContent(pointer, attributeList, password?.size ?: 0, password))
library.CFRelease(pointer)
}
@@ -107,7 +121,7 @@ fun findGenericPassword(serviceName: ByteArray, accountName: String?): Credentia
private interface MacOsKeychainLibrary : Library {
fun SecKeychainAddGenericPassword(keychain: Pointer?, serviceNameLength: Int, serviceName: ByteArray, accountNameLength: Int, accountName: ByteArray?, passwordLength: Int, passwordData: ByteArray?, itemRef: Pointer? = null): Int
fun SecKeychainItemModifyContent(itemRef: Pointer, /*SecKeychainAttributeList**/ attrList: Pointer?, length: Int, data: ByteArray?): Int
fun SecKeychainItemModifyContent(itemRef: Pointer, /*SecKeychainAttributeList**/ attrList: Any?, length: Int, data: ByteArray?): Int
fun SecKeychainFindGenericPassword(keychainOrArray: Pointer?,
serviceNameLength: Int,
@@ -201,10 +215,13 @@ internal class SecKeychainAttributeList : Structure {
constructor(p: Pointer) : super(p) {
}
constructor() : super() {
}
override fun getFieldOrder() = listOf("count", "attr")
}
internal class SecKeychainAttribute : Structure {
internal class SecKeychainAttribute : Structure, Structure.ByReference {
@JvmField
var tag = 0
@JvmField
@@ -215,6 +232,9 @@ internal class SecKeychainAttribute : Structure {
internal constructor(p: Pointer) : super(p) {
}
internal constructor() : super() {
}
override fun getFieldOrder() = listOf("tag", "length", "data")
}
@@ -28,7 +28,7 @@ internal class CredentialStoreTest {
}
@Test
fun keepass() {
fun KeePass() {
doTest(KeePassCredentialStore())
}
@@ -41,6 +41,15 @@ internal class CredentialStoreTest {
testEmptyAccountName(KeyChainCredentialStore())
}
@Test
fun `mac - changedAccountName`() {
if (!SystemInfo.isMacIntel64 || UsefulTestCase.IS_UNDER_TEAMCITY) {
return
}
testChangedAccountName(KeyChainCredentialStore())
}
@Test
fun `linux - testEmptyAccountName`() {
if (!SystemInfo.isLinux || UsefulTestCase.IS_UNDER_TEAMCITY) {
@@ -51,12 +60,12 @@ internal class CredentialStoreTest {
}
@Test
fun `keepass - testEmptyAccountName`() {
fun `KeePass - testEmptyAccountName`() {
testEmptyAccountName(KeePassCredentialStore())
}
@Test
fun `keepass - testChangedAccountName`() {
fun `KeePass - changedAccountName`() {
testChangedAccountName(KeePassCredentialStore())
}
@@ -103,12 +112,16 @@ internal class CredentialStoreTest {
val serviceNameOnlyAttributes = CredentialAttributes("Test IJ — ${randomString()}")
try {
val credentials = Credentials(randomString(), "pass")
val newUserName = randomString()
var newUserName = randomString()
val newPassword = randomString()
store.set(serviceNameOnlyAttributes, credentials)
assertThat(store.get(serviceNameOnlyAttributes)).isEqualTo(credentials)
store.set(CredentialAttributes(serviceNameOnlyAttributes.serviceName, newUserName), Credentials(newUserName, newPassword))
assertThat(store.get(serviceNameOnlyAttributes)).isEqualTo(Credentials(newUserName, newPassword))
newUserName = randomString()
store.set(CredentialAttributes(serviceNameOnlyAttributes.serviceName, newUserName), Credentials(newUserName, newPassword))
assertThat(store.get(serviceNameOnlyAttributes)!!.userName).isEqualTo(newUserName)
}
finally {
store.set(serviceNameOnlyAttributes, null)