implemented EncryptedMailConfig, needs testing
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -18,6 +18,7 @@ public class Constants {
|
|||||||
public static final String CONFIG_PATH = "LIGHTOIDC_CONFIG_PATH";
|
public static final String CONFIG_PATH = "LIGHTOIDC_CONFIG_PATH";
|
||||||
public static final String CONFIRMED = "confirmed";
|
public static final String CONFIRMED = "confirmed";
|
||||||
public static final String DAYS = "days";
|
public static final String DAYS = "days";
|
||||||
|
public static final String ENCRYPTION_KEY = "encryption_key";
|
||||||
public static final String ERROR_DESCRIPTION = "error_description";
|
public static final String ERROR_DESCRIPTION = "error_description";
|
||||||
public static final String EXPIRATION = "expiration";
|
public static final String EXPIRATION = "expiration";
|
||||||
public static final String EXPIRES_IN = "expires_in";
|
public static final String EXPIRES_IN = "expires_in";
|
||||||
@@ -39,6 +40,7 @@ public class Constants {
|
|||||||
public static final String REDIRECT_URIS = "redirect_uris";
|
public static final String REDIRECT_URIS = "redirect_uris";
|
||||||
public static final String REQUEST_NOT_SUPPORTED = "request_not_supported";
|
public static final String REQUEST_NOT_SUPPORTED = "request_not_supported";
|
||||||
public static final String RESPONSE_TYPE = "response_type";
|
public static final String RESPONSE_TYPE = "response_type";
|
||||||
|
public static final String SALT = "salt";
|
||||||
public static final String SCOPE = "scope";
|
public static final String SCOPE = "scope";
|
||||||
public static final String SECRET = "secret";
|
public static final String SECRET = "secret";
|
||||||
public static final String SESSION_DURATION = "session_duration";
|
public static final String SESSION_DURATION = "session_duration";
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ dependencies {
|
|||||||
implementation project(':de.srsoftware.oidc.backend')
|
implementation project(':de.srsoftware.oidc.backend')
|
||||||
implementation project(':de.srsoftware.oidc.web')
|
implementation project(':de.srsoftware.oidc.web')
|
||||||
implementation project(':de.srsoftware.utils')
|
implementation project(':de.srsoftware.utils')
|
||||||
|
implementation project(':de.srsoftware.oidc.datastore.encrypted')
|
||||||
implementation project(':de.srsoftware.oidc.datastore.file')
|
implementation project(':de.srsoftware.oidc.datastore.file')
|
||||||
implementation project(':de.srsoftware.oidc.datastore.sqlite')
|
implementation project(':de.srsoftware.oidc.datastore.sqlite')
|
||||||
implementation 'org.json:json:20240303'
|
implementation 'org.json:json:20240303'
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import de.srsoftware.logging.ColorLogger;
|
|||||||
import de.srsoftware.oidc.api.*;
|
import de.srsoftware.oidc.api.*;
|
||||||
import de.srsoftware.oidc.api.data.User;
|
import de.srsoftware.oidc.api.data.User;
|
||||||
import de.srsoftware.oidc.backend.*;
|
import de.srsoftware.oidc.backend.*;
|
||||||
|
import de.srsoftware.oidc.datastore.encrypted.EncryptedMailConfig;
|
||||||
import de.srsoftware.oidc.datastore.file.FileStoreProvider;
|
import de.srsoftware.oidc.datastore.file.FileStoreProvider;
|
||||||
import de.srsoftware.oidc.datastore.file.PlaintextKeyStore;
|
import de.srsoftware.oidc.datastore.file.PlaintextKeyStore;
|
||||||
import de.srsoftware.oidc.datastore.sqlite.*;
|
import de.srsoftware.oidc.datastore.sqlite.*;
|
||||||
@@ -110,10 +111,19 @@ public class Application {
|
|||||||
|
|
||||||
private static MailConfig setupMailConfig(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException {
|
private static MailConfig setupMailConfig(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException {
|
||||||
var mailConfigLocation = new File(config.getOrDefault("mail_config_storage",defaultFile));
|
var mailConfigLocation = new File(config.getOrDefault("mail_config_storage",defaultFile));
|
||||||
return switch (extension(mailConfigLocation)){
|
var mailConfig = switch (extension(mailConfigLocation)){
|
||||||
case "db", "sqlite", "sqlite3" -> new SqliteMailConfig(connectionProvider.get(mailConfigLocation));
|
case "db", "sqlite", "sqlite3" -> new SqliteMailConfig(connectionProvider.get(mailConfigLocation));
|
||||||
default -> fileStoreProvider.get(mailConfigLocation);
|
default -> fileStoreProvider.get(mailConfigLocation);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Optional<String> encryptionKey = config.get(ENCRYPTION_KEY);
|
||||||
|
var salt = config.getOrDefault(SALT,uuid());
|
||||||
|
|
||||||
|
|
||||||
|
if (encryptionKey.isPresent()){
|
||||||
|
mailConfig = new EncryptedMailConfig(mailConfig,encryptionKey.get(),salt);
|
||||||
|
}
|
||||||
|
return mailConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UserService setupUserService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider, UuidHasher passHasher) throws SQLException {
|
private static UserService setupUserService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider, UuidHasher passHasher) throws SQLException {
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
/* © SRSoftware 2024 */
|
/* © SRSoftware 2024 */
|
||||||
package de.srsoftware.oidc.app;
|
package de.srsoftware.oidc.app;
|
||||||
|
|
||||||
|
import static java.util.Optional.empty;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
public class Configuration {
|
public class Configuration {
|
||||||
@@ -30,6 +33,17 @@ public class Configuration {
|
|||||||
return json.getString(key);
|
return json.getString(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> Optional<T> get(String key) {
|
||||||
|
if (!json.has(key)) return empty();
|
||||||
|
var o = json.get(key);
|
||||||
|
try {
|
||||||
|
@SuppressWarnings("unchecked") var result = (T)o;
|
||||||
|
return Optional.of(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Configuration save() {
|
public Configuration save() {
|
||||||
try {
|
try {
|
||||||
Files.writeString(storageFile, json.toString(2));
|
Files.writeString(storageFile, json.toString(2));
|
||||||
|
|||||||
22
de.srsoftware.oidc.datastore.encrypted/build.gradle
Normal file
22
de.srsoftware.oidc.datastore.encrypted/build.gradle
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'de.srsoftware'
|
||||||
|
version = '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation platform('org.junit:junit-bom:5.10.0')
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||||
|
implementation project(':de.srsoftware.oidc.api')
|
||||||
|
implementation 'com.sun.mail:jakarta.mail:2.0.1'
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/* © SRSoftware 2024 */
|
||||||
|
package de.srsoftware.oidc.datastore.encrypted;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.KeySpec;
|
||||||
|
import java.util.Base64;
|
||||||
|
import javax.crypto.*;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
public class EncryptedConfig {
|
||||||
|
private final Cipher cipher;
|
||||||
|
private static final int KEY_LENGTH = 256;
|
||||||
|
private static final int ITERATION_COUNT = 65536;
|
||||||
|
private final SecretKeySpec secretKeySpec;
|
||||||
|
|
||||||
|
public EncryptedConfig(String key, String salt) {
|
||||||
|
try {
|
||||||
|
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||||
|
KeySpec spec = new PBEKeySpec(key.toCharArray(), salt.getBytes(), ITERATION_COUNT, KEY_LENGTH);
|
||||||
|
SecretKey tmp = factory.generateSecret(spec);
|
||||||
|
secretKeySpec = new SecretKeySpec(tmp.getEncoded(), "AES");
|
||||||
|
|
||||||
|
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encrypt(String plain) {
|
||||||
|
SecureRandom secureRandom = new SecureRandom();
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
secureRandom.nextBytes(iv);
|
||||||
|
IvParameterSpec initVector = new IvParameterSpec(iv);
|
||||||
|
try {
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, initVector);
|
||||||
|
byte[] cipherText = cipher.doFinal(plain.getBytes(UTF_8));
|
||||||
|
byte[] encryptedData = new byte[iv.length + cipherText.length];
|
||||||
|
System.arraycopy(iv, 0, encryptedData, 0, iv.length);
|
||||||
|
System.arraycopy(cipherText, 0, encryptedData, iv.length, cipherText.length);
|
||||||
|
|
||||||
|
return Base64.getEncoder().encodeToString(encryptedData);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String decrypt(String secret) {
|
||||||
|
byte[] encryptedData = Base64.getDecoder().decode(secret);
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
System.arraycopy(encryptedData, 0, iv, 0, iv.length);
|
||||||
|
IvParameterSpec ivspec = new IvParameterSpec(iv);
|
||||||
|
try {
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivspec);
|
||||||
|
byte[] cipherText = new byte[encryptedData.length - 16];
|
||||||
|
System.arraycopy(encryptedData, 16, cipherText, 0, cipherText.length);
|
||||||
|
|
||||||
|
byte[] decryptedText = cipher.doFinal(cipherText);
|
||||||
|
return new String(decryptedText, UTF_8);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/* © SRSoftware 2024 */
|
||||||
|
package de.srsoftware.oidc.datastore.encrypted;
|
||||||
|
|
||||||
|
/* © SRSoftware 2024 */
|
||||||
|
|
||||||
|
import static de.srsoftware.oidc.api.Constants.*;
|
||||||
|
|
||||||
|
import de.srsoftware.oidc.api.MailConfig;
|
||||||
|
import jakarta.mail.Authenticator;
|
||||||
|
import jakarta.mail.PasswordAuthentication;
|
||||||
|
|
||||||
|
|
||||||
|
public class EncryptedMailConfig extends EncryptedConfig implements MailConfig {
|
||||||
|
private final MailConfig storage;
|
||||||
|
private Authenticator auth;
|
||||||
|
|
||||||
|
public EncryptedMailConfig(MailConfig storage, String encryotionKey, String salt) {
|
||||||
|
super(encryotionKey, salt);
|
||||||
|
this.storage = storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailConfig save() {
|
||||||
|
return storage.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String senderAddress() {
|
||||||
|
return decrypt(storage.senderAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailConfig senderAddress(String newValue) {
|
||||||
|
storage.senderAddress(encrypt(newValue));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String senderPassword() {
|
||||||
|
return decrypt(storage.senderPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailConfig senderPassword(String newValue) {
|
||||||
|
storage.senderPassword(encrypt(newValue));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean smtpAuth() {
|
||||||
|
return storage.smtpAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailConfig smtpAuth(boolean newValue) {
|
||||||
|
storage.smtpAuth(newValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authenticator authenticator() {
|
||||||
|
if (auth == null) {
|
||||||
|
auth = new Authenticator() {
|
||||||
|
// override the getPasswordAuthentication method
|
||||||
|
protected PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
return new PasswordAuthentication(senderAddress(), senderPassword());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String smtpHost() {
|
||||||
|
return decrypt(storage.smtpHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailConfig smtpHost(String newValue) {
|
||||||
|
storage.smtpHost(encrypt(newValue));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int smtpPort() {
|
||||||
|
return storage.smtpPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailConfig smtpPort(int newValue) {
|
||||||
|
storage.smtpPort(newValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean startTls() {
|
||||||
|
return storage.startTls();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailConfig startTls(boolean newValue) {
|
||||||
|
storage.startTls(newValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,4 +8,5 @@ include 'de.srsoftware.oidc.datastore.file'
|
|||||||
include 'de.srsoftware.oidc.web'
|
include 'de.srsoftware.oidc.web'
|
||||||
include 'de.srsoftware.utils'
|
include 'de.srsoftware.utils'
|
||||||
include 'de.srsoftware.oidc.datastore.sqlite'
|
include 'de.srsoftware.oidc.datastore.sqlite'
|
||||||
|
include 'de.srsoftware.oidc.datastore.encrypted'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user