Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit fea5e80

Browse files
committed
Added auth key generation for encrypted communication of sensitive information.
1 parent ea53193 commit fea5e80

File tree

6 files changed

+96
-28
lines changed

6 files changed

+96
-28
lines changed

src/main/scala/org/codeoverflow/chatoverflow/ChatOverflow.scala

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ class ChatOverflow(val pluginFolderPath: String,
3030
val typeRegistry = new TypeRegistry(requirementPackage)
3131
val credentialsService = new CredentialsService(s"$configFolderPath/credentials")
3232
val configService = new ConfigurationService(s"$configFolderPath/config.xml")
33+
private var loaded = false
34+
35+
/**
36+
* Returns if configs and credentials had been loaded.
37+
*
38+
* @return true, if has been called successfully in this run of the framework.
39+
*/
40+
def isLoaded: Boolean = loaded
3341

3442
/**
3543
* Initializes all parts of chat overflow. These can be accessed trough the public variables.
@@ -54,7 +62,7 @@ class ChatOverflow(val pluginFolderPath: String,
5462
ConnectorRegistry.setTypeRegistry(typeRegistry)
5563
logger debug "Finished updating type registry."
5664

57-
if (requirePasswordOnStartup) {
65+
if (requirePasswordOnStartup && !loaded) {
5866
logger debug "Loading configs and credentials."
5967
askForPassword()
6068
load()
@@ -66,24 +74,32 @@ class ChatOverflow(val pluginFolderPath: String,
6674

6775
/**
6876
* Loads all config settings and credentials from the config folder.
77+
* Note that its only possible once per run to load everything successfully.
6978
*/
7079
def load(): Boolean = {
71-
val currentTime = System.currentTimeMillis()
72-
var success = true
80+
if (loaded) {
81+
false
82+
} else {
83+
val currentTime = System.currentTimeMillis()
84+
var success = true
85+
86+
// Start by loading connectors
87+
if (!configService.loadConnectors())
88+
success = false
7389

74-
// Start by loading connectors
75-
if (!configService.loadConnectors())
76-
success = false
90+
// Then load credentials that can be put into the connectors
91+
if (success && !credentialsService.load())
92+
success = false
7793

78-
// Then load credentials that can be put into the connectors
79-
if (!credentialsService.load())
80-
success = false
94+
// Finish by loading plugin instances
95+
if (success && !configService.loadPluginInstances(pluginInstanceRegistry, pluginFramework, typeRegistry))
96+
success = false
8197

82-
// Finish by loading plugin instances
83-
configService.loadPluginInstances(pluginInstanceRegistry, pluginFramework, typeRegistry)
98+
logger info s"Loading took ${System.currentTimeMillis() - currentTime} ms."
8499

85-
logger info s"Loading took ${System.currentTimeMillis() - currentTime} ms."
86-
success
100+
loaded = success
101+
success
102+
}
87103
}
88104

89105
private def enableFrameworkSecurity(): Unit = {

src/main/scala/org/codeoverflow/chatoverflow/configuration/CredentialsService.scala

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ import scala.collection.mutable
1515
class CredentialsService(val credentialsFilePath: String) extends WithLogger {
1616
private[this] var password = Array[Char]()
1717

18+
/**
19+
* Generates a authentication key using the password of the framework.
20+
*
21+
* @return an auth key based the password an an random array
22+
*/
23+
def generateAuthKey(): String = {
24+
CryptoUtil.generateAuthKey(this.password.mkString)
25+
}
26+
1827
/**
1928
* Sets the password. Needed to load or save credentials.
2029
*
@@ -23,12 +32,20 @@ class CredentialsService(val credentialsFilePath: String) extends WithLogger {
2332
def setPassword(password: Array[Char]): Unit = this.password = password
2433

2534
/**
26-
* Returns true, if a password is set. Note that this says nothing about the correctness of it.
35+
* Checks if the specified password is correct by trying to decrypt the saved credentials.
2736
*
28-
* @return true, if the password length is greater than zero
37+
* @param password the password to test
38+
* @return true, if the credentials file exists and could be decrypted
2939
*/
30-
def isLoggedIn: Boolean = {
31-
password.length > 0
40+
def checkPasswordCorrectness(password: Array[Char]): Boolean = {
41+
try {
42+
val encrypted = scala.io.Source.fromFile(credentialsFilePath)
43+
val decrypted = CryptoUtil.decrypt(password, encrypted.getLines().mkString)
44+
encrypted.close
45+
decrypted.isDefined
46+
} catch {
47+
case _: Exception => false
48+
}
3249
}
3350

3451
/**

src/main/scala/org/codeoverflow/chatoverflow/configuration/CryptoUtil.scala

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.codeoverflow.chatoverflow.configuration
22

3-
import java.security.SecureRandom
3+
import java.nio.charset.StandardCharsets
4+
import java.security.{MessageDigest, SecureRandom}
45

56
import javax.crypto.spec.{IvParameterSpec, PBEKeySpec, SecretKeySpec}
67
import javax.crypto.{Cipher, SecretKeyFactory}
@@ -14,6 +15,23 @@ object CryptoUtil {
1415

1516
// TODO: This code should be reviewed by an expert to find potential security issues.
1617

18+
// Used for the run-unique auth key
19+
private val runSpecificRandom = generateIV
20+
21+
/**
22+
* Generates a run-unique authentication key using a supplied password.
23+
*
24+
* @param password a password to communicate with the framework
25+
* @return an auth key based the password an an random array
26+
*/
27+
def generateAuthKey(password: String): String = {
28+
29+
val authBase = runSpecificRandom.mkString + password
30+
31+
val digest = MessageDigest.getInstance("SHA-256")
32+
digest.digest(authBase.getBytes(StandardCharsets.UTF_8)).mkString
33+
}
34+
1735
/**
1836
* Encrypts the provided plaintext using AES.
1937
*

src/main/scala/org/codeoverflow/chatoverflow/ui/web/rest/config/ConfigController.scala

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.codeoverflow.chatoverflow.ui.web.rest.config
22

33
import org.codeoverflow.chatoverflow.Launcher
44
import org.codeoverflow.chatoverflow.api.APIVersion
5+
import org.codeoverflow.chatoverflow.configuration.CryptoUtil
56
import org.codeoverflow.chatoverflow.ui.web.JsonServlet
67
import org.codeoverflow.chatoverflow.ui.web.rest.DTOs.{ConfigInfo, Password, ResultMessage}
78
import org.scalatra.swagger._
@@ -20,18 +21,32 @@ class ConfigController(implicit val swagger: Swagger) extends JsonServlet with C
2021
}
2122

2223
get("/login", operation(getLogin)) {
23-
chatOverflow.credentialsService.isLoggedIn
24+
chatOverflow.isLoaded
2425
}
2526

2627
post("/login", operation(postLogin)) {
2728
parsedAs[Password] {
2829
case Password(password) =>
29-
chatOverflow.credentialsService.setPassword(password.toCharArray)
30-
if (!chatOverflow.load()) {
31-
// TODO: Could have more details with a more detailed loading process
32-
ResultMessage(success = false, "Unable to load. Wrong password?")
30+
31+
// Check for password correctness first
32+
if (!chatOverflow.credentialsService.checkPasswordCorrectness(password.toCharArray)) {
33+
ResultMessage(success = false, "Unable to load. Wrong password!")
3334
} else {
34-
ResultMessage(success = true)
35+
36+
// Password is correct. Login if first call.
37+
if (!chatOverflow.isLoaded) {
38+
chatOverflow.credentialsService.setPassword(password.toCharArray)
39+
if (!chatOverflow.load()) {
40+
ResultMessage(success = false, "Unable to load.")
41+
} else {
42+
43+
// "Loading was successful, here is your auth key based on your supplied password"
44+
ResultMessage(success = true, CryptoUtil.generateAuthKey(password))
45+
}
46+
} else {
47+
// "Loading was not needed, but here is your key based on your password"
48+
ResultMessage(success = true, CryptoUtil.generateAuthKey(password))
49+
}
3550
}
3651
}
3752
}

src/main/scala/org/codeoverflow/chatoverflow/ui/web/rest/config/ConfigControllerDefinition.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ trait ConfigControllerDefinition extends SwaggerSupport {
1818

1919
val getLogin: OperationBuilder =
2020
(apiOperation[Boolean]("getLogin")
21-
summary "Returns if the framework is already supplied with a password to decrypt its credentials."
22-
description "Returns true, if a password has been set. Note that this says nothing about its correctness.")
21+
summary "Returns if the framework is already loaded."
22+
description "Returns true, if the framework had been loaded previously with success.")
2323

2424
val postLogin: OperationBuilder =
2525
(apiOperation[ResultMessage]("postLogin")
2626
summary "Logs in the the framework with the given password, loads config and credentials."
27-
description "Tries to decrypt the credentials using the provided password. Does load configuration and credentials."
28-
parameter bodyParam[Password]("body").description("Requires the user framework password."))
27+
description "Tries to decrypt the credentials using the provided password. If already loaded, does only return the communication auth key."
28+
parameter bodyParam[Password]("body").description("Requires the user framework password. The auth key is based on this input."))
2929

3030
override protected def applicationDescription: String = "Handles configuration and information retrieval."
3131
}

src/main/scala/org/codeoverflow/chatoverflow/ui/web/rest/connector/ConnectorController.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class ConnectorController(implicit val swagger: Swagger) extends JsonServlet wit
2323
}
2424

2525
post("/", operation(postConnector)) {
26+
// TODO: Should implicitly create the credentials object
2627
parsedAs[ConnectorRef] {
2728
case ConnectorRef(sourceIdentifier, uniqueTypeString) =>
2829
val connector = ConnectorRegistry.getConnector(sourceIdentifier, uniqueTypeString)
@@ -47,6 +48,7 @@ class ConnectorController(implicit val swagger: Swagger) extends JsonServlet wit
4748
}
4849

4950
delete("/:sourceIdentifier/:qualifiedConnectorType", operation(deleteConnector)) {
51+
// TODO: Should implicitly delete the credentials object
5052
val sourceIdentifier = params("sourceIdentifier")
5153
val qualifiedConnectorType = params("qualifiedConnectorType")
5254

0 commit comments

Comments
 (0)