diff options
author | Marvin Borner | 2019-06-20 16:50:27 +0200 |
---|---|---|
committer | Marvin Borner | 2019-06-20 16:50:27 +0200 |
commit | 8cd0aeb5af65d771d1952a37a6cf7fd457796bf2 (patch) | |
tree | 5eaf50359358f5b0e3f80a8b36b6dd224f0edd87 | |
parent | a34a0d45da548bef8b18da04d2947fb0ff0edae0 (diff) |
Added AES file encryption
Co-authored-by: LarsVomMars <lars@kroenner.eu>
-rw-r--r-- | src/main/kotlin/App.kt | 2 | ||||
-rw-r--r-- | src/main/kotlin/CryptoHandler.kt | 50 | ||||
-rw-r--r-- | src/main/kotlin/DatabaseController.kt | 20 | ||||
-rw-r--r-- | src/main/kotlin/FileController.kt | 93 | ||||
-rw-r--r-- | src/main/kotlin/LogFilter.kt | 2 |
5 files changed, 120 insertions, 47 deletions
diff --git a/src/main/kotlin/App.kt b/src/main/kotlin/App.kt index 181149d..55058be 100644 --- a/src/main/kotlin/App.kt +++ b/src/main/kotlin/App.kt @@ -12,7 +12,7 @@ import org.slf4j.* import java.net.* import kotlin.system.* -const val debug = true +const val debug = false var silent = true var port = 7000 // TODO: Add abstract and secure file home support for windows/BSD/macOS diff --git a/src/main/kotlin/CryptoHandler.kt b/src/main/kotlin/CryptoHandler.kt new file mode 100644 index 0000000..03396f2 --- /dev/null +++ b/src/main/kotlin/CryptoHandler.kt @@ -0,0 +1,50 @@ +package space.anity + +import java.io.* +import java.security.* +import javax.crypto.* +import javax.crypto.spec.* + +class CryptoHandler @Throws(NoSuchPaddingException::class, NoSuchAlgorithmException::class) +internal constructor(private val secretKey: SecretKey, cipher: String) { + private val cipher: Cipher = Cipher.getInstance(cipher) + + @Throws(InvalidKeyException::class, IOException::class) + internal fun encrypt(content: String, fileName: String): ByteArray { + cipher.init(Cipher.ENCRYPT_MODE, secretKey) + val iv: ByteArray = cipher.iv + + FileOutputStream(fileName).use { fileOut -> + CipherOutputStream(fileOut, cipher).use { cipherOut -> + cipherOut.write(content.toByteArray()) + } + } + + return iv + } + + @Throws(InvalidAlgorithmParameterException::class, InvalidKeyException::class, IOException::class) + internal fun decrypt(fileName: String, iv: ByteArray): String { + var content = "" + + FileInputStream(fileName).use { fileIn -> + cipher.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv)) + + CipherInputStream(fileIn, cipher).use { cipherIn -> + InputStreamReader(cipherIn).use { inputReader -> + BufferedReader(inputReader).use { reader -> + val sb = StringBuilder() + var line: String? = reader.readLine() + while (line != null) { + sb.append(line) + line = reader.readLine() + } + content = sb.toString() + } + } + } + } + + return content + } +} diff --git a/src/main/kotlin/DatabaseController.kt b/src/main/kotlin/DatabaseController.kt index 8ae39a3..d60f278 100644 --- a/src/main/kotlin/DatabaseController.kt +++ b/src/main/kotlin/DatabaseController.kt @@ -25,6 +25,7 @@ class DatabaseController { val userId = integer("userId").references(UserData.id) val accessId = varchar("accessId", 64).uniqueIndex() val isShared = bool("isShared").default(false) + val encryptIV = binary("iv", 16) // empty if directory } /** @@ -221,7 +222,6 @@ class DatabaseController { try { UserData.select { UserData.verification eq verificationId }.map { it[UserData.id] }[0] } catch (err: Exception) { - log.error(err.toString()) -1 } } @@ -235,7 +235,6 @@ class DatabaseController { try { UserData.select { UserData.id eq userId }.map { it[UserData.darkTheme] }[0] } catch (err: Exception) { - log.error(err.toString()) false } } @@ -310,7 +309,6 @@ class DatabaseController { } userRoles } catch (err: Exception) { - log.error(err.toString()) listOf(Roles.GUEST) } } @@ -319,7 +317,7 @@ class DatabaseController { /** * Adds the uploaded file to the database */ - fun addFile(fileLocation: String, usersId: Int, isDirectoryBool: Boolean = false): Boolean { + fun addFile(fileLocation: String, usersId: Int, isDirectoryBool: Boolean = false, iv: ByteArray = ByteArray(16)): Boolean { return transaction { try { if (FileLocation.select { (FileLocation.path eq fileLocation) and (FileLocation.userId eq usersId) }.empty()) { @@ -328,6 +326,7 @@ class DatabaseController { it[userId] = usersId it[accessId] = generateRandomString() it[isDirectory] = isDirectoryBool + it[encryptIV] = iv } true } else { @@ -356,6 +355,19 @@ class DatabaseController { } /** + * Returns IV of given file + */ + fun getFileIV(fileLocation: String, userId: Int): ByteArray { + return transaction { + try { + FileLocation.select { (FileLocation.path eq fileLocation) and (FileLocation.userId eq userId) }.map { it[FileLocation.encryptIV] }[0] + } catch (err: Exception) { + ByteArray(16) + } + } + } + + /** * Returns the accessId of the given file */ fun getAccessId(fileLocation: String, userId: Int): String { diff --git a/src/main/kotlin/FileController.kt b/src/main/kotlin/FileController.kt index 1b48b4c..38fdd4e 100644 --- a/src/main/kotlin/FileController.kt +++ b/src/main/kotlin/FileController.kt @@ -1,35 +1,40 @@ package space.anity import io.javalin.* -import io.javalin.core.util.* import io.javalin.rendering.template.TemplateUtil.model import org.slf4j.* import java.io.* import java.nio.charset.* import java.nio.file.* import java.text.* +import javax.crypto.* +import javax.crypto.spec.* class FileController { private val log = LoggerFactory.getLogger(this.javaClass.name) + private val staticEncPwd = "asdffdsaasdffdsa" + private val secretKey: SecretKey = SecretKeySpec(staticEncPwd.toByteArray(), "AES") + /** * Crawls the requested file and either renders the directory view or the file view */ fun crawl(ctx: Context) { try { - val usersFileHome = "$fileHome/${userHandler.getVerifiedUserId(ctx)}" + val userId = userHandler.getVerifiedUserId(ctx) + val usersFileHome = "$fileHome/$userId" val firstParam = ctx.splat(0) ?: "" val fileLocation = "$usersFileHome/$firstParam" - File(usersFileHome).mkdirs() + File(fileLocation).mkdirs() when { - ctx.queryParam("raw") != null -> ctx.result(FileInputStream(File(fileLocation))) + ctx.queryParam("raw") != null -> ctx.result(decrypt(fileLocation, userId)) File(fileLocation).isDirectory -> { val files = ArrayList<Array<String>>() Files.list(Paths.get("$usersFileHome/$firstParam/")).forEach { val filename = it.toString() .drop(usersFileHome.length + (if (firstParam.isNotEmpty()) firstParam.length + 2 else 1)) val filePath = "$usersFileHome${it.toString().drop(usersFileHome.length)}" - files.add(addToFileListing(filePath, filename)) + files.add(addToFileListing(filePath, filename, ctx)) } files.sortWith(compareBy { it.first() }) ctx.render( @@ -41,10 +46,10 @@ class FileController { ) ) } - isHumanReadable(File(fileLocation)) -> handleHumanReadableFile(fileLocation, ctx) + isHumanReadable(decrypt(fileLocation, userId).toByteArray()) -> handleHumanReadableFile(fileLocation, ctx) else -> { ctx.contentType(Files.probeContentType(Paths.get(fileLocation))) - ctx.result(FileInputStream(File(fileLocation))) + ctx.result(decrypt(fileLocation, userId)) } } } catch (err: Exception) { @@ -54,6 +59,14 @@ class FileController { } /** + * Decrypts a file using the [filePath] and the crypto helping class + */ + private fun decrypt(fileLocation: String, userId: Int): String { + val cryptoHandler = CryptoHandler(secretKey, "AES/CBC/PKCS5Padding") + return cryptoHandler.decrypt(fileLocation, databaseController.getFileIV(fileLocation, userId)) + } + + /** * Gets directory size recursively */ private fun getDirectorySize(directory: File): Long { @@ -68,13 +81,7 @@ class FileController { /** * Checks whether the file is binary or human-readable (text) */ - private fun isHumanReadable(file: File): Boolean { - val input = FileInputStream(file) - var size = input.available() - if (size > 1000) size = 1000 - val data = ByteArray(size) - input.read(data) - input.close() + private fun isHumanReadable(data: ByteArray): Boolean { val text = String(data, Charset.forName("ISO-8859-1")) val replacedText = text.replace( ("[a-zA-Z0-9ßöäü\\.\\*!\"§\\$\\%&/()=\\?@~'#:,;\\+><\\|\\[\\]\\{\\}\\^°²³\\\\ \\n\\r\\t_\\-`´âêîôÂÊÔÎáéíóàèìòÁÉÍÓÀÈÌÒ©‰¢£¥€±¿»«¼½¾™ª]").toRegex(), @@ -99,25 +106,29 @@ class FileController { * Saves multipart media data into requested directory */ fun upload(ctx: Context) { - ctx.uploadedFiles("file").forEach { (_, content, name, _) -> - val fixedName = name.replace(":", "/") // "fix" for Firefox.. - val userId = userHandler.getVerifiedUserId(ctx) - var addPath = "" + try { + ctx.uploadedFiles("file").forEach { (_, content, name, _) -> + val fixedName = name.replace(":", "/") // "fix" for Firefox.. + val userId = userHandler.getVerifiedUserId(ctx) + val fileLocation = "$fileHome/$userId/$fixedName" + var addPath = "" - fixedName.split("/").forEach { - addPath += "$it/" - if (!fixedName.endsWith(it)) databaseController.addFile(addPath, userId, true) - } + val stringContent = content.bufferedReader().use { it.readText() } - if (databaseController.addFile(fixedName, userId)) { - FileUtil.streamToFile( - content, - "$fileHome/$userId/$fixedName" - ) + fixedName.split("/").forEach { + addPath += "$it/" + if (!fixedName.endsWith(it)) databaseController.addFile(addPath, userId, true) + } + + val cryptoHandler = CryptoHandler(secretKey, "AES/CBC/PKCS5Padding") + val iv = cryptoHandler.encrypt(stringContent, fileLocation) + databaseController.addFile(fixedName, userId, false, iv) } - } - ctx.json("success") + ctx.json("success") + } catch (err: Exception) { + log.error(err.toString()) + } } /** @@ -189,7 +200,8 @@ class FileController { if (sharedFileData.userId > 0 && fileLocation.isNotEmpty()) { val sharedFileLocation = "$fileHome/${sharedFileData.userId}/$fileLocation" if (!sharedFileData.isDirectory) { - if (isHumanReadable(File(sharedFileLocation))) handleHumanReadableFile(sharedFileLocation, ctx) + if (isHumanReadable(decrypt(fileLocation, userHandler.getVerifiedUserId(ctx)).toByteArray())) + handleHumanReadableFile(sharedFileLocation, ctx) else { // TODO: Fix name of downloaded file ("shared") ctx.contentType(Files.probeContentType(Paths.get(sharedFileLocation))) @@ -201,7 +213,7 @@ class FileController { val filename = it.toString() .drop(sharedFileLocation.length) val filePath = "$sharedFileLocation$filename" - files.add(addToFileListing(filePath, filename)) + files.add(addToFileListing(filePath, filename, ctx)) } files.sortWith(compareBy { it.first() }) ctx.render( @@ -222,15 +234,16 @@ class FileController { /** * Adds a file to the file array used in the file listing view */ - private fun addToFileListing(filePath: String, filename: String): Array<String> { - val file = File(filePath) + private fun addToFileListing(fileLocation: String, filename: String, ctx: Context): Array<String> { + val file = File(fileLocation) val fileSize = if (file.isDirectory) getDirectorySize(file) else file.length() return arrayOf( // TODO: Clean up array responses if (file.isDirectory) "$filename/" else filename, humanReadableBytes(fileSize), SimpleDateFormat("MM/dd/yyyy HH:mm:ss").format(file.lastModified()).toString(), - if (file.isDirectory) "true" else isHumanReadable(file).toString(), + if (file.isDirectory) "true" + else isHumanReadable(decrypt(fileLocation, userHandler.getVerifiedUserId(ctx)).toByteArray()).toString(), file.isDirectory.toString(), fileSize.toString(), // unformatted file size file.lastModified().toString() // unformatted last modified date @@ -240,15 +253,13 @@ class FileController { /** * Handles the rendering of human readable files */ - private fun handleHumanReadableFile(filePath: String, ctx: Context) { + private fun handleHumanReadableFile(fileLocation: String, ctx: Context) { + val content = decrypt(fileLocation, userHandler.getVerifiedUserId(ctx)) ctx.render( "fileview.rocker.html", model( - "content", Files.readAllLines( - Paths.get(filePath), - Charsets.UTF_8 - ).joinToString(separator = "\n"), - "filename", File(filePath).name, - "extension", File(filePath).extension, + "content", content, + "filename", File(fileLocation).name, + "extension", File(fileLocation).extension, "ctx", ctx ) ) diff --git a/src/main/kotlin/LogFilter.kt b/src/main/kotlin/LogFilter.kt index 087b2b8..df52f8f 100644 --- a/src/main/kotlin/LogFilter.kt +++ b/src/main/kotlin/LogFilter.kt @@ -18,7 +18,7 @@ class LogFilter : Filter<ILoggingEvent>() { return if (event.level == Level.INFO && isUseful) { FilterReply.ACCEPT } else { - if ((!silent || event.message.contains("Help")) && event.level != Level.DEBUG) { + if ((!silent || event.message.contains("Help")) && event.level != Level.DEBUG && isUseful) { FilterReply.ACCEPT } else { FilterReply.DENY |