certs impl

This commit is contained in:
anhefti 2021-05-04 21:59:04 +02:00
parent 1a67ec773d
commit 6bf1551028
45 changed files with 810 additions and 311 deletions

View file

@ -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,

View file

@ -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);

View file

@ -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<ClientCredentials> 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<ClientCredentials> 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<CharSequence> 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<CharSequence> 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<CharSequence> 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<CharSequence> decrypt(final CharSequence cipher);
}

View file

@ -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<ClientCredentials> 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<ClientCredentials> 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<CharSequence> 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<CharSequence> 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<CharSequence> encrypt(final CharSequence text) {
return this.cryptor.encrypt(text);
}
@Override
public CharSequence decrypt(final CharSequence text) {
public Result<CharSequence> 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());

View file

@ -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);
}

View file

@ -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<CharSequence> 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<CharSequence> 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<PKCS12KeyStoreSpi> 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<PrivateKey> getPrivateKey(final PKCS12KeyStoreSpi store, final String alias) {
return Result.tryCatch(() -> {
return (PrivateKey) store.engineGetKey(alias, Utils.toCharArray(this.internalPWD));
});
}
static Result<CharSequence> 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<CharSequence> 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;
}
});
}
}

View file

@ -382,7 +382,8 @@ public final class Result<T> {
@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<T> {

View file

@ -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"));

View file

@ -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(

View file

@ -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);
}

View file

@ -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<CharSequence> {
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<CharSequence> {
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) {

View file

@ -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 {

View file

@ -36,7 +36,7 @@ public class RemoveCertificate extends RestCall<Collection<EntityKey>> {
}),
HttpMethod.DELETE,
MediaType.APPLICATION_FORM_URLENCODED,
API.CERTIFICATE_ENDPOINT + API.CERTIFICATE_ALIAS_VAR_PATH_SEGMENT);
API.CERTIFICATE_ENDPOINT);
}
}

View file

@ -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<Certificate> getCertificate(final Long institutionId, String alias);
Result<Certificates> getCertificates(Long institutionId);
Result<CertificateInfo> addCertificate(Long institutionId, String alias, Certificate certificate);
Result<CertificateInfo> addCertificate(
Long institutionId,
String alias,
Certificate certificate);
Result<CertificateInfo> addCertificate(
Long institutionId,
String alias,
Certificate certificate,
PrivateKey privateKey);
Result<EntityKey> removeCertificate(Long institutionId, String alias);
Result<Collection<String>> getAllIdentityAlias(Long institutionId);
static Result<CertificateInfo> getDataFromCertificate(final Certificates certificates, final String alias) {
return Result.tryCatch(() -> {
final X509Certificate certificate = (X509Certificate) certificates.keyStore.engineGetCertificate(alias);

View file

@ -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<Certificate> 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<Certificates> 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<CertificateInfo> 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<Collection<String>> 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<Certificates> 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<String> removeAlias(final String aliases, final String alias) {
final Collection<String> listFromString = new ArrayList<>(Utils.getListFromString(aliases));
listFromString.remove(alias);
return listFromString;
}
private Result<Certificates> storeUpdate(final Certificates certificates) {
return Result.tryCatch(() -> {

View file

@ -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;
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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));

View file

@ -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);

View file

@ -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,

View file

@ -34,6 +34,7 @@ public interface CertificateService {
Long institutionId,
CertificateFileType certificateFileType,
String alias,
CharSequence password,
InputStream in);
Result<EntityKey> removeCertificate(Long institutionId, String alias);

View file

@ -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();
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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<X509Certificate> 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<X509Certificate> loadCertFromPEM(final Long institutionId, final InputStream in) {
private Result<X509Certificate> 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<Pair<X509Certificate, PrivateKey>> 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<String> 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);
});
}
}

View file

@ -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<Strategy> 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<Strategy> 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;
}
}

View file

@ -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<CharSequence> 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()));
}
}

View file

@ -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;

View file

@ -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<Strategy> 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<Strategy> 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());
}
}

View file

@ -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");
}
}
}
}

View file

@ -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<Strategy> STRATEGIES = Utils.immutableSetOf(
Strategy.PASSWORD_PSWD,
Strategy.PASSWORD_PWCC);
private final JNCryptor cryptor;
protected PasswordEncryptor(final JNCryptor cryptor) {
this.cryptor = cryptor;
}
@Override
public Set<Strategy> 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");
}
}
}
}

View file

@ -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<CharSequence, Certificate> certificateStore;
public final Certificate certificate;
private EncryptionContext(
final Strategy strategy,
final CharSequence password,
final Function<CharSequence, Certificate> 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<CharSequence, Certificate> certificateStore) {
final Certificate certificate) {
checkCertificateBased(strategy);
return new EncryptionContext(strategy, null, certificateStore);
return new EncryptionContext(strategy, null, certificate);
}
static void checkPasswordBased(final Strategy strategy) {

View file

@ -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,

View file

@ -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<String> 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);

View file

@ -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)

View file

@ -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<SEBCl
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadSEBConfig(
@PathVariable final String modelId,
@RequestParam(
name = Entity.FILTER_ATTR_INSTITUTION,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@RequestParam(name = EXAM.ATTR_ID, required = false) final Long examId,
final HttpServletResponse response) throws IOException {
checkReadPrivilege(institutionId);
this.entityDAO.byModelId(modelId)
.flatMap(this.authorization::checkWrite)
.map(this.userActivityLogDAO::logExport);

View file

@ -119,7 +119,13 @@ public class ModelObjectJSONGenerator {
VDIType.NO, null, null, null,
true, "fallbackStartURL", 20000L, (short) 3, (short) 1000, "fallbackPassword",
"fallbackPasswordConfirm",
"quitPassword", "quitPasswordConfirm", DateTime.now(), "encryptSecret", "encryptSecretConfirm", true);
"quitPassword",
"quitPasswordConfirm",
DateTime.now(),
"encryptSecret",
"encryptSecretConfirm",
"certAlias",
true);
System.out.println(domainObject.getClass().getSimpleName() + ":");
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));

View file

@ -21,16 +21,16 @@ public class CryptorTest {
public void testEncryptDecrypt() {
final String clientName = "simpleClientName";
String encrypted =
Cryptor.encrypt(clientName, "secret1").toString();
String decrypted = Cryptor.decrypt(encrypted, "secret1").toString();
Cryptor.encrypt(clientName, "secret1").getOrThrow().toString();
String decrypted = Cryptor.decrypt(encrypted, "secret1").getOrThrow().toString();
assertEquals(clientName, decrypted);
final String clientSecret = "fbjreij39ru29305ruࣣàèLöäöäü65%(/%(ç87";
encrypted =
Cryptor.encrypt(clientSecret, "secret1").toString();
decrypted = Cryptor.decrypt(encrypted, "secret1").toString();
Cryptor.encrypt(clientSecret, "secret1").getOrThrow().toString();
decrypted = Cryptor.decrypt(encrypted, "secret1").getOrThrow().toString();
assertEquals(clientSecret, decrypted);
}
@ -45,16 +45,16 @@ public class CryptorTest {
final String clientName = "simpleClientName";
String encrypted =
cryptor.encrypt(clientName).toString();
String decrypted = cryptor.decrypt(encrypted).toString();
cryptor.encrypt(clientName).getOrThrow().toString();
String decrypted = cryptor.decrypt(encrypted).getOrThrow().toString();
assertEquals(clientName, decrypted);
final String clientSecret = "fbjreij39ru29305ruࣣàèLöäöäü65%(/%(ç87";
encrypted =
cryptor.encrypt(clientSecret).toString();
decrypted = cryptor.decrypt(encrypted).toString();
cryptor.encrypt(clientSecret).getOrThrow().toString();
decrypted = cryptor.decrypt(encrypted).getOrThrow().toString();
assertEquals(clientSecret, decrypted);
}

View file

@ -124,6 +124,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
null,
"password",
null,
"certAlias",
null))
.call();
@ -153,6 +154,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
null,
"password",
"password",
"certAlias",
null))
.call()
.getOrThrow();

View file

@ -2124,7 +2124,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
try {
new SEBClientBot(
credentials.clientIdAsString(),
this.cryptor.decrypt(credentials.secret).toString(),
this.cryptor.decrypt(credentials.secret).getOrThrow().toString(),
exam.getModelId(),
String.valueOf(exam.institutionId));
Thread.sleep(1000);

View file

@ -65,7 +65,9 @@ public class PasswordEncryptorTest {
@Test
public void test2() throws IOException {
final JNCryptor cryptor = new AES256JNCryptor();
final PasswordEncryptor encryptor = new PasswordEncryptor(cryptor);
final PasswordCryptor encryptor = new PasswordCryptor(
new PasswordEncryptor(),
new PasswordDecryptor(cryptor));
final String config = "<TestConfig></TestConfig>";
final String pwd = "password";

View file

@ -99,7 +99,9 @@ public class SebConfigEncryptionServiceImplTest {
private SEBConfigEncryptionServiceImpl sebConfigEncryptionServiceImpl() {
final JNCryptor cryptor = new AES256JNCryptor();
final List<SEBConfigCryptor> encryptors = Arrays.asList(
new PasswordEncryptor(cryptor),
new PasswordCryptor(
new PasswordEncryptor(),
new PasswordDecryptor(cryptor)),
new NoneEncryptor());
return new SEBConfigEncryptionServiceImpl(encryptors);
}

View file

@ -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(