Mercurial > cgi-bin > hgweb.cgi > PassMan
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 |