aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarvin Borner2019-06-20 16:50:27 +0200
committerMarvin Borner2019-06-20 16:50:27 +0200
commit8cd0aeb5af65d771d1952a37a6cf7fd457796bf2 (patch)
tree5eaf50359358f5b0e3f80a8b36b6dd224f0edd87
parenta34a0d45da548bef8b18da04d2947fb0ff0edae0 (diff)
Added AES file encryption
Co-authored-by: LarsVomMars <lars@kroenner.eu>
-rw-r--r--src/main/kotlin/App.kt2
-rw-r--r--src/main/kotlin/CryptoHandler.kt50
-rw-r--r--src/main/kotlin/DatabaseController.kt20
-rw-r--r--src/main/kotlin/FileController.kt93
-rw-r--r--src/main/kotlin/LogFilter.kt2
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