SEBSERV-135 try to encrypt config but still fails

This commit is contained in:
anhefti 2021-05-06 17:10:42 +02:00
parent 6bf1551028
commit c222f4216e
11 changed files with 190 additions and 106 deletions

View file

@ -29,6 +29,7 @@ public class CertificateInfo implements Entity {
UNKNOWN,
DIGITAL_SIGNATURE,
DATA_ENCIPHERMENT,
DATA_ENCIPHERMENT_PRIVATE_KEY,
KEY_CERT_SIGN
}

View file

@ -104,6 +104,8 @@ public class SEBClientConfigForm implements TemplateComposer {
private static final LocTextKey QUIT_PASSWORD_CONFIRM_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.hashedQuitPassword.confirm");
private static final LocTextKey FORM_ENCRYPT_CERT_KEY =
new LocTextKey("sebserver.clientconfig.form.certificate");
private static final LocTextKey FORM_ENCRYPT_SECRET_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.encryptSecret");
private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
@ -283,6 +285,14 @@ public class SEBClientConfigForm implements TemplateComposer {
.mandatory(!isReadonly))
.withDefaultSpanEmptyCell(3)
.withDefaultSpanInput(3)
.addField(FormBuilder.singleSelection(
SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ALIAS,
FORM_ENCRYPT_CERT_KEY,
clientConfig.encryptCertificateAlias,
() -> this.pageService.getResourceService().identityCertificatesResources()))
.withDefaultSpanEmptyCell(3)
.withDefaultSpanInput(3)
.addField(FormBuilder.password(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET,

View file

@ -138,11 +138,14 @@ public final class SelectionFieldBuilder extends FieldBuilder<String> {
gridData.horizontalIndent = 0;
label.setLayoutData(gridData);
final Supplier<String> valueSupplier = () -> this.itemsSupplier.get().stream()
.filter(tuple -> valueKey.equals(tuple._1))
.findFirst()
.map(tuple -> tuple._2)
.orElse(Constants.EMPTY_NOTE);
final Supplier<String> valueSupplier = () -> (StringUtils.isBlank(valueKey))
? Constants.EMPTY_NOTE
: this.itemsSupplier.get()
.stream()
.filter(tuple -> valueKey.equals(tuple._1))
.findFirst()
.map(tuple -> tuple._2)
.orElse(Constants.EMPTY_NOTE);
final Consumer<Text> updateFunction = t -> t.setText(valueSupplier.get());
label.setText(valueSupplier.get());

View file

@ -20,9 +20,12 @@ import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
@ -43,6 +46,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction.WhiteListPath;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.CertificateInfo;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
@ -72,6 +76,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamNames
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExams;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.cert.GetCertificateNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNodeNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNodes;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetViews;
@ -85,6 +90,8 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
* combo-box content. */
public class ResourceService {
private static final Logger log = LoggerFactory.getLogger(ResourceService.class);
private static final String MISSING_CLIENT_PING_NAME_KEY = "MISSING";
public static final Comparator<Tuple<String>> RESOURCE_COMPARATOR = Comparator.comparing(t -> t._2);
@ -732,4 +739,19 @@ public class ResourceService {
.collect(Collectors.toList());
}
public List<Tuple<String>> identityCertificatesResources() {
return Stream.concat(
Stream.of(new EntityName("", EntityType.CERTIFICATE, "")),
this.restService.getBuilder(GetCertificateNames.class)
.withQueryParam(
CertificateInfo.FILTER_ATTR_TYPE,
String.valueOf(CertificateInfo.CertificateType.DATA_ENCIPHERMENT_PRIVATE_KEY))
.call()
.onError(error -> log.warn("Failed to get identity certificate names: {}", error.getMessage()))
.getOr(Collections.emptyList())
.stream())
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
.collect(Collectors.toList());
}
}

View file

@ -10,23 +10,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;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.joda.time.DateTime;
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.CertificateType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Certificates;
import ch.ethz.seb.sebserver.gbl.util.Result;
@ -52,79 +40,10 @@ public interface CertificateDAO {
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);
if (certificate != null) {
Result<CertificateInfo> getDataFromCertificate(Certificates certificates, String alias);
final X509Certificate cert = certificate;
return new CertificateInfo(
extractAlias(cert, alias),
new DateTime(cert.getNotBefore()),
new DateTime(cert.getNotAfter()),
getTypes(cert));
Result<Collection<String>> getIdentityAlias(Long institutionId);
} else {
throw new NoSuchElementException("X509Certificate with alias: " + alias);
}
});
}
static String extractAlias(final X509Certificate certificate, final String alias) {
if (StringUtils.isNotBlank(alias)) {
return alias;
}
try {
final X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject();
final RDN cn = x500name.getRDNs(BCStyle.CN)[0];
final String dn = IETFUtils.valueToString(cn.getFirst().getValue());
if (StringUtils.isBlank(dn)) {
return String.valueOf(certificate.getSerialNumber());
} else {
return dn.replace(" ", "_").toLowerCase();
}
} catch (final CertificateEncodingException e) {
return String.valueOf(certificate.getSerialNumber());
}
}
static EnumSet<CertificateType> getTypes(final X509Certificate cert) {
// KeyUsage ::= BIT STRING {
// digitalSignature (0),
// nonRepudiation (1),
// keyEncipherment (2),
// dataEncipherment (3),
// keyAgreement (4),
// keyCertSign (5),
// cRLSign (6),
// encipherOnly (7),
// decipherOnly (8) }
final boolean[] keyUsage = cert.getKeyUsage();
final EnumSet<CertificateType> result = EnumSet.noneOf(CertificateType.class);
// digitalSignature
if (keyUsage[0]) {
result.add(CertificateType.DIGITAL_SIGNATURE);
}
// dataEncipherment
if (keyUsage[3]) {
result.add(CertificateType.DATA_ENCIPHERMENT);
}
// keyCertSign
if (keyUsage[5]) {
result.add(CertificateType.KEY_CERT_SIGN);
}
if (result.isEmpty()) {
result.add(CertificateType.UNKNOWN);
}
return result;
}
String extractAlias(X509Certificate a, String alias);
}

View file

@ -12,17 +12,27 @@ import java.io.ByteArrayInputStream;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi;
import org.joda.time.DateTime;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -35,6 +45,7 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
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.CertificateType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Certificates;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
@ -109,7 +120,7 @@ public class CertificateDAOImpl implements CertificateDAO {
return getCertificatesFromPersistent(institutionId)
.flatMap(record -> addCertificate(record, alias, certificate, privateKey))
.flatMap(this::storeUpdate)
.flatMap(certs -> CertificateDAO.getDataFromCertificate(certs, alias))
.flatMap(certs -> getDataFromCertificate(certs, alias))
.onError(TransactionHandler::rollback);
}
@ -136,6 +147,102 @@ public class CertificateDAOImpl implements CertificateDAO {
.collect(Collectors.toList()));
}
@Override
public Result<CertificateInfo> getDataFromCertificate(final Certificates certificates, final String alias) {
return Result.tryCatch(() -> {
final X509Certificate certificate = (X509Certificate) certificates.keyStore.engineGetCertificate(alias);
if (certificate != null) {
final X509Certificate cert = certificate;
return new CertificateInfo(
extractAlias(cert, alias),
new DateTime(cert.getNotBefore()),
new DateTime(cert.getNotAfter()),
getTypes(certificates, cert));
} else {
throw new NoSuchElementException("X509Certificate with alias: " + alias);
}
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<String>> getIdentityAlias(final Long institutionId) {
return getCertificates(institutionId)
.map(certs -> certs.aliases
.stream()
.filter(alias -> this.cryptor.getPrivateKey(certs.keyStore, alias).hasValue())
.collect(Collectors.toList()));
}
@Override
public String extractAlias(final X509Certificate certificate, final String alias) {
if (StringUtils.isNotBlank(alias)) {
return alias;
}
try {
final X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject();
final RDN cn = x500name.getRDNs(BCStyle.CN)[0];
final String dn = IETFUtils.valueToString(cn.getFirst().getValue());
if (StringUtils.isBlank(dn)) {
return String.valueOf(certificate.getSerialNumber());
} else {
return dn.replace(" ", "_").toLowerCase();
}
} catch (final CertificateEncodingException e) {
log.warn("Error while trying to get alias form certificate subject name. Use serial number as alias");
return String.valueOf(certificate.getSerialNumber());
}
}
private EnumSet<CertificateType> getTypes(
final Certificates certificates,
final X509Certificate cert) {
// KeyUsage ::= BIT STRING {
// digitalSignature (0),
// nonRepudiation (1),
// keyEncipherment (2),
// dataEncipherment (3),
// keyAgreement (4),
// keyCertSign (5),
// cRLSign (6),
// encipherOnly (7),
// decipherOnly (8) }
final boolean[] keyUsage = cert.getKeyUsage();
final EnumSet<CertificateType> result = EnumSet.noneOf(CertificateType.class);
// digitalSignature
if (keyUsage[0]) {
result.add(CertificateType.DIGITAL_SIGNATURE);
}
// dataEncipherment
if (keyUsage[3]) {
final String alias = certificates.keyStore.engineGetCertificateAlias(cert);
if (this.cryptor.getPrivateKey(certificates.keyStore, alias).hasValue()) {
result.add(CertificateType.DATA_ENCIPHERMENT_PRIVATE_KEY);
} else {
result.add(CertificateType.DATA_ENCIPHERMENT);
}
}
// keyCertSign
if (keyUsage[5]) {
result.add(CertificateType.KEY_CERT_SIGN);
}
if (result.isEmpty()) {
result.add(CertificateType.UNKNOWN);
}
return result;
}
private Certificates createNewCertificateStore(final Long institutionId) {
return this.cryptor.createNewEmptyKeyStore()
.map(store -> new CertificateRecord(

View file

@ -53,7 +53,7 @@ public class CertificateServiceImpl implements CertificateService {
return this.certificateDAO
.getCertificates(institutionId)
.flatMap(certs -> CertificateDAO.getDataFromCertificate(certs, alias));
.flatMap(certs -> this.certificateDAO.getDataFromCertificate(certs, alias));
}
@Override
@ -86,14 +86,14 @@ public class CertificateServiceImpl implements CertificateService {
return loadCertFromPEM(in)
.flatMap(cert -> this.certificateDAO.addCertificate(
institutionId,
CertificateDAO.extractAlias(cert, alias),
this.certificateDAO.extractAlias(cert, alias),
cert));
case PKCS12:
return loadCertFromPKC(in, password)
.flatMap(pair -> this.certificateDAO.addCertificate(
institutionId,
CertificateDAO.extractAlias(pair.a, alias),
this.certificateDAO.extractAlias(pair.a, alias),
pair.a,
pair.b));
default:
@ -133,7 +133,7 @@ public class CertificateServiceImpl implements CertificateService {
return certificates.aliases
.stream()
.map(alias -> CertificateDAO.getDataFromCertificate(certificates, alias))
.map(alias -> this.certificateDAO.getDataFromCertificate(certificates, alias))
.flatMap(Result::onErrorLogAndSkip)
.filter(predicate)
.collect(Collectors.toList());

View file

@ -12,15 +12,20 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -44,6 +49,7 @@ public class CertificateSymetricKeyCryptor implements SEBConfigCryptor {
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_BITS = 256;
private static final int ENCRYPTION_KEY_LENGTH = 32;
private static final int KEY_LENGTH_SIZE = 4;
@ -82,6 +88,7 @@ public class CertificateSymetricKeyCryptor implements SEBConfigCryptor {
final byte[] generateParameter = generateParameter(certificate, publicKeyHash, symetricKey);
output.write(generateParameter, 0, generateParameter.length);
this.passwordEncryptor.encrypt(output, input, symetricKeyBase64);
} catch (final Exception e) {
@ -106,17 +113,27 @@ public class CertificateSymetricKeyCryptor implements SEBConfigCryptor {
}
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[] generatePublicKeyHash(final Certificate cert)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] publicKey = cert.getPublicKey().getEncoded();
final PublicKey publicKey2 = cert.getPublicKey();
final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
final PublicKey _publicKey = keyFactory.generatePublic(keySpec);
publicKey = _publicKey.getEncoded();
final String algorithm = publicKey2.getAlgorithm();
final String encodeHex = String.valueOf(Hex.encodeHex(publicKey));
final String sha1Hex = DigestUtils.sha1Hex(publicKey);
final String sha256Hex = DigestUtils.sha256Hex(publicKey);
return DigestUtils.sha1(publicKey);
}
private byte[] generateSymetricKey() throws NoSuchAlgorithmException {
final KeyGenerator keyGenerator = KeyGenerator.getInstance(Constants.AES);
keyGenerator.init(ENCRYPTION_KEY_LENGTH);
return keyGenerator.generateKey().getEncoded();
keyGenerator.init(ENCRYPTION_KEY_BITS);
final byte[] encoded = keyGenerator.generateKey().getEncoded();
return encoded;
}
@SuppressWarnings("resource")
@ -135,8 +152,9 @@ public class CertificateSymetricKeyCryptor implements SEBConfigCryptor {
data.write(publicKeyHash, 0, publicKeyHash.length);
data.write(encryptedKeyLength, 0, encryptedKeyLength.length);
data.write(encryptedKey, 0, encryptedKey.length);
final byte[] byteArray = data.toByteArray();
return data.toByteArray();
return byteArray;
}
private byte[] generateEncryptedKey(final Certificate cert, final byte[] symetricKey) throws Exception {

View file

@ -523,7 +523,7 @@ public class ClientConfigServiceImpl implements ClientConfigService {
this.sebConfigEncryptionService.streamEncrypted(
out,
(withPasswordEncryption) ? in : passEncryptionIn,
(withPasswordEncryption) ? passEncryptionIn : in,
buildCertificateEncryptionContext(config));
}

View file

@ -63,7 +63,7 @@ public class PasswordEncryptor {
log.error("Error while trying to read/write form/to streams: ", e);
} finally {
try {
input.close();
IOUtils.closeQuietly(input);
if (encryptOutput != null) {
encryptOutput.flush();
encryptOutput.close();

View file

@ -729,6 +729,9 @@ sebserver.clientconfig.form.encryptSecret.confirm=Confirm Password
sebserver.clientconfig.form.encryptSecret.confirm.tooltip=Please retype the given password for confirmation
sebserver.clientconfig.form.sebConfigPurpose=Configuration Purpose
sebserver.clientconfig.form.sebConfigPurpose.tooltip=This indicates whether this connection configuration shall be used to configure the SEB Client or to start an exam
sebserver.clientconfig.form.certificate=Encrypt with Certificate
sebserver.clientconfig.form.certificate.tooltip=Choose identity certificate to be used for encrypting the connection configuration
sebserver.clientconfig.config.purpose.START_EXAM=Starting an Exam
sebserver.clientconfig.config.purpose.START_EXAM.tooltip=If the connection configuration is loaded via a SEB-Link, the local configuration will not be overwritten.
@ -1547,7 +1550,8 @@ sebserver.certificate.list.column.validTo=Valid To
sebserver.certificate.list.column.type=Types
sebserver.certificate.list.column.type.UNKNOWN=Unknown
sebserver.certificate.list.column.type.DIGITAL_SIGNATURE=TLS/SSL
sebserver.certificate.list.column.type.DATA_ENCIPHERMENT=Identity
sebserver.certificate.list.column.type.DATA_ENCIPHERMENT=Encipherment
sebserver.certificate.list.column.type.DATA_ENCIPHERMENT_PRIVATE_KEY=Identity
sebserver.certificate.list.column.type.KEY_CERT_SIGN=CA
sebserver.certificate.form.alias=Alias