comparison src/main/kotlin/name/blackcap/passman/Database.kt @ 21:ea65ab890f66

More work to support interactive feature.
author David Barts <n5jrn@me.com>
date Tue, 02 Jul 2024 11:27:39 -0700
parents 7d80cbcb67bb
children
comparison
equal deleted inserted replaced
20:4391afcf6bd0 21:ea65ab890f66
10 10
11 companion object { 11 companion object {
12 private const val PLAINTEXT = "This is a test." 12 private const val PLAINTEXT = "This is a test."
13 private const val SALT_LENGTH = 16 13 private const val SALT_LENGTH = 16
14 private const val DEFAULT_PROMPT = "Decryption key: " 14 private const val DEFAULT_PROMPT = "Decryption key: "
15 lateinit var default: Database
15 16
16 fun open(passwordPrompt: String = DEFAULT_PROMPT, fileName: String = DB_FILE, 17 fun open(passwordPrompt: String = DEFAULT_PROMPT, fileName: String = DB_FILE,
17 create: Boolean = true): Database { 18 create: Boolean = true): Database {
18 val exists = Files.exists(Path.of(fileName)) 19 val exists = Files.exists(Path.of(fileName))
19 if (!exists) { 20 if (!exists) {
20 if (create) { 21 if (create) {
21 error("initializing database ${see(fileName)}") 22 error("initializing database ${see(fileName)}")
22 } else { 23 } else {
23 die("${see(fileName)} not found") 24 throw DatabaseException("${see(fileName)} not found")
24 } 25 }
25 } 26 }
26 val masterPassword = getPassword(passwordPrompt, !exists) 27 val masterPassword = try {
28 getPassword(passwordPrompt, !exists)
29 } catch (e: ConsoleException) {
30 throw DatabaseException(e.message, cause = e)
31 }
27 val conn = DriverManager.getConnection("jdbc:sqlite:$fileName") 32 val conn = DriverManager.getConnection("jdbc:sqlite:$fileName")
28 val enc = if (exists) { reuse(conn, masterPassword) } else { init(conn, masterPassword) } 33 val enc = if (exists) { reuse(conn, masterPassword) } else { init(conn, masterPassword) }
29 val ret = Database(conn, enc) 34 val ret = Database(conn, enc)
30 verifyPassword(ret) 35 verifyPassword(ret)
31 return ret 36 return ret
35 try { 40 try {
36 connection.prepareStatement("select value from blobs where name = ?").use { 41 connection.prepareStatement("select value from blobs where name = ?").use {
37 it.setString(1, "salt") 42 it.setString(1, "salt")
38 val result = it.executeQuery() 43 val result = it.executeQuery()
39 if (!result.next()) { 44 if (!result.next()) {
40 die("corrupt database, missing salt parameter") 45 throw DatabaseException("corrupt database, missing salt parameter")
41 } 46 }
42 val salt = result.getBytes(1) 47 val salt = result.getBytes(1)
43 return Encryption(masterPassword, salt) 48 return Encryption(masterPassword, salt)
44 } 49 }
45 } catch (e: SQLException) { 50 } catch (e: SQLException) {
46 e.printStackTrace() 51 e.printStackTrace()
47 die("unable to reopen database") 52 throw DatabaseException("unable to reopen database", e)
48 throw RuntimeException("this will never happen")
49 } 53 }
50 } 54 }
51 55
52 private fun init(connection: Connection, masterPassword: CharArray): Encryption { 56 private fun init(connection: Connection, masterPassword: CharArray): Encryption {
53 try { 57 try {
81 stmt.execute() 85 stmt.execute()
82 } 86 }
83 return encryption 87 return encryption
84 } catch (e: SQLException) { 88 } catch (e: SQLException) {
85 e.printStackTrace() 89 e.printStackTrace()
86 die("unable to initialize database") 90 throw DatabaseException("unable to initialize database", e)
87 throw RuntimeException("this will never happen")
88 } 91 }
89 } 92 }
90 93
91 private fun verifyPassword(database: Database) { 94 private fun verifyPassword(database: Database) {
92 try { 95 try {
93 database.connection.prepareStatement("select value from blobs where name = ?").use { stmt -> 96 database.connection.prepareStatement("select value from blobs where name = ?").use { stmt ->
94 stmt.setString(1, "test") 97 stmt.setString(1, "test")
95 val result = stmt.executeQuery() 98 val result = stmt.executeQuery()
96 if (!result.next()) { 99 if (!result.next()) {
97 die("corrupt database, missing test parameter") 100 throw DatabaseException("corrupt database, missing test parameter")
98 } 101 }
99 val readFromDb = result.getDecryptedString(1, database.encryption) 102 val readFromDb = result.getDecryptedString(1, database.encryption)
100 if (readFromDb != PLAINTEXT) { 103 if (readFromDb != PLAINTEXT) {
101 /* might also get thrown by getDecryptedString if bad */ 104 /* might also get thrown by getDecryptedString if bad */
102 throw GeneralSecurityException("bad key!") 105 throw GeneralSecurityException("bad key!")
103 } 106 }
104 } 107 }
105 } catch (e: SQLException) { 108 } catch (e: SQLException) {
106 e.printStackTrace() 109 e.printStackTrace()
107 die("unable to verify decryption key") 110 throw DatabaseException("unable to verify decryption key", e)
108 } catch (e: GeneralSecurityException) { 111 } catch (e: GeneralSecurityException) {
109 die("invalid decryption key") 112 throw DatabaseException("invalid decryption key", e)
110 } 113 }
111 } 114 }
112 } 115 }
113 116
114 fun makeKey(name: String): Long = Hashing.hash(encryption.encryptFromString0(name.lowercase())) 117 fun makeKey(name: String): Long = Hashing.hash(encryption.encryptFromString0(name.lowercase()))
115 } 118 }
119
120 class DatabaseException(message: String, cause: Throwable? = null) : MessagedException(message, cause)
116 121
117 fun ResultSet.getDecryptedString(columnIndex: Int, encryption: Encryption): String? { 122 fun ResultSet.getDecryptedString(columnIndex: Int, encryption: Encryption): String? {
118 return encryption.decryptToString(getBytes(columnIndex) ?: return null) 123 return encryption.decryptToString(getBytes(columnIndex) ?: return null)
119 } 124 }
120 125