From 6bf1551028e54b2cb65d4853cc148b071a68ed65 Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 4 May 2021 21:59:04 +0200 Subject: [PATCH] certs impl --- .../ClientHttpRequestFactoryService.java | 11 +- .../ch/ethz/seb/sebserver/gbl/Constants.java | 7 + .../gbl/client/ClientCredentialService.java | 12 +- .../client/ClientCredentialServiceImpl.java | 54 +++--- .../gbl/model/sebconfig/SEBClientConfig.java | 13 ++ .../ethz/seb/sebserver/gbl/util/Cryptor.java | 86 ++++++---- .../ethz/seb/sebserver/gbl/util/Result.java | 3 +- .../gui/content/CertificateImportPopup.java | 4 - .../gui/content/SEBClientConfigForm.java | 13 +- .../ch/ethz/seb/sebserver/gui/form/Form.java | 4 +- .../gui/form/PasswordFieldBuilder.java | 15 +- .../examconfig/impl/PasswordFieldBuilder.java | 4 +- .../api/seb/cert/RemoveCertificate.java | 2 +- .../servicelayer/dao/CertificateDAO.java | 17 +- .../dao/impl/CertificateDAOImpl.java | 62 ++++++- .../dao/impl/ExamConfigurationMapDAOImpl.java | 1 + .../dao/impl/LmsSetupDAOImpl.java | 12 +- .../dao/impl/SEBClientConfigDAOImpl.java | 20 ++- .../lms/impl/LmsAPIServiceImpl.java | 6 +- .../impl/edx/OpenEdxRestTemplateFactory.java | 4 +- .../moodle/MoodleRestTemplateFactory.java | 4 +- .../sebconfig/CertificateService.java | 1 + .../sebconfig/SEBConfigEncryptionContext.java | 3 +- .../sebconfig/SEBConfigEncryptionService.java | 11 +- .../impl/AES256JNCryptorEmptyPwdSupport.java | 10 +- ...6JNCryptorOutputStreamEmptyPwdSupport.java | 8 +- .../impl/CertificateServiceImpl.java | 64 ++++--- .../impl/CertificateSymetricKeyCryptor.java | 156 ++++++++++++++++++ .../impl/ClientConfigServiceImpl.java | 118 +++++++++---- .../sebconfig/impl/ExamConfigServiceImpl.java | 3 +- .../sebconfig/impl/PasswordCryptor.java | 64 +++++++ .../sebconfig/impl/PasswordDecryptor.java | 115 +++++++++++++ .../sebconfig/impl/PasswordEncryptor.java | 110 +----------- .../impl/SEBConfigEncryptionServiceImpl.java | 25 ++- .../proctoring/JitsiProctoringService.java | 5 +- .../proctoring/ZoomProctoringService.java | 17 +- .../weblayer/api/CertificateController.java | 8 +- .../api/SEBClientConfigController.java | 8 + .../gbl/model/ModelObjectJSONGenerator.java | 8 +- .../seb/sebserver/gbl/util/CryptorTest.java | 16 +- .../gui/integration/ClientConfigTest.java | 2 + .../integration/UseCasesIntegrationTest.java | 2 +- .../sebconfig/impl/PasswordEncryptorTest.java | 4 +- .../SebConfigEncryptionServiceImplTest.java | 4 +- .../ExamJITSIProctoringServiceTest.java | 5 +- 45 files changed, 810 insertions(+), 311 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/CertificateSymetricKeyCryptor.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordCryptor.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordDecryptor.java diff --git a/src/main/java/ch/ethz/seb/sebserver/ClientHttpRequestFactoryService.java b/src/main/java/ch/ethz/seb/sebserver/ClientHttpRequestFactoryService.java index f3ea00d9..567b1152 100644 --- a/src/main/java/ch/ethz/seb/sebserver/ClientHttpRequestFactoryService.java +++ b/src/main/java/ch/ethz/seb/sebserver/ClientHttpRequestFactoryService.java @@ -45,6 +45,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.ResourceUtils; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService; import ch.ethz.seb.sebserver.gbl.client.ProxyData; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; @@ -200,7 +201,7 @@ public class ClientHttpRequestFactoryService { .loadTrustMaterial(trustStoreFile, password) .setKeyStoreType(this.environment.getProperty( "server.ssl.key-store-type", - "pkcs12")) + Constants.PKCS_12)) .build(); } @@ -218,9 +219,11 @@ public class ClientHttpRequestFactoryService { .setSSLContext(sslContext) .build(); final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client); + factory.setConnectionRequestTimeout(this.connectionRequestTimeout); factory.setConnectTimeout(this.connectTimeout); factory.setReadTimeout(this.readTimeout); + return factory; } } @@ -246,8 +249,10 @@ public class ClientHttpRequestFactoryService { if (proxy.clientCredentials != null && StringUtils.isNotBlank(proxy.clientCredentials.clientId)) { final CredentialsProvider credsProvider = new BasicCredentialsProvider(); final String plainClientId = proxy.clientCredentials.clientIdAsString(); - final String plainClientSecret = Utils.toString(this.clientCredentialService - .getPlainClientSecret(proxy.clientCredentials)); + final CharSequence secret = this.clientCredentialService + .getPlainClientSecret(proxy.clientCredentials) + .getOrThrow(); + final String plainClientSecret = Utils.toString(secret); credsProvider.setCredentials( AuthScope.ANY, diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index 9e579b95..059fda0d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -130,6 +130,13 @@ public final class Constants { public static final int GZIP_CM = 8; public static final String SHA_256 = "SHA-256"; + public static final String X_509 = "X.509"; + public static final String PKCS_12 = "pkcs12"; + public static final String SHA_1 = "SHA-1"; + public static final String AES = "AES"; + public static final String AES_CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; + public static final String HMAC_ALGORITHM = "HmacSHA256"; + public static final String KEY_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA1"; public static final RGB WHITE_RGB = new RGB(255, 255, 255); public static final RGB BLACK_RGB = new RGB(0, 0, 0); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentialService.java b/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentialService.java index 6614b6b5..d28e6664 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentialService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentialService.java @@ -37,7 +37,7 @@ public interface ClientCredentialService { * @param secretPlaintext the plain secret text * @param accessTokenPlaintext the plain accessToken text * @return encrypted client credentials */ - ClientCredentials encryptClientCredentials( + Result encryptClientCredentials( CharSequence clientIdPlaintext, CharSequence secretPlaintext, CharSequence accessTokenPlaintext); @@ -48,7 +48,7 @@ public interface ClientCredentialService { * @param clientIdPlaintext the plain clientId text * @param secretPlaintext the plain secret text * @return encrypted client credentials */ - default ClientCredentials encryptClientCredentials( + default Result encryptClientCredentials( final CharSequence clientIdPlaintext, final CharSequence secretPlaintext) { @@ -59,13 +59,13 @@ public interface ClientCredentialService { * * @param credentials ClientCredentials containing the secret to decrypt * @return decrypted plain text secret */ - CharSequence getPlainClientSecret(ClientCredentials credentials); + Result getPlainClientSecret(ClientCredentials credentials); /** Use this to get a decrypted plain text accessToken form given ClientCredentials * * @param credentials ClientCredentials containing the accessToken to decrypt * @return decrypted plain text accessToken */ - CharSequence getPlainAccessToken(ClientCredentials credentials); + Result getPlainAccessToken(ClientCredentials credentials); /** Encrypts a given plain text that uses {@link org.springframework.security.crypto.encrypt.Encryptors#stronger} * and a randomly generated salt that is added to the cipher text. @@ -73,12 +73,12 @@ public interface ClientCredentialService { * * @param text the plain text to encrypt * @return encrypted cipher text with additional salt */ - CharSequence encrypt(final CharSequence text); + Result encrypt(final CharSequence text); /** Decrypt a given cipher that was encrypted with the method used by this services encrypt method. * * @param cipher the cipher text with additional salt * @return plain text decrypt from the given cipher */ - CharSequence decrypt(final CharSequence cipher); + Result decrypt(final CharSequence cipher); } \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentialServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentialServiceImpl.java index ea3a9ce1..89913ab7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentialServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentialServiceImpl.java @@ -13,8 +13,6 @@ import java.security.SecureRandom; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -25,8 +23,6 @@ import ch.ethz.seb.sebserver.gbl.util.Result; @Service public class ClientCredentialServiceImpl implements ClientCredentialService { - private static final Logger log = LoggerFactory.getLogger(ClientCredentialServiceImpl.class); - private final Cryptor cryptor; protected ClientCredentialServiceImpl(final Cryptor cryptor) { @@ -35,38 +31,35 @@ public class ClientCredentialServiceImpl implements ClientCredentialService { @Override public Result generatedClientCredentials() { - return Result.tryCatch(() -> { - try { - - return encryptClientCredentials( - generateClientId(), - generateClientSecret()); - - } catch (final Exception e) { - log.error("Error while trying to generate client credentials: ", e); - throw new RuntimeException("cause: ", e); - } - }); + return encryptClientCredentials( + generateClientId(), + generateClientSecret()); } @Override - public ClientCredentials encryptClientCredentials( + public Result encryptClientCredentials( final CharSequence clientIdPlaintext, final CharSequence secretPlaintext, final CharSequence accessTokenPlaintext) { - return new ClientCredentials( - clientIdPlaintext, - (StringUtils.isNoneBlank(secretPlaintext)) - ? this.cryptor.encrypt(secretPlaintext).toString() - : null, - (StringUtils.isNoneBlank(accessTokenPlaintext)) - ? this.cryptor.encrypt(accessTokenPlaintext).toString() - : null); + return Result.tryCatch(() -> { + return new ClientCredentials( + clientIdPlaintext, + (StringUtils.isNoneBlank(secretPlaintext)) + ? this.cryptor.encrypt(secretPlaintext) + .getOrThrow() + .toString() + : null, + (StringUtils.isNoneBlank(accessTokenPlaintext)) + ? this.cryptor.encrypt(accessTokenPlaintext) + .getOrThrow() + .toString() + : null); + }); } @Override - public CharSequence getPlainClientSecret(final ClientCredentials credentials) { + public Result getPlainClientSecret(final ClientCredentials credentials) { if (credentials == null || !credentials.hasSecret()) { return null; } @@ -75,21 +68,21 @@ public class ClientCredentialServiceImpl implements ClientCredentialService { } @Override - public CharSequence getPlainAccessToken(final ClientCredentials credentials) { + public Result getPlainAccessToken(final ClientCredentials credentials) { if (credentials == null || !credentials.hasAccessToken()) { - return null; + return Result.ofRuntimeError("No token available"); } return this.cryptor.decrypt(credentials.accessToken); } @Override - public CharSequence encrypt(final CharSequence text) { + public Result encrypt(final CharSequence text) { return this.cryptor.encrypt(text); } @Override - public CharSequence decrypt(final CharSequence text) { + public Result decrypt(final CharSequence text) { return this.cryptor.decrypt(text); } @@ -104,7 +97,6 @@ public class ClientCredentialServiceImpl implements ClientCredentialService { } public static CharSequence generateClientSecret() { - // TODO find a better way to generate a random char array instead of using RandomStringUtils.random which uses a String return RandomStringUtils.random( 64, 0, possibleCharacters.length - 1, false, false, possibleCharacters, new SecureRandom()); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/SEBClientConfig.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/SEBClientConfig.java index a65a729b..7864cabd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/SEBClientConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/SEBClientConfig.java @@ -47,6 +47,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable { public static final String ATTR_QUIT_PASSWORD = "hashedQuitPassword"; public static final String ATTR_QUIT_PASSWORD_CONFIRM = "hashedQuitPasswordConfirm"; public static final String ATTR_ENCRYPT_SECRET_CONFIRM = "confirm_encrypt_secret"; + public static final String ATTR_ENCRYPT_CERTIFICATE_ALIAS = "cert_alias"; public static final String FILTER_ATTR_CREATION_DATE = "creation_date"; @@ -157,6 +158,9 @@ public final class SEBClientConfig implements GrantEntity, Activatable { @JsonProperty(ATTR_ENCRYPT_SECRET_CONFIRM) public final CharSequence encryptSecretConfirm; + @JsonProperty(ATTR_ENCRYPT_CERTIFICATE_ALIAS) + public final String encryptCertificateAlias; + @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) public final Boolean active; @@ -185,6 +189,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable { @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_DATE) final DateTime date, @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET) final CharSequence encryptSecret, @JsonProperty(ATTR_ENCRYPT_SECRET_CONFIRM) final CharSequence encryptSecretConfirm, + @JsonProperty(ATTR_ENCRYPT_CERTIFICATE_ALIAS) final String encryptCertificateAlias, @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active) { this.id = id; @@ -210,6 +215,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable { this.date = date; this.encryptSecret = encryptSecret; this.encryptSecretConfirm = encryptSecretConfirm; + this.encryptCertificateAlias = encryptCertificateAlias; this.active = active; } @@ -247,6 +253,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable { this.date = postParams.getDateTime(Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE); this.encryptSecret = postParams.getCharSequence(Domain.SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET); this.encryptSecretConfirm = postParams.getCharSequence(ATTR_ENCRYPT_SECRET_CONFIRM); + this.encryptCertificateAlias = postParams.getString(ATTR_ENCRYPT_CERTIFICATE_ALIAS); this.active = false; } @@ -331,6 +338,10 @@ public final class SEBClientConfig implements GrantEntity, Activatable { return this.encryptSecret; } + public String getEncryptCertificateAlias() { + return this.encryptCertificateAlias; + } + @JsonIgnore public CharSequence getEncryptSecretConfirm() { return this.encryptSecretConfirm; @@ -430,6 +441,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable { this.date, Constants.EMPTY_NOTE, Constants.EMPTY_NOTE, + Constants.EMPTY_NOTE, this.active); } @@ -456,6 +468,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable { DateTime.now(DateTimeZone.UTC), null, null, + null, false); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java index e376792b..3c023a0e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java @@ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.gbl.util; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.PrivateKey; +import java.security.cert.Certificate; import org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi; import org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.BCPKCS12KeyStore; @@ -22,6 +24,7 @@ import org.springframework.security.crypto.encrypt.Encryptors; import org.springframework.security.crypto.keygen.KeyGenerators; import org.springframework.stereotype.Service; +/** Cryptor dealing with internal encryption and decryption. */ @Lazy @Service public class Cryptor { @@ -34,11 +37,19 @@ public class Cryptor { this.internalPWD = environment.getProperty("sebserver.webservice.internalSecret"); } - public CharSequence encrypt(final CharSequence text) { + /** Use this to encrypt a text with the internal password + * + * @param text The text to encrypt with the internal password + * @return the encrypted text cipher */ + public Result encrypt(final CharSequence text) { return encrypt(text, this.internalPWD); } - public CharSequence decrypt(final CharSequence text) { + /** Use this to decrypt a cipher text with the internal password + * + * @param text The cipher text to decrypt with the internal password + * @return the plain text */ + public Result decrypt(final CharSequence text) { return decrypt(text, this.internalPWD); } @@ -64,17 +75,40 @@ public class Cryptor { } } - public static CharSequence encrypt(final CharSequence text, final CharSequence secret) { - if (text == null) { - throw new IllegalArgumentException("Text has null reference"); - } + public Result addPrivateKey( + final PKCS12KeyStoreSpi keyStore, + final PrivateKey privateKey, + final String alias, + final Certificate certificate) { - if (secret == null) { - log.warn("No internal secret supplied: skip encryption"); - return text; - } + return Result.tryCatch(() -> { - try { + keyStore.engineSetKeyEntry( + alias, + privateKey, + Utils.toCharArray(this.internalPWD), + new Certificate[] { certificate }); + + return keyStore; + }); + } + + public Result getPrivateKey(final PKCS12KeyStoreSpi store, final String alias) { + return Result.tryCatch(() -> { + return (PrivateKey) store.engineGetKey(alias, Utils.toCharArray(this.internalPWD)); + }); + } + + static Result encrypt(final CharSequence text, final CharSequence secret) { + return Result.tryCatch(() -> { + if (text == null) { + throw new IllegalArgumentException("Text has null reference"); + } + + if (secret == null) { + log.warn("No internal secret supplied: skip encryption"); + return text; + } final CharSequence salt = KeyGenerators.string().generateKey(); final CharSequence cipher = Encryptors @@ -84,23 +118,19 @@ public class Cryptor { return new StringBuilder(cipher) .append(salt); - } catch (final Exception e) { - log.error("Failed to encrypt text: {}", e.getMessage()); - throw e; - } + }); } - public static CharSequence decrypt(final CharSequence cipher, final CharSequence secret) { - if (cipher == null) { - throw new IllegalArgumentException("Cipher has null reference"); - } + static Result decrypt(final CharSequence cipher, final CharSequence secret) { + return Result.tryCatch(() -> { + if (cipher == null) { + throw new IllegalArgumentException("Cipher has null reference"); + } - if (secret == null) { - log.warn("No internal secret supplied: skip decryption"); - return cipher; - } - - try { + if (secret == null) { + log.warn("No internal secret supplied: skip decryption"); + return cipher; + } final int length = cipher.length(); final int cipherTextLength = length - 16; @@ -111,9 +141,7 @@ public class Cryptor { .delux(secret, salt) .decrypt(cipherText.toString()); - } catch (final Exception e) { - log.error("Failed to decrypt text: {}", e.getMessage()); - throw e; - } + }); } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java index 1772d7ab..ff41ba42 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java @@ -382,7 +382,8 @@ public final class Result { @Override public String toString() { - return "Result [value=" + this.value + ", error=" + this.error + "]"; + throw new RuntimeException("!!!!!!!!!!!!!"); + //return "Result [value=" + this.value + ", error=" + this.error + "]"; } public interface TryCatchSupplier { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/CertificateImportPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/CertificateImportPopup.java index 459bed6f..75e84e46 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/CertificateImportPopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/CertificateImportPopup.java @@ -15,8 +15,6 @@ import java.util.function.Supplier; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -48,8 +46,6 @@ import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection; @GuiProfile public class CertificateImportPopup { - private static final Logger log = LoggerFactory.getLogger(SEBExamConfigImportPopup.class); - private final static PageMessageException MISSING_PASSWORD = new PageMessageException( new LocTextKey("sebserver.certificate.action.import.missing-password")); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientConfigForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientConfigForm.java index 713012e6..d5106c80 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientConfigForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientConfigForm.java @@ -242,6 +242,11 @@ public class SEBClientConfigForm implements TemplateComposer { final boolean showVDIAttrs = clientConfig.vdiType == VDIType.VM_WARE; final boolean showFallbackAttrs = BooleanUtils.isTrue(clientConfig.fallback); + final CharSequence pwd = (formHandleAnchor.formHandle == null) + ? clientConfig.getEncryptSecret() + : this.cryptor.encrypt(clientConfig.getEncryptSecret()) + .getOrThrow(); + PageService.clearComposite(formContent); final FormBuilder formBuilder = this.pageService.formBuilder( @@ -282,9 +287,7 @@ public class SEBClientConfigForm implements TemplateComposer { .addField(FormBuilder.password( Domain.SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET, FORM_ENCRYPT_SECRET_TEXT_KEY, - (formHandleAnchor.formHandle == null) - ? clientConfig.getEncryptSecret() - : this.cryptor.encrypt(clientConfig.getEncryptSecret()))) + pwd)) .withDefaultSpanEmptyCell(3) .addFieldIf( @@ -292,9 +295,7 @@ public class SEBClientConfigForm implements TemplateComposer { () -> FormBuilder.password( SEBClientConfig.ATTR_ENCRYPT_SECRET_CONFIRM, FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY, - (formHandleAnchor.formHandle == null) - ? clientConfig.getEncryptSecret() - : this.cryptor.encrypt(clientConfig.getEncryptSecretConfirm()))) + pwd)) .withDefaultSpanInput(2) .addField(FormBuilder.text( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java index 349bea2c..cd44d9ab 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java @@ -320,7 +320,9 @@ public final class Form implements FormBinding { @Override public String getStringValue() {return pwdInput.getValue() != null ? pwdInput.getValue().toString() : null;} @Override public void setStringValue(final String value) { if (StringUtils.isNotBlank(value)) { - pwdInput.setValue(Form.this.cryptor.decrypt(value)); + final CharSequence pwd = Form.this.cryptor.decrypt(value) + .getOrThrow(); + pwdInput.setValue(pwd); } else { pwdInput.setValue(value); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/PasswordFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/PasswordFieldBuilder.java index a7eb6b24..7c3e14d8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/PasswordFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/PasswordFieldBuilder.java @@ -8,22 +8,27 @@ package ch.ethz.seb.sebserver.gui.form; -import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; -import ch.ethz.seb.sebserver.gui.service.page.PageService; -import ch.ethz.seb.sebserver.gui.widget.PasswordInput; import org.apache.commons.lang3.StringUtils; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.PageService; +import ch.ethz.seb.sebserver.gui.widget.PasswordInput; public class PasswordFieldBuilder extends FieldBuilder { + private static final Logger log = LoggerFactory.getLogger(PasswordFieldBuilder.class); + PasswordFieldBuilder(final String name, final LocTextKey label, final CharSequence value) { super(name, label, value); } @Override - void build(FormBuilder builder) { + void build(final FormBuilder builder) { final boolean readonly = builder.readonly || this.readonly; final Control titleLabel = createTitleLabel(builder.formParent, builder, this); final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput); @@ -32,6 +37,8 @@ public class PasswordFieldBuilder extends FieldBuilder { input.setEditable(!readonly); input.setValue((StringUtils.isNotBlank(this.value)) ? builder.cryptor.decrypt(this.value) + .onError(error -> log.error("Failed to internally decrypt password: {}", error.getMessage())) + .getOr(this.value) : this.value); if (builder.pageService.getFormTooltipMode() == PageService.FormTooltipMode.INPUT) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PasswordFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PasswordFieldBuilder.java index d2a75986..1d3d44e9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PasswordFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PasswordFieldBuilder.java @@ -154,7 +154,9 @@ public class PasswordFieldBuilder implements InputFieldBuilder { @Override protected void setValueToControl(final String value) { if (StringUtils.isNotBlank(value)) { - final CharSequence pwd = this.cryptor.decrypt(value); + final CharSequence pwd = this.cryptor + .decrypt(value) + .getOrThrow(); this.control.setValue(pwd.toString()); this.confirm.setValue(pwd.toString()); } else { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/cert/RemoveCertificate.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/cert/RemoveCertificate.java index 8ca63db3..5f72fccd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/cert/RemoveCertificate.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/cert/RemoveCertificate.java @@ -36,7 +36,7 @@ public class RemoveCertificate extends RestCall> { }), HttpMethod.DELETE, MediaType.APPLICATION_FORM_URLENCODED, - API.CERTIFICATE_ENDPOINT + API.CERTIFICATE_ALIAS_VAR_PATH_SEGMENT); + API.CERTIFICATE_ENDPOINT); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/CertificateDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/CertificateDAO.java index 5c50642f..bdaf1edb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/CertificateDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/CertificateDAO.java @@ -8,9 +8,11 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao; +import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; +import java.util.Collection; import java.util.EnumSet; import java.util.NoSuchElementException; @@ -31,12 +33,25 @@ import ch.ethz.seb.sebserver.gbl.util.Result; /** Concrete EntityDAO interface of Certificate entities */ public interface CertificateDAO { + Result getCertificate(final Long institutionId, String alias); + Result getCertificates(Long institutionId); - Result addCertificate(Long institutionId, String alias, Certificate certificate); + Result addCertificate( + Long institutionId, + String alias, + Certificate certificate); + + Result addCertificate( + Long institutionId, + String alias, + Certificate certificate, + PrivateKey privateKey); Result removeCertificate(Long institutionId, String alias); + Result> getAllIdentityAlias(Long institutionId); + static Result getDataFromCertificate(final Certificates certificates, final String alias) { return Result.tryCatch(() -> { final X509Certificate certificate = (X509Certificate) certificates.keyStore.engineGetCertificate(alias); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/CertificateDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/CertificateDAOImpl.java index d5f10975..30b84b7c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/CertificateDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/CertificateDAOImpl.java @@ -10,12 +10,14 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl; import java.io.ByteArrayInputStream; import java.security.KeyStoreException; +import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -64,7 +66,19 @@ public class CertificateDAOImpl implements CertificateDAO { } @Override - @Transactional + @Transactional(readOnly = true) + public Result getCertificate(final Long institutionId, final String alias) { + return getCertificates(institutionId) + .map(certs -> { + if (!certs.aliases.contains(alias)) { + throw new ResourceNotFoundException(EntityType.CERTIFICATE, alias); + } + return certs.keyStore.engineGetCertificate(alias); + }); + } + + @Override + @Transactional(readOnly = true) public Result getCertificates(final Long institutionId) { return getCertificatesFromPersistent(institutionId) @@ -81,8 +95,19 @@ public class CertificateDAOImpl implements CertificateDAO { final String alias, final Certificate certificate) { + return this.addCertificate(institutionId, alias, certificate, null); + } + + @Override + @Transactional + public Result addCertificate( + final Long institutionId, + final String alias, + final Certificate certificate, + final PrivateKey privateKey) { + return getCertificatesFromPersistent(institutionId) - .flatMap(record -> addCertificate(record, alias, certificate)) + .flatMap(record -> addCertificate(record, alias, certificate, privateKey)) .flatMap(this::storeUpdate) .flatMap(certs -> CertificateDAO.getDataFromCertificate(certs, alias)) .onError(TransactionHandler::rollback); @@ -99,6 +124,18 @@ public class CertificateDAOImpl implements CertificateDAO { .onError(TransactionHandler::rollback); } + @Override + @Transactional(readOnly = true) + public Result> getAllIdentityAlias(final Long institutionId) { + return getCertificates(institutionId) + .map(certs -> certs.aliases + .stream() + .filter(alias -> this.cryptor + .getPrivateKey(certs.keyStore, alias) + .hasValue()) + .collect(Collectors.toList())); + } + private Certificates createNewCertificateStore(final Long institutionId) { return this.cryptor.createNewEmptyKeyStore() .map(store -> new CertificateRecord( @@ -121,12 +158,13 @@ public class CertificateDAOImpl implements CertificateDAO { private Result addCertificate( final CertificateRecord record, final String alias, - final Certificate certificate) { + final Certificate certificate, + final PrivateKey privateKey) { return loadCertificateStore(record.getCertStore()) .map(store -> checkPresent(alias, store, certificate)) .map(store -> { - addToStore(alias, certificate, store); + addToStore(alias, certificate, privateKey, store); return new Certificates( record.getId(), record.getInstitutionId(), @@ -138,10 +176,18 @@ public class CertificateDAOImpl implements CertificateDAO { private void addToStore( final String alias, final Certificate certificate, + final PrivateKey privateKey, final PKCS12KeyStoreSpi store) { try { + // Add the certificate to the key store store.engineSetCertificateEntry(alias, certificate); + // Add the private key to the key store with internal password protection + if (privateKey != null) { + this.cryptor.addPrivateKey(store, privateKey, alias, certificate) + .onError(error -> log.error("Failed to add private key for certificate: {}", alias, error)); + } + } catch (final KeyStoreException e) { throw new RuntimeException("Failed to add certificate to keystore. Cause: ", e); } @@ -188,7 +234,7 @@ public class CertificateDAOImpl implements CertificateDAO { return new Certificates( record.getId(), record.getInstitutionId(), - joinAliases(record.getAliases(), alias), + removeAlias(record.getAliases(), alias), store); }); } @@ -203,6 +249,12 @@ public class CertificateDAOImpl implements CertificateDAO { } } + private Collection removeAlias(final String aliases, final String alias) { + final Collection listFromString = new ArrayList<>(Utils.getListFromString(aliases)); + listFromString.remove(alias); + return listFromString; + } + private Result storeUpdate(final Certificates certificates) { return Result.tryCatch(() -> { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java index cc5a5761..5391b966 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java @@ -546,6 +546,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO { final CharSequence encrypted_encrypt_secret = examConfigurationMap.hasEncryptionSecret() ? this.clientCredentialService.encrypt(examConfigurationMap.encryptSecret) + .getOrThrow() : null; return (encrypted_encrypt_secret != null) ? encrypted_encrypt_secret.toString() : null; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java index 65dcfb98..5710b6b4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java @@ -349,7 +349,6 @@ public class LmsSetupDAOImpl implements LmsSetupDAO { record.getLmsProxyAuthUsername(), record.getLmsProxyAuthSecret()); - final CharSequence plainAccessToken = this.clientCredentialService.getPlainAccessToken(clientCredentials); return Result.tryCatch(() -> new LmsSetup( record.getId(), record.getInstitutionId(), @@ -358,7 +357,10 @@ public class LmsSetupDAOImpl implements LmsSetupDAO { Utils.toString(clientCredentials.clientId), null, record.getLmsUrl(), - Utils.toString(plainAccessToken), + Utils.toString( + this.clientCredentialService + .getPlainAccessToken(clientCredentials) + .getOr(null)), record.getLmsProxyHost(), record.getLmsProxyPort(), Utils.toString(proxyCredentials.clientId), @@ -390,14 +392,16 @@ public class LmsSetupDAOImpl implements LmsSetupDAO { ? new ClientCredentials(null, null) : this.clientCredentialService.encryptClientCredentials( lmsSetup.proxyAuthUsername, - lmsSetup.proxyAuthSecret); + lmsSetup.proxyAuthSecret) + .getOrThrow(); } private ClientCredentials createAPIClientCredentials(final LmsSetup lmsSetup) { return this.clientCredentialService.encryptClientCredentials( lmsSetup.lmsAuthName, lmsSetup.lmsAuthSecret, - lmsSetup.lmsRestApiToken); + lmsSetup.lmsRestApiToken) + .getOrThrow(); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SEBClientConfigDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SEBClientConfigDAOImpl.java index 59c5f71c..1b010d9f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SEBClientConfigDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SEBClientConfigDAOImpl.java @@ -428,6 +428,9 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO { record.getDate(), record.getEncryptSecret(), null, + additionalAttributes.containsKey(SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ALIAS) + ? additionalAttributes.get(SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ALIAS).getValue() + : null, BooleanUtils.toBooleanObject(record.getActive()))); } @@ -438,7 +441,9 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO { } final CharSequence encrypted_encrypt_secret = sebClientConfig.hasEncryptionSecret() - ? this.clientCredentialService.encrypt(sebClientConfig.encryptSecret) + ? this.clientCredentialService + .encrypt(sebClientConfig.encryptSecret) + .getOrThrow() : null; return (encrypted_encrypt_secret != null) ? encrypted_encrypt_secret.toString() : null; } @@ -590,6 +595,19 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO { configId, SEBClientConfig.ATTR_QUIT_PASSWORD); } + + if (StringUtils.isNotBlank(sebClientConfig.encryptCertificateAlias)) { + this.additionalAttributesDAO.saveAdditionalAttribute( + EntityType.SEB_CLIENT_CONFIGURATION, + configId, + SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ALIAS, + sebClientConfig.encryptCertificateAlias); + } else { + this.additionalAttributesDAO.delete( + EntityType.SEB_CLIENT_CONFIGURATION, + configId, + SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ALIAS); + } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java index 1553f9fa..df4728c4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java @@ -132,7 +132,8 @@ public class LmsAPIServiceImpl implements LmsAPIService { final ClientCredentials lmsCredentials = this.clientCredentialService.encryptClientCredentials( lmsSetup.lmsAuthName, lmsSetup.lmsAuthSecret, - lmsSetup.lmsRestApiToken); + lmsSetup.lmsRestApiToken) + .getOrThrow(); final ProxyData proxyData = (StringUtils.isNoneBlank(lmsSetup.proxyHost)) ? new ProxyData( @@ -140,7 +141,8 @@ public class LmsAPIServiceImpl implements LmsAPIService { lmsSetup.proxyPort, this.clientCredentialService.encryptClientCredentials( lmsSetup.proxyAuthUsername, - lmsSetup.proxyAuthSecret)) + lmsSetup.proxyAuthSecret) + .getOrThrow()) : null; return test(createLmsSetupTemplate(lmsSetup, lmsCredentials, proxyData)); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxRestTemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxRestTemplateFactory.java index 1e18d39a..f1454ad4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxRestTemplateFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxRestTemplateFactory.java @@ -140,7 +140,9 @@ final class OpenEdxRestTemplateFactory { final String accessTokenRequestPath) throws URISyntaxException { final CharSequence plainClientId = credentials.clientId; - final CharSequence plainClientSecret = this.clientCredentialService.getPlainClientSecret(credentials); + final CharSequence plainClientSecret = this.clientCredentialService + .getPlainClientSecret(credentials) + .getOrThrow(); final ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); details.setAccessTokenUri(lmsSetup.lmsApiUrl + accessTokenRequestPath); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactory.java index ca3417ad..1db8ded9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactory.java @@ -164,7 +164,9 @@ class MoodleRestTemplateFactory { final String accessTokenRequestPath) { final CharSequence plainClientId = credentials.clientId; - final CharSequence plainClientSecret = this.clientCredentialService.getPlainClientSecret(credentials); + final CharSequence plainClientSecret = this.clientCredentialService + .getPlainClientSecret(credentials) + .getOrThrow(); final MoodleAPIRestTemplate restTemplate = new MoodleAPIRestTemplate( this.jsonMapper, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/CertificateService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/CertificateService.java index 217993e5..59fc12c8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/CertificateService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/CertificateService.java @@ -34,6 +34,7 @@ public interface CertificateService { Long institutionId, CertificateFileType certificateFileType, String alias, + CharSequence password, InputStream in); Result removeCertificate(Long institutionId, String alias); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SEBConfigEncryptionContext.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SEBConfigEncryptionContext.java index fd099e49..94bd71c9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SEBConfigEncryptionContext.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SEBConfigEncryptionContext.java @@ -29,9 +29,8 @@ public interface SEBConfigEncryptionContext { /** Get a defined Certificate if supported. * - * @param key The key of the Certificate to get * @return a defined Certificate * @throws UnsupportedOperationException if not supported */ - Certificate getCertificate(CharSequence key); + Certificate getCertificate(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SEBConfigEncryptionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SEBConfigEncryptionService.java index e1a0a4a5..ec5cd30f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SEBConfigEncryptionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SEBConfigEncryptionService.java @@ -38,12 +38,11 @@ public interface SEBConfigEncryptionService { PASSWORD_PSWD(Type.PASSWORD, "pswd"), /** Password encryption with 'pwcc' header */ PASSWORD_PWCC(Type.PASSWORD, "pwcc"), - -// NOTE not supported yet but eventually needed for SEB config import. -// PUBLIC_KEY_HASH(Type.CERTIFICATE, "pkhs"), -// PUBLIC_KEY_HASH_SYMMETRIC_KEY(Type.CERTIFICATE, "phsk") - - ; + /** Encryption with public/private asymmetric keys and symmetric key */ + PUBLIC_KEY_HASH_SYMMETRIC_KEY(Type.CERTIFICATE, "phsk"), + // NOTE not supported yet but eventually needed for SEB config import. + /** Encryption with public/private key */ + PUBLIC_KEY_HASH(Type.CERTIFICATE, "pkhs"); public final Type type; public final byte[] header; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/AES256JNCryptorEmptyPwdSupport.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/AES256JNCryptorEmptyPwdSupport.java index a1e3643c..2ba105d4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/AES256JNCryptorEmptyPwdSupport.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/AES256JNCryptorEmptyPwdSupport.java @@ -19,11 +19,11 @@ import javax.crypto.spec.SecretKeySpec; import org.cryptonode.jncryptor.AES256JNCryptor; import org.cryptonode.jncryptor.CryptorException; +import ch.ethz.seb.sebserver.gbl.Constants; + class AES256JNCryptorEmptyPwdSupport extends AES256JNCryptor { - static final String KEY_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA1"; static final int AES_256_KEY_SIZE = 256 / 8; - static final String AES_NAME = "AES"; private static final SecureRandom SECURE_RANDOM = new SecureRandom(); protected AES256JNCryptorEmptyPwdSupport() { @@ -44,14 +44,14 @@ class AES256JNCryptorEmptyPwdSupport extends AES256JNCryptor { public SecretKey keyForPassword(final char[] password, final byte[] salt) throws CryptorException { try { final SecretKeyFactory factory = SecretKeyFactory - .getInstance(KEY_DERIVATION_ALGORITHM); + .getInstance(Constants.KEY_DERIVATION_ALGORITHM); final SecretKey tmp = factory.generateSecret(new PBEKeySpec(password, salt, getPBKDFIterations(), AES_256_KEY_SIZE * 8)); - return new SecretKeySpec(tmp.getEncoded(), AES_NAME); + return new SecretKeySpec(tmp.getEncoded(), Constants.AES); } catch (final GeneralSecurityException e) { throw new CryptorException(String.format( "Failed to generate key from password using %s.", - KEY_DERIVATION_ALGORITHM), e); + Constants.KEY_DERIVATION_ALGORITHM), e); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/AES256JNCryptorOutputStreamEmptyPwdSupport.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/AES256JNCryptorOutputStreamEmptyPwdSupport.java index dfb453d4..5f1c9fe1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/AES256JNCryptorOutputStreamEmptyPwdSupport.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/AES256JNCryptorOutputStreamEmptyPwdSupport.java @@ -22,12 +22,12 @@ import javax.crypto.spec.IvParameterSpec; import org.apache.commons.lang3.Validate; import org.cryptonode.jncryptor.CryptorException; +import ch.ethz.seb.sebserver.gbl.Constants; + class AES256JNCryptorOutputStreamEmptyPwdSupport extends OutputStream { static final int SALT_LENGTH = 8; static final int AES_BLOCK_SIZE = 16; - static final String AES_CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; - static final String HMAC_ALGORITHM = "HmacSHA256"; static final int VERSION = 3; static final int FLAG_PASSWORD = 0x01; @@ -86,11 +86,11 @@ class AES256JNCryptorOutputStreamEmptyPwdSupport extends OutputStream { this.iv = iv; try { - final Cipher cipher = Cipher.getInstance(AES_CIPHER_ALGORITHM); + final Cipher cipher = Cipher.getInstance(Constants.AES_CIPHER_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new IvParameterSpec(iv)); try { - final Mac mac = Mac.getInstance(HMAC_ALGORITHM); + final Mac mac = Mac.getInstance(Constants.HMAC_ALGORITHM); mac.init(hmacKey); this.macOutputStream = new MacOutputStream(out, mac); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/CertificateServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/CertificateServiceImpl.java index 6de4b861..591a0bea 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/CertificateServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/CertificateServiceImpl.java @@ -9,24 +9,30 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; import java.io.InputStream; +import java.security.KeyStore; +import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Base64; import java.util.Collection; +import java.util.Enumeration; import java.util.function.Predicate; import java.util.stream.Collectors; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.sebconfig.CertificateInfo; import ch.ethz.seb.sebserver.gbl.model.sebconfig.CertificateInfo.CertificateFileType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Certificates; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Pair; import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.CertificateDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.CertificateService; @@ -72,13 +78,27 @@ public class CertificateServiceImpl implements CertificateService { final Long institutionId, final CertificateFileType certificateFileType, final String alias, + final CharSequence password, final InputStream in) { - return loadCertFromInput(institutionId, certificateFileType, in) - .flatMap(cert -> this.certificateDAO.addCertificate( - institutionId, - CertificateDAO.extractAlias(cert, alias), - cert)); + switch (certificateFileType) { + case PEM: + return loadCertFromPEM(in) + .flatMap(cert -> this.certificateDAO.addCertificate( + institutionId, + CertificateDAO.extractAlias(cert, alias), + cert)); + + case PKCS12: + return loadCertFromPKC(in, password) + .flatMap(pair -> this.certificateDAO.addCertificate( + institutionId, + CertificateDAO.extractAlias(pair.a, alias), + pair.a, + pair.b)); + default: + return Result.ofRuntimeError("Unsupported certificate type"); + } } @Override @@ -123,26 +143,26 @@ public class CertificateServiceImpl implements CertificateService { return getDataFromCertificates(certificates, data -> true); } - private Result loadCertFromInput( - final Long institutionId, - final CertificateFileType certificateFileType, - final InputStream in) { - - switch (certificateFileType) { - case PEM: - return loadCertFromPEM(institutionId, in); - case PKCS12: - return Result.ofRuntimeError("Not supported yet"); - default: - return Result.ofRuntimeError("Unsupported certificate type"); - } - } - - private Result loadCertFromPEM(final Long institutionId, final InputStream in) { + private Result loadCertFromPEM(final InputStream in) { return Result.tryCatch(() -> { - final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + final CertificateFactory certFactory = CertificateFactory.getInstance(Constants.X_509); return (X509Certificate) certFactory.generateCertificate(in); }); } + private Result> loadCertFromPKC( + final InputStream in, + final CharSequence password) { + + return Result.tryCatch(() -> { + final KeyStore ks = KeyStore.getInstance(Constants.PKCS_12); + ks.load(in, Utils.toCharArray(password)); + final Enumeration aliases = ks.aliases(); + final String alias = aliases.nextElement(); + final X509Certificate certificate = (X509Certificate) ks.getCertificate(alias); + final PrivateKey pKey = (PrivateKey) ks.getKey(alias, Utils.toCharArray(password)); + return new Pair<>(certificate, pKey); + }); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/CertificateSymetricKeyCryptor.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/CertificateSymetricKeyCryptor.java new file mode 100644 index 00000000..7d456449 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/CertificateSymetricKeyCryptor.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.util.Base64; +import java.util.Set; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; + +import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigCryptor; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionContext; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionService.Strategy; + +@Lazy +@Component +@WebServiceProfile +public class CertificateSymetricKeyCryptor implements SEBConfigCryptor { + + private static final Logger log = LoggerFactory.getLogger(CertificateSymetricKeyCryptor.class); + + private static final Set STRATEGIES = Utils.immutableSetOf( + Strategy.PUBLIC_KEY_HASH_SYMMETRIC_KEY); + private static final int PUBLIC_KEY_HASH_SIZE = 20; + private static final int ENCRYPTION_KEY_LENGTH = 32; + private static final int KEY_LENGTH_SIZE = 4; + + private final PasswordEncryptor passwordEncryptor; + private final PasswordDecryptor passwordDecryptor; + + public CertificateSymetricKeyCryptor( + final PasswordEncryptor passwordEncryptor, + final PasswordDecryptor passwordDecryptor) { + + this.passwordEncryptor = passwordEncryptor; + this.passwordDecryptor = passwordDecryptor; + } + + @Override + public Set strategies() { + return STRATEGIES; + } + + @Override + public void encrypt( + final OutputStream output, + final InputStream input, + final SEBConfigEncryptionContext context) { + + if (log.isDebugEnabled()) { + log.debug("*** Start streaming asynchronous certificate encryption"); + } + + try { + + final Certificate certificate = context.getCertificate(); + final byte[] publicKeyHash = generatePublicKeyHash(certificate); + final byte[] symetricKey = generateSymetricKey(); + final CharSequence symetricKeyBase64 = Base64.getEncoder().encodeToString(symetricKey); + final byte[] generateParameter = generateParameter(certificate, publicKeyHash, symetricKey); + + output.write(generateParameter, 0, generateParameter.length); + this.passwordEncryptor.encrypt(output, input, symetricKeyBase64); + + } catch (final Exception e) { + log.error("Error while trying to stream and encrypt data: ", e); + } + } + + @Override + public void decrypt( + final OutputStream output, + final InputStream input, + final SEBConfigEncryptionContext context) { + + // TODO Auto-generated method stub + try { + final Certificate certificate = context.getCertificate(); + final byte[] publicKeyHash = parsePublicKeyHash(input); + + } catch (final Exception e) { + log.error("Error while trying to stream and decrypt data: ", e); + } + + } + + private byte[] generatePublicKeyHash(final Certificate cert) throws NoSuchAlgorithmException { + final byte[] publicKey = cert.getPublicKey().getEncoded(); + final MessageDigest md = MessageDigest.getInstance(Constants.SHA_1); + final byte[] hash = md.digest(publicKey); + return hash; + } + + private byte[] generateSymetricKey() throws NoSuchAlgorithmException { + final KeyGenerator keyGenerator = KeyGenerator.getInstance(Constants.AES); + keyGenerator.init(ENCRYPTION_KEY_LENGTH); + return keyGenerator.generateKey().getEncoded(); + } + + @SuppressWarnings("resource") + private byte[] generateParameter( + final Certificate cert, + final byte[] publicKeyHash, + final byte[] symetricKey) throws Exception { + + final ByteArrayOutputStream data = new ByteArrayOutputStream(); + final byte[] encryptedKey = generateEncryptedKey(cert, symetricKey); + final byte[] encryptedKeyLength = ByteBuffer + .allocate(KEY_LENGTH_SIZE) + .putInt(encryptedKey.length) + .array(); + + data.write(publicKeyHash, 0, publicKeyHash.length); + data.write(encryptedKeyLength, 0, encryptedKeyLength.length); + data.write(encryptedKey, 0, encryptedKey.length); + + return data.toByteArray(); + } + + private byte[] generateEncryptedKey(final Certificate cert, final byte[] symetricKey) throws Exception { + final String algorithm = cert.getPublicKey().getAlgorithm(); + final Cipher encryptCipher = Cipher.getInstance(algorithm); + encryptCipher.init(Cipher.ENCRYPT_MODE, cert); + final byte[] cipherText = encryptCipher.doFinal(symetricKey); + return cipherText; + } + + private byte[] parsePublicKeyHash(final InputStream input) throws IOException { + final byte[] publicKeyHash = new byte[PUBLIC_KEY_HASH_SIZE]; + input.read(publicKeyHash, 0, publicKeyHash.length); + return publicKeyHash; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java index a3617392..b9f88158 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java @@ -8,6 +8,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PipedInputStream; @@ -15,6 +16,7 @@ import java.io.PipedOutputStream; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; import java.util.Base64; import java.util.Collection; import java.util.Collections; @@ -54,10 +56,11 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.WebserviceInfo; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.CertificateDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionContext; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionService; -import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionService.Strategy; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SEBConfigEncryptionServiceImpl.EncryptionContext; import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfiguration; @@ -165,6 +168,7 @@ public class ClientConfigServiceImpl implements ClientConfigService { private final PasswordEncoder clientPasswordEncoder; private final ZipService zipService; private final WebserviceInfo webserviceInfo; + private final CertificateDAO certificateDAO; private final long defaultPingInterval; protected ClientConfigServiceImpl( @@ -172,8 +176,9 @@ public class ClientConfigServiceImpl implements ClientConfigService { final ClientCredentialService clientCredentialService, final SEBConfigEncryptionService sebConfigEncryptionService, final ZipService zipService, - @Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder clientPasswordEncoder, final WebserviceInfo webserviceInfo, + final CertificateDAO certificateDAO, + @Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder clientPasswordEncoder, @Value("${sebserver.webservice.api.exam.defaultPingInterval:1000}") final long defaultPingInterval) { this.sebClientConfigDAO = sebClientConfigDAO; @@ -182,6 +187,7 @@ public class ClientConfigServiceImpl implements ClientConfigService { this.zipService = zipService; this.clientPasswordEncoder = clientPasswordEncoder; this.webserviceInfo = webserviceInfo; + this.certificateDAO = certificateDAO; this.defaultPingInterval = defaultPingInterval; } @@ -224,18 +230,13 @@ public class ClientConfigServiceImpl implements ClientConfigService { final SEBClientConfig config = this.sebClientConfigDAO .byModelId(modelId).getOrThrow(); - final CharSequence encryptionPassword = this.sebClientConfigDAO - .getConfigPasswordCipher(config.getModelId()) - .getOr((config.getConfigPurpose() == ConfigPurpose.START_EXAM) ? null : StringUtils.EMPTY); - - exportSEBClientConfiguration(output, examId, config, encryptionPassword); + exportSEBClientConfiguration(output, examId, config); } protected void exportSEBClientConfiguration( final OutputStream output, final Long examId, - final SEBClientConfig config, - final CharSequence encryptionPassword) { + final SEBClientConfig config) { final String plainTextXMLContent = extractXMLContent(config, examId); @@ -263,9 +264,10 @@ public class ClientConfigServiceImpl implements ClientConfigService { // ZIP plain text this.zipService.write(pOut, plainIn); - if (encryptionPassword != null) { - // encrypt zipped plain text and add header - passwordEncryption(zipOut, encryptionPassword, config.getConfigPurpose(), pIn); + if (StringUtils.isNotBlank(config.encryptCertificateAlias)) { + certificateEncryption(zipOut, config, pIn); + } else if (config.hasEncryptionSecret()) { + passwordEncryption(zipOut, config, pIn); } else { // just add plain text header this.sebConfigEncryptionService.streamEncrypted( @@ -290,6 +292,34 @@ public class ClientConfigServiceImpl implements ClientConfigService { } } + private SEBConfigEncryptionContext buildCertificateEncryptionContext(final SEBClientConfig config) { + + final Certificate certificate = this.certificateDAO.getCertificate( + config.institutionId, + String.valueOf(config.getEncryptCertificateAlias())) + .getOrThrow(); + + return EncryptionContext.contextOf( + SEBConfigEncryptionService.Strategy.PUBLIC_KEY_HASH_SYMMETRIC_KEY, + certificate); + } + + private SEBConfigEncryptionContext buildPasswordEncryptionContext(final SEBClientConfig config) { + final CharSequence encryptionPassword = this.sebClientConfigDAO + .getConfigPasswordCipher(config.getModelId()) + .getOr((config.getConfigPurpose() == ConfigPurpose.START_EXAM) ? null : StringUtils.EMPTY); + final CharSequence plainTextPassword = (StringUtils.isNotBlank(encryptionPassword)) + ? getPlainTextPassword( + encryptionPassword, + config.configPurpose) + : null; + return EncryptionContext.contextOf( + (config.configPurpose == ConfigPurpose.CONFIGURE_CLIENT) + ? SEBConfigEncryptionService.Strategy.PASSWORD_PWCC + : SEBConfigEncryptionService.Strategy.PASSWORD_PSWD, + plainTextPassword); + } + private String extractXMLContent(final SEBClientConfig config, final Long examId) { final String fallbackAddition = getFallbackAddition(config); @@ -309,7 +339,8 @@ public class ClientConfigServiceImpl implements ClientConfigService { .getOrThrow(); final CharSequence plainClientId = sebClientCredentials.clientId; final CharSequence plainClientSecret = this.clientCredentialService - .getPlainClientSecret(sebClientCredentials); + .getPlainClientSecret(sebClientCredentials) + .getOrThrow(); final String plainTextConfig = String.format( SEB_CLIENT_CONFIG_TEMPLATE_XML, @@ -389,7 +420,9 @@ public class ClientConfigServiceImpl implements ClientConfigService { config.fallbackAttemptInterval); if (StringUtils.isNotBlank(config.fallbackPassword)) { - final CharSequence decrypt = this.clientCredentialService.decrypt(config.fallbackPassword); + final CharSequence decrypt = this.clientCredentialService + .decrypt(config.fallbackPassword) + .getOrThrow(); fallbackAddition += String.format( SEB_CLIENT_CONFIG_STRING_TEMPLATE, SEBClientConfig.ATTR_FALLBACK_PASSWORD, @@ -397,7 +430,9 @@ public class ClientConfigServiceImpl implements ClientConfigService { } if (StringUtils.isNotBlank(config.quitPassword)) { - final CharSequence decrypt = this.clientCredentialService.decrypt(config.quitPassword); + final CharSequence decrypt = this.clientCredentialService + .decrypt(config.quitPassword) + .getOrThrow(); fallbackAddition += String.format( SEB_CLIENT_CONFIG_STRING_TEMPLATE, SEBClientConfig.ATTR_QUIT_PASSWORD, @@ -428,7 +463,9 @@ public class ClientConfigServiceImpl implements ClientConfigService { final ClientCredentials credentials = this.sebClientConfigDAO .getSEBClientCredentials(config.getModelId()) .getOrThrow(); - final CharSequence plainClientSecret = this.clientCredentialService.getPlainClientSecret(credentials); + final CharSequence plainClientSecret = this.clientCredentialService + .getPlainClientSecret(credentials) + .getOrThrow(); final String basicAuth = credentials.clientId + String.valueOf(Constants.COLON) + plainClientSecret; @@ -463,28 +500,46 @@ public class ClientConfigServiceImpl implements ClientConfigService { checkAccess(config); } + private void certificateEncryption( + final OutputStream out, + final SEBClientConfig config, + final InputStream in) throws IOException { + + if (log.isDebugEnabled()) { + log.debug("*** SEB client configuration with certificate based encryption"); + } + + final boolean withPasswordEncryption = config.hasEncryptionSecret(); + + PipedOutputStream passEncryptionOut = null; + PipedInputStream passEncryptionIn = null; + + if (withPasswordEncryption) { + // encrypt with password first + passEncryptionOut = new PipedOutputStream(); + passEncryptionIn = new PipedInputStream(passEncryptionOut); + passwordEncryption(passEncryptionOut, config, in); + } + + this.sebConfigEncryptionService.streamEncrypted( + out, + (withPasswordEncryption) ? in : passEncryptionIn, + buildCertificateEncryptionContext(config)); + } + private void passwordEncryption( final OutputStream output, - final CharSequence encryptionPassword, - final ConfigPurpose configPurpose, + final SEBClientConfig config, final InputStream input) { if (log.isDebugEnabled()) { log.debug("*** SEB client configuration with password based encryption"); } - final CharSequence plainTextPassword = getPlainTextPassword( - encryptionPassword, - configPurpose); - this.sebConfigEncryptionService.streamEncrypted( output, input, - EncryptionContext.contextOf( - (configPurpose == ConfigPurpose.CONFIGURE_CLIENT) - ? Strategy.PASSWORD_PWCC - : Strategy.PASSWORD_PSWD, - plainTextPassword)); + buildPasswordEncryptionContext(config)); } private CharSequence getPlainTextPassword( @@ -493,7 +548,9 @@ public class ClientConfigServiceImpl implements ClientConfigService { CharSequence plainTextPassword = (encryptionPassword == StringUtils.EMPTY) ? StringUtils.EMPTY - : this.clientCredentialService.decrypt(encryptionPassword); + : this.clientCredentialService + .decrypt(encryptionPassword) + .getOrThrow(); if (configPurpose == ConfigPurpose.CONFIGURE_CLIENT && plainTextPassword != StringUtils.EMPTY) { MessageDigest digest; @@ -518,7 +575,10 @@ public class ClientConfigServiceImpl implements ClientConfigService { * @return encoded clientSecret for that SEBClientConfiguration with clientId or null of not existing */ private Result getEncodedClientConfigSecret(final String clientId) { return this.sebClientConfigDAO.getConfigPasswordCipherByClientName(clientId) - .map(cipher -> this.clientPasswordEncoder.encode(this.clientCredentialService.decrypt(cipher))); + .map(cipher -> this.clientPasswordEncoder + .encode(this.clientCredentialService + .decrypt(cipher) + .getOrThrow())); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java index 46870da6..41be3fd9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java @@ -188,7 +188,8 @@ public class ExamConfigServiceImpl implements ExamConfigService { } final CharSequence encryptionPasswordPlaintext = this.clientCredentialService - .decrypt(passwordCipher); + .decrypt(passwordCipher) + .getOrThrow(); PipedOutputStream plainOut = null; PipedInputStream zipIn = null; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordCryptor.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordCryptor.java new file mode 100644 index 00000000..dd68774e --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordCryptor.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Set; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigCryptor; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionContext; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionService.Strategy; + +@Lazy +@Component +@WebServiceProfile +public class PasswordCryptor implements SEBConfigCryptor { + + private static final Set STRATEGIES = Utils.immutableSetOf( + Strategy.PASSWORD_PSWD, + Strategy.PASSWORD_PWCC); + + private final PasswordEncryptor passwordEncryptor; + private final PasswordDecryptor passwordDecryptor; + + public PasswordCryptor(final PasswordEncryptor passwordEncryptor, final PasswordDecryptor passwordDecryptor) { + this.passwordEncryptor = passwordEncryptor; + this.passwordDecryptor = passwordDecryptor; + } + + @Override + public Set strategies() { + return STRATEGIES; + } + + @Override + public void encrypt( + final OutputStream output, + final InputStream input, + final SEBConfigEncryptionContext context) { + + this.passwordEncryptor.encrypt(output, input, context.getPassword()); + } + + @Override + public void decrypt( + final OutputStream output, + final InputStream input, + final SEBConfigEncryptionContext context) { + + this.passwordDecryptor.decrypt(output, input, context.getPassword()); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordDecryptor.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordDecryptor.java new file mode 100644 index 00000000..9d0e1bf4 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordDecryptor.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; + +import org.apache.commons.io.IOUtils; +import org.cryptonode.jncryptor.AES256JNCryptorInputStream; +import org.cryptonode.jncryptor.CryptorException; +import org.cryptonode.jncryptor.JNCryptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; + +@Lazy +@Component +@WebServiceProfile +public class PasswordDecryptor { + + private static final Logger log = LoggerFactory.getLogger(PasswordDecryptor.class); + + private final JNCryptor cryptor; + + public PasswordDecryptor(final JNCryptor cryptor) { + this.cryptor = cryptor; + } + + void decrypt( + final OutputStream output, + final InputStream input, + final CharSequence password) { + + try { + final byte[] version = new byte[Constants.JN_CRYPTOR_VERSION_HEADER_SIZE]; + final int read = input.read(version); + if (read != Constants.JN_CRYPTOR_VERSION_HEADER_SIZE) { + throw new IllegalArgumentException("Failed to verify RNCrypt version from input stream file header."); + } + + final SequenceInputStream sequenceInputStream = new SequenceInputStream( + new ByteArrayInputStream(version), + input); + + if (version[0] == 3) { + + if (log.isDebugEnabled()) { + log.debug("*** Start streaming asynchronous password decryption"); + } + + AES256JNCryptorInputStream encryptInput = null; + try { + + encryptInput = new AES256JNCryptorInputStream( + sequenceInputStream, + Utils.toCharArray(password)); + + IOUtils.copyLarge(encryptInput, output); + + } catch (final IOException e) { + log.error("Error while trying to read/write form/to streams: ", e); + } finally { + IOUtils.closeQuietly(encryptInput); + } + } else { + // AES256JNCryptorInputStream supports only decryption of AES256 version 3 encrypted data + // Workaround: stop streaming and use AES256JNCryptor which supports both, version 2 and 3 + log.info("Trying to decrypt with AES256JNCryptor by load all data into memory..."); + + try { + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + IOUtils.copy(sequenceInputStream, out); + final byte[] ciphertext = out.toByteArray(); + + final byte[] decryptData = this.cryptor.decryptData(ciphertext, Utils.toCharArray(password)); + final ByteArrayInputStream decryptedIn = new ByteArrayInputStream(decryptData); + IOUtils.copyLarge(decryptedIn, output); + + } catch (final IOException | CryptorException e) { + log.error("Error while trying to none-streaming decrypt: ", e); + } + } + } catch (final IOException e) { + log.error("Unexpected error while decryption: ", e); + } finally { + try { + output.flush(); + output.close(); + } catch (final IOException e) { + log.error("Failed to close streams"); + } + + if (log.isDebugEnabled()) { + log.debug("*** Finish streaming asynchronous decryption"); + } + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordEncryptor.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordEncryptor.java index 1974cd70..42ba67be 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordEncryptor.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordEncryptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -8,19 +8,13 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.SequenceInputStream; -import java.util.Set; import org.apache.commons.io.IOUtils; -import org.cryptonode.jncryptor.AES256JNCryptorInputStream; import org.cryptonode.jncryptor.AES256JNCryptorOutputStream; import org.cryptonode.jncryptor.CryptorException; -import org.cryptonode.jncryptor.JNCryptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; @@ -29,47 +23,26 @@ import org.springframework.stereotype.Component; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; -import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigCryptor; -import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionContext; -import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionService.Strategy; @Lazy @Component @WebServiceProfile -public class PasswordEncryptor implements SEBConfigCryptor { +public class PasswordEncryptor { private static final Logger log = LoggerFactory.getLogger(PasswordEncryptor.class); - private static final Set STRATEGIES = Utils.immutableSetOf( - Strategy.PASSWORD_PSWD, - Strategy.PASSWORD_PWCC); - - private final JNCryptor cryptor; - - protected PasswordEncryptor(final JNCryptor cryptor) { - this.cryptor = cryptor; - } - - @Override - public Set strategies() { - return STRATEGIES; - } - - @Override public void encrypt( final OutputStream output, final InputStream input, - final SEBConfigEncryptionContext context) { + final CharSequence password) { if (log.isDebugEnabled()) { - log.debug("*** Start streaming asynchronous encryption"); + log.debug("*** Start streaming asynchronous password encryption"); } OutputStream encryptOutput = null; try { - final CharSequence password = context.getPassword(); - if (password.length() == 0) { encryptOutput = new AES256JNCryptorOutputStreamEmptyPwdSupport( output, @@ -105,79 +78,4 @@ public class PasswordEncryptor implements SEBConfigCryptor { } } - @Override - public void decrypt( - final OutputStream output, - final InputStream input, - final SEBConfigEncryptionContext context) { - - final CharSequence password = context.getPassword(); - - try { - final byte[] version = new byte[Constants.JN_CRYPTOR_VERSION_HEADER_SIZE]; - final int read = input.read(version); - if (read != Constants.JN_CRYPTOR_VERSION_HEADER_SIZE) { - throw new IllegalArgumentException("Failed to verify RNCrypt version from input stream file header."); - } - - final SequenceInputStream sequenceInputStream = new SequenceInputStream( - new ByteArrayInputStream(version), - input); - - if (version[0] == 3) { - - if (log.isDebugEnabled()) { - log.debug("*** Start streaming asynchronous decryption"); - } - - AES256JNCryptorInputStream encryptInput = null; - try { - - encryptInput = new AES256JNCryptorInputStream( - sequenceInputStream, - Utils.toCharArray(password)); - - IOUtils.copyLarge(encryptInput, output); - - } catch (final IOException e) { - log.error("Error while trying to read/write form/to streams: ", e); - } finally { - IOUtils.closeQuietly(encryptInput); - } - } else { - // AES256JNCryptorInputStream supports only decryption of AES256 version 3 encrypted data - // Workaround: stop streaming and use AES256JNCryptor which supports both, version 2 and 3 - log.info("Trying to decrypt with AES256JNCryptor by load all data into memory..."); - - try { - - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - IOUtils.copy(sequenceInputStream, out); - final byte[] ciphertext = out.toByteArray(); - - //cryptor.setPBKDFIterations(Constants.JN_CRYPTOR_ITERATIONS); - final byte[] decryptData = this.cryptor.decryptData(ciphertext, Utils.toCharArray(password)); - final ByteArrayInputStream decryptedIn = new ByteArrayInputStream(decryptData); - IOUtils.copyLarge(decryptedIn, output); - - } catch (final IOException | CryptorException e) { - log.error("Error while trying to none-streaming decrypt: ", e); - } - } - } catch (final IOException e) { - log.error("Unexpected error while decryption: ", e); - } finally { - try { - output.flush(); - output.close(); - } catch (final IOException e) { - log.error("Failed to close streams"); - } - - if (log.isDebugEnabled()) { - log.debug("*** Finish streaming asynchronous decryption"); - } - } - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SEBConfigEncryptionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SEBConfigEncryptionServiceImpl.java index af6b6537..dc62045d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SEBConfigEncryptionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SEBConfigEncryptionServiceImpl.java @@ -20,7 +20,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.concurrent.Future; -import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; @@ -74,7 +73,7 @@ public final class SEBConfigEncryptionServiceImpl implements SEBConfigEncryption pin = new PipedInputStream(pout); if (log.isDebugEnabled()) { - log.debug("Password encryption with strategy: {}", strategy); + log.debug("Encryption with strategy: {}", strategy); } output.write(strategy.header); @@ -193,16 +192,16 @@ public final class SEBConfigEncryptionServiceImpl implements SEBConfigEncryption public final Strategy strategy; public final CharSequence password; - public final Function certificateStore; + public final Certificate certificate; private EncryptionContext( final Strategy strategy, final CharSequence password, - final Function certificateStore) { + final Certificate certificate) { this.strategy = strategy; this.password = password; - this.certificateStore = certificateStore; + this.certificate = certificate; } @Override @@ -216,24 +215,24 @@ public final class SEBConfigEncryptionServiceImpl implements SEBConfigEncryption } @Override - public Certificate getCertificate(final CharSequence key) { - if (this.certificateStore == null) { - throw new UnsupportedOperationException(); - } - return this.certificateStore.apply(key); + public Certificate getCertificate() { + return this.certificate; } - static SEBConfigEncryptionContext contextOf(final Strategy strategy, final CharSequence password) { + static SEBConfigEncryptionContext contextOf( + final Strategy strategy, + final CharSequence password) { + checkPasswordBased(strategy); return new EncryptionContext(strategy, password, null); } static SEBConfigEncryptionContext contextOf( final Strategy strategy, - final Function certificateStore) { + final Certificate certificate) { checkCertificateBased(strategy); - return new EncryptionContext(strategy, null, certificateStore); + return new EncryptionContext(strategy, null, certificate); } static void checkPasswordBased(final Strategy strategy) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java index 36f2179d..e26a23ac 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java @@ -307,7 +307,10 @@ public class JitsiProctoringService implements ExamProctoringService { .build() .getHost(); - final CharSequence decryptedSecret = this.cryptor.decrypt(appSecret); + final CharSequence decryptedSecret = this.cryptor + .decrypt(appSecret) + .getOrThrow(); + final String token = internalCreateAccessToken( appKey, decryptedSecret, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java index 9e78afe1..e20a019f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java @@ -159,7 +159,9 @@ public class ZoomProctoringService implements ExamProctoringService { final ClientCredentials credentials = new ClientCredentials( proctoringSettings.appKey, - this.cryptor.encrypt(proctoringSettings.appSecret)); + this.cryptor + .encrypt(proctoringSettings.appSecret) + .getOrThrow()); final ResponseEntity result = this.zoomRestTemplate .testServiceConnection( @@ -182,7 +184,9 @@ public class ZoomProctoringService implements ExamProctoringService { proctoringSettings.serverURL, proctoringSettings.collectingRoomSize, proctoringSettings.appKey, - this.cryptor.encrypt(proctoringSettings.appSecret)); + this.cryptor + .encrypt(proctoringSettings.appSecret) + .getOrThrow()); disposeServiceRoomsForExam( proctoringSettings.examId, @@ -511,7 +515,10 @@ public class ZoomProctoringService implements ExamProctoringService { try { - final CharSequence decryptedSecret = this.cryptor.decrypt(credentials.secret); + final CharSequence decryptedSecret = this.cryptor + .decrypt(credentials.secret) + .getOrThrow(); + final StringBuilder builder = new StringBuilder(); final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); @@ -551,7 +558,9 @@ public class ZoomProctoringService implements ExamProctoringService { try { final String apiKey = credentials.clientIdAsString(); final int status = host ? 1 : 0; - final CharSequence decryptedSecret = this.cryptor.decrypt(credentials.secret); + final CharSequence decryptedSecret = this.cryptor + .decrypt(credentials.secret) + .getOrThrow(); final Mac hasher = Mac.getInstance("HmacSHA256"); final String ts = Long.toString(System.currentTimeMillis() - 30000); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/CertificateController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/CertificateController.java index 53ad813d..cd89e160 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/CertificateController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/CertificateController.java @@ -215,7 +215,12 @@ public class CertificateController { try { inputStream = new BufferedInputStream(request.getInputStream()); - return this.certificateService.addCertificate(institutionId, certificateFileType, alias, inputStream) + return this.certificateService.addCertificate( + institutionId, + certificateFileType, + alias, + password, + inputStream) .flatMap(certData -> this.userActivityLogDAO.log(UserLogActivityType.IMPORT, certData)) .getOrThrow(); @@ -229,7 +234,6 @@ public class CertificateController { } @RequestMapping( - path = API.CERTIFICATE_ALIAS_VAR_PATH_SEGMENT, method = RequestMethod.DELETE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java index fd3430e1..605dd841 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java @@ -39,6 +39,7 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM; +import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; @@ -46,6 +47,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; @@ -85,9 +87,15 @@ public class SEBClientConfigController extends ActivatableEntityController encryptors = Arrays.asList( - new PasswordEncryptor(cryptor), + new PasswordCryptor( + new PasswordEncryptor(), + new PasswordDecryptor(cryptor)), new NoneEncryptor()); return new SEBConfigEncryptionServiceImpl(encryptors); } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java index 864b8097..91f31231 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java @@ -19,13 +19,14 @@ import org.mockito.Mockito; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection; import ch.ethz.seb.sebserver.gbl.util.Cryptor; +import ch.ethz.seb.sebserver.gbl.util.Result; public class ExamJITSIProctoringServiceTest { @Test public void testTokenPayload() throws InvalidKeyException, NoSuchAlgorithmException { final Cryptor cryptorMock = Mockito.mock(Cryptor.class); - Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn("fbvgeghergrgrthrehreg123"); + Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123")); final JitsiProctoringService examJITSIProctoringService = new JitsiProctoringService(null, null, cryptorMock, null); @@ -59,7 +60,7 @@ public class ExamJITSIProctoringServiceTest { @Test public void testCreateProctoringURL() { final Cryptor cryptorMock = Mockito.mock(Cryptor.class); - Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn("fbvgeghergrgrthrehreg123"); + Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123")); final JitsiProctoringService examJITSIProctoringService = new JitsiProctoringService(null, null, cryptorMock, null); final ProctoringRoomConnection data = examJITSIProctoringService.createProctoringConnection(