annotate src/main/kotlin/name/blackcap/passman/Database.kt @ 7:f245b9a53495

Efficiency improvements.
author David Barts <n5jrn@me.com>
date Tue, 20 Sep 2022 21:54:32 -0700
parents 711cc42e96d7
children 698c4a3d758d
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
1 package name.blackcap.passman
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
2
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
3 import java.nio.file.Files
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
4 import java.nio.file.Path
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
5 import java.security.GeneralSecurityException
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
6 import java.security.SecureRandom
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
7 import java.sql.*
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
8
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
9 class Database private constructor(val connection: Connection, val encryption: Encryption){
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
10
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
11 companion object {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
12 private const val PLAINTEXT = "This is a test."
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
13 private const val SALT_LENGTH = 16
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
14 private const val DEFAULT_PROMPT = "Decryption key: "
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
15
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
16 fun open(passwordPrompt: String = DEFAULT_PROMPT, fileName: String = DB_FILE,
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
17 create: Boolean = true): Database {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
18 val exists = Files.exists(Path.of(fileName))
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
19 if (!exists) {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
20 if (create) {
5
ad997df1f560 Fix see() to be about as good as sccc.
David Barts <n5jrn@me.com>
parents: 3
diff changeset
21 error("initializing database ${see(fileName)}")
0
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
22 } else {
5
ad997df1f560 Fix see() to be about as good as sccc.
David Barts <n5jrn@me.com>
parents: 3
diff changeset
23 die("${see(fileName)} not found")
0
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
24 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
25 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
26 val masterPassword = getPassword(passwordPrompt, !exists)
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
27 val conn = DriverManager.getConnection("jdbc:sqlite:$fileName")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
28 val enc = if (exists) { reuse(conn, masterPassword) } else { init(conn, masterPassword) }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
29 val ret = Database(conn, enc)
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
30 verifyPassword(ret)
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
31 return ret
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
32 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
33
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
34 private fun reuse(connection: Connection, masterPassword: CharArray): Encryption {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
35 try {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
36 connection.prepareStatement("select value from blobs where name = ?").use {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
37 it.setString(1, "salt")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
38 val result = it.executeQuery()
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
39 if (!result.next()) {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
40 die("corrupt database, missing salt parameter")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
41 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
42 val salt = result.getBytes(1)
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
43 return Encryption(masterPassword, salt)
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
44 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
45 } catch (e: SQLException) {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
46 e.printStackTrace()
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
47 die("unable to reopen database")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
48 throw RuntimeException("this will never happen")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
49 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
50 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
51
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
52 private fun init(connection: Connection, masterPassword: CharArray): Encryption {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
53 try {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
54 connection.createStatement().use { stmt ->
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
55 stmt.executeUpdate("create table integers ( name string not null, value integer )")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
56 stmt.executeUpdate("create table reals ( name string not null, value integer )")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
57 stmt.executeUpdate("create table strings ( name string not null, value real )")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
58 stmt.executeUpdate("create table blobs ( name string not null, value blob )")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
59 stmt.executeUpdate(
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
60 "create table passwords (" +
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
61 "id integer not null primary key, " +
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
62 "name blob not null, " +
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
63 "username blob not null, " +
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
64 "password blob not null, " +
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
65 "notes blob, " +
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
66 "created integer, " +
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
67 "modified integer, " +
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
68 "accessed integer )"
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
69 )
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
70 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
71 val salt = ByteArray(SALT_LENGTH).also { SecureRandom().nextBytes(it) }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
72 val encryption = Encryption(masterPassword, salt)
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
73 connection.prepareStatement("insert into blobs (name, value) values (?, ?)").use {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
74 it.setString(1, "salt")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
75 it.setBytes(2, salt)
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
76 it.execute()
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
77 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
78 connection.prepareStatement("insert into blobs (name, value) values (?, ?)").use { stmt ->
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
79 stmt.setString(1, "test")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
80 stmt.setEncryptedString(2, PLAINTEXT, encryption)
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
81 stmt.execute()
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
82 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
83 return encryption
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
84 } catch (e: SQLException) {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
85 e.printStackTrace()
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
86 die("unable to initialize database")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
87 throw RuntimeException("this will never happen")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
88 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
89 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
90
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
91 private fun verifyPassword(database: Database) {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
92 try {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
93 database.connection.prepareStatement("select value from blobs where name = ?").use { stmt ->
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
94 stmt.setString(1, "test")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
95 val result = stmt.executeQuery()
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
96 if (!result.next()) {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
97 die("corrupt database, missing test parameter")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
98 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
99 val readFromDb = result.getDecryptedString(1, database.encryption)
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
100 if (readFromDb != PLAINTEXT) {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
101 /* might also get thrown by getDecryptedString if bad */
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
102 throw GeneralSecurityException("bad key!")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
103 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
104 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
105 } catch (e: SQLException) {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
106 e.printStackTrace()
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
107 die("unable to verify decryption key")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
108 } catch (e: GeneralSecurityException) {
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
109 die("invalid decryption key")
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
110 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
111 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
112 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
113
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
114 fun makeKey(name: String): Long = Hashing.hash(encryption.encryptFromString0(name.lowercase()))
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
115 }
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
116
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
117 public fun ResultSet.getDecryptedString(columnIndex: Int, encryption: Encryption) =
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
118 encryption.decryptToString(getBytes(columnIndex))
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
119
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
120 public fun ResultSet.getDecrypted(columnIndex: Int, encryption: Encryption) =
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
121 encryption.decrypt(getBytes(columnIndex))
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
122
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
123 public fun PreparedStatement.setEncryptedString(columnIndex: Int, value: String, encryption: Encryption) =
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
124 setBytes(columnIndex, encryption.encryptFromString(value))
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
125
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
126 public fun PreparedStatement.setEncrypted(columnIndex: Int, value: CharArray, encryption: Encryption) =
a6cfdffcaa94 Initial commit, incomplete but it runs sorta.
David Barts <n5jrn@me.com>
parents:
diff changeset
127 setBytes(columnIndex, encryption.encrypt(value))