reuse SecureRandom on keepass store save

This commit is contained in:
Vladimir Krivosheev
2018-10-18 12:41:02 +02:00
parent dacb5d2700
commit de5f2667a5
5 changed files with 21 additions and 19 deletions
@@ -82,8 +82,7 @@ internal class KeePassDatabase(private val rootElement: Element = createEmptyDat
internal fun protectValue(value: CharSequence) = StringProtectedByStreamCipher(value, secureStringCipher.value)
@Synchronized
fun save(credentials: KeePassCredentials, outputStream: OutputStream) {
val secureRandom = createSecureRandom()
fun save(credentials: KeePassCredentials, outputStream: OutputStream, secureRandom: SecureRandom) {
val kdbxHeader = KdbxHeader(secureRandom)
kdbxHeader.writeKdbxHeader(outputStream)
@@ -43,6 +43,7 @@ private fun readKeePassDatabase(credentials: KeePassCredentials, inputStream: In
internal class KdbxPassword(password: ByteArray) : KeePassCredentials {
companion object {
// KdbxPassword hashes value, so, it can be cleared before file write (to reduce time when master password exposed in memory)
fun createAndClear(value: ByteArray): KeePassCredentials {
val result = KdbxPassword(value)
value.fill(0)
@@ -84,10 +84,11 @@ internal class KeePassCredentialStore constructor(internal val dbFile: Path,
}
try {
val secureRandom = createSecureRandom()
val masterKey = masterKeyStorage.load()
val kdbxPassword: KdbxPassword
if (masterKey == null) {
val key = generateRandomMasterKey(masterKeyEncryptionSpec, createSecureRandom())
val key = generateRandomMasterKey(masterKeyEncryptionSpec, secureRandom)
kdbxPassword = KdbxPassword(key.value!!)
masterKeyStorage.save(key)
}
@@ -96,7 +97,9 @@ internal class KeePassCredentialStore constructor(internal val dbFile: Path,
masterKey.fill(0)
}
dbFile.writeSafe { db.save(kdbxPassword, it) }
dbFile.writeSafe {
db.save(kdbxPassword, it, secureRandom)
}
dbFile.setOwnerPermissions()
}
catch (e: Throwable) {
@@ -124,12 +127,10 @@ internal class KeePassCredentialStore constructor(internal val dbFile: Path,
isNeedToSave.set(db.isDirty)
}
/**
* [MasterKey.value] will be cleared on set
*/
fun setMasterPassword(masterKey: MasterKey) {
@TestOnly
fun setMasterPassword(masterKey: MasterKey, secureRandom: SecureRandom) {
// KdbxPassword hashes value, so, it can be cleared before file write (to reduce time when master password exposed in memory)
saveDatabase(dbFile, db, masterKey, masterKeyStorage)
saveDatabase(dbFile, db, masterKey, masterKeyStorage, secureRandom)
}
override fun markDirty() {
@@ -149,10 +150,10 @@ internal fun generateRandomMasterKey(masterKeyEncryptionSpec: EncryptionSpec, se
return MasterKey(Base64.getEncoder().withoutPadding().encode(bytes), isAutoGenerated = true, encryptionSpec = masterKeyEncryptionSpec)
}
internal fun saveDatabase(dbFile: Path, db: KeePassDatabase, masterKey: MasterKey, masterKeyStorage: MasterKeyFileStorage) {
internal fun saveDatabase(dbFile: Path, db: KeePassDatabase, masterKey: MasterKey, masterKeyStorage: MasterKeyFileStorage, secureRandom: SecureRandom) {
val kdbxPassword = KdbxPassword(masterKey.value!!)
masterKeyStorage.save(masterKey)
dbFile.writeSafe { db.save(kdbxPassword, it) }
dbFile.writeSafe { db.save(kdbxPassword, it, secureRandom) }
dbFile.setOwnerPermissions()
}
@@ -86,7 +86,7 @@ internal open class KeePassFileManager(private val file: Path,
}
}
else {
saveDatabase(file, KeePassDatabase(), generateRandomMasterKey(masterKeyEncryptionSpec, secureRandom.value), masterKeyFileStorage)
saveDatabase(file, KeePassDatabase(), generateRandomMasterKey(masterKeyEncryptionSpec, secureRandom.value), masterKeyFileStorage, secureRandom.value)
}
}
@@ -130,9 +130,8 @@ internal open class KeePassFileManager(private val file: Path,
val contextComponent = event?.getData(PlatformDataKeys.CONTEXT_COMPONENT)
// to open old database, key can be required, so, to avoid showing 2 dialogs, check it before
val store = try {
val db = if (file.exists()) loadKdbx(file, KdbxPassword(this.masterKeyFileStorage.load() ?: throw IncorrectMasterPasswordException(isFileMissed = true))) else KeePassDatabase()
KeePassCredentialStore(file, this.masterKeyFileStorage, db)
val db = try {
if (file.exists()) loadKdbx(file, KdbxPassword(this.masterKeyFileStorage.load() ?: throw IncorrectMasterPasswordException(isFileMissed = true))) else KeePassDatabase()
}
catch (e: IncorrectMasterPasswordException) {
// ok, old key is required
@@ -140,7 +139,7 @@ internal open class KeePassFileManager(private val file: Path,
}
return requestMasterPassword("Set Master Password", contextComponent = contextComponent) {
store.setMasterPassword(createMasterKey(it))
saveDatabase(file, db, createMasterKey(it), masterKeyFileStorage, secureRandom.value)
null
}
}
@@ -183,7 +182,7 @@ internal open class KeePassFileManager(private val file: Path,
@Suppress("MemberVisibilityCanBePrivate")
protected fun doSetNewMasterPassword(current: CharArray, new: CharArray): Boolean {
val db = loadKdbx(file, createAndClear(current.toByteArrayAndClear()))
saveDatabase(file, db, createMasterKey(new), masterKeyFileStorage)
saveDatabase(file, db, createMasterKey(new), masterKeyFileStorage, secureRandom.value)
return false
}
@@ -245,7 +245,7 @@ internal class KeePassFileManagerTest {
checkEntry(db)
dbFile.outputStream().use {
db.save(kdbxPassword, it)
db.save(kdbxPassword, it, secureRandomHolder.secureRandom)
}
db = loadKdbx(dbFile, kdbxPassword)
@@ -271,7 +271,9 @@ internal class KeePassFileManagerTest {
private fun createStore() = createStore(fsRule.fs.getPath("/"))
}
internal fun KeePassCredentialStore.setMasterKey(value: String) = setMasterPassword(MasterKey(value.toByteArray(), isAutoGenerated = false, encryptionSpec = defaultEncryptionSpec))
internal fun KeePassCredentialStore.setMasterKey(value: String) {
setMasterPassword(MasterKey(value.toByteArray(), isAutoGenerated = false, encryptionSpec = defaultEncryptionSpec), KeePassFileManagerTest.secureRandomHolder.secureRandom)
}
@Suppress("TestFunctionName")
private class TestKeePassFileManager(