From 9ff3d1cf4dcfe33a4f921cd8e8c84e41ea30717f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Tue, 18 Feb 2020 14:46:45 +0100 Subject: [PATCH 01/26] Initial commit for Certificate Authority --- certificate-authority/pom.xml | 153 ++++++++++++++ .../arrowhead-certificate-authority.service | 15 ++ certificate-authority/src/deb/control/config | 20 ++ certificate-authority/src/deb/control/control | 10 + .../src/deb/control/postinst | 94 +++++++++ certificate-authority/src/deb/control/postrm | 56 ++++++ certificate-authority/src/deb/control/preinst | 35 ++++ certificate-authority/src/deb/control/prerm | 45 +++++ ...icateAuthorityApplicationInitListener.java | 69 +++++++ .../CertificateAuthorityController.java | 83 ++++++++ .../CertificateAuthorityMain.java | 28 +++ .../CertificateAuthorityService.java | 187 ++++++++++++++++++ .../security/CAAccessControlFilter.java | 41 ++++ .../security/CASecurityConfig.java | 12 ++ .../swagger/CASwaggerConfig.java | 28 +++ ...itional-spring-configuration-metadata.json | 47 +++++ .../src/main/resources/application.properties | 59 ++++++ .../certificates/certificate_authority.p12 | Bin 0 -> 7629 bytes .../resources/certificates/truststore.p12 | Bin 0 -> 1162 bytes .../src/main/resources/log4j2.xml | 46 +++++ .../main/resources/swagger/swagger-ui.html | 75 +++++++ .../common/ApplicationInitListener.java | 9 +- .../eu/arrowhead/common/CommonConstants.java | 10 +- .../java/eu/arrowhead/common/Utilities.java | 44 ++++- .../CertificateSigningRequestDTO.java | 24 +++ .../CertificateSigningResponseDTO.java | 24 +++ pom.xml | 4 +- scripts/certificate_authority_privileges.sql | 43 ++++ scripts/create_empty_arrowhead_db.sql | 7 +- scripts/start_coresystems_local.bat | 4 + scripts/stop_coresystems.bat | 2 + 31 files changed, 1266 insertions(+), 8 deletions(-) create mode 100644 certificate-authority/pom.xml create mode 100644 certificate-authority/src/deb/arrowhead-certificate-authority.service create mode 100644 certificate-authority/src/deb/control/config create mode 100644 certificate-authority/src/deb/control/control create mode 100644 certificate-authority/src/deb/control/postinst create mode 100644 certificate-authority/src/deb/control/postrm create mode 100644 certificate-authority/src/deb/control/preinst create mode 100644 certificate-authority/src/deb/control/prerm create mode 100644 certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java create mode 100644 certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java create mode 100644 certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityMain.java create mode 100644 certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java create mode 100644 certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java create mode 100644 certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CASecurityConfig.java create mode 100644 certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/swagger/CASwaggerConfig.java create mode 100644 certificate-authority/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 certificate-authority/src/main/resources/application.properties create mode 100644 certificate-authority/src/main/resources/certificates/certificate_authority.p12 create mode 100644 certificate-authority/src/main/resources/certificates/truststore.p12 create mode 100644 certificate-authority/src/main/resources/log4j2.xml create mode 100644 certificate-authority/src/main/resources/swagger/swagger-ui.html create mode 100644 core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java create mode 100644 core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningResponseDTO.java create mode 100644 scripts/certificate_authority_privileges.sql diff --git a/certificate-authority/pom.xml b/certificate-authority/pom.xml new file mode 100644 index 000000000..257563a28 --- /dev/null +++ b/certificate-authority/pom.xml @@ -0,0 +1,153 @@ + + 4.0.0 + + eu.arrowhead + core + ${revision} + + + arrowhead-certificate-authority + jar + Arrowhead Certificate Authority + Certificate Authority core service + + + + eu.arrowhead + arrowhead-core-common + ${revision} + + + + org.bouncycastle + bcprov-jdk15on + ${bouncy.castle.version} + + + + org.bouncycastle + bcpkix-jdk15on + ${bouncy.castle.version} + + + org.jetbrains + annotations + 18.0.0 + compile + + + + + + + maven-resources-plugin + ${mvn.resources.version} + + + copy-resources + + validate + + copy-resources + + + ${basedir}/target + + + src/main/resources + + application.properties + log4j2.xml + + + + + + + + + + jdeb + org.vafer + 1.8 + + + package + + jdeb + + + true + [YYMMddHHmm].${git.commit.id.abbrev} + target/${project.artifactId}_${revision}.deb + true + ${basedir}/src/deb/control + + + file + ${project.build.directory}/${project.build.finalName}.jar + + perm + /usr/share/arrowhead/certificate_authority + 755 + + + + + link + /usr/share/arrowhead/certificate_authority/${project.artifactId}.jar + /usr/share/arrowhead/certificate_authority/${project.build.finalName}.jar + + + + file + ${project.build.directory}/log4j2.xml + + perm + /etc/arrowhead/systems/certificate_authority + 644 + + + + + file + ${project.build.directory}/application.properties + + perm + /etc/arrowhead/systems/certificate_authority + 644 + + + + + link + /usr/share/arrowhead/certificate_authority/application.properties + /etc/arrowhead/systems/certificate_authority/application.properties + + + + file + ${project.basedir}/src/deb/arrowhead-certificate-authority.service + + perm + /etc/systemd/system + 644 + + + + + file + ${project.basedir}/../scripts/certificate_authority_privileges.sql + + perm + /usr/share/arrowhead/conf + + + + + + + + + + diff --git a/certificate-authority/src/deb/arrowhead-certificate-authority.service b/certificate-authority/src/deb/arrowhead-certificate-authority.service new file mode 100644 index 000000000..31d62af19 --- /dev/null +++ b/certificate-authority/src/deb/arrowhead-certificate-authority.service @@ -0,0 +1,15 @@ +[Unit] +Description=arrowhead-certificate-authority +After=network.target mysql.target + +[Service] +WorkingDirectory=/etc/arrowhead/systems/certificate_authority +ExecStart=/usr/bin/java -Dlog4j.configurationFile=file:/etc/arrowhead/systems/certificate_authority/log4j2.xml -jar /usr/share/arrowhead/certificate_authority/arrowhead-certificate-authority.jar +ExecStartPost=/bin/bash -c 'sleep 10' +TimeoutStopSec=5 +Type=simple +User=arrowhead +Group=arrowhead + +[Install] +WantedBy=default.target diff --git a/certificate-authority/src/deb/control/config b/certificate-authority/src/deb/control/config new file mode 100644 index 000000000..1c83ed2f4 --- /dev/null +++ b/certificate-authority/src/deb/control/config @@ -0,0 +1,20 @@ +#!/bin/sh -e + +. /usr/share/debconf/confmodule + +if logname 2>/dev/null; then + USER=$(logname) + HOME=$(eval echo "~$USER") +else + USER="root" + HOME="/root" +fi + +# Get installation type +db_input high arrowhead-core-common/db_host || true +db_input high arrowhead-core-common/mysql_password_ca || true +db_input high arrowhead-core-common/sr_host || true + +# Run dialogs +db_go || true + diff --git a/certificate-authority/src/deb/control/control b/certificate-authority/src/deb/control/control new file mode 100644 index 000000000..1b7ccbf25 --- /dev/null +++ b/certificate-authority/src/deb/control/control @@ -0,0 +1,10 @@ +Package: arrowhead-certificate-authority +Version: [[version]] +Section: contrib/java +Priority: optional +Architecture: all +Maintainer: Rajmund Bocsi +Homepage: http://www.arrowhead.eu +Description: Arrowhead Certificate Authority +Distribution: development +Depends: java-runtime-headless, openssl, virtual-mysql-server, arrowhead-core-common diff --git a/certificate-authority/src/deb/control/postinst b/certificate-authority/src/deb/control/postinst new file mode 100644 index 000000000..79e93a8f3 --- /dev/null +++ b/certificate-authority/src/deb/control/postinst @@ -0,0 +1,94 @@ +#!/bin/sh +# postinst script for arrowhead-certificate-authority +# +# see: dh_installdeb(1) + +set -e + +. /usr/share/debconf/confmodule + +SYSTEM_NAME="certificate_authority" +PKG_NAME="arrowhead-certificate-authority" + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + configure) + . /usr/share/arrowhead/conf/ahconf.sh + SYSTEM_DIR="${AH_SYSTEMS_DIR}/${SYSTEM_NAME}" + + db_get arrowhead-core-common/mysql_password_ca; system_passwd=$RET || true + db_unregister arrowhead-core-common/mysql_password_ca + + # Generate password (if required) + if [ -z "${system_passwd}" ]; then + system_passwd="$(openssl rand -base64 12)" + fi + + echo "Configuring MySQL database..." >&2 + ah_db_tables_and_user ${SYSTEM_NAME} ${SYSTEM_NAME}_privileges.sql ${system_passwd} + + if [ ! -d "${SYSTEM_DIR}" ]; then + mkdir -p ${SYSTEM_DIR} + fi + + ah_cert_signed_system ${SYSTEM_NAME} + + if [ ! -d "${AH_SYSTEMS_DIR}/sysop" ]; then + mkdir -p ${AH_SYSTEMS_DIR}/sysop + fi + + ah_cert_signed_system sysop + + # modify log config and application.properties + + ah_transform_log_file ${SYSTEM_DIR} + + db_get arrowhead-core-common/db_host; db_host=$RET || true + + mv ${SYSTEM_DIR}/application.properties ${SYSTEM_DIR}/application.properties.orig + cat ${SYSTEM_DIR}/application.properties.orig | \ + sed -r -e "\#^.*spring\.datasource\.url=#s#.*\$#spring.datasource.url=jdbc:mysql://"${db_host}":3306/arrowhead?serverTimezone=Europe/Budapest#" \ + -e "\#^.*spring\.datasource\.password=#s#.*\$#spring.datasource.password=${system_passwd}#" \ + -e "\#^.*server\.address=#s#.*\$#server.address=${OWN_IP}#" \ + -e "\#^.*server\.ssl\.key-store=#s#.*\$#server.ssl.key-store=file:${SYSTEM_DIR}/${SYSTEM_NAME}.p12#" \ + -e "\#^.*server\.ssl\.key-store-password=#s#.*\$#server.ssl.key-store-password=${AH_PASS_CERT}#" \ + -e "\#^.*server\.ssl\.key-password=#s#.*\$#server.ssl.key-password=${AH_PASS_CERT}#" \ + -e "\#^.*server\.ssl\.trust-store=#s#.*\$#server.ssl.trust-store=file:${AH_CONF_DIR}/truststore.p12#" \ + -e "\#^.*server\.ssl\.trust-store-password=#s#.*\$#server.ssl.trust-store-password=${AH_PASS_CERT}#" > ${SYSTEM_DIR}/application.properties + + rm ${SYSTEM_DIR}/application.properties.orig + chown arrowhead:arrowhead ${SYSTEM_DIR}/application.properties + chmod 640 ${SYSTEM_DIR}/application.properties + + echo "Starting ${PKG_NAME}..." >&2 + systemctl daemon-reload + systemctl enable ${PKG_NAME}.service + systemctl start ${PKG_NAME}.service + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/certificate-authority/src/deb/control/postrm b/certificate-authority/src/deb/control/postrm new file mode 100644 index 000000000..1af2f066c --- /dev/null +++ b/certificate-authority/src/deb/control/postrm @@ -0,0 +1,56 @@ +#!/bin/sh +# postrm script for arrowhead-certificate-authority +# +# see: dh_installdeb(1) + +set -e + +. /usr/share/debconf/confmodule + +SYSTEM_NAME="certificate_authority" + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + purge) + AH_CONF_DIR="/etc/arrowhead" + AH_SYSTEMS_DIR="${AH_CONF_DIR}/systems" + SYSTEM_DIR="${AH_SYSTEMS_DIR}/${SYSTEM_NAME}" + + rm -f \ + /var/log/arrowhead/${SYSTEM_NAME}*.log \ + ${SYSTEM_DIR}/log4j2.xml \ + ${SYSTEM_DIR}/application.properties \ + ${SYSTEM_DIR}/${SYSTEM_NAME}.p12 \ + ${AH_SYSTEMS_DIR}/sysop/sysop.p12 + rmdir ${SYSTEM_DIR} 2>/dev/null || true + rmdir ${AH_SYSTEMS_DIR}/sysop 2>/dev/null || true + db_purge + ;; + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/certificate-authority/src/deb/control/preinst b/certificate-authority/src/deb/control/preinst new file mode 100644 index 000000000..a0a30f7d9 --- /dev/null +++ b/certificate-authority/src/deb/control/preinst @@ -0,0 +1,35 @@ +#!/bin/sh +# preinst script for arrowhead-certificate-authority +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + install|upgrade) + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/certificate-authority/src/deb/control/prerm b/certificate-authority/src/deb/control/prerm new file mode 100644 index 000000000..4c2d565a7 --- /dev/null +++ b/certificate-authority/src/deb/control/prerm @@ -0,0 +1,45 @@ +#!/bin/sh +# prerm script for arrowhead-certificate-authority +# +# see: dh_installdeb(1) + +set -e + +PKG_NAME="arrowhead-certificate-authority" + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + remove) + systemctl stop ${PKG_NAME}.service + systemctl disable ${PKG_NAME}.service + ;; + + upgrade|deconfigure) + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java new file mode 100644 index 000000000..576e90843 --- /dev/null +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java @@ -0,0 +1,69 @@ +package eu.arrowhead.core.certificate_authority; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +import eu.arrowhead.common.ApplicationInitListener; +import eu.arrowhead.common.CommonConstants; +import eu.arrowhead.common.CoreDefaults; +import eu.arrowhead.common.database.service.CommonDBService; +import eu.arrowhead.common.exception.DataNotFoundException; + +@Component +public class CertificateAuthorityApplicationInitListener extends ApplicationInitListener { + + //================================================================================================= + // members + + @Autowired + private CommonDBService commonDBService; + + //================================================================================================= + // assistant methods + + //------------------------------------------------------------------------------------------------- + @Override + protected void customInit(final ContextRefreshedEvent event) { + logger.debug("customInit started..."); + if (!isOwnCloudRegistered()) { + registerOwnCloud(event.getApplicationContext()); + } + } + + //------------------------------------------------------------------------------------------------- + private boolean isOwnCloudRegistered() { + logger.debug("isOwnCloudRegistered started..."); + try { + commonDBService.getOwnCloud(sslProperties.isSslEnabled()); + return true; + } catch (final DataNotFoundException ex) { + return false; + } + } + + //------------------------------------------------------------------------------------------------- + private void registerOwnCloud(final ApplicationContext appContext) { + logger.debug("registerOwnCloud started..."); + + if (!standaloneMode) { + String name = CoreDefaults.DEFAULT_OWN_CLOUD_NAME; + String operator = CoreDefaults.DEFAULT_OWN_CLOUD_OPERATOR; + + if (sslProperties.isSslEnabled()) { + @SuppressWarnings("unchecked") + final Map context = appContext.getBean(CommonConstants.ARROWHEAD_CONTEXT, Map.class); + final String serverCN = (String) context.get(CommonConstants.SERVER_COMMON_NAME); + final String[] serverFields = serverCN.split("\\."); + name = serverFields[1]; + operator = serverFields[2]; + } + + commonDBService.insertOwnCloud(operator, name, sslProperties.isSslEnabled(), null); + logger.info("{}.{} own cloud is registered in {} mode.", name, operator, getModeString()); + } + } +} \ No newline at end of file diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java new file mode 100644 index 000000000..c5d4c5787 --- /dev/null +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java @@ -0,0 +1,83 @@ +package eu.arrowhead.core.certificate_authority; + +import eu.arrowhead.common.CommonConstants; +import eu.arrowhead.common.CoreCommonConstants; +import eu.arrowhead.common.Defaults; +import eu.arrowhead.common.dto.internal.CertificateSigningRequestDTO; +import eu.arrowhead.common.dto.internal.CertificateSigningResponseDTO; +import eu.arrowhead.common.exception.BadPayloadException; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import org.apache.http.HttpStatus; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +@Api(tags = { CoreCommonConstants.SWAGGER_TAG_ALL }) +@CrossOrigin(maxAge = Defaults.CORS_MAX_AGE, allowCredentials = Defaults.CORS_ALLOW_CREDENTIALS, + allowedHeaders = { HttpHeaders.ORIGIN, HttpHeaders.CONTENT_TYPE, HttpHeaders.ACCEPT, HttpHeaders.AUTHORIZATION } +) +@RestController +@RequestMapping(CommonConstants.CERTIFICATE_AUTHRORITY_URI) +public class CertificateAuthorityController { + + //================================================================================================= + // members +// + private static final String GET_CLOUD_COMMON_NAME_HTTP_200_MESSAGE = "Cloud Common Name returned"; + private static final String SIGN_CERTIFICATE_HTTP_200_MESSAGE = "Successful certificate signing"; + private static final String SIGN_CERTIFICATE_HTTP_400_MESSAGE = "Invalid Certificate Signing Request"; + + private final Logger logger = LogManager.getLogger(CertificateAuthorityController.class); + + @Autowired + private CertificateAuthorityService certificateAuthorityService; + + //================================================================================================= + // methods + + //------------------------------------------------------------------------------------------------- + @ApiOperation(value = "Return an echo message with the purpose of testing the core service availability", response = String.class, tags = { CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = CoreCommonConstants.SWAGGER_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) + }) + @GetMapping(path = CommonConstants.ECHO_URI) + public String echoService() { + return "Got it!"; + } + + //------------------------------------------------------------------------------------------------- + @ApiOperation(value = "Return the cloud's Common Name", response = String.class, tags = { CoreCommonConstants.SWAGGER_TAG_CLIENT }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = GET_CLOUD_COMMON_NAME_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) + }) + @GetMapping(path = CommonConstants.OP_CA_CLOUD_COMMON_NAME_URI) + public String getCloudCommonName() { + logger.debug("getCloudCommonName started ..."); + + return certificateAuthorityService.getCloudCommonName(); + } + + //------------------------------------------------------------------------------------------------- + @ApiOperation(value = "Return a signed certificate", response = CertificateSigningResponseDTO.class, tags = { CoreCommonConstants.SWAGGER_TAG_CLIENT}) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.SC_OK, message = SIGN_CERTIFICATE_HTTP_200_MESSAGE), + @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = SIGN_CERTIFICATE_HTTP_400_MESSAGE), + @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = CoreCommonConstants.SWAGGER_HTTP_401_MESSAGE), + @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) + }) + @PostMapping(path = CommonConstants.OP_CA_SIGN_CERTIFICATE_URI, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody public CertificateSigningResponseDTO signCertificate(@RequestBody final CertificateSigningRequestDTO request) { + return certificateAuthorityService.signCertificate(request); + } +} diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityMain.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityMain.java new file mode 100644 index 000000000..a984109d5 --- /dev/null +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityMain.java @@ -0,0 +1,28 @@ +package eu.arrowhead.core.certificate_authority; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import eu.arrowhead.common.CommonConstants; +import eu.arrowhead.common.CoreCommonConstants; +import eu.arrowhead.common.database.repository.RefreshableRepositoryImpl; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@SpringBootApplication +@ComponentScan(CommonConstants.BASE_PACKAGE) +@EntityScan(CoreCommonConstants.DATABASE_ENTITY_PACKAGE) +@EnableJpaRepositories(basePackages = CoreCommonConstants.DATABASE_REPOSITORY_PACKAGE, repositoryBaseClass = RefreshableRepositoryImpl.class) +@EnableSwagger2 +public class CertificateAuthorityMain { + + //================================================================================================= + // methods + + //------------------------------------------------------------------------------------------------- + public static void main(final String[] args) { + SpringApplication.run(CertificateAuthorityMain.class, args); + } +} \ No newline at end of file diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java new file mode 100644 index 000000000..ed37a5036 --- /dev/null +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java @@ -0,0 +1,187 @@ +package eu.arrowhead.core.certificate_authority; + +import eu.arrowhead.common.CommonConstants; +import eu.arrowhead.common.SSLProperties; +import eu.arrowhead.common.Utilities; +import eu.arrowhead.common.dto.internal.CertificateSigningRequestDTO; +import eu.arrowhead.common.dto.internal.CertificateSigningResponseDTO; +import eu.arrowhead.common.exception.AuthException; +import eu.arrowhead.common.exception.BadPayloadException; +import eu.arrowhead.common.exception.DataNotFoundException; +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.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.cert.CertIOException; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentVerifierProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder; +import org.bouncycastle.pkcs.PKCSException; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.math.BigInteger; +import java.security.*; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Base64; +import java.util.Date; +import java.util.Map; +import java.util.ServiceConfigurationError; + +@Service +public class CertificateAuthorityService { + + @Autowired + private SSLProperties sslProperties; + + @Autowired + private ApplicationContext appContext; + + private SecureRandom random; + private KeyStore keyStore; + + @PostConstruct + private void init() { + random = new SecureRandom(); + random.reseed(); + keyStore = getKeyStore(); + } + + public String getCloudCommonName() { + try { + final X500Name issuer = new JcaX509CertificateHolder(getServerCertificate(appContext)).getIssuer(); + final RDN cn = issuer.getRDNs(BCStyle.CN)[0]; + return IETFUtils.valueToString(cn.getFirst().getValue()); + } catch (CertificateEncodingException e) { + throw new ServiceConfigurationError("Cannot get cloud common name from server cert.", e); + } + } + + public CertificateSigningResponseDTO signCertificate(CertificateSigningRequestDTO request) { + JcaPKCS10CertificationRequest csr = decodePKCS10CSR(request); + checkCommonName(csr, getCloudCommonName()); + checkCsrSignature(csr); + + final PrivateKey cloudPrivateKey = Utilities.getPrivateKey(keyStore, getCloudCommonName(), sslProperties.getKeyPassword()); + final X509Certificate serverCertificate = getServerCertificate(appContext); + final X509Certificate clientCertificate = buildCertificate(csr.getSubject(), getClientKey(csr), cloudPrivateKey, serverCertificate); + + final String encodedClientCertificate = encodeCertificate(clientCertificate); + final String encodedServerCertificate = encodeCertificate(serverCertificate); + + return new CertificateSigningResponseDTO(encodedClientCertificate + " " + encodedServerCertificate); + } + + private KeyStore getKeyStore() { + try { + final KeyStore keystore = KeyStore.getInstance(sslProperties.getKeyStoreType()); + keystore.load(sslProperties.getKeyStore().getInputStream(), sslProperties.getKeyStorePassword().toCharArray()); + return keystore; + } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) { + throw new ServiceConfigurationError("Cannot open keystore: " + e.getMessage()); + } + } + + private JcaPKCS10CertificationRequest decodePKCS10CSR(CertificateSigningRequestDTO csr) { + try { + final byte[] csrBytes = Base64.getDecoder().decode(csr.getCertificatePem()); + return new JcaPKCS10CertificationRequest(csrBytes); + } catch (IOException ex) { + throw new BadPayloadException("Failed to parse request as a PKCS10 CSR", ex); + } + } + + private String encodeCertificate(X509Certificate certificate) { + try { + return Base64.getEncoder().encodeToString(certificate.getEncoded()); + } catch (CertificateEncodingException e) { + throw new AuthException("Certificate encoding failed! (" + e.getMessage() + ")", e); + } + } + + private void checkCommonName(JcaPKCS10CertificationRequest csr, String cloudCN) { + final String clientCN = Utilities.getCertCNFromSubject(csr.getSubject().toString()); + if (!Utilities.isKeyStoreCNArrowheadValid(clientCN, cloudCN)) { + throw new BadPayloadException("Certificate request does not have a valid common name! Valid common name: {systemName}." + cloudCN); + } + } + + private void checkCsrSignature(JcaPKCS10CertificationRequest csr) { + Security.addProvider(new BouncyCastleProvider()); + try { + ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().setProvider("BC").build(csr.getSubjectPublicKeyInfo()); + if (!csr.isSignatureValid(verifierProvider)) { + throw new AuthException("Certificate request has invalid signature! (key pair does not match)"); + } + } catch (OperatorCreationException | PKCSException e) { + throw new AuthException("Encapsulated " + e.getClass().getCanonicalName() + ": " + e.getMessage(), e); + } + } + + private PublicKey getClientKey(JcaPKCS10CertificationRequest csr) { + try { + return csr.getPublicKey(); + } catch (InvalidKeyException | NoSuchAlgorithmException e) { + //This should not be possible after a successful signature verification + throw new DataNotFoundException("Extracting the public key from the CSR failed (" + e.getMessage() + ")", e); + } + } + + private X509Certificate getServerCertificate(final ApplicationContext appContext) { + @SuppressWarnings("unchecked") final Map context = appContext.getBean(CommonConstants.ARROWHEAD_CONTEXT, Map.class); + return (X509Certificate) context.get(CommonConstants.SERVER_CERTIFICATE); + } + + private X509Certificate buildCertificate(X500Name csrSubject, PublicKey clientKey, PrivateKey cloudPrivateKey, X509Certificate serverCertificate) { + final ZonedDateTime now = LocalDateTime.now().atZone(ZoneId.systemDefault()); + final Date validFrom = Date.from(now.minusSeconds(5).toInstant()); + final Date validUntil = Date.from(now.plusYears(1).toInstant()); + + final BigInteger serial = BigInteger.valueOf(now.toInstant().toEpochMilli()).multiply(BigInteger.valueOf(random.nextLong())); + + X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(serverCertificate, serial, validFrom, validUntil, csrSubject, clientKey); + + /* Adding the following extensions to the new certificate: + 1) The subject key identifier provides a hashed value that should uniquely identify the public key + 2) The authority key identifier provides a hashed value that should uniquely identify the issuer of the certificate + 3) And this basic constraint is for forbidding issuing other certificates under this certificate + */ + try { + JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); + builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(clientKey)); + builder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(serverCertificate)); + builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); + } catch (NoSuchAlgorithmException | CertIOException | CertificateEncodingException e) { + throw new AuthException("Appending extensions to the certificate failed! (" + e.getMessage() + ")", e); + } + + + JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA512withRSA").setProvider("BC"); + try { + return new JcaX509CertificateConverter().setProvider("BC") + .getCertificate(builder.build(signerBuilder.build(cloudPrivateKey))); + } catch (CertificateException e) { + throw new AuthException("Certificate encoding failed! (" + e.getMessage() + ")", e); + } catch (OperatorCreationException e) { + throw new AuthException("Certificate signing failed! (" + e.getMessage() + ")", e); + } + } +} diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java new file mode 100644 index 000000000..5a147e1dc --- /dev/null +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java @@ -0,0 +1,41 @@ +package eu.arrowhead.core.certificate_authority.security; + +import java.util.Map; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import eu.arrowhead.common.CommonConstants; +import eu.arrowhead.common.CoreCommonConstants; +import eu.arrowhead.common.Utilities; +import eu.arrowhead.common.core.CoreSystem; +import eu.arrowhead.common.core.CoreSystemService; +import eu.arrowhead.common.dto.shared.ServiceQueryFormDTO; +import eu.arrowhead.common.dto.shared.ServiceRegistryRequestDTO; +import eu.arrowhead.common.exception.AuthException; +import eu.arrowhead.common.security.CoreSystemAccessControlFilter; + +@Component +@ConditionalOnProperty(name = CommonConstants.SERVER_SSL_ENABLED, matchIfMissing = true) +public class CAAccessControlFilter extends CoreSystemAccessControlFilter { + + //================================================================================================= + // members + + //================================================================================================= + // assistant methods + + //------------------------------------------------------------------------------------------------- + @Override + protected void checkClientAuthorized(final String clientCN, final String method, final String requestTarget, final String requestJSON, final Map queryParams) { + super.checkClientAuthorized(clientCN, method, requestTarget, requestJSON, queryParams); + + final String cloudCN = getServerCloudCN(); + + if (requestTarget.contains(CoreCommonConstants.MGMT_URI)) { + // Only the local System Operator can use these methods + checkIfLocalSystemOperator(clientCN, cloudCN, requestTarget); + } + } +} \ No newline at end of file diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CASecurityConfig.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CASecurityConfig.java new file mode 100644 index 000000000..5f129c51d --- /dev/null +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CASecurityConfig.java @@ -0,0 +1,12 @@ +package eu.arrowhead.core.certificate_authority.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + +import eu.arrowhead.common.security.DefaultSecurityConfig; + +@Configuration +@EnableWebSecurity +public class CASecurityConfig extends DefaultSecurityConfig { + +} \ No newline at end of file diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/swagger/CASwaggerConfig.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/swagger/CASwaggerConfig.java new file mode 100644 index 000000000..ae0b4321e --- /dev/null +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/swagger/CASwaggerConfig.java @@ -0,0 +1,28 @@ +package eu.arrowhead.core.certificate_authority.swagger; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import eu.arrowhead.common.CommonConstants; +import eu.arrowhead.common.swagger.DefaultSwaggerConfig; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@EnableSwagger2 +@Configuration +public class CASwaggerConfig extends DefaultSwaggerConfig { + + //================================================================================================= + // methods + + //------------------------------------------------------------------------------------------------- + public CASwaggerConfig() { + super(CommonConstants.CORE_SYSTEM_CERTIFICATE_AUTHORITY); + } + + //------------------------------------------------------------------------------------------------- + @Bean + public Docket customizeSwagger() { + return configureSwaggerForCoreSystem(this.getClass().getPackageName()); + } +} \ No newline at end of file diff --git a/certificate-authority/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/certificate-authority/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000..af4455de4 --- /dev/null +++ b/certificate-authority/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,47 @@ +{"properties": [ + { + "name": "ping_scheduled", + "type": "java.lang.Boolean", + "description": "A description for 'ping_scheduled'" + }, + { + "name": "ping_timeout", + "type": "java.lang.Long", + "description": "A description for 'ping_timeout'" + }, + { + "name": "ping_interval", + "type": "java.lang.Long", + "description": "A description for 'ping_interval'" + }, + { + "name": "ttl_scheduled", + "type": "java.lang.Boolean", + "description": "A description for 'ttl_scheduled'" + }, + { + "name": "ttl_interval", + "type": "java.lang.Long", + "description": "A description for 'ttl_interval'" + }, + { + "name": "disable.hostname.verifier", + "type": "java.lang.Boolean", + "description": "A description for 'disable.hostname.verifier'" + }, + { + "name": "log_all_request_and_response", + "type": "java.lang.Boolean", + "description": "A description for 'log_all_request_and_response'" + }, + { + "name": "use_strict_service_intf_name_verifier", + "type": "java.lang.Boolean", + "description": "A description for 'use_strict_service_intf_name_verifier'" + }, + { + "name": "core_system_name", + "type": "java.lang.String", + "description": "A description for 'core_system_name'" + } +]} \ No newline at end of file diff --git a/certificate-authority/src/main/resources/application.properties b/certificate-authority/src/main/resources/application.properties new file mode 100644 index 000000000..93f325bd4 --- /dev/null +++ b/certificate-authority/src/main/resources/application.properties @@ -0,0 +1,59 @@ +############################################ +### APPLICATION PARAMETERS ### +############################################ + +# Database connection (mandatory) +# Change the server timezone if neccessary +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/arrowhead?serverTimezone=Europe/Budapest +spring.datasource.username=certificate_authority +spring.datasource.password=FsdG6Kgf9QpPfv2 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect +# use true only for debugging +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.hibernate.ddl-auto=none + +# Certificate Authority web-server parameters +server.address=127.0.0.1 +server.port=8448 +domain.name=127.0.0.1 +domain.port=8448 + +############################################ +### CUSTOM PARAMETERS ### +############################################ + +# Name of the core system +core_system_name=certificate_authority + +# Show all request/response in debug log +log_all_request_and_response=false + +# Service Registry web-server parameters (to register the Certificate-Authority services) +sr_address=127.0.0.1 +sr_port=8443 + +############################################ +### SECURE MODE ### +############################################ + +# configure secure mode + +# Set this to false to disable https mode +server.ssl.enabled=true + +server.ssl.key-store-type=PKCS12 +server.ssl.key-store=classpath:certificates/certificate_authority.p12 +server.ssl.key-store-password=123456 +server.ssl.key-alias=certificate_authority +server.ssl.key-password=123456 +server.ssl.client-auth=need +server.ssl.trust-store-type=PKCS12 +server.ssl.trust-store=classpath:certificates/truststore.p12 +server.ssl.trust-store-password=123456 + +#If true, http client does not check whether the hostname is match one of the server's SAN in its certificate +#Just for testing, DO NOT USE this feature in production environment +disable.hostname.verifier=false diff --git a/certificate-authority/src/main/resources/certificates/certificate_authority.p12 b/certificate-authority/src/main/resources/certificates/certificate_authority.p12 new file mode 100644 index 0000000000000000000000000000000000000000..cdeb7cbbb57901241252ec8ca5b241aaca4298dc GIT binary patch literal 7629 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 literal 0 HcmV?d00001 diff --git a/certificate-authority/src/main/resources/log4j2.xml b/certificate-authority/src/main/resources/log4j2.xml new file mode 100644 index 000000000..fcce0981a --- /dev/null +++ b/certificate-authority/src/main/resources/log4j2.xml @@ -0,0 +1,46 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex + + INFO + INFO + . + + + + + + + + ${LOG_PATTERN} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/certificate-authority/src/main/resources/swagger/swagger-ui.html b/certificate-authority/src/main/resources/swagger/swagger-ui.html new file mode 100644 index 000000000..4dec726e9 --- /dev/null +++ b/certificate-authority/src/main/resources/swagger/swagger-ui.html @@ -0,0 +1,75 @@ + + + + + + Certificate Authority + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + diff --git a/core-common/src/main/java/eu/arrowhead/common/ApplicationInitListener.java b/core-common/src/main/java/eu/arrowhead/common/ApplicationInitListener.java index be8b61a76..6cf626be8 100644 --- a/core-common/src/main/java/eu/arrowhead/common/ApplicationInitListener.java +++ b/core-common/src/main/java/eu/arrowhead/common/ApplicationInitListener.java @@ -187,12 +187,15 @@ private void obtainKeys(final KeyStore keyStore, final ApplicationContext appCon logger.debug("obtainKeys started..."); @SuppressWarnings("unchecked") final Map context = appContext.getBean(CommonConstants.ARROWHEAD_CONTEXT, Map.class); - - publicKey = Utilities.getFirstCertFromKeyStore(keyStore).getPublicKey(); + + final X509Certificate serverCertificate = Utilities.getFirstCertFromKeyStore(keyStore); + publicKey = serverCertificate.getPublicKey(); context.put(CommonConstants.SERVER_PUBLIC_KEY, publicKey); - + final PrivateKey privateKey = Utilities.getPrivateKey(keyStore, sslProperties.getKeyPassword()); context.put(CommonConstants.SERVER_PRIVATE_KEY, privateKey); + + context.put(CommonConstants.SERVER_CERTIFICATE, serverCertificate); } //------------------------------------------------------------------------------------------------- diff --git a/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java b/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java index 7ef651842..457f71c82 100644 --- a/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java +++ b/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java @@ -14,8 +14,9 @@ public class CommonConstants { // members public static final String BASE_PACKAGE = "eu.arrowhead"; - + public static final String CORE_SYSTEM_AUTHORIZATION = "Authorization"; + public static final String CORE_SYSTEM_CERTIFICATE_AUTHORITY = "Certificate Authority"; public static final String CORE_SYSTEM_CHOREOGRAPHER = "Choreographer"; public static final String CORE_SYSTEM_EVENT_HANDLER = "Event Handler"; public static final String CORE_SYSTEM_GATEKEEPER = "Gatekeeper"; @@ -48,6 +49,7 @@ public class CommonConstants { public static final String SERVER_COMMON_NAME = "server.common.name"; public static final String SERVER_PUBLIC_KEY = "server.public.key"; public static final String SERVER_PRIVATE_KEY = "server.private.key"; + public static final String SERVER_CERTIFICATE = "server.certificate"; public static final String HTTPS = "https"; public static final String HTTP = "http"; @@ -77,7 +79,11 @@ public class CommonConstants { public static final String OP_AUTH_INTRA_CHECK_URI = "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/intracloud/check"; public static final String OP_AUTH_INTER_CHECK_URI = "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/intercloud/check"; public static final String OP_AUTH_SUBSCRIPTION_CHECK_URI = "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/subscription/check"; - + + public static final String CERTIFICATE_AUTHRORITY_URI = "/certificate-authority"; + public static final String OP_CA_CLOUD_COMMON_NAME_URI = "/name"; + public static final String OP_CA_SIGN_CERTIFICATE_URI = "/sign"; + public static final String ORCHESTRATOR_URI = "/orchestrator"; public static final String OP_ORCH_PROCESS = "/orchestration"; public static final String ORCHESTRATON_FLAG_MATCHMAKING = "matchmaking"; diff --git a/core-common/src/main/java/eu/arrowhead/common/Utilities.java b/core-common/src/main/java/eu/arrowhead/common/Utilities.java index 790ea5832..e5a228bcd 100644 --- a/core-common/src/main/java/eu/arrowhead/common/Utilities.java +++ b/core-common/src/main/java/eu/arrowhead/common/Utilities.java @@ -376,6 +376,30 @@ public static X509Certificate getFirstCertFromKeyStore(final KeyStore keystore) } } + //------------------------------------------------------------------------------------------------- + public static X509Certificate getCloudCertFromKeyStore(final KeyStore keystore) { + Assert.notNull(keystore, "Key store is not defined."); + + try { + final Enumeration enumeration = keystore.aliases(); + while (enumeration.hasMoreElements()) { + final String alias = enumeration.nextElement(); + final String[] aliasParts = alias.split("\\."); + if (aliasParts.length == 3 && aliasParts[1].equals(AH_MASTER_NAME) && aliasParts[2].equals(AH_MASTER_SUFFIX)) { + return (X509Certificate) keystore.getCertificate(alias); + } + } + + final String errorMsg = "Getting the cloud cert from keystore failed. " + + "Cannot find alias in the following format: {cloudname}." + AH_MASTER_NAME + "." + AH_MASTER_SUFFIX; + logger.error(errorMsg); + throw new ServiceConfigurationError(errorMsg); + } catch (final KeyStoreException | NoSuchElementException ex) { + logger.error("Getting the cloud cert from keystore failed...", ex); + throw new ServiceConfigurationError("Getting the cloud cert from keystore failed...", ex); + } + } + //------------------------------------------------------------------------------------------------- public static PrivateKey getPrivateKey(final KeyStore keystore, final String keyPass) { Assert.notNull(keystore, "Key store is not defined."); @@ -404,6 +428,24 @@ public static PrivateKey getPrivateKey(final KeyStore keystore, final String key return privateKey; } + //------------------------------------------------------------------------------------------------- + public static PrivateKey getPrivateKey(final KeyStore keystore, final String alias, final String keyPass) { + Assert.notNull(keystore, "Key store is not defined."); + Assert.notNull(keyPass, "Password is not defined."); + + try { + PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, keyPass.toCharArray()); + if (privateKey != null) { + return privateKey; + } else { + throw new ServiceConfigurationError("Getting the private key failed, key store aliases do not identify a key."); + } + } catch (final KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException ex) { + logger.error("Getting the private key from key store failed...", ex); + throw new ServiceConfigurationError("Getting the private key from key store failed...", ex); + } + } + //------------------------------------------------------------------------------------------------- public static PublicKey getPublicKeyFromBase64EncodedString(final String encodedKey) { Assert.isTrue(!isEmpty(encodedKey), "Encoded key is null or blank"); @@ -502,4 +544,4 @@ private static PublicKey generatePublicKeyFromByteArray(final byte[] keyBytes) { throw new AuthException("Public key decoding failed due wrong input key", ex); } } -} \ No newline at end of file +} diff --git a/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java b/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java new file mode 100644 index 000000000..078b9abaf --- /dev/null +++ b/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java @@ -0,0 +1,24 @@ +package eu.arrowhead.common.dto.internal; + +import java.io.Serializable; + +public class CertificateSigningRequestDTO implements Serializable { + + private static final long serialVersionUID = -6810780579000655432L; + + private String certificatePem; + + public CertificateSigningRequestDTO() {} + + public CertificateSigningRequestDTO(String certificatePem) { + this.certificatePem = certificatePem; + } + + public String getCertificatePem() { + return certificatePem; + } + + public void setCertificatePem(String certificatePem) { + this.certificatePem = certificatePem; + } +} diff --git a/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningResponseDTO.java b/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningResponseDTO.java new file mode 100644 index 000000000..2a73e0619 --- /dev/null +++ b/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningResponseDTO.java @@ -0,0 +1,24 @@ +package eu.arrowhead.common.dto.internal; + +import java.io.Serializable; + +public class CertificateSigningResponseDTO implements Serializable { + + private static final long serialVersionUID = -6810780579000655432L; + + private String certificatePem; + + public CertificateSigningResponseDTO() {} + + public CertificateSigningResponseDTO(String certificatePem) { + this.certificatePem = certificatePem; + } + + public String getCertificatePem() { + return certificatePem; + } + + public void setCertificatePem(String certificatePem) { + this.certificatePem = certificatePem; + } +} diff --git a/pom.xml b/pom.xml index 185899f38..dc92e26db 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ ${revision} pom core-java-spring - Arrowhead Core Sevices using Spring Boot + Arrowhead Core Services using Spring Boot https://github.com/arrowhead-f/core-java-spring @@ -26,6 +26,7 @@ core-common serviceregistry authorization + certificate-authority orchestrator eventhandler gatekeeper @@ -54,6 +55,7 @@ 3.9 2.0.1 5.15.9 + 1.64 UTF-8 diff --git a/scripts/certificate_authority_privileges.sql b/scripts/certificate_authority_privileges.sql new file mode 100644 index 000000000..b09cc1cbb --- /dev/null +++ b/scripts/certificate_authority_privileges.sql @@ -0,0 +1,43 @@ +USE `arrowhead`; + +REVOKE ALL, GRANT OPTION FROM 'certificate_authority'@'localhost'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority_interface_connection` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'certificate_authority'@'localhost'; + +REVOKE ALL, GRANT OPTION FROM 'certificate_authority'@'%'; + +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority_interface_connection` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'certificate_authority'@'%'; + +FLUSH PRIVILEGES; diff --git a/scripts/create_empty_arrowhead_db.sql b/scripts/create_empty_arrowhead_db.sql index 27a130088..c98a30316 100644 --- a/scripts/create_empty_arrowhead_db.sql +++ b/scripts/create_empty_arrowhead_db.sql @@ -40,4 +40,9 @@ source gatekeeper_privileges.sql -- Gateway CREATE USER IF NOT EXISTS 'gateway'@'localhost' IDENTIFIED BY 'LfiSM9DpGfDEP5g'; CREATE USER IF NOT EXISTS 'gateway'@'%' IDENTIFIED BY 'LfiSM9DpGfDEP5g'; -source gateway_privileges.sql \ No newline at end of file +source gateway_privileges.sql + +-- Certificate Authority +CREATE USER IF NOT EXISTS 'certificate_authority'@'localhost' IDENTIFIED BY 'FsdG6Kgf9QpPfv2'; +CREATE USER IF NOT EXISTS 'certificate_authority'@'%' IDENTIFIED BY 'FsdG6Kgf9QpPfv2'; +source certificate_authority_privileges.sql diff --git a/scripts/start_coresystems_local.bat b/scripts/start_coresystems_local.bat index 179670d0b..e812085fa 100644 --- a/scripts/start_coresystems_local.bat +++ b/scripts/start_coresystems_local.bat @@ -26,6 +26,10 @@ cd ..\..\orchestrator\target START "" /B "cmd /c javaw -jar arrowhead-orchestrator-4.1.3.jar > sout_orch.log 2>&1" echo Orchestrator started +cd ..\..\certificate-authority\target +START "" /B "cmd /c javaw -jar arrowhead-certificate-authority-4.1.3.jar > sout_ca.log 2>&1" +echo Certificate Authority started + cd %parent_path% ::Kill self diff --git a/scripts/stop_coresystems.bat b/scripts/stop_coresystems.bat index dc6f3a234..31ab118f0 100644 --- a/scripts/stop_coresystems.bat +++ b/scripts/stop_coresystems.bat @@ -10,6 +10,7 @@ FOR /F "tokens=1" %%p in ('"jps -v | find "gatekeeper""') DO taskkill /pid %%p > FOR /F "tokens=1" %%p in ('"jps -v | find "eventhandler""') DO taskkill /pid %%p > NUL 2>&1 FOR /F "tokens=1" %%p in ('"jps -v | find "gateway""') DO taskkill /pid %%p > NUL 2>&1 FOR /F "tokens=1" %%p in ('"jps -v | find "authorization""') DO taskkill /pid %%p > NUL 2>&1 +FOR /F "tokens=1" %%p in ('"jps -v | find "certificate""') DO taskkill /pid %%p > NUL 2>&1 timeout /t %time_to_sleep% /nobreak > NUL FOR /F "tokens=1" %%p in ('"jps -v | find "serviceregistry""') DO taskkill /pid %%p > NUL 2>&1 @@ -27,6 +28,7 @@ IF "%STILL_THERE%"=="""" ( FOR /F "tokens=1" %%p in ('"jps -v | find "eventhandler""') DO taskkill /F /pid %%p FOR /F "tokens=1" %%p in ('"jps -v | find "gateway""') DO taskkill /F /pid %%p FOR /F "tokens=1" %%p in ('"jps -v | find "authorization""') DO taskkill /F /pid %%p + FOR /F "tokens=1" %%p in ('"jps -v | find "certificate""') DO taskkill /F /pid %%p FOR /F "tokens=1" %%p in ('"jps -v | find "serviceregistry""') DO taskkill /F /pid %%p echo Core systems forcefully killed ) From 3225848db0f7b871d3cc4e6524043352df1bf8df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Tue, 25 Feb 2020 11:27:32 +0100 Subject: [PATCH 02/26] Sign CSR with the cloud cert instead of the server cert --- .../CertificateAuthorityController.java | 2 - .../CertificateAuthorityService.java | 62 ++++++++++-------- .../certificates/certificate_authority.p12 | Bin 7629 -> 8485 bytes .../java/eu/arrowhead/common/Utilities.java | 28 +++++++- .../CertificateSigningRequestDTO.java | 14 ++-- .../CertificateSigningResponseDTO.java | 18 ++--- 6 files changed, 77 insertions(+), 47 deletions(-) diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java index c5d4c5787..a2275d366 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java @@ -5,7 +5,6 @@ import eu.arrowhead.common.Defaults; import eu.arrowhead.common.dto.internal.CertificateSigningRequestDTO; import eu.arrowhead.common.dto.internal.CertificateSigningResponseDTO; -import eu.arrowhead.common.exception.BadPayloadException; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; @@ -14,7 +13,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java index ed37a5036..b186ec08c 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java @@ -1,6 +1,5 @@ package eu.arrowhead.core.certificate_authority; -import eu.arrowhead.common.CommonConstants; import eu.arrowhead.common.SSLProperties; import eu.arrowhead.common.Utilities; import eu.arrowhead.common.dto.internal.CertificateSigningRequestDTO; @@ -8,6 +7,9 @@ import eu.arrowhead.common.exception.AuthException; import eu.arrowhead.common.exception.BadPayloadException; import eu.arrowhead.common.exception.DataNotFoundException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; @@ -28,7 +30,6 @@ import org.bouncycastle.pkcs.PKCSException; import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; @@ -41,34 +42,36 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.Base64; -import java.util.Date; -import java.util.Map; -import java.util.ServiceConfigurationError; +import java.util.*; @Service public class CertificateAuthorityService { - @Autowired - private SSLProperties sslProperties; + private static final Logger logger = LogManager.getLogger(CertificateAuthorityService.class); @Autowired - private ApplicationContext appContext; + private SSLProperties sslProperties; private SecureRandom random; private KeyStore keyStore; + private X509Certificate rootCertificate; + private X509Certificate cloudCertificate; + @PostConstruct private void init() { random = new SecureRandom(); random.reseed(); keyStore = getKeyStore(); + + rootCertificate = Utilities.getRootCertFromKeyStore(keyStore); + cloudCertificate = Utilities.getCloudCertFromKeyStore(keyStore); } public String getCloudCommonName() { try { - final X500Name issuer = new JcaX509CertificateHolder(getServerCertificate(appContext)).getIssuer(); - final RDN cn = issuer.getRDNs(BCStyle.CN)[0]; + final X500Name subject = new JcaX509CertificateHolder(cloudCertificate).getSubject(); + final RDN cn = subject.getRDNs(BCStyle.CN)[0]; return IETFUtils.valueToString(cn.getFirst().getValue()); } catch (CertificateEncodingException e) { throw new ServiceConfigurationError("Cannot get cloud common name from server cert.", e); @@ -76,18 +79,18 @@ public String getCloudCommonName() { } public CertificateSigningResponseDTO signCertificate(CertificateSigningRequestDTO request) { - JcaPKCS10CertificationRequest csr = decodePKCS10CSR(request); + final JcaPKCS10CertificationRequest csr = decodePKCS10CSR(request); checkCommonName(csr, getCloudCommonName()); checkCsrSignature(csr); + logger.info("Signing certificate for " + csr.getSubject().toString() + "..."); + final PrivateKey cloudPrivateKey = Utilities.getPrivateKey(keyStore, getCloudCommonName(), sslProperties.getKeyPassword()); - final X509Certificate serverCertificate = getServerCertificate(appContext); - final X509Certificate clientCertificate = buildCertificate(csr.getSubject(), getClientKey(csr), cloudPrivateKey, serverCertificate); - final String encodedClientCertificate = encodeCertificate(clientCertificate); - final String encodedServerCertificate = encodeCertificate(serverCertificate); + final X509Certificate clientCertificate = buildCertificate(csr.getSubject(), getClientKey(csr), cloudPrivateKey, cloudCertificate); + final List encodedCertificateChain = buildEncodedCertificateChain(clientCertificate); - return new CertificateSigningResponseDTO(encodedClientCertificate + " " + encodedServerCertificate); + return new CertificateSigningResponseDTO(encodedCertificateChain); } private KeyStore getKeyStore() { @@ -102,9 +105,10 @@ private KeyStore getKeyStore() { private JcaPKCS10CertificationRequest decodePKCS10CSR(CertificateSigningRequestDTO csr) { try { - final byte[] csrBytes = Base64.getDecoder().decode(csr.getCertificatePem()); + final byte[] csrBytes = Base64.getDecoder().decode(csr.getEncodedCSR()); return new JcaPKCS10CertificationRequest(csrBytes); - } catch (IOException ex) { + } catch (Exception ex) { + logger.error("Failed to parse request as a PKCS10 CSR, because: " + ex.toString()); throw new BadPayloadException("Failed to parse request as a PKCS10 CSR", ex); } } @@ -117,6 +121,14 @@ private String encodeCertificate(X509Certificate certificate) { } } + private List buildEncodedCertificateChain(X509Certificate clientCertificate) { + final ArrayList encodedCertificateChain = new ArrayList<>(); + encodedCertificateChain.add(encodeCertificate(clientCertificate)); + encodedCertificateChain.add(encodeCertificate(cloudCertificate)); + encodedCertificateChain.add(encodeCertificate(rootCertificate)); + return encodedCertificateChain; + } + private void checkCommonName(JcaPKCS10CertificationRequest csr, String cloudCN) { final String clientCN = Utilities.getCertCNFromSubject(csr.getSubject().toString()); if (!Utilities.isKeyStoreCNArrowheadValid(clientCN, cloudCN)) { @@ -145,19 +157,14 @@ private PublicKey getClientKey(JcaPKCS10CertificationRequest csr) { } } - private X509Certificate getServerCertificate(final ApplicationContext appContext) { - @SuppressWarnings("unchecked") final Map context = appContext.getBean(CommonConstants.ARROWHEAD_CONTEXT, Map.class); - return (X509Certificate) context.get(CommonConstants.SERVER_CERTIFICATE); - } - - private X509Certificate buildCertificate(X500Name csrSubject, PublicKey clientKey, PrivateKey cloudPrivateKey, X509Certificate serverCertificate) { + private X509Certificate buildCertificate(X500Name csrSubject, PublicKey clientKey, PrivateKey cloudPrivateKey, X509Certificate cloudCertificate) { final ZonedDateTime now = LocalDateTime.now().atZone(ZoneId.systemDefault()); final Date validFrom = Date.from(now.minusSeconds(5).toInstant()); final Date validUntil = Date.from(now.plusYears(1).toInstant()); final BigInteger serial = BigInteger.valueOf(now.toInstant().toEpochMilli()).multiply(BigInteger.valueOf(random.nextLong())); - X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(serverCertificate, serial, validFrom, validUntil, csrSubject, clientKey); + X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(cloudCertificate, serial, validFrom, validUntil, csrSubject, clientKey); /* Adding the following extensions to the new certificate: 1) The subject key identifier provides a hashed value that should uniquely identify the public key @@ -167,13 +174,12 @@ private X509Certificate buildCertificate(X500Name csrSubject, PublicKey clientKe try { JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(clientKey)); - builder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(serverCertificate)); + builder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(cloudCertificate)); builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); } catch (NoSuchAlgorithmException | CertIOException | CertificateEncodingException e) { throw new AuthException("Appending extensions to the certificate failed! (" + e.getMessage() + ")", e); } - JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA512withRSA").setProvider("BC"); try { return new JcaX509CertificateConverter().setProvider("BC") diff --git a/certificate-authority/src/main/resources/certificates/certificate_authority.p12 b/certificate-authority/src/main/resources/certificates/certificate_authority.p12 index cdeb7cbbb57901241252ec8ca5b241aaca4298dc..57fba65b49798f1e23f97587495bfe2d7d1abf60 100644 GIT binary patch delta 6981 zcmV-L8@lApJEcM*FoGc=0s#Xsf*{%k2`Yw2hW8Bt2LYghAj<@TAjdF*Aj7dDwgrEl zmpA&;ZF2mEY@&(~`oiV7U4Tde0|3KN1cC&}4r4h40}REsCIP4@2=06HY% ziaaF6l?X+S=rF!Q0tnjY*A4=aB{6@@3^_Dy@)wNO)3H6T>kmmZ^a3J1^;_CuyWatl zO4m{dX8)bcBbR(mkbhV0{3@C_e&fIUrQ>@1m;}?tAUe^40f)i(3XxZ>dfm@K(hoc+ z(}Rgw=HkwNU4E&&1ej>HC>EC9wl?LsNCw+@9YB@T11VT;=MwT)RD@!@k6V95Y_4(n z3qb4-!nXRkK2EW&j(Zm=TjSNX5sOB{HVc`o@LOkt@rxpydt0{B!;;bFUr3^}R95bK zC@s_=aApf<77bE>8ZoVbh`*Qx=f0l?GSgn7bjN|v~64pUizX0e2BA-*!exK4(z>Fc|RcD8I1^z`b>mYN^27K2g%rV8n0AhcN#`!+@!-ZU+ zP*3t*DPmU>;{)-_($yGJg?7nnM?de&%bV@#Cw-3;aA_@ue+M_fQ+51PpdJxiHgzEw z5vuBlSggEebkYes88CwcudvmfL}@?&M?pz%XSDKy_pd%#Hin9~Y(S7dKOJnWyrZ3Y z@);rV%;|eDF5h#12qb?7sjC5GPv1s4%7#{a7XjHS9W{Ly5h<{QM4Zs{S>B^1ft~6_E-1KO}usa#&7uXrgD1VC8a&S9gRwC2~Evw zBK-wmkE1848fnCh!Wi>o8SBPkRzd3ZnE#^zYZBN9c9C#etSx^e$FMCN>&JJ$`LnfB zP=ZEAeEz-eH(6;KUU|B^>d8ji{I~I2gAATfs+nV)Wwv-fdwHq&3*StF6yKX?2BFjg zRp#4QF+A{7Wh#ckUo}$p{8r)c(9-^+qs0$pjVf8}(c(Rg?Tm>$PN=Jy9VKZ2iA9C$ zVqIVF(-&7uVK;xz(=bV5V?+-NlY6`}*B!kXr@)n`c^Bcy z zi;!`o!xgl)Sk-7m1u_iUSdQE9WPQgEQ!kP+I1S?03b22{+h>VU8JfHl`|rAhkfQf` zgBF32VzH~njo`WC>FQwHbE0Fp(uR&u@*{3ONk6gOQrAm2~rc$nz$)NR#22F(!c_-Vh zaSJ;7p#l)clhji#&yQ)hs1!b+?9F3%ezHXab=~d?O+f!z1^iU?5>5zs))E<~{^-i@ z67BtS@1igX&+@ian=x)MNd^fjhDe6@4FL%hF+7vb3RNgFGC4CaIWjddF))G^v<3+( zhDe6@4FLxRpn?^pFoG4K0s#Opf)$#ROA8%;l4Sb1weA;n4nG7>STS|IlRmF80s{cU zP=JCJV1xSvZM$VDaV~a#bC*80y>5sw-DTnkh zj@jpMjsers5=$h&5CTu(FLGbY40M?!QP3X80KI2n|J`t3Xv4O1rLxba5;0pDkHG?e zx8IAF<`Gr#3^>HFs@Y#r9^TF%uzpqLYGZLGu(BsTzI>F|?_Y6UId{n;|Q;7+Zp!9k&u*_Ct`;?Sd|;cw+Yie_IS^ zhK)i1dz41Z&G6jGSHHhg03#1UI@Ae&8ipSoEJF%&5^<%Z?0g~+n$BkVAB}9*yxv9U z0|8Bba`@B=wg`pTRCn+;C1E!yVX3vA}7<7-rF0l zZ`r0W!p_xvuA^rgr?{tHtUTsD3M137DCT#IgbVZ#FWk1F>M~SOR6bwg4lgr*KvPQ5 z*@E4j7u-UH`WJa;n>9G(=V(y=3h*-&=zK)5iVp%eN!cYgpLPH>Y(U|bf-NA&K(jbuUNb#~eyraa{ zn1PuK19Ua4q_rq}d1?A2ur3OJR#orEe2S)=X

<_%}MaZbl*OYMInC6qjW8O}AXh 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 XFSF+%CRl_jyI8l;L3_Fa0|ADhQC@lN delta 6119 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 diff --git a/core-common/src/main/java/eu/arrowhead/common/Utilities.java b/core-common/src/main/java/eu/arrowhead/common/Utilities.java index e5a228bcd..7e8c1398a 100644 --- a/core-common/src/main/java/eu/arrowhead/common/Utilities.java +++ b/core-common/src/main/java/eu/arrowhead/common/Utilities.java @@ -385,13 +385,13 @@ public static X509Certificate getCloudCertFromKeyStore(final KeyStore keystore) while (enumeration.hasMoreElements()) { final String alias = enumeration.nextElement(); final String[] aliasParts = alias.split("\\."); - if (aliasParts.length == 3 && aliasParts[1].equals(AH_MASTER_NAME) && aliasParts[2].equals(AH_MASTER_SUFFIX)) { + if (aliasParts.length == 4 && aliasParts[2].equals(AH_MASTER_NAME) && aliasParts[3].equals(AH_MASTER_SUFFIX)) { return (X509Certificate) keystore.getCertificate(alias); } } final String errorMsg = "Getting the cloud cert from keystore failed. " + - "Cannot find alias in the following format: {cloudname}." + AH_MASTER_NAME + "." + AH_MASTER_SUFFIX; + "Cannot find alias in the following format: {cloudname}.{cloudoperator}." + AH_MASTER_NAME + "." + AH_MASTER_SUFFIX; logger.error(errorMsg); throw new ServiceConfigurationError(errorMsg); } catch (final KeyStoreException | NoSuchElementException ex) { @@ -400,6 +400,30 @@ public static X509Certificate getCloudCertFromKeyStore(final KeyStore keystore) } } + //------------------------------------------------------------------------------------------------- + public static X509Certificate getRootCertFromKeyStore(final KeyStore keystore) { + Assert.notNull(keystore, "Key store is not defined."); + + try { + final Enumeration enumeration = keystore.aliases(); + while (enumeration.hasMoreElements()) { + final String alias = enumeration.nextElement(); + final String[] aliasParts = alias.split("\\."); + if (aliasParts.length == 2 && aliasParts[0].equals(AH_MASTER_NAME) && aliasParts[1].equals(AH_MASTER_SUFFIX)) { + return (X509Certificate) keystore.getCertificate(alias); + } + } + + final String errorMsg = "Getting the root cert from keystore failed. " + + "Cannot find alias in the following format: " + AH_MASTER_NAME + "." + AH_MASTER_SUFFIX; + logger.error(errorMsg); + throw new ServiceConfigurationError(errorMsg); + } catch (final KeyStoreException | NoSuchElementException ex) { + logger.error("Getting the root cert from keystore failed...", ex); + throw new ServiceConfigurationError("Getting the root cert from keystore failed...", ex); + } + } + //------------------------------------------------------------------------------------------------- public static PrivateKey getPrivateKey(final KeyStore keystore, final String keyPass) { Assert.notNull(keystore, "Key store is not defined."); diff --git a/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java b/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java index 078b9abaf..0231f9bcd 100644 --- a/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java +++ b/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java @@ -6,19 +6,19 @@ public class CertificateSigningRequestDTO implements Serializable { private static final long serialVersionUID = -6810780579000655432L; - private String certificatePem; + private String encodedCSR; public CertificateSigningRequestDTO() {} - public CertificateSigningRequestDTO(String certificatePem) { - this.certificatePem = certificatePem; + public CertificateSigningRequestDTO(String encodedCSR) { + this.encodedCSR = encodedCSR; } - public String getCertificatePem() { - return certificatePem; + public String getEncodedCSR() { + return encodedCSR; } - public void setCertificatePem(String certificatePem) { - this.certificatePem = certificatePem; + public void setEncodedCSR(String encodedCSR) { + this.encodedCSR = encodedCSR; } } diff --git a/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningResponseDTO.java b/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningResponseDTO.java index 2a73e0619..cb8726244 100644 --- a/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningResponseDTO.java +++ b/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningResponseDTO.java @@ -1,24 +1,26 @@ package eu.arrowhead.common.dto.internal; import java.io.Serializable; +import java.util.List; public class CertificateSigningResponseDTO implements Serializable { private static final long serialVersionUID = -6810780579000655432L; - private String certificatePem; + private List certificateChain; - public CertificateSigningResponseDTO() {} + public CertificateSigningResponseDTO() { + } - public CertificateSigningResponseDTO(String certificatePem) { - this.certificatePem = certificatePem; + public CertificateSigningResponseDTO(List certificateChain) { + this.certificateChain = certificateChain; } - public String getCertificatePem() { - return certificatePem; + public List getCertificateChain() { + return certificateChain; } - public void setCertificatePem(String certificatePem) { - this.certificatePem = certificatePem; + public void setCertificateChain(List certificateChain) { + this.certificateChain = certificateChain; } } From e4485c3db5a8bc9bcc5a1bc6f892fcc2d731b493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Tue, 25 Feb 2020 16:03:11 +0100 Subject: [PATCH 03/26] Refactor, code clean-up in CA --- ...icateAuthorityApplicationInitListener.java | 62 +------- .../CertificateAuthorityService.java | 115 ++------------ .../CertificateAuthorityUtils.java | 143 ++++++++++++++++++ .../security/CAAccessControlFilter.java | 6 - 4 files changed, 159 insertions(+), 167 deletions(-) create mode 100644 certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java index 576e90843..159dd1c88 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java @@ -1,69 +1,9 @@ package eu.arrowhead.core.certificate_authority; -import java.util.Map; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; - import eu.arrowhead.common.ApplicationInitListener; -import eu.arrowhead.common.CommonConstants; -import eu.arrowhead.common.CoreDefaults; -import eu.arrowhead.common.database.service.CommonDBService; -import eu.arrowhead.common.exception.DataNotFoundException; @Component public class CertificateAuthorityApplicationInitListener extends ApplicationInitListener { - //================================================================================================= - // members - - @Autowired - private CommonDBService commonDBService; - - //================================================================================================= - // assistant methods - - //------------------------------------------------------------------------------------------------- - @Override - protected void customInit(final ContextRefreshedEvent event) { - logger.debug("customInit started..."); - if (!isOwnCloudRegistered()) { - registerOwnCloud(event.getApplicationContext()); - } - } - - //------------------------------------------------------------------------------------------------- - private boolean isOwnCloudRegistered() { - logger.debug("isOwnCloudRegistered started..."); - try { - commonDBService.getOwnCloud(sslProperties.isSslEnabled()); - return true; - } catch (final DataNotFoundException ex) { - return false; - } - } - - //------------------------------------------------------------------------------------------------- - private void registerOwnCloud(final ApplicationContext appContext) { - logger.debug("registerOwnCloud started..."); - - if (!standaloneMode) { - String name = CoreDefaults.DEFAULT_OWN_CLOUD_NAME; - String operator = CoreDefaults.DEFAULT_OWN_CLOUD_OPERATOR; - - if (sslProperties.isSslEnabled()) { - @SuppressWarnings("unchecked") - final Map context = appContext.getBean(CommonConstants.ARROWHEAD_CONTEXT, Map.class); - final String serverCN = (String) context.get(CommonConstants.SERVER_COMMON_NAME); - final String[] serverFields = serverCN.split("\\."); - name = serverFields[1]; - operator = serverFields[2]; - } - - commonDBService.insertOwnCloud(operator, name, sslProperties.isSslEnabled(), null); - logger.info("{}.{} own cloud is registered in {} mode.", name, operator, getModeString()); - } - } -} \ No newline at end of file +} diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java index b186ec08c..1ff0e8b78 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java @@ -4,9 +4,6 @@ import eu.arrowhead.common.Utilities; import eu.arrowhead.common.dto.internal.CertificateSigningRequestDTO; import eu.arrowhead.common.dto.internal.CertificateSigningResponseDTO; -import eu.arrowhead.common.exception.AuthException; -import eu.arrowhead.common.exception.BadPayloadException; -import eu.arrowhead.common.exception.DataNotFoundException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -14,27 +11,13 @@ import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x500.style.IETFUtils; -import org.bouncycastle.asn1.x509.BasicConstraints; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.cert.CertIOException; -import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; -import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.operator.ContentVerifierProvider; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder; -import org.bouncycastle.pkcs.PKCSException; import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.io.IOException; -import java.math.BigInteger; import java.security.*; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; @@ -79,15 +62,16 @@ public String getCloudCommonName() { } public CertificateSigningResponseDTO signCertificate(CertificateSigningRequestDTO request) { - final JcaPKCS10CertificationRequest csr = decodePKCS10CSR(request); - checkCommonName(csr, getCloudCommonName()); - checkCsrSignature(csr); + final JcaPKCS10CertificationRequest csr = CertificateAuthorityUtils.decodePKCS10CSR(request); + CertificateAuthorityUtils.checkCommonName(csr, getCloudCommonName()); + CertificateAuthorityUtils.checkCsrSignature(csr); logger.info("Signing certificate for " + csr.getSubject().toString() + "..."); - final PrivateKey cloudPrivateKey = Utilities.getPrivateKey(keyStore, getCloudCommonName(), sslProperties.getKeyPassword()); + final PrivateKey cloudPrivateKey = Utilities.getPrivateKey(keyStore, getCloudCommonName(), + sslProperties.getKeyPassword()); - final X509Certificate clientCertificate = buildCertificate(csr.getSubject(), getClientKey(csr), cloudPrivateKey, cloudCertificate); + final X509Certificate clientCertificate = buildCertificate(csr, cloudPrivateKey, cloudCertificate); final List encodedCertificateChain = buildEncodedCertificateChain(clientCertificate); return new CertificateSigningResponseDTO(encodedCertificateChain); @@ -96,98 +80,29 @@ public CertificateSigningResponseDTO signCertificate(CertificateSigningRequestDT private KeyStore getKeyStore() { try { final KeyStore keystore = KeyStore.getInstance(sslProperties.getKeyStoreType()); - keystore.load(sslProperties.getKeyStore().getInputStream(), sslProperties.getKeyStorePassword().toCharArray()); + keystore.load(sslProperties.getKeyStore().getInputStream(), + sslProperties.getKeyStorePassword().toCharArray()); return keystore; } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) { throw new ServiceConfigurationError("Cannot open keystore: " + e.getMessage()); } } - private JcaPKCS10CertificationRequest decodePKCS10CSR(CertificateSigningRequestDTO csr) { - try { - final byte[] csrBytes = Base64.getDecoder().decode(csr.getEncodedCSR()); - return new JcaPKCS10CertificationRequest(csrBytes); - } catch (Exception ex) { - logger.error("Failed to parse request as a PKCS10 CSR, because: " + ex.toString()); - throw new BadPayloadException("Failed to parse request as a PKCS10 CSR", ex); - } - } - - private String encodeCertificate(X509Certificate certificate) { - try { - return Base64.getEncoder().encodeToString(certificate.getEncoded()); - } catch (CertificateEncodingException e) { - throw new AuthException("Certificate encoding failed! (" + e.getMessage() + ")", e); - } - } - private List buildEncodedCertificateChain(X509Certificate clientCertificate) { final ArrayList encodedCertificateChain = new ArrayList<>(); - encodedCertificateChain.add(encodeCertificate(clientCertificate)); - encodedCertificateChain.add(encodeCertificate(cloudCertificate)); - encodedCertificateChain.add(encodeCertificate(rootCertificate)); + encodedCertificateChain.add(CertificateAuthorityUtils.encodeCertificate(clientCertificate)); + encodedCertificateChain.add(CertificateAuthorityUtils.encodeCertificate(cloudCertificate)); + encodedCertificateChain.add(CertificateAuthorityUtils.encodeCertificate(rootCertificate)); return encodedCertificateChain; } - private void checkCommonName(JcaPKCS10CertificationRequest csr, String cloudCN) { - final String clientCN = Utilities.getCertCNFromSubject(csr.getSubject().toString()); - if (!Utilities.isKeyStoreCNArrowheadValid(clientCN, cloudCN)) { - throw new BadPayloadException("Certificate request does not have a valid common name! Valid common name: {systemName}." + cloudCN); - } - } - - private void checkCsrSignature(JcaPKCS10CertificationRequest csr) { - Security.addProvider(new BouncyCastleProvider()); - try { - ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().setProvider("BC").build(csr.getSubjectPublicKeyInfo()); - if (!csr.isSignatureValid(verifierProvider)) { - throw new AuthException("Certificate request has invalid signature! (key pair does not match)"); - } - } catch (OperatorCreationException | PKCSException e) { - throw new AuthException("Encapsulated " + e.getClass().getCanonicalName() + ": " + e.getMessage(), e); - } - } - - private PublicKey getClientKey(JcaPKCS10CertificationRequest csr) { - try { - return csr.getPublicKey(); - } catch (InvalidKeyException | NoSuchAlgorithmException e) { - //This should not be possible after a successful signature verification - throw new DataNotFoundException("Extracting the public key from the CSR failed (" + e.getMessage() + ")", e); - } - } - - private X509Certificate buildCertificate(X500Name csrSubject, PublicKey clientKey, PrivateKey cloudPrivateKey, X509Certificate cloudCertificate) { + private X509Certificate buildCertificate(JcaPKCS10CertificationRequest csr, PrivateKey cloudPrivateKey, + X509Certificate cloudCertificate) { final ZonedDateTime now = LocalDateTime.now().atZone(ZoneId.systemDefault()); final Date validFrom = Date.from(now.minusSeconds(5).toInstant()); final Date validUntil = Date.from(now.plusYears(1).toInstant()); - final BigInteger serial = BigInteger.valueOf(now.toInstant().toEpochMilli()).multiply(BigInteger.valueOf(random.nextLong())); - - X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(cloudCertificate, serial, validFrom, validUntil, csrSubject, clientKey); - - /* Adding the following extensions to the new certificate: - 1) The subject key identifier provides a hashed value that should uniquely identify the public key - 2) The authority key identifier provides a hashed value that should uniquely identify the issuer of the certificate - 3) And this basic constraint is for forbidding issuing other certificates under this certificate - */ - try { - JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); - builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(clientKey)); - builder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(cloudCertificate)); - builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); - } catch (NoSuchAlgorithmException | CertIOException | CertificateEncodingException e) { - throw new AuthException("Appending extensions to the certificate failed! (" + e.getMessage() + ")", e); - } - - JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA512withRSA").setProvider("BC"); - try { - return new JcaX509CertificateConverter().setProvider("BC") - .getCertificate(builder.build(signerBuilder.build(cloudPrivateKey))); - } catch (CertificateException e) { - throw new AuthException("Certificate encoding failed! (" + e.getMessage() + ")", e); - } catch (OperatorCreationException e) { - throw new AuthException("Certificate signing failed! (" + e.getMessage() + ")", e); - } + return CertificateAuthorityUtils.buildCertificate(csr, cloudPrivateKey, cloudCertificate, validFrom, validUntil, + random); } } diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java new file mode 100644 index 000000000..54158a4de --- /dev/null +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java @@ -0,0 +1,143 @@ +package eu.arrowhead.core.certificate_authority; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Base64; +import java.util.Date; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.cert.CertIOException; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentVerifierProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder; +import org.bouncycastle.pkcs.PKCSException; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; + +import eu.arrowhead.common.Utilities; +import eu.arrowhead.common.dto.internal.CertificateSigningRequestDTO; +import eu.arrowhead.common.exception.AuthException; +import eu.arrowhead.common.exception.BadPayloadException; +import eu.arrowhead.common.exception.DataNotFoundException; + +class CertificateAuthorityUtils { + + private static final Logger logger = LogManager.getLogger(CertificateAuthorityService.class); + + static JcaPKCS10CertificationRequest decodePKCS10CSR(CertificateSigningRequestDTO csr) { + try { + final byte[] csrBytes = Base64.getDecoder().decode(csr.getEncodedCSR()); + return new JcaPKCS10CertificationRequest(csrBytes); + } catch (Exception ex) { + logger.error("Failed to parse request as a PKCS10 CSR, because: " + ex.getMessage()); + throw new BadPayloadException("Failed to parse request as a PKCS10 CSR", ex); + } + } + + static String encodeCertificate(X509Certificate certificate) { + try { + return Base64.getEncoder().encodeToString(certificate.getEncoded()); + } catch (CertificateEncodingException e) { + throw new AuthException("Certificate encoding failed! (" + e.getMessage() + ")", e); + } + } + + static void checkCommonName(JcaPKCS10CertificationRequest csr, String cloudCN) { + final String clientCN = Utilities.getCertCNFromSubject(csr.getSubject().toString()); + if (!Utilities.isKeyStoreCNArrowheadValid(clientCN, cloudCN)) { + throw new BadPayloadException( + "Certificate request does not have a valid common name! Valid common name: {systemName}." + + cloudCN); + } + } + + static void checkCsrSignature(JcaPKCS10CertificationRequest csr) { + Security.addProvider(new BouncyCastleProvider()); + try { + ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().setProvider("BC") + .build(csr.getSubjectPublicKeyInfo()); + if (!csr.isSignatureValid(verifierProvider)) { + throw new AuthException("Certificate request has invalid signature! (key pair does not match)"); + } + } catch (OperatorCreationException | PKCSException e) { + throw new AuthException("Encapsulated " + e.getClass().getCanonicalName() + ": " + e.getMessage(), e); + } + } + + static PublicKey getClientKey(JcaPKCS10CertificationRequest csr) { + try { + return csr.getPublicKey(); + } catch (InvalidKeyException | NoSuchAlgorithmException e) { + // This should not be possible after a successful signature verification + throw new DataNotFoundException("Extracting the public key from the CSR failed (" + e.getMessage() + ")", + e); + } + } + + static X509Certificate buildCertificate(JcaPKCS10CertificationRequest csr, PrivateKey cloudPrivateKey, + X509Certificate cloudCertificate, Date validFrom, Date validUntil, SecureRandom random) { + + final ZonedDateTime now = LocalDateTime.now().atZone(ZoneId.systemDefault()); + final BigInteger serial = BigInteger.valueOf(now.toInstant().toEpochMilli()) + .multiply(BigInteger.valueOf(random.nextLong())); + + final PublicKey clientKey = getClientKey(csr); + + X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(cloudCertificate, serial, validFrom, + validUntil, csr.getSubject(), clientKey); + + /** + * Adding the following extensions to the new certificate: + * + * 1) The subject key identifier provides a hashed value that should uniquely + * identify the public key + * + * 2) The authority key identifier provides a hashed value that should uniquely + * identify the issuer of the certificate + * + * 3) And this basic constraint is for forbidding issuing other certificates + * under this certificate + */ + try { + JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); + builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(clientKey)); + builder.addExtension(Extension.authorityKeyIdentifier, false, + extUtils.createAuthorityKeyIdentifier(cloudCertificate)); + builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); + } catch (NoSuchAlgorithmException | CertIOException | CertificateEncodingException e) { + throw new AuthException("Appending extensions to the certificate failed! (" + e.getMessage() + ")", e); + } + + JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA512withRSA").setProvider("BC"); + try { + return new JcaX509CertificateConverter().setProvider("BC") + .getCertificate(builder.build(signerBuilder.build(cloudPrivateKey))); + } catch (CertificateException e) { + throw new AuthException("Certificate encoding failed! (" + e.getMessage() + ")", e); + } catch (OperatorCreationException e) { + throw new AuthException("Certificate signing failed! (" + e.getMessage() + ")", e); + } + } + + private CertificateAuthorityUtils() { + } +} diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java index 5a147e1dc..475614cd6 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java @@ -3,17 +3,11 @@ import java.util.Map; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import eu.arrowhead.common.CommonConstants; import eu.arrowhead.common.CoreCommonConstants; -import eu.arrowhead.common.Utilities; import eu.arrowhead.common.core.CoreSystem; -import eu.arrowhead.common.core.CoreSystemService; -import eu.arrowhead.common.dto.shared.ServiceQueryFormDTO; -import eu.arrowhead.common.dto.shared.ServiceRegistryRequestDTO; -import eu.arrowhead.common.exception.AuthException; import eu.arrowhead.common.security.CoreSystemAccessControlFilter; @Component From c9148f4870659cd77418b1245b47404e21664086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Tue, 25 Feb 2020 21:12:43 +0100 Subject: [PATCH 04/26] Add CA to Docker related files --- docker-all/example/docker-compose.yml | 2 + .../certificateauthority.properties | 59 +++++++++++++++++++ docker-all/example/sql/setup.sql | 43 +++++++++++++- .../certificateauthority.properties | 59 +++++++++++++++++++ docker/docker-compose.yml | 12 +++- 5 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 docker-all/example/properties/certificateauthority.properties create mode 100644 docker/core_system_config/certificateauthority.properties diff --git a/docker-all/example/docker-compose.yml b/docker-all/example/docker-compose.yml index ab10fdf71..3e193d153 100644 --- a/docker-all/example/docker-compose.yml +++ b/docker-all/example/docker-compose.yml @@ -27,6 +27,7 @@ services: #- ./properties/eventhandler.properties:/opt/arrowhead-core/eventhandler/application.properties - ./properties/gatekeeper.properties:/opt/arrowhead-core/gatekeeper/application.properties - ./properties/gateway.properties:/opt/arrowhead-core/gateway/application.properties + - ./properties/certificateauthority.properties:/opt/arrowhead-core/certificate-authority/application.properties ports: - 8443:8443 # Service Registry - 8445:8445 # Authorization @@ -34,6 +35,7 @@ services: #- 8455:8455 # Event Handler - 8449:8449 # Gatekeeper - 8453:8453 # Gateway + - 8448:8448 # Certificate Authority volumes: arrowhead_core_mysql: diff --git a/docker-all/example/properties/certificateauthority.properties b/docker-all/example/properties/certificateauthority.properties new file mode 100644 index 000000000..93f325bd4 --- /dev/null +++ b/docker-all/example/properties/certificateauthority.properties @@ -0,0 +1,59 @@ +############################################ +### APPLICATION PARAMETERS ### +############################################ + +# Database connection (mandatory) +# Change the server timezone if neccessary +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/arrowhead?serverTimezone=Europe/Budapest +spring.datasource.username=certificate_authority +spring.datasource.password=FsdG6Kgf9QpPfv2 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect +# use true only for debugging +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.hibernate.ddl-auto=none + +# Certificate Authority web-server parameters +server.address=127.0.0.1 +server.port=8448 +domain.name=127.0.0.1 +domain.port=8448 + +############################################ +### CUSTOM PARAMETERS ### +############################################ + +# Name of the core system +core_system_name=certificate_authority + +# Show all request/response in debug log +log_all_request_and_response=false + +# Service Registry web-server parameters (to register the Certificate-Authority services) +sr_address=127.0.0.1 +sr_port=8443 + +############################################ +### SECURE MODE ### +############################################ + +# configure secure mode + +# Set this to false to disable https mode +server.ssl.enabled=true + +server.ssl.key-store-type=PKCS12 +server.ssl.key-store=classpath:certificates/certificate_authority.p12 +server.ssl.key-store-password=123456 +server.ssl.key-alias=certificate_authority +server.ssl.key-password=123456 +server.ssl.client-auth=need +server.ssl.trust-store-type=PKCS12 +server.ssl.trust-store=classpath:certificates/truststore.p12 +server.ssl.trust-store-password=123456 + +#If true, http client does not check whether the hostname is match one of the server's SAN in its certificate +#Just for testing, DO NOT USE this feature in production environment +disable.hostname.verifier=false diff --git a/docker-all/example/sql/setup.sql b/docker-all/example/sql/setup.sql index 755b9a6e5..1ad2be0ae 100644 --- a/docker-all/example/sql/setup.sql +++ b/docker-all/example/sql/setup.sql @@ -537,4 +537,45 @@ GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'gateway'@'%'; GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'gateway'@'%'; GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'gateway'@'%'; -FLUSH PRIVILEGES; \ No newline at end of file +-- Certificate Authority +DROP USER IF EXISTS 'certificate_authority'@'localhost'; +CREATE USER IF NOT EXISTS 'certificate_authority'@'localhost' IDENTIFIED BY 'FsdG6Kgf9QpPfv2'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority_interface_connection` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'certificate_authority'@'localhost'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'certificate_authority'@'localhost'; + +DROP USER IF EXISTS 'certificate_authority'@'%'; +CREATE USER IF NOT EXISTS 'certificate_authority'@'%' IDENTIFIED BY 'FsdG6Kgf9QpPfv2'; +GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority_interface_connection` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'certificate_authority'@'%'; +GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'certificate_authority'@'%'; + +FLUSH PRIVILEGES; diff --git a/docker/core_system_config/certificateauthority.properties b/docker/core_system_config/certificateauthority.properties new file mode 100644 index 000000000..93f325bd4 --- /dev/null +++ b/docker/core_system_config/certificateauthority.properties @@ -0,0 +1,59 @@ +############################################ +### APPLICATION PARAMETERS ### +############################################ + +# Database connection (mandatory) +# Change the server timezone if neccessary +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/arrowhead?serverTimezone=Europe/Budapest +spring.datasource.username=certificate_authority +spring.datasource.password=FsdG6Kgf9QpPfv2 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect +# use true only for debugging +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.hibernate.ddl-auto=none + +# Certificate Authority web-server parameters +server.address=127.0.0.1 +server.port=8448 +domain.name=127.0.0.1 +domain.port=8448 + +############################################ +### CUSTOM PARAMETERS ### +############################################ + +# Name of the core system +core_system_name=certificate_authority + +# Show all request/response in debug log +log_all_request_and_response=false + +# Service Registry web-server parameters (to register the Certificate-Authority services) +sr_address=127.0.0.1 +sr_port=8443 + +############################################ +### SECURE MODE ### +############################################ + +# configure secure mode + +# Set this to false to disable https mode +server.ssl.enabled=true + +server.ssl.key-store-type=PKCS12 +server.ssl.key-store=classpath:certificates/certificate_authority.p12 +server.ssl.key-store-password=123456 +server.ssl.key-alias=certificate_authority +server.ssl.key-password=123456 +server.ssl.client-auth=need +server.ssl.trust-store-type=PKCS12 +server.ssl.trust-store=classpath:certificates/truststore.p12 +server.ssl.trust-store-password=123456 + +#If true, http client does not check whether the hostname is match one of the server's SAN in its certificate +#Just for testing, DO NOT USE this feature in production environment +disable.hostname.verifier=false diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 5748354fa..ebeb3c0c3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -72,7 +72,17 @@ services: volumes: - ./core_system_config/gateway.properties:/gateway/application.properties ports: - - 8453:8453 + - 8453:8453 + + certificateauthority: + container_name: certificateauthority + image: svetlint/certificateauthority:latest + depends_on: + - mysql + volumes: + - ./core_system_config/certificateauthority.properties:/certificateauthority/application.properties + ports: + - 8448:8448 volumes: mysql: From 83eda37f2c74919e0438646af83c7e01abc326af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Tue, 25 Feb 2020 21:18:50 +0100 Subject: [PATCH 05/26] Add CA to start/stop scripts --- scripts/initSQL.sh | 3 ++- scripts/start_coresystems.bat | 4 ++++ scripts/start_coresystems.sh | 5 +++++ scripts/start_coresystems_local.sh | 4 ++++ scripts/stop_coresystems.sh | 4 +++- 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/scripts/initSQL.sh b/scripts/initSQL.sh index 123f4d41e..4e84421d9 100644 --- a/scripts/initSQL.sh +++ b/scripts/initSQL.sh @@ -16,4 +16,5 @@ wget https://raw.githubusercontent.com/arrowhead-f/core-java-spring/master/scrip wget https://raw.githubusercontent.com/arrowhead-f/core-java-spring/master/scripts/event_handler_privileges.sql wget https://raw.githubusercontent.com/arrowhead-f/core-java-spring/master/scripts/choreographer_privileges.sql wget https://raw.githubusercontent.com/arrowhead-f/core-java-spring/master/scripts/gatekeeper_privileges.sql -wget https://raw.githubusercontent.com/arrowhead-f/core-java-spring/master/scripts/gateway_privileges.sql \ No newline at end of file +wget https://raw.githubusercontent.com/arrowhead-f/core-java-spring/master/scripts/gateway_privileges.sql +wget https://raw.githubusercontent.com/arrowhead-f/core-java-spring/master/scripts/certificate_authority_privileges.sql diff --git a/scripts/start_coresystems.bat b/scripts/start_coresystems.bat index 3e96b0ad8..2f86c4be2 100644 --- a/scripts/start_coresystems.bat +++ b/scripts/start_coresystems.bat @@ -36,6 +36,10 @@ cd ..\..\choreographer\target START "" /B "cmd /c javaw -jar arrowhead-choreographer-4.1.3.jar > sout_choreographer.log 2>&1" echo Choreographer started +cd ..\..\certificate-authority\target +START "" /B "cmd /c javaw -jar arrowhead-certificate-authority-4.1.3.jar > sout_ca.log 2>&1" +echo Certificate Authority started + cd %parent_path% ::Kill self diff --git a/scripts/start_coresystems.sh b/scripts/start_coresystems.sh index 4d3c7044b..24dbf6fde 100644 --- a/scripts/start_coresystems.sh +++ b/scripts/start_coresystems.sh @@ -35,3 +35,8 @@ echo Orchestrator started cd ../../choreographer/target nohup java -jar $(find . -maxdepth 1 -name arrowhead-choreographer-\*.jar | sort | tail -n1) &> sout_choreographer.log & echo Choreographer started + +cd ../../certificate-authority/target +nohup java -jar $(find . -maxdepth 1 -name arrowhead-certificate-authority-\*.jar | sort | tail -n1) &> sout_ca.log & +echo Certificate Authority started + diff --git a/scripts/start_coresystems_local.sh b/scripts/start_coresystems_local.sh index 884b5cb5d..1d0f3e9a1 100644 --- a/scripts/start_coresystems_local.sh +++ b/scripts/start_coresystems_local.sh @@ -26,3 +26,7 @@ echo Authorization started cd ../../orchestrator/target nohup java -jar $(find . -maxdepth 1 -name arrowhead-orchestrator-\*.jar | sort | tail -n1) &> sout_orch.log & echo Orchestrator started + +cd ../../certificate-authority/target +nohup java -jar $(find . -maxdepth 1 -name arrowhead-certificate-authority-\*.jar | sort | tail -n1) &> sout_ca.log & +echo Certificate Authority started diff --git a/scripts/stop_coresystems.sh b/scripts/stop_coresystems.sh index b19816282..1540cc284 100644 --- a/scripts/stop_coresystems.sh +++ b/scripts/stop_coresystems.sh @@ -7,6 +7,7 @@ pkill -f gatekeeper pkill -f authorization pkill -f eventhandler pkill -f gateway +pkill -f certificate-authority sleep 5s pkill -f serviceregistry sleep 2s @@ -19,8 +20,9 @@ then kill -KILL $(ps aux | grep 'authorization' | awk '{print $2}') kill -KILL $(ps aux | grep 'eventhandler' | awk '{print $2}') kill -KILL $(ps aux | grep 'gateway' | awk '{print $2}') + kill -KILL $(ps aux | grep 'certificate-authority' | awk '{print $2}') kill -KILL $(ps aux | grep 'serviceregistry' | awk '{print $2}') echo Core systems forcefully killed else echo Core systems killed -fi \ No newline at end of file +fi From 07767e545b7d0743f543f198d4b74d590692ec9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Wed, 26 Feb 2020 14:13:22 +0100 Subject: [PATCH 06/26] Add tests for CA --- .../CertificateAuthorityControllerTest.java | 185 ++++++++++++++++++ .../src/test/resources/banner.txt | 0 .../test/resources/certificates/notvalid.pem | 20 ++ .../src/test/resources/certificates/valid.csr | 1 + .../src/test/resources/certificates/valid.pem | 20 ++ .../src/test/resources/log4j2.xml | 49 +++++ 6 files changed, 275 insertions(+) create mode 100644 certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityControllerTest.java create mode 100644 certificate-authority/src/test/resources/banner.txt create mode 100644 certificate-authority/src/test/resources/certificates/notvalid.pem create mode 100644 certificate-authority/src/test/resources/certificates/valid.csr create mode 100644 certificate-authority/src/test/resources/certificates/valid.pem create mode 100644 certificate-authority/src/test/resources/log4j2.xml diff --git a/certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityControllerTest.java b/certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityControllerTest.java new file mode 100644 index 000000000..6e426cf34 --- /dev/null +++ b/certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityControllerTest.java @@ -0,0 +1,185 @@ +package eu.arrowhead.core.certificate_authority; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import eu.arrowhead.common.dto.internal.CertificateSigningRequestDTO; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = CertificateAuthorityMain.class) +@AutoConfigureMockMvc +public class CertificateAuthorityControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void testEchoWithoutCert() throws Exception { + mockMvc.perform(get("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/certificate-authority/echo") + .secure(true)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void testEchoWithInvalidCert() throws Exception { + mockMvc.perform(get("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/certificate-authority/echo") + .secure(true) + .with(x509("certificates/notvalid.pem"))) + .andExpect(status().isUnauthorized()); + } + + @Test + public void testEchoWithValidCert() throws Exception { + mockMvc.perform(get("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/certificate-authority/echo") + .secure(true) + .with(x509("certificates/valid.pem"))) + .andExpect(status().isOk()); + } + + @Test + public void testGetCloudCommonNameWithoutCert() throws Exception { + mockMvc.perform(get("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/certificate-authority/name") + .secure(true)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void testGetCloudCommonNameWithInvalidCert() throws Exception { + mockMvc.perform(get("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/certificate-authority/name") + .secure(true) + .with(x509("certificates/notvalid.pem"))) + .andExpect(status().isUnauthorized()); + } + + @Test + public void testGetCloudCommonNameWithValidCert() throws Exception { + mockMvc.perform(get("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/certificate-authority/name") + .secure(true) + .with(x509("certificates/valid.pem"))) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_PLAIN)) + .andExpect(content().string("testcloud2.aitia.arrowhead.eu")); + } + + @Test + public void testSignWithoutCert() throws Exception { + mockMvc.perform(post("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/certificate-authority/sign") + .secure(true)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void testSignWithInvalidCert() throws Exception { + mockMvc.perform(post("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/certificate-authority/sign") + .secure(true) + .with(x509("certificates/notvalid.pem"))) + .andExpect(status().isUnauthorized()); + } + + @Test + public void testSignGet() throws Exception { + mockMvc.perform(get("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/certificate-authority/sign") + .secure(true) + .with(x509("certificates/valid.pem"))) + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void testSignPut() throws Exception { + mockMvc.perform(put("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/certificate-authority/sign") + .secure(true) + .with(x509("certificates/valid.pem"))) + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void testSignPatch() throws Exception { + mockMvc.perform(patch("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/certificate-authority/sign") + .secure(true) + .with(x509("certificates/valid.pem"))) + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void testSignDelete() throws Exception { + mockMvc.perform(delete("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/certificate-authority/sign") + .secure(true) + .with(x509("certificates/valid.pem"))) + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void testSignHead() throws Exception { + mockMvc.perform(head("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/certificate-authority/sign") + .secure(true) + .with(x509("certificates/valid.pem"))) + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void testSignOptions() throws Exception { + mockMvc.perform(options("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/certificate-authority/sign") + .secure(true) + .with(x509("certificates/valid.pem"))) + .andExpect(status().isOk()); + } + + @Test + public void testSignPostInvalidContentType() throws Exception { + mockMvc.perform(post("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/certificate-authority/sign") + .secure(true) + .with(x509("certificates/valid.pem"))) + .andExpect(status().isUnsupportedMediaType()); + } + + @Test + public void testSignPostWithValidCSR() throws Exception { + mockMvc.perform(post("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/certificate-authority/sign") + .secure(true) + .with(x509("certificates/valid.pem")) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(asJsonString(new CertificateSigningRequestDTO(getResourceContent("certificates/valid.csr"))))) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.certificateChain").isArray()); + } + + private static String asJsonString(final Object obj) { + try { + return new ObjectMapper().writeValueAsString(obj); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static String getResourceContent(String resourcePath) throws IOException { + File resource = new ClassPathResource(resourcePath).getFile(); + return new String(Files.readAllBytes(resource.toPath())).trim(); + } +} diff --git a/certificate-authority/src/test/resources/banner.txt b/certificate-authority/src/test/resources/banner.txt new file mode 100644 index 000000000..e69de29bb diff --git a/certificate-authority/src/test/resources/certificates/notvalid.pem b/certificate-authority/src/test/resources/certificates/notvalid.pem new file mode 100644 index 000000000..a70c46837 --- /dev/null +++ b/certificate-authority/src/test/resources/certificates/notvalid.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDMzCCAhugAwIBAgIEXNlw3zANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh +cnJvd2hlYWQuZXUwHhcNMTkwNTEzMTMyNzU5WhcNMjkwNTEzMTMyNzU5WjAoMSYw +JAYDVQQDDB10ZXN0Y2xvdWQxLmFpdGlhLmFycm93aGVhZC5ldTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANTIWrd0mcvxx6LfPG6RvviN5VY1n9uphoYm +CoRA47scc0duc6kIWsIydEsKD9xEd2WdClbQXVazLSF+OWGocxBuyPaAjgu4DdVY +pJLgAaeSzbcjqS+A6bB3B6i8A9RxCWwtDJ5EJsUuIOa9yqcfkmdXVOHpNlwycPeH +FEOIUdUcd+RAL0Fw4h39a3HmTDf4nb2Px/3CWvZk89woxDXURJfyDl2fJyB3kLbp +4HWBht9YZNw2ZuIT1fO5rfm2XGEaPiUseI4KEwDyhW36HWOxv3oP9lMWjCaeWFg1 +A4q6sbjm+fsEsZJavgPWANINCTxKyy/IclwmxKimS6aDBHHyW6UCAwEAAaN2MHQw +DwYDVR0TBAgwBgEB/wIBAjBCBgNVHSMEOzA5gBSaipkeMUwesuYttprtxx4xyt07 +/qEbpBkwFzEVMBMGA1UEAwwMYXJyb3doZWFkLmV1ggRc1T6tMB0GA1UdDgQWBBT0 +jA9ICBgevPSr86oEvVJ8uDamJTANBgkqhkiG9w0BAQsFAAOCAQEAiy1OVJulFL2V +HOYlERD2MJCgIaVKoN6GGxGXQ7mBAjGE+am+dKvbGZ9l5ADRKxKgd1T0hklfTZJU +j7CHBp1JJ7cj7OkFyountQcdTJ3WVLDMxfRRictRwuEUaKMry4nmcU2WpDTJ+DSV +H6qW3VlXxJKnGe1M3u14wUx5k0iXr4S2oYkq/WZ9TUkLWKmzVPDlU3x6iSsyZj/5 +1a5o2qNceezzpGhcvoiHwQ0MFm9OLDYrh61Zb4M7jhn2AocHV4f3Y0P0IfdLlXq5 +MIgOK1ds2BekemekJ3VnW/TDj8fY6sxWl8aWFYJ1RhmVWTODiN9xu9XPyzAO5ryk +zG4AOQa5rQ== +-----END CERTIFICATE----- diff --git a/certificate-authority/src/test/resources/certificates/valid.csr b/certificate-authority/src/test/resources/certificates/valid.csr new file mode 100644 index 000000000..d1d8fc393 --- /dev/null +++ b/certificate-authority/src/test/resources/certificates/valid.csr @@ -0,0 +1 @@ +MIIDKTCCAhECAQAwTDEPMA0GA1UECgwGZWlsYWJzMQswCQYDVQQGEwJodTEsMCoGA1UEAwwjc3lzb3AudGVzdGNsb3VkMi5haXRpYS5hcnJvd2hlYWQuZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgKgJZOMH5QiLxYLz6bKcDAOkLvh13EIVG3W6CdNJpbYcYljnuZlGZ+38A+HLsL4fJfLOPvsrpMKSv2NfEDKSuQaHJt0nchM26WiVkiYfZXPxYduZy9wqi1069V7E2i7vYsqJEytugjEhEk+1O1rtFuGZXIitNUSUBUNYWNkc/sr3wCGgZrcAOnTFuHSorUGm4Kc8qbrgNtjnbPCvnyZJp7IvsyAT51bBYfH2vhf9Yjv/apAiKjwNO/Hfo8cYq6u3CnFpXSj6BD5DVhwPWZ9IenfG1hACdldEsiNoN3ssZYwbuPcduq6SSNOHIqtQIp+spdAUi0FQnnw8kmryxPGgvAgMBAAGggZcwFAYJKoZIhvcNAQkHMQcMBTI0NTM0MBUGCSqGSIb3DQEJAjEIDAZlaWxhYnMwaAYJKoZIhvcNAQkOMVswWTAPBgNVHREECDAGhwR/AAABMCcGA1UdIwQgMB6AFAQwTQgjkz3eZj0WWqfRyV4jppdPoQCCBFzZYhkwHQYDVR0OBBYEFAVG6MVZFFyHQXW6qlQWA4EfXWSIMA0GCSqGSIb3DQEBCwUAA4IBAQAkRvc+/O/GWx3dg6CgnpWSCFZKnaaD1l+1Rls93qZZ3jP1tukK+7xh+1AcRNnB5h5tNW0Zaa7KrYIS/RX2wsqGRIil4iTmGaa2/MPFY3GAm65891JuF4t0dk/5GcOJjZOAEV603ils9qxZ1np3asQRGiFAXbvL1tr+2iaQR2olfsn00qSw+zYICKPtBAJhUcmxavMrlZPoiCiJzWqQZMNOlYzJitim8N8rMwKZnwmDdgqgjkVrIQXKVh3Q2xw2mGlqPIRToLSgnGtLQ5caNVhBeF1OnfBxziP8SVckazTSlJ8uVm8an6Jt2R6VQf1avuclAxWAwS8CyAn5TpJNzVjL diff --git a/certificate-authority/src/test/resources/certificates/valid.pem b/certificate-authority/src/test/resources/certificates/valid.pem new file mode 100644 index 000000000..56eae293d --- /dev/null +++ b/certificate-authority/src/test/resources/certificates/valid.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDLzCCAhegAwIBAgIEXPYrATANBgkqhkiG9w0BAQsFADAoMSYwJAYDVQQDDB10 +ZXN0Y2xvdWQyLmFpdGlhLmFycm93aGVhZC5ldTAeFw0xOTA2MDQwODI1MzdaFw0y +MDA2MDMwODI1MzdaMC4xLDAqBgNVBAMMI3N5c29wLnRlc3RjbG91ZDIuYWl0aWEu +YXJyb3doZWFkLmV1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoCoC +WTjB+UIi8WC8+mynAwDpC74ddxCFRt1ugnTSaW2HGJY57mZRmft/APhy7C+HyXyz +j77K6TCkr9jXxAykrkGhybdJ3ITNulolZImH2Vz8WHbmcvcKotdOvVexNou72LKi +RMrboIxIRJPtTta7RbhmVyIrTVElAVDWFjZHP7K98AhoGa3ADp0xbh0qK1BpuCnP +Km64DbY52zwr58mSaeyL7MgE+dWwWHx9r4X/WI7/2qQIio8DTvx36PHGKurtwpxa +V0o+gQ+Q1YcD1mfSHp3xtYQAnZXRLIjaDd7LGWMG7j3HbqukkjThyKrUCKfrKXQF +ItBUJ58PJJq8sTxoLwIDAQABo1swWTAPBgNVHREECDAGhwR/AAABMCcGA1UdIwQg +MB6AFAQwTQgjkz3eZj0WWqfRyV4jppdPoQCCBFzZYhkwHQYDVR0OBBYEFAVG6MVZ +FFyHQXW6qlQWA4EfXWSIMA0GCSqGSIb3DQEBCwUAA4IBAQBUEExzkf2bH4fF/tyP +r7DxO94aqQOySGJq/25DzfZQOpuK5Ud3MPpXSlxYw+MB5rRDJu0NzGUX+ha4mG3T +dXxd3X2QaDFR0SYUD4J0hTtmYp9TzgCXnU+J7t6sku/LYFJrCl5m4o124ShZXBgR +Dj/gDVd4C6InTPhpIPHaD6D97wKyObbC3V1dGiVs5Z5/+wUJdUFUmnfAb1rbX0nV +0TTKluWTtRT4cjBeIFs7MZiThMx8bgSE4mZIC1ry4PikjAap/oEIQVxbBTbl+5q8 +xhPbPn+JhZwQp39a1oPMEQog0wQhtKvZrYx//H1o3mzRo1ZFzL+R/LCKKElMPMXx +UHKC +-----END CERTIFICATE----- diff --git a/certificate-authority/src/test/resources/log4j2.xml b/certificate-authority/src/test/resources/log4j2.xml new file mode 100644 index 000000000..0b21f3cce --- /dev/null +++ b/certificate-authority/src/test/resources/log4j2.xml @@ -0,0 +1,49 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex + + OFF + DEBUG + + + + + + + + ${LOG_PATTERN} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8049b8efd51e4aa9f039badb11ebbf9b37e72333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Wed, 26 Feb 2020 14:25:29 +0100 Subject: [PATCH 07/26] Add configuration options to CA --- .../certificate_authority/CAProperties.java | 24 +++++++++++++++++++ .../CertificateAuthorityService.java | 10 ++++++-- .../eu/arrowhead/common/CommonConstants.java | 7 +++++- .../java/eu/arrowhead/common/Defaults.java | 9 ++++--- 4 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CAProperties.java diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CAProperties.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CAProperties.java new file mode 100644 index 000000000..a466f1fe3 --- /dev/null +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CAProperties.java @@ -0,0 +1,24 @@ +package eu.arrowhead.core.certificate_authority; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import eu.arrowhead.common.CommonConstants; + +@Component +public class CAProperties { + + @Value(CommonConstants.$CA_CERT_VALIDITY_NEG_OFFSET_MILLIS) + private long certValidityNegativeOffsetMillis; + + @Value(CommonConstants.$CA_CERT_VALIDITY_POS_OFFSET_MILLIS) + private long certValidityPositiveOffsetMillis; + + public long getCertValidityNegativeOffsetMillis() { + return certValidityNegativeOffsetMillis; + } + + public long getCertValidityPositiveOffsetMillis() { + return certValidityPositiveOffsetMillis; + } +} diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java index 1ff0e8b78..5478381c3 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java @@ -22,6 +22,7 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.time.Duration; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -35,6 +36,9 @@ public class CertificateAuthorityService { @Autowired private SSLProperties sslProperties; + @Autowired + private CAProperties caProperties; + private SecureRandom random; private KeyStore keyStore; @@ -99,8 +103,10 @@ private List buildEncodedCertificateChain(X509Certificate clientCertific private X509Certificate buildCertificate(JcaPKCS10CertificationRequest csr, PrivateKey cloudPrivateKey, X509Certificate cloudCertificate) { final ZonedDateTime now = LocalDateTime.now().atZone(ZoneId.systemDefault()); - final Date validFrom = Date.from(now.minusSeconds(5).toInstant()); - final Date validUntil = Date.from(now.plusYears(1).toInstant()); + final Date validFrom = Date.from( + now.minus(Duration.ofMillis(caProperties.getCertValidityPositiveOffsetMillis())).toInstant()); + final Date validUntil = Date.from( + now.plus(Duration.ofMillis(caProperties.getCertValidityPositiveOffsetMillis())).toInstant()); return CertificateAuthorityUtils.buildCertificate(csr, cloudPrivateKey, cloudCertificate, validFrom, validUntil, random); diff --git a/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java b/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java index 457f71c82..d80a91573 100644 --- a/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java +++ b/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java @@ -155,6 +155,11 @@ public class CommonConstants { public static final String DISABLE_HOSTNAME_VERIFIER = "disable.hostname.verifier"; public static final String $DISABLE_HOSTNAME_VERIFIER_WD = "${" + DISABLE_HOSTNAME_VERIFIER + ":" + Defaults.DEFAULT_DISABLE_HOSTNAME_VERIFIER + "}"; + public static final String CA_CERT_VALIDITY_NEG_OFFSET_MILLIS = "ca.validity.negative-offest-millis"; + public static final String $CA_CERT_VALIDITY_NEG_OFFSET_MILLIS = "${" + CA_CERT_VALIDITY_NEG_OFFSET_MILLIS + ":" + Defaults.DEFAULT_CA_CERT_VALIDITY_NEG_OFFSET_MILLIS + "}"; + public static final String CA_CERT_VALIDITY_POS_OFFSET_MILLIS = "ca.validity.positive-offest-millis"; + public static final String $CA_CERT_VALIDITY_POS_OFFSET_MILLIS = "${" + CA_CERT_VALIDITY_POS_OFFSET_MILLIS + ":" + Defaults.DEFAULT_CA_CERT_VALIDITY_POS_OFFSET_MILLIS + "}"; + public static final String JWT_CLAIM_CONSUMER_ID = "cid"; public static final String JWT_CLAIM_SERVICE_ID = "sid"; public static final String JWT_CLAIM_INTERFACE_ID = "iid"; @@ -202,4 +207,4 @@ public class CommonConstants { private CommonConstants() { throw new UnsupportedOperationException(); } -} \ No newline at end of file +} diff --git a/core-common/src/main/java/eu/arrowhead/common/Defaults.java b/core-common/src/main/java/eu/arrowhead/common/Defaults.java index e5db9ed31..20aa16ee1 100644 --- a/core-common/src/main/java/eu/arrowhead/common/Defaults.java +++ b/core-common/src/main/java/eu/arrowhead/common/Defaults.java @@ -19,7 +19,7 @@ public class Defaults { public static final int DEFAULT_GATEWAY_PORT = 8453; public static final int DEFAULT_EVENT_HANDLER_PORT = 8455; public static final int DEFAULT_CHOREOGRAPHER_PORT = 8457; - public static final int DEFAULT_CERTIFICATE_AUTHORITY_PORT = 8459; + public static final int DEFAULT_CERTIFICATE_AUTHORITY_PORT = 8448; // HTTP client defaults public static final int DEFAULT_CONNECTION_TIMEOUT = 30000; @@ -29,9 +29,12 @@ public class Defaults { // CORS defaults public static final long CORS_MAX_AGE = 600; public static final String CORS_ALLOW_CREDENTIALS = "true"; - + + // CA defaults + public static final long DEFAULT_CA_CERT_VALIDITY_NEG_OFFSET_MILLIS = 5000; + public static final long DEFAULT_CA_CERT_VALIDITY_POS_OFFSET_MILLIS = 1000 * 3600 * 24 * 365; //------------------------------------------------------------------------------------------------- private Defaults() { throw new UnsupportedOperationException(); } -} \ No newline at end of file +} From 7533efe090add13a411d7744619f643160c36d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Wed, 26 Feb 2020 15:03:13 +0100 Subject: [PATCH 08/26] Disable logging in CA tests --- certificate-authority/src/test/resources/log4j2.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certificate-authority/src/test/resources/log4j2.xml b/certificate-authority/src/test/resources/log4j2.xml index 0b21f3cce..931cbfebb 100644 --- a/certificate-authority/src/test/resources/log4j2.xml +++ b/certificate-authority/src/test/resources/log4j2.xml @@ -5,7 +5,7 @@ %d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex OFF - DEBUG + OFF From 153d03614cbe8615011a3a492cf93df3c4ae7fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Wed, 26 Feb 2020 15:35:43 +0100 Subject: [PATCH 09/26] Add CA to the CoreSystem's list --- .../src/main/java/eu/arrowhead/common/CommonConstants.java | 2 ++ .../src/main/java/eu/arrowhead/common/core/CoreSystem.java | 4 ++-- .../java/eu/arrowhead/common/core/CoreSystemService.java | 7 +++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java b/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java index d80a91573..f64ace8cc 100644 --- a/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java +++ b/core-common/src/main/java/eu/arrowhead/common/CommonConstants.java @@ -42,6 +42,8 @@ public class CommonConstants { public static final String CORE_SERVICE_EVENT_HANDLER_SUBSCRIBE = "event-subscribe"; public static final String CORE_SERVICE_EVENT_HANDLER_UNSUBSCRIBE = "event-unsubscribe"; public static final String CORE_SERVICE_EVENT_HANDLER_PUBLISH_AUTH_UPDATE = "event-publish-auth-update"; + + public static final String CORE_SERVICE_CERTIFICATE_AUTHORITY_SIGN = "ca-sign"; public static final String COMMON_FIELD_NAME_ID = "id"; diff --git a/core-common/src/main/java/eu/arrowhead/common/core/CoreSystem.java b/core-common/src/main/java/eu/arrowhead/common/core/CoreSystem.java index 4e39afa2a..5c9e053c2 100644 --- a/core-common/src/main/java/eu/arrowhead/common/core/CoreSystem.java +++ b/core-common/src/main/java/eu/arrowhead/common/core/CoreSystem.java @@ -23,7 +23,7 @@ public enum CoreSystem { , CoreSystemService.EVENT_UNSUBSCRIBE_SERVICE, CoreSystemService.EVENT_PUBLISH_AUTH_UPDATE_SERVICE)), GATEWAY(Defaults.DEFAULT_GATEWAY_PORT, List.of(CoreSystemService.GATEWAY_PUBLIC_KEY_SERVICE, CoreSystemService.GATEWAY_PROVIDER_SERVICE,CoreSystemService.GATEWAY_CONSUMER_SERVICE)), CHOREOGRAPHER(Defaults.DEFAULT_CHOREOGRAPHER_PORT, List.of()), // TODO: add services - CERTIFICATE_AUTHORITY(Defaults.DEFAULT_CERTIFICATE_AUTHORITY_PORT, List.of()); // TODO: add services + CERTIFICATE_AUTHORITY(Defaults.DEFAULT_CERTIFICATE_AUTHORITY_PORT, List.of(CoreSystemService.CERTIFICATE_AUTHORITY_SIGN_SERVICE)); //================================================================================================= // members @@ -47,4 +47,4 @@ private CoreSystem(final int defaultPort, final List services this.services = services != null ? Collections.unmodifiableList(services) : List.of(); this.defaultPort = defaultPort; } -} \ No newline at end of file +} diff --git a/core-common/src/main/java/eu/arrowhead/common/core/CoreSystemService.java b/core-common/src/main/java/eu/arrowhead/common/core/CoreSystemService.java index 39f6db738..dcd649ae5 100644 --- a/core-common/src/main/java/eu/arrowhead/common/core/CoreSystemService.java +++ b/core-common/src/main/java/eu/arrowhead/common/core/CoreSystemService.java @@ -33,7 +33,10 @@ public enum CoreSystemService { EVENT_PUBLISH_SERVICE(CommonConstants.CORE_SERVICE_EVENT_HANDLER_PUBLISH, CommonConstants.EVENT_HANDLER_URI + CommonConstants.OP_EVENT_HANDLER_PUBLISH), EVENT_SUBSCRIBE_SERVICE(CommonConstants.CORE_SERVICE_EVENT_HANDLER_SUBSCRIBE, CommonConstants.EVENT_HANDLER_URI + CommonConstants.OP_EVENT_HANDLER_SUBSCRIBE), EVENT_UNSUBSCRIBE_SERVICE(CommonConstants.CORE_SERVICE_EVENT_HANDLER_UNSUBSCRIBE, CommonConstants.EVENT_HANDLER_URI + CommonConstants.OP_EVENT_HANDLER_UNSUBSCRIBE), - EVENT_PUBLISH_AUTH_UPDATE_SERVICE(CommonConstants.CORE_SERVICE_EVENT_HANDLER_PUBLISH_AUTH_UPDATE, CommonConstants.EVENT_HANDLER_URI + CommonConstants.OP_EVENT_HANDLER_PUBLISH_AUTH_UPDATE); + EVENT_PUBLISH_AUTH_UPDATE_SERVICE(CommonConstants.CORE_SERVICE_EVENT_HANDLER_PUBLISH_AUTH_UPDATE, CommonConstants.EVENT_HANDLER_URI + CommonConstants.OP_EVENT_HANDLER_PUBLISH_AUTH_UPDATE), + + CERTIFICATE_AUTHORITY_SIGN_SERVICE(CommonConstants.CORE_SERVICE_CERTIFICATE_AUTHORITY_SIGN, CommonConstants.CERTIFICATE_AUTHRORITY_URI + CommonConstants.OP_CA_SIGN_CERTIFICATE_URI); + //TODO: additional services @@ -61,4 +64,4 @@ private CoreSystemService(final String serviceDefinition, final String serviceUr this.serviceDefinition = serviceDefinition; this.serviceUri = serviceUri; } -} \ No newline at end of file +} From 01879d7dc73c570f7bf8558b8540de6424fde174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Thu, 27 Feb 2020 10:34:37 +0100 Subject: [PATCH 10/26] Add missing CA project files to version control --- certificate-authority/.classpath | 55 ++++++++++++++++++++++++++++++++ certificate-authority/.project | 23 +++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 certificate-authority/.classpath create mode 100644 certificate-authority/.project diff --git a/certificate-authority/.classpath b/certificate-authority/.classpath new file mode 100644 index 000000000..6e0021feb --- /dev/null +++ b/certificate-authority/.classpath @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/certificate-authority/.project b/certificate-authority/.project new file mode 100644 index 000000000..9b4a8b591 --- /dev/null +++ b/certificate-authority/.project @@ -0,0 +1,23 @@ + + + arrowhead-certificate-authority + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + From cfd3eb874a4cf85de2a3587c14124cd8a289c947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Wed, 4 Mar 2020 15:06:35 +0100 Subject: [PATCH 11/26] Fail at startup if cloud certificate is not valid --- .../CertificateAuthorityService.java | 20 +++++-------------- .../CertificateAuthorityUtils.java | 17 ++++++++++++++++ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java index 5478381c3..bc6284422 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java @@ -7,11 +7,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -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.pkcs.jcajce.JcaPKCS10CertificationRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -19,7 +14,6 @@ import javax.annotation.PostConstruct; import java.io.IOException; import java.security.*; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Duration; @@ -44,6 +38,7 @@ public class CertificateAuthorityService { private X509Certificate rootCertificate; private X509Certificate cloudCertificate; + private String cloudCommonName; @PostConstruct private void init() { @@ -53,26 +48,21 @@ private void init() { rootCertificate = Utilities.getRootCertFromKeyStore(keyStore); cloudCertificate = Utilities.getCloudCertFromKeyStore(keyStore); + cloudCommonName = CertificateAuthorityUtils.getCloudCommonName(cloudCertificate); } public String getCloudCommonName() { - try { - final X500Name subject = new JcaX509CertificateHolder(cloudCertificate).getSubject(); - final RDN cn = subject.getRDNs(BCStyle.CN)[0]; - return IETFUtils.valueToString(cn.getFirst().getValue()); - } catch (CertificateEncodingException e) { - throw new ServiceConfigurationError("Cannot get cloud common name from server cert.", e); - } + return cloudCommonName; } public CertificateSigningResponseDTO signCertificate(CertificateSigningRequestDTO request) { final JcaPKCS10CertificationRequest csr = CertificateAuthorityUtils.decodePKCS10CSR(request); - CertificateAuthorityUtils.checkCommonName(csr, getCloudCommonName()); + CertificateAuthorityUtils.checkCommonName(csr, cloudCommonName); CertificateAuthorityUtils.checkCsrSignature(csr); logger.info("Signing certificate for " + csr.getSubject().toString() + "..."); - final PrivateKey cloudPrivateKey = Utilities.getPrivateKey(keyStore, getCloudCommonName(), + final PrivateKey cloudPrivateKey = Utilities.getPrivateKey(keyStore, cloudCommonName, sslProperties.getKeyPassword()); final X509Certificate clientCertificate = buildCertificate(csr, cloudPrivateKey, cloudCertificate); diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java index 54158a4de..8d45f1062 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java @@ -15,14 +15,20 @@ import java.time.ZonedDateTime; import java.util.Base64; import java.util.Date; +import java.util.ServiceConfigurationError; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +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.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.cert.CertIOException; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -61,6 +67,17 @@ static String encodeCertificate(X509Certificate certificate) { } } + static String getCloudCommonName(X509Certificate cloudCertificate) { + try { + final X500Name subject = new JcaX509CertificateHolder(cloudCertificate).getSubject(); + final RDN cn = subject.getRDNs(BCStyle.CN)[0]; + return IETFUtils.valueToString(cn.getFirst().getValue()); + } catch (CertificateEncodingException | NullPointerException e) { + logger.error("Cannot get common name from cloud cert, because: " + e.getMessage()); + throw new ServiceConfigurationError("Cannot get common name from cloud cert.", e); + } + } + static void checkCommonName(JcaPKCS10CertificationRequest csr, String cloudCN) { final String clientCN = Utilities.getCertCNFromSubject(csr.getSubject().toString()); if (!Utilities.isKeyStoreCNArrowheadValid(clientCN, cloudCN)) { From 048a1a17e93f28d6af4e5c12bf0e1c73fd35e84e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Wed, 4 Mar 2020 15:57:57 +0100 Subject: [PATCH 12/26] Extract string literals to constants --- .../certificate_authority/CertificateAuthorityUtils.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java index 8d45f1062..aa2c28409 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java @@ -49,6 +49,9 @@ class CertificateAuthorityUtils { private static final Logger logger = LogManager.getLogger(CertificateAuthorityService.class); + private static final String PROVIDER = "BC"; + private static final String SIGNATURE_ALGORITM = "SHA512withRSA"; + static JcaPKCS10CertificationRequest decodePKCS10CSR(CertificateSigningRequestDTO csr) { try { final byte[] csrBytes = Base64.getDecoder().decode(csr.getEncodedCSR()); @@ -90,7 +93,7 @@ static void checkCommonName(JcaPKCS10CertificationRequest csr, String cloudCN) { static void checkCsrSignature(JcaPKCS10CertificationRequest csr) { Security.addProvider(new BouncyCastleProvider()); try { - ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().setProvider("BC") + ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().setProvider(PROVIDER) .build(csr.getSubjectPublicKeyInfo()); if (!csr.isSignatureValid(verifierProvider)) { throw new AuthException("Certificate request has invalid signature! (key pair does not match)"); @@ -144,9 +147,9 @@ static X509Certificate buildCertificate(JcaPKCS10CertificationRequest csr, Priva throw new AuthException("Appending extensions to the certificate failed! (" + e.getMessage() + ")", e); } - JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA512withRSA").setProvider("BC"); + JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITM).setProvider(PROVIDER); try { - return new JcaX509CertificateConverter().setProvider("BC") + return new JcaX509CertificateConverter().setProvider(PROVIDER) .getCertificate(builder.build(signerBuilder.build(cloudPrivateKey))); } catch (CertificateException e) { throw new AuthException("Certificate encoding failed! (" + e.getMessage() + ")", e); From c6091f841b95918212b15582d4b2d50f24edda3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Wed, 4 Mar 2020 15:59:22 +0100 Subject: [PATCH 13/26] Initialize Security provider only once --- .../CertificateAuthorityApplicationInitListener.java | 11 +++++++++++ .../CertificateAuthorityUtils.java | 3 --- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java index 159dd1c88..aa3e97061 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityApplicationInitListener.java @@ -1,9 +1,20 @@ package eu.arrowhead.core.certificate_authority; +import java.security.Security; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import eu.arrowhead.common.ApplicationInitListener; @Component public class CertificateAuthorityApplicationInitListener extends ApplicationInitListener { + @Override + protected void customInit(ContextRefreshedEvent event) { + logger.debug("customInit started..."); + + Security.addProvider(new BouncyCastleProvider()); + } + } diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java index aa2c28409..06e8b73c3 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java @@ -6,7 +6,6 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; -import java.security.Security; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -31,7 +30,6 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentVerifierProvider; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; @@ -91,7 +89,6 @@ static void checkCommonName(JcaPKCS10CertificationRequest csr, String cloudCN) { } static void checkCsrSignature(JcaPKCS10CertificationRequest csr) { - Security.addProvider(new BouncyCastleProvider()); try { ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().setProvider(PROVIDER) .build(csr.getSubjectPublicKeyInfo()); From 995b4e9d7890769fbad7a74731752a43fb8a3c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Wed, 4 Mar 2020 16:14:21 +0100 Subject: [PATCH 14/26] Fix typo --- .../core/certificate_authority/CertificateAuthorityUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java index 06e8b73c3..e6ede305f 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java @@ -122,7 +122,7 @@ static X509Certificate buildCertificate(JcaPKCS10CertificationRequest csr, Priva X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(cloudCertificate, serial, validFrom, validUntil, csr.getSubject(), clientKey); - /** + /* * Adding the following extensions to the new certificate: * * 1) The subject key identifier provides a hashed value that should uniquely From 654c2128d5e4f8e42dbe1f412d4655d333e18396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Thu, 5 Mar 2020 16:21:01 +0100 Subject: [PATCH 15/26] Add SAN extensions to the certificate --- .../CertificateAuthorityUtils.java | 76 +++++++++++++++---- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java index e6ede305f..742d58aeb 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java @@ -12,18 +12,25 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Base64; import java.util.Date; +import java.util.List; import java.util.ServiceConfigurationError; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.pkcs.Attribute; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 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.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.cert.CertIOException; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; @@ -48,7 +55,7 @@ class CertificateAuthorityUtils { private static final Logger logger = LogManager.getLogger(CertificateAuthorityService.class); private static final String PROVIDER = "BC"; - private static final String SIGNATURE_ALGORITM = "SHA512withRSA"; + private static final String SIGNATURE_ALGORITHM = "SHA512withRSA"; static JcaPKCS10CertificationRequest decodePKCS10CSR(CertificateSigningRequestDTO csr) { try { @@ -122,20 +129,43 @@ static X509Certificate buildCertificate(JcaPKCS10CertificationRequest csr, Priva X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(cloudCertificate, serial, validFrom, validUntil, csr.getSubject(), clientKey); - /* + addCertificateExtensions(builder, csr, clientKey, cloudCertificate); + + JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER); + try { + return new JcaX509CertificateConverter().setProvider(PROVIDER) + .getCertificate(builder.build(signerBuilder.build(cloudPrivateKey))); + } catch (CertificateException e) { + throw new AuthException("Certificate encoding failed! (" + e.getMessage() + ")", e); + } catch (OperatorCreationException e) { + throw new AuthException("Certificate signing failed! (" + e.getMessage() + ")", e); + } + } + + /** * Adding the following extensions to the new certificate: + *

    + *
  1. The subject alternative name makes possible to use the certificate for + * accessing a host via IP address or hostname
  2. * - * 1) The subject key identifier provides a hashed value that should uniquely - * identify the public key + *
  3. The subject key identifier provides a hashed value that should uniquely + * identify the public key
  4. * - * 2) The authority key identifier provides a hashed value that should uniquely - * identify the issuer of the certificate + *
  5. The authority key identifier provides a hashed value that should uniquely + * identify the issuer of the certificate
  6. * - * 3) And this basic constraint is for forbidding issuing other certificates - * under this certificate + *
  7. And this basic constraint is for forbidding issuing other certificates + * under this certificate
  8. + *
*/ + static void addCertificateExtensions(X509v3CertificateBuilder builder, JcaPKCS10CertificationRequest csr, + PublicKey clientKey, X509Certificate cloudCertificate) { try { JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); + + final List subjectAlternativeNames = getSubjectAlternativeNames(csr); + builder.addExtension(Extension.subjectAlternativeName, false, + new GeneralNames(subjectAlternativeNames.toArray(new GeneralName[] {}))); builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(clientKey)); builder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(cloudCertificate)); @@ -143,16 +173,30 @@ static X509Certificate buildCertificate(JcaPKCS10CertificationRequest csr, Priva } catch (NoSuchAlgorithmException | CertIOException | CertificateEncodingException e) { throw new AuthException("Appending extensions to the certificate failed! (" + e.getMessage() + ")", e); } + } - JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITM).setProvider(PROVIDER); - try { - return new JcaX509CertificateConverter().setProvider(PROVIDER) - .getCertificate(builder.build(signerBuilder.build(cloudPrivateKey))); - } catch (CertificateException e) { - throw new AuthException("Certificate encoding failed! (" + e.getMessage() + ")", e); - } catch (OperatorCreationException e) { - throw new AuthException("Certificate signing failed! (" + e.getMessage() + ")", e); + static List getSubjectAlternativeNames(JcaPKCS10CertificationRequest csr) { + List alternativeNames = new ArrayList<>(); + + for (final Attribute attribute : csr.getAttributes()) { + if (attribute == null) { + continue; + } + if (attribute.getAttrType().equals(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) { + final Extensions extensions = Extensions.getInstance(attribute.getAttrValues().getObjectAt(0)); + final GeneralNames generalNames = GeneralNames.fromExtensions(extensions, + Extension.subjectAlternativeName); + if (generalNames != null && generalNames.getNames() != null && generalNames.getNames().length > 0) { + for (final GeneralName name : generalNames.getNames()) { + if (name.getTagNo() == GeneralName.dNSName || name.getTagNo() == GeneralName.iPAddress) { + alternativeNames.add(name); + } + } + } + } } + + return alternativeNames; } private CertificateAuthorityUtils() { From 039ffaa38fd6c7524adc593e5b2ccf623bc142e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Thu, 5 Mar 2020 16:46:31 +0100 Subject: [PATCH 16/26] Remove unnecessary DB permissions for CA --- docker-all/example/sql/setup.sql | 32 ------------------ scripts/certificate_authority_privileges.sql | 34 -------------------- 2 files changed, 66 deletions(-) diff --git a/docker-all/example/sql/setup.sql b/docker-all/example/sql/setup.sql index 1ad2be0ae..8fc8e0225 100644 --- a/docker-all/example/sql/setup.sql +++ b/docker-all/example/sql/setup.sql @@ -540,42 +540,10 @@ GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'gateway'@'%'; -- Certificate Authority DROP USER IF EXISTS 'certificate_authority'@'localhost'; CREATE USER IF NOT EXISTS 'certificate_authority'@'localhost' IDENTIFIED BY 'FsdG6Kgf9QpPfv2'; -GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority_interface_connection` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'certificate_authority'@'localhost'; GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'certificate_authority'@'localhost'; DROP USER IF EXISTS 'certificate_authority'@'%'; CREATE USER IF NOT EXISTS 'certificate_authority'@'%' IDENTIFIED BY 'FsdG6Kgf9QpPfv2'; -GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority_interface_connection` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'certificate_authority'@'%'; GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'certificate_authority'@'%'; FLUSH PRIVILEGES; diff --git a/scripts/certificate_authority_privileges.sql b/scripts/certificate_authority_privileges.sql index b09cc1cbb..e39d0c9f7 100644 --- a/scripts/certificate_authority_privileges.sql +++ b/scripts/certificate_authority_privileges.sql @@ -1,43 +1,9 @@ USE `arrowhead`; REVOKE ALL, GRANT OPTION FROM 'certificate_authority'@'localhost'; - -GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority_interface_connection` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'certificate_authority'@'localhost'; -GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'certificate_authority'@'localhost'; GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'certificate_authority'@'localhost'; REVOKE ALL, GRANT OPTION FROM 'certificate_authority'@'%'; - -GRANT ALL PRIVILEGES ON `arrowhead`.`system_` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`service_definition` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`service_interface` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`certificate_authority_interface_connection` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`relay` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gatekeeper_relay` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`cloud_gateway_relay` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_intra_cloud_interface_connection` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`authorization_inter_cloud_interface_connection` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`orchestrator_store` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`subscription` TO 'certificate_authority'@'%'; -GRANT ALL PRIVILEGES ON `arrowhead`.`subscription_publisher_connection` TO 'certificate_authority'@'%'; GRANT ALL PRIVILEGES ON `arrowhead`.`logs` TO 'certificate_authority'@'%'; FLUSH PRIVILEGES; From cb0ba2099f9d2479430b37c2caa9eb092001a7df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Thu, 5 Mar 2020 17:27:11 +0100 Subject: [PATCH 17/26] Add description for the configurable CA properties --- ...itional-spring-configuration-metadata.json | 30 ++++--------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/serviceregistry/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/serviceregistry/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 8ccdfc698..2623b21e2 100644 --- a/serviceregistry/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/serviceregistry/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1,28 +1,13 @@ {"properties": [ { - "name": "ping_scheduled", - "type": "java.lang.Boolean", - "description": "A description for 'ping_scheduled'" - }, - { - "name": "ping_timeout", - "type": "java.lang.Long", - "description": "A description for 'ping_timeout'" - }, - { - "name": "ping_interval", + "name": "ca.validity.negative-offest-millis", "type": "java.lang.Long", - "description": "A description for 'ping_interval'" + "description": "The negative offset in milliseconds from the current time for the beginning of the validity of the generated certificates" }, { - "name": "ttl_scheduled", - "type": "java.lang.Boolean", - "description": "A description for 'ttl_scheduled'" - }, - { - "name": "ttl_interval", + "name": "ca.validity.positive-offest-millis", "type": "java.lang.Long", - "description": "A description for 'ttl_interval'" + "description": "The offset in milliseconds from the current time for the end of the validity of the generated certificates" }, { "name": "disable.hostname.verifier", @@ -34,11 +19,6 @@ "type": "java.lang.Boolean", "description": "A description for 'log_all_request_and_response'" }, - { - "name": "use_strict_service_intf_name_verifier", - "type": "java.lang.Boolean", - "description": "A description for 'use_strict_service_intf_name_verifier'" - }, { "name": "core_system_name", "type": "java.lang.String", @@ -54,4 +34,4 @@ "type": "java.lang.Integer", "description": "A description for 'domain.port'" } -]} \ No newline at end of file +]} From 23a57e082fee29c6b8132e65ec5f8a09751d4047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Thu, 5 Mar 2020 17:34:32 +0100 Subject: [PATCH 18/26] Removed unnecessary lines from CA deb scripts --- certificate-authority/src/deb/control/postinst | 6 ------ certificate-authority/src/deb/control/postrm | 5 ++--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/certificate-authority/src/deb/control/postinst b/certificate-authority/src/deb/control/postinst index 79e93a8f3..e7ed018aa 100644 --- a/certificate-authority/src/deb/control/postinst +++ b/certificate-authority/src/deb/control/postinst @@ -44,12 +44,6 @@ case "$1" in ah_cert_signed_system ${SYSTEM_NAME} - if [ ! -d "${AH_SYSTEMS_DIR}/sysop" ]; then - mkdir -p ${AH_SYSTEMS_DIR}/sysop - fi - - ah_cert_signed_system sysop - # modify log config and application.properties ah_transform_log_file ${SYSTEM_DIR} diff --git a/certificate-authority/src/deb/control/postrm b/certificate-authority/src/deb/control/postrm index 1af2f066c..9b46d8e3a 100644 --- a/certificate-authority/src/deb/control/postrm +++ b/certificate-authority/src/deb/control/postrm @@ -31,12 +31,11 @@ case "$1" in rm -f \ /var/log/arrowhead/${SYSTEM_NAME}*.log \ - ${SYSTEM_DIR}/log4j2.xml \ + ${SYSTEM_DIR}/log4j2.xml \ ${SYSTEM_DIR}/application.properties \ ${SYSTEM_DIR}/${SYSTEM_NAME}.p12 \ - ${AH_SYSTEMS_DIR}/sysop/sysop.p12 rmdir ${SYSTEM_DIR} 2>/dev/null || true - rmdir ${AH_SYSTEMS_DIR}/sysop 2>/dev/null || true + rmdir ${AH_SYSTEMS_DIR}/sysop 2>/dev/null || true db_purge ;; remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) From b23d7922e6affacf9c1c0d3b992a976a6547d64d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Tue, 10 Mar 2020 15:03:45 +0100 Subject: [PATCH 19/26] Validate CertificateSigningRequest --- .../CertificateAuthorityController.java | 33 ++++++++++++++++--- .../CertificateAuthorityService.java | 28 ++++++++++++++-- .../CertificateAuthorityControllerTest.java | 22 +++++++++++++ .../CertificateSigningRequestDTO.java | 3 ++ 4 files changed, 80 insertions(+), 6 deletions(-) diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java index a2275d366..75608ddb3 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityController.java @@ -1,10 +1,15 @@ package eu.arrowhead.core.certificate_authority; +import java.util.List; + +import javax.validation.Valid; + import eu.arrowhead.common.CommonConstants; import eu.arrowhead.common.CoreCommonConstants; import eu.arrowhead.common.Defaults; import eu.arrowhead.common.dto.internal.CertificateSigningRequestDTO; import eu.arrowhead.common.dto.internal.CertificateSigningResponseDTO; +import eu.arrowhead.common.exception.BadPayloadException; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; @@ -15,7 +20,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @Api(tags = { CoreCommonConstants.SWAGGER_TAG_ALL }) @CrossOrigin(maxAge = Defaults.CORS_MAX_AGE, allowCredentials = Defaults.CORS_ALLOW_CREDENTIALS, @@ -27,7 +40,7 @@ public class CertificateAuthorityController { //================================================================================================= // members -// + // private static final String GET_CLOUD_COMMON_NAME_HTTP_200_MESSAGE = "Cloud Common Name returned"; private static final String SIGN_CERTIFICATE_HTTP_200_MESSAGE = "Successful certificate signing"; private static final String SIGN_CERTIFICATE_HTTP_400_MESSAGE = "Invalid Certificate Signing Request"; @@ -67,7 +80,7 @@ public String getCloudCommonName() { } //------------------------------------------------------------------------------------------------- - @ApiOperation(value = "Return a signed certificate", response = CertificateSigningResponseDTO.class, tags = { CoreCommonConstants.SWAGGER_TAG_CLIENT}) + @ApiOperation(value = "Return a signed certificate", response = CertificateSigningResponseDTO.class, tags = { CoreCommonConstants.SWAGGER_TAG_CLIENT }) @ApiResponses(value = { @ApiResponse(code = HttpStatus.SC_OK, message = SIGN_CERTIFICATE_HTTP_200_MESSAGE), @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = SIGN_CERTIFICATE_HTTP_400_MESSAGE), @@ -75,7 +88,19 @@ public String getCloudCommonName() { @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = CoreCommonConstants.SWAGGER_HTTP_500_MESSAGE) }) @PostMapping(path = CommonConstants.OP_CA_SIGN_CERTIFICATE_URI, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody public CertificateSigningResponseDTO signCertificate(@RequestBody final CertificateSigningRequestDTO request) { + @ResponseBody + public CertificateSigningResponseDTO signCertificate(@Valid @RequestBody final CertificateSigningRequestDTO request, BindingResult bindingResult) { + handleBindingResult(bindingResult); return certificateAuthorityService.signCertificate(request); } + + private void handleBindingResult(BindingResult bindingResult) { + if (bindingResult.hasErrors()) { + final List allErrors = bindingResult.getAllErrors(); + for (ObjectError error : allErrors) { + logger.debug(error.toString()); + } + throw new BadPayloadException(allErrors.get(0).getDefaultMessage()); + } + } } diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java index bc6284422..55f6a2f9d 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityService.java @@ -4,6 +4,7 @@ import eu.arrowhead.common.Utilities; import eu.arrowhead.common.dto.internal.CertificateSigningRequestDTO; import eu.arrowhead.common.dto.internal.CertificateSigningResponseDTO; +import eu.arrowhead.common.exception.InvalidParameterException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -13,14 +14,21 @@ import javax.annotation.PostConstruct; import java.io.IOException; -import java.security.*; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Duration; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.ServiceConfigurationError; @Service public class CertificateAuthorityService { @@ -56,6 +64,8 @@ public String getCloudCommonName() { } public CertificateSigningResponseDTO signCertificate(CertificateSigningRequestDTO request) { + verifyCertificateSigningRequest(request); + final JcaPKCS10CertificationRequest csr = CertificateAuthorityUtils.decodePKCS10CSR(request); CertificateAuthorityUtils.checkCommonName(csr, cloudCommonName); CertificateAuthorityUtils.checkCsrSignature(csr); @@ -82,6 +92,20 @@ private KeyStore getKeyStore() { } } + private static void verifyCertificateSigningRequest(CertificateSigningRequestDTO request) { + if (request == null) { + logger.error("CertificateSigningRequest cannot be null"); + throw new InvalidParameterException("CertificateSigningRequest cannot be null"); + } + + final String encodedCSR = request.getEncodedCSR(); + + if (encodedCSR == null || encodedCSR.isEmpty()) { + logger.error("CertificateSigningRequest cannot be empty"); + throw new InvalidParameterException("CertificateSigningRequest cannot be empty"); + } + } + private List buildEncodedCertificateChain(X509Certificate clientCertificate) { final ArrayList encodedCertificateChain = new ArrayList<>(); encodedCertificateChain.add(CertificateAuthorityUtils.encodeCertificate(clientCertificate)); diff --git a/certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityControllerTest.java b/certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityControllerTest.java index 6e426cf34..dda6e9b0b 100644 --- a/certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityControllerTest.java +++ b/certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityControllerTest.java @@ -157,6 +157,28 @@ public void testSignPostInvalidContentType() throws Exception { .andExpect(status().isUnsupportedMediaType()); } + @Test + public void testSignPostWithNullCSR() throws Exception { + mockMvc.perform(post("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/certificate-authority/sign") + .secure(true) + .with(x509("certificates/valid.pem")) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(asJsonString(new CertificateSigningRequestDTO(null)))) + .andExpect(status().isBadRequest()); + } + + @Test + public void testSignPostWithInvalidCSR() throws Exception { + mockMvc.perform(post("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/certificate-authority/sign") + .secure(true) + .with(x509("certificates/valid.pem")) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(asJsonString(new CertificateSigningRequestDTO("INVALID")))) + .andExpect(status().isBadRequest()); + } + @Test public void testSignPostWithValidCSR() throws Exception { mockMvc.perform(post("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/certificate-authority/sign") diff --git a/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java b/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java index 0231f9bcd..780c30e81 100644 --- a/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java +++ b/core-common/src/main/java/eu/arrowhead/common/dto/internal/CertificateSigningRequestDTO.java @@ -2,10 +2,13 @@ import java.io.Serializable; +import javax.validation.constraints.NotBlank; + public class CertificateSigningRequestDTO implements Serializable { private static final long serialVersionUID = -6810780579000655432L; + @NotBlank(message = "The encodedCSR is mandatory") private String encodedCSR; public CertificateSigningRequestDTO() {} From 846944ef0495c13eb6d6f43324edb34fcd8d6d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Tue, 10 Mar 2020 15:38:26 +0100 Subject: [PATCH 20/26] Add NPE checks, tests --- .../CertificateAuthorityUtils.java | 49 ++++++---- .../CertificateAuthorityUtilsTest.java | 94 +++++++++++++++++++ 2 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtilsTest.java diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java index 742d58aeb..ac012b205 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtils.java @@ -49,6 +49,7 @@ import eu.arrowhead.common.exception.AuthException; import eu.arrowhead.common.exception.BadPayloadException; import eu.arrowhead.common.exception.DataNotFoundException; +import eu.arrowhead.common.exception.InvalidParameterException; class CertificateAuthorityUtils { @@ -61,16 +62,16 @@ static JcaPKCS10CertificationRequest decodePKCS10CSR(CertificateSigningRequestDT try { final byte[] csrBytes = Base64.getDecoder().decode(csr.getEncodedCSR()); return new JcaPKCS10CertificationRequest(csrBytes); - } catch (Exception ex) { - logger.error("Failed to parse request as a PKCS10 CSR, because: " + ex.getMessage()); - throw new BadPayloadException("Failed to parse request as a PKCS10 CSR", ex); + } catch (Exception e) { + logger.error("Failed to parse request as a PKCS10 CSR, because: " + e.getMessage()); + throw new BadPayloadException("Failed to parse request as a PKCS10 CSR", e); } } static String encodeCertificate(X509Certificate certificate) { try { return Base64.getEncoder().encodeToString(certificate.getEncoded()); - } catch (CertificateEncodingException e) { + } catch (CertificateEncodingException | NullPointerException e) { throw new AuthException("Certificate encoding failed! (" + e.getMessage() + ")", e); } } @@ -87,6 +88,12 @@ static String getCloudCommonName(X509Certificate cloudCertificate) { } static void checkCommonName(JcaPKCS10CertificationRequest csr, String cloudCN) { + if (csr == null) { + throw new BadPayloadException("CSR cannot be null"); + } + if (cloudCN == null || cloudCN.isEmpty()) { + throw new BadPayloadException("CloudCN cannot be null"); + } final String clientCN = Utilities.getCertCNFromSubject(csr.getSubject().toString()); if (!Utilities.isKeyStoreCNArrowheadValid(clientCN, cloudCN)) { throw new BadPayloadException( @@ -97,20 +104,21 @@ static void checkCommonName(JcaPKCS10CertificationRequest csr, String cloudCN) { static void checkCsrSignature(JcaPKCS10CertificationRequest csr) { try { - ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().setProvider(PROVIDER) + final ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder() + .setProvider(PROVIDER) .build(csr.getSubjectPublicKeyInfo()); if (!csr.isSignatureValid(verifierProvider)) { - throw new AuthException("Certificate request has invalid signature! (key pair does not match)"); + throw new BadPayloadException("Certificate request has invalid signature! (key pair does not match)"); } - } catch (OperatorCreationException | PKCSException e) { - throw new AuthException("Encapsulated " + e.getClass().getCanonicalName() + ": " + e.getMessage(), e); + } catch (OperatorCreationException | PKCSException | NullPointerException e) { + throw new BadPayloadException("Encapsulated " + e.getClass().getCanonicalName() + ": " + e.getMessage(), e); } } static PublicKey getClientKey(JcaPKCS10CertificationRequest csr) { try { return csr.getPublicKey(); - } catch (InvalidKeyException | NoSuchAlgorithmException e) { + } catch (InvalidKeyException | NoSuchAlgorithmException | NullPointerException e) { // This should not be possible after a successful signature verification throw new DataNotFoundException("Extracting the public key from the CSR failed (" + e.getMessage() + ")", e); @@ -136,28 +144,28 @@ static X509Certificate buildCertificate(JcaPKCS10CertificationRequest csr, Priva return new JcaX509CertificateConverter().setProvider(PROVIDER) .getCertificate(builder.build(signerBuilder.build(cloudPrivateKey))); } catch (CertificateException e) { - throw new AuthException("Certificate encoding failed! (" + e.getMessage() + ")", e); + throw new BadPayloadException("Certificate encoding failed! (" + e.getMessage() + ")", e); } catch (OperatorCreationException e) { - throw new AuthException("Certificate signing failed! (" + e.getMessage() + ")", e); + throw new BadPayloadException("Certificate signing failed! (" + e.getMessage() + ")", e); } } /** - * Adding the following extensions to the new certificate: + * Adding the following extensions to the new certificate: *
    *
  1. The subject alternative name makes possible to use the certificate for * accessing a host via IP address or hostname
  2. - * + * *
  3. The subject key identifier provides a hashed value that should uniquely * identify the public key
  4. - * + * *
  5. The authority key identifier provides a hashed value that should uniquely * identify the issuer of the certificate
  6. - * + * *
  7. And this basic constraint is for forbidding issuing other certificates * under this certificate
  8. *
- */ + */ static void addCertificateExtensions(X509v3CertificateBuilder builder, JcaPKCS10CertificationRequest csr, PublicKey clientKey, X509Certificate cloudCertificate) { try { @@ -170,14 +178,17 @@ static void addCertificateExtensions(X509v3CertificateBuilder builder, JcaPKCS10 builder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(cloudCertificate)); builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); - } catch (NoSuchAlgorithmException | CertIOException | CertificateEncodingException e) { - throw new AuthException("Appending extensions to the certificate failed! (" + e.getMessage() + ")", e); + } catch (NoSuchAlgorithmException | CertIOException | CertificateEncodingException | NullPointerException e) { + throw new InvalidParameterException("Appending extensions to the certificate failed! (" + e.getMessage() + ")", e); } } static List getSubjectAlternativeNames(JcaPKCS10CertificationRequest csr) { - List alternativeNames = new ArrayList<>(); + if (csr == null) { + throw new InvalidParameterException("CSR cannot be null"); + } + List alternativeNames = new ArrayList<>(); for (final Attribute attribute : csr.getAttributes()) { if (attribute == null) { continue; diff --git a/certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtilsTest.java b/certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtilsTest.java new file mode 100644 index 000000000..a11fe32a8 --- /dev/null +++ b/certificate-authority/src/test/java/eu/arrowhead/core/certificate_authority/CertificateAuthorityUtilsTest.java @@ -0,0 +1,94 @@ +package eu.arrowhead.core.certificate_authority; + +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Base64; +import java.util.ServiceConfigurationError; + +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; +import org.hamcrest.core.IsInstanceOf; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.springframework.core.io.ClassPathResource; +import org.springframework.test.context.junit4.SpringRunner; + +import eu.arrowhead.common.dto.internal.CertificateSigningRequestDTO; +import eu.arrowhead.common.exception.AuthException; +import eu.arrowhead.common.exception.BadPayloadException; +import eu.arrowhead.common.exception.DataNotFoundException; + +@RunWith(SpringRunner.class) +public class CertificateAuthorityUtilsTest { + + @Rule + public ExpectedException thrownException = ExpectedException.none(); + + @Test(expected = BadPayloadException.class) + public void testDecodePKCS10CSRNull() { + CertificateAuthorityUtils.decodePKCS10CSR(null); + } + + @Test(expected = BadPayloadException.class) + public void testDecodePKCS10CSREmpty() { + CertificateAuthorityUtils.decodePKCS10CSR(new CertificateSigningRequestDTO()); + } + + @Test(expected = BadPayloadException.class) + public void testDecodePKCS10CSRInvalidString() { + CertificateAuthorityUtils.decodePKCS10CSR(new CertificateSigningRequestDTO("Invalid CSR String")); + } + + @Test + public void testEncodeCertificateNull() { + thrownException.expect(IsInstanceOf.instanceOf(AuthException.class)); + thrownException.expectCause(IsInstanceOf.instanceOf(NullPointerException.class)); + CertificateAuthorityUtils.encodeCertificate(null); + } + + @Test(expected = ServiceConfigurationError.class) + public void testGetCloudCommonNameNullCert() { + CertificateAuthorityUtils.getCloudCommonName(null); + } + + @Test(expected = BadPayloadException.class) + public void testCheckCommonNameNullCSR() { + CertificateAuthorityUtils.checkCommonName(null, "cloudCN"); + } + + @Test(expected = BadPayloadException.class) + public void testCheckCommonNameNullCloudCN() throws NullPointerException, IOException { + final JcaPKCS10CertificationRequest csr = getResourceCSR("certificates/valid.csr"); + assertNotNull(csr); + CertificateAuthorityUtils.checkCommonName(csr, null); + } + + @Test(expected = BadPayloadException.class) + public void testCheckCsrSignatureNull() { + CertificateAuthorityUtils.checkCsrSignature(null); + } + + @Test + public void testGetClientKeyNull() { + thrownException.expect(DataNotFoundException.class); + thrownException.expectCause(IsInstanceOf.instanceOf(NullPointerException.class)); + CertificateAuthorityUtils.getClientKey(null); + } + + private static String getResourceContentAsString(final String resourcePath) throws IOException { + final File resource = new ClassPathResource(resourcePath).getFile(); + return new String(Files.readAllBytes(resource.toPath())).trim(); + } + + private static JcaPKCS10CertificationRequest getResourceCSR( + final String resourcePath) + throws IOException, NullPointerException { + final String resourceBytes = getResourceContentAsString(resourcePath); + final byte[] csrBytes = Base64.getDecoder().decode(resourceBytes); + return new JcaPKCS10CertificationRequest(csrBytes); + } +} From 69d6720c2a4db0e813bb6ff6ddb378b66f73f336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Mon, 16 Mar 2020 11:39:18 +0100 Subject: [PATCH 21/26] Add documentation --- README.md | 109 +++++++++++++++++- ...eleaseNotes-4.1.3-CertificateAuthority.odt | Bin 0 -> 10850 bytes 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 documentation/certificate_authority/ReleaseNotes-4.1.3-CertificateAuthority.odt diff --git a/README.md b/README.md index c1a70b759..6aa500b7c 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,14 @@ Please be aware, that 4.1.3 is __NOT__ backwards compatible with 4.1.2. If you h * [Client](#gateway_endpoints_client) * [Private](#gateway_endpoints_private) * [Management](#gateway_endpoints_mgmt) - 6. [QoS Monitor (Quality of Service Monitor)](#qos_monitor) + 7. [Certificate Authority](#ca) + * [System Design Description Overview](#ca_sdd) + * [Services and Use Cases](#ca_usecases) + * [Endpoints](#ca_endpoints) + * [Client](#ca_endpoints_client) + * [Private](#ca_endpoints_private) + * [Management](#ca_endpoints_mgmt) + 8. [QoS Monitor (Quality of Service Monitor)](#qos_monitor) * [System Design Description Overview](#qos_monitor_sdd) * [Services and Use Cases](#qos_monitor_usecases) * [Endpoints](#qos_monitor_endpoints) @@ -6273,6 +6280,106 @@ __ActiveSession__ is the output. | `responseControlQueue` | control queue of response messaging through the the Relay | | `sessionStartedAt` | Time stamp of session start | + + +# Certificate Authority + + + +## System Design Description Overview + +The purpose of the Certificate Authority supporting core system is issuing signed certificates to be used in the local cloud. + + + +## Provided services + +The Certificate Authority provides the following services: +* [Echo](#ca_endpoints_get_echo) +* [Certificate signing](#ca_endpoints_sign) + + + +## Use cases + +The main use case of the Certificate Authority is to issue signed certificates to new consumers coming via the Onboarding Controller. + + + +## Endpoints + + + +### Client endpoint description
+ +| Function | URL subpath | Method | Input | Output | +| -------- | ----------- | ------ | ----- | ------ | +| [Echo](#ca_endpoints_get_echo) | /echo | GET | - | OK | + +
+ +### Private endpoint description
+ +| Function | URL subpath | Method | Input | Output | +| -------- | ----------- | ------ | ----- | ------ | +| [Sign CSR with the Cloud Certificate](#ca_endpoints_sign) | /sign | POST | CertificateSigningRequest | [CertificateSigningResponse](#ca_certificate_signing_response) | + +
+ +### Echo +``` +GET /certificate-authority/echo +``` + +Returns a "Got it" message with the purpose of testing the core service availability. + + + +### Sign CSR with the Cloud Certificate + +``` +POST /certificate-authority/sign +``` + +Returns the whole certificate chain beginning with the newly generated leaf certificate and ending with the root certificate. + +Each certificate's issuer is the same as the subject of the following one. The issuer of the root certificate is the same as the subject. + +__Sign CSR with the Cloud Certificate__ inputs: + +`https://ca_ip:ca_port/certificate-authority/sign` + +| __Sign CSR with the Cloud Certificate__ query parameters | +| ------------------------------------------------------- | + +| Parameter | Description | Necessity | Format/Limitations | +| --------- | ----------- | --------- | ----------- | +| `encodedCSR` | PKCS #10 Certificate Signing Request | mandatory | Base64 encoded CSR | + + + +__Sign CSR with the Cloud Certificate__ output : + +```json +{ + "certificateChain": [ + "", + "", + "" + ] +} +``` + +| Field | Description | +| ----- | ----------- | +| `certificateChain` | The whole certificate chain in an array of PEM encoded strings | + + + +### Management endpoint description
+ +The Certificate Authority does not have management endpoints. +
# QOS MONITOR (QUALITY OF SERVICE MONITOR) diff --git a/documentation/certificate_authority/ReleaseNotes-4.1.3-CertificateAuthority.odt b/documentation/certificate_authority/ReleaseNotes-4.1.3-CertificateAuthority.odt new file mode 100644 index 0000000000000000000000000000000000000000..eb222ba709c30f0200003ac0f789052b47cc9f0f GIT binary patch literal 10850 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 Date: Wed, 18 Mar 2020 12:24:14 +0100 Subject: [PATCH 22/26] Do not remove sysop dir --- certificate-authority/src/deb/control/postrm | 1 - 1 file changed, 1 deletion(-) diff --git a/certificate-authority/src/deb/control/postrm b/certificate-authority/src/deb/control/postrm index 9b46d8e3a..5ec593529 100644 --- a/certificate-authority/src/deb/control/postrm +++ b/certificate-authority/src/deb/control/postrm @@ -35,7 +35,6 @@ case "$1" in ${SYSTEM_DIR}/application.properties \ ${SYSTEM_DIR}/${SYSTEM_NAME}.p12 \ rmdir ${SYSTEM_DIR} 2>/dev/null || true - rmdir ${AH_SYSTEMS_DIR}/sysop 2>/dev/null || true db_purge ;; remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) From f3b245fde3d386f8eb6780561cfe571924527b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Wed, 18 Mar 2020 12:25:44 +0100 Subject: [PATCH 23/26] Minor code clean-up --- .../certificate_authority/security/CAAccessControlFilter.java | 3 +-- .../main/java/eu/arrowhead/common/core/CoreSystemService.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java index 475614cd6..0af563874 100644 --- a/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java +++ b/certificate-authority/src/main/java/eu/arrowhead/core/certificate_authority/security/CAAccessControlFilter.java @@ -7,7 +7,6 @@ import eu.arrowhead.common.CommonConstants; import eu.arrowhead.common.CoreCommonConstants; -import eu.arrowhead.common.core.CoreSystem; import eu.arrowhead.common.security.CoreSystemAccessControlFilter; @Component @@ -32,4 +31,4 @@ protected void checkClientAuthorized(final String clientCN, final String method, checkIfLocalSystemOperator(clientCN, cloudCN, requestTarget); } } -} \ No newline at end of file +} diff --git a/core-common/src/main/java/eu/arrowhead/common/core/CoreSystemService.java b/core-common/src/main/java/eu/arrowhead/common/core/CoreSystemService.java index 2a4f2f078..2cbe9f535 100644 --- a/core-common/src/main/java/eu/arrowhead/common/core/CoreSystemService.java +++ b/core-common/src/main/java/eu/arrowhead/common/core/CoreSystemService.java @@ -35,7 +35,7 @@ public enum CoreSystemService { EVENT_UNSUBSCRIBE_SERVICE(CommonConstants.CORE_SERVICE_EVENT_HANDLER_UNSUBSCRIBE, CommonConstants.EVENT_HANDLER_URI + CommonConstants.OP_EVENT_HANDLER_UNSUBSCRIBE), EVENT_PUBLISH_AUTH_UPDATE_SERVICE(CommonConstants.CORE_SERVICE_EVENT_HANDLER_PUBLISH_AUTH_UPDATE, CommonConstants.EVENT_HANDLER_URI + CommonConstants.OP_EVENT_HANDLER_PUBLISH_AUTH_UPDATE), - // CA services + // CA services CERTIFICATE_AUTHORITY_SIGN_SERVICE(CommonConstants.CORE_SERVICE_CERTIFICATE_AUTHORITY_SIGN, CommonConstants.CERTIFICATE_AUTHRORITY_URI + CommonConstants.OP_CA_SIGN_CERTIFICATE_URI), // QoS Monitor services From 8e027c14e6a0d9d0d1e2162bfd0874807294592d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Wed, 18 Mar 2020 12:26:34 +0100 Subject: [PATCH 24/26] Add CA target folder to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index eb1e1b535..ab1f74c70 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ wrapper /mvnw.cmd /orchestrator/target /serviceregistry/target +/certificate-authority/target /.settings /authorization/.settings /core-common/.settings From 89abd74342470d4b65858edd59170a60ae64c731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Tue, 24 Mar 2020 22:32:10 +0100 Subject: [PATCH 25/26] Fix integer overflow error in CA --- core-common/src/main/java/eu/arrowhead/common/Defaults.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-common/src/main/java/eu/arrowhead/common/Defaults.java b/core-common/src/main/java/eu/arrowhead/common/Defaults.java index 0522f9c0b..f26508c58 100644 --- a/core-common/src/main/java/eu/arrowhead/common/Defaults.java +++ b/core-common/src/main/java/eu/arrowhead/common/Defaults.java @@ -33,7 +33,7 @@ public class Defaults { // CA defaults public static final long DEFAULT_CA_CERT_VALIDITY_NEG_OFFSET_MILLIS = 5000; - public static final long DEFAULT_CA_CERT_VALIDITY_POS_OFFSET_MILLIS = 1000 * 3600 * 24 * 365; + public static final long DEFAULT_CA_CERT_VALIDITY_POS_OFFSET_MILLIS = 1000L * 3600 * 24 * 365; //------------------------------------------------------------------------------------------------- private Defaults() { throw new UnsupportedOperationException(); From f4d2de38f634c399a58818111aa1e269ac0a71d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Krist=C3=B3f?= Date: Thu, 26 Mar 2020 01:27:33 +0100 Subject: [PATCH 26/26] Generate CA keystore with deb installer --- .../src/deb/control/postinst | 8 +- core-common/src/deb/ahconf.sh | 117 ++++++++++++++++-- 2 files changed, 108 insertions(+), 17 deletions(-) diff --git a/certificate-authority/src/deb/control/postinst b/certificate-authority/src/deb/control/postinst index e7ed018aa..a47d9de5a 100644 --- a/certificate-authority/src/deb/control/postinst +++ b/certificate-authority/src/deb/control/postinst @@ -42,13 +42,7 @@ case "$1" in mkdir -p ${SYSTEM_DIR} fi - ah_cert_signed_system ${SYSTEM_NAME} - - # modify log config and application.properties - - ah_transform_log_file ${SYSTEM_DIR} - - db_get arrowhead-core-common/db_host; db_host=$RET || true + ah_ca_keystore ${SYSTEM_NAME} mv ${SYSTEM_DIR}/application.properties ${SYSTEM_DIR}/application.properties.orig cat ${SYSTEM_DIR}/application.properties.orig | \ diff --git a/core-common/src/deb/ahconf.sh b/core-common/src/deb/ahconf.sh index 1242632b7..0b2ea2cb3 100644 --- a/core-common/src/deb/ahconf.sh +++ b/core-common/src/deb/ahconf.sh @@ -190,6 +190,23 @@ ah_ip_valid () { fi } +ah_subject_alternative_names() { + host=${1} + ip=${2} + + san="IP:127.0.0.1,DNS:localhost,DNS:${host},IP:${ip}" + + if [ ! -z ${AH_DOMAIN_NAME} ]; then + if ah_ip_valid ${AH_DOMAIN_NAME}; then + san=${san},IP:${AH_DOMAIN_NAME} + else + san=${san},DNS:${AH_DOMAIN_NAME} + fi + fi + + return san +} + ah_cert_signed_system () { name=${1} passwd=${2} @@ -218,15 +235,7 @@ ah_cert_signed_system () { src_file="${AH_CLOUDS_DIR}/${AH_CLOUD_NAME}.p12" if [ ! -f "${file}" ]; then - san="IP:127.0.0.1,DNS:localhost,DNS:${host},IP:${ip}" - - if [ ! -z ${AH_DOMAIN_NAME} ]; then - if ah_ip_valid ${AH_DOMAIN_NAME}; then - san=${san},IP:${AH_DOMAIN_NAME} - else - san=${san},DNS:${AH_DOMAIN_NAME} - fi - fi + san=ah_subject_alternative_names ${host} ${ip} ah_cert ${path} ${name} "${name}.${AH_CLOUD_NAME}.${AH_OPERATOR}.arrowhead.eu" ${passwd} @@ -266,6 +275,94 @@ ah_cert_signed_system () { fi } +# Create the keystore for the CA system +# Apart from being a regular Arrowhead compliant system keystore, +# it also has to contain the private key of the cloud +ah_ca_keystore () { + ca_sys_name=${1} # name of the CA system + host=${2} # hostname of the CA system (default: `hostname`) + ip=${3} # IP address of the CA system (default: $OWN_IP) + cloud_keystore=${4} # path of the cloud keystore (default: "${AH_CLOUDS_DIR}/${AH_CLOUD_NAME}.p12") + ca_password=${5} # password used in the CA keystore (default: $AH_PASS_CERT) + cloud_password=${6} # password used in the cloud keystore (default: $AH_PASS_CERT) + cloud_alias_src=${7} # cloud cert alias in the cloud keystore (default: $AH_CLOUD_NAME) + cloud_alias_dst=${8} # cloud cert alias in the CA keystore (default: $AH_CLOUD_NAME) + + if [ -z ${host} ]; then + host=`hostname` + fi + + if [ -z ${ip} ]; then + ip=${OWN_IP} + fi + + if [ -z ${cloud_keystore} ]; then + cloud_keystore="${AH_CLOUDS_DIR}/${AH_CLOUD_NAME}.p12" + fi + + if [ -z ${ca_password} ]; then + ca_password=${AH_PASS_CERT} + fi + + if [ -z ${cloud_password} ]; then + cloud_password=${ca_password} + fi + + if [ -z ${cloud_alias_src} ]; then + cloud_alias_src=${AH_CLOUD_NAME} + fi + + if [ -z ${cloud_alias_dst} ]; then + cloud_alias_dst=${cloud_alias_src} + fi + + ca_sys_dir="${AH_SYSTEMS_DIR}/${ca_sys_name}" + ca_keystore="${ca_sys_dir}/${ca_sys_name}.p12" + + if [ ! -f "${ca_keystore}" ]; then + san=ah_subject_alternative_names ${host} ${ip} + + # generate CA system keypair + ah_cert ${ca_sys_dir} ${ca_sys_name} "${ca_sys_name}.${AH_CLOUD_NAME}.${AH_OPERATOR}.arrowhead.eu" ${ca_password} + + # import cloud keypair + # this is necessary, because CA signs system certificates with the cloud cert + keytool -importkeystore \ + -srckeypass ${cloud_password} \ + -srcstorepass ${cloud_password} \ + -destkeypass ${ca_password} \ + -deststorepass ${ca_password} \ + -srcalias ${cloud_alias_src} \ + -destalias ${cloud_alias_dst} \ + -srckeystore "${cloud_keystore}" \ + -destkeystore "${ca_keystore}" \ + -deststoretype PKCS12 + + # sign CA system cert with the cloud cert + keytool -certreq \ + -alias ${ca_sys_name} \ + -keypass ${ca_password} \ + -keystore "${ca_keystore}" \ + -storepass ${ca_password} \ + | keytool -gencert \ + -alias ${cloud_alias_dst} \ + -keypass ${ca_password} \ + -keystore "${ca_keystore}" \ + -storepass ${ca_password} \ + -validity 3650 \ + -ext SubjectAlternativeName=${san} \ + | keytool -importcert \ + -alias ${ca_sys_name} \ + -keypass ${ca_password} \ + -keystore "${ca_keystore}" \ + -storepass ${ca_password} \ + -noprompt + + # import root certificate + ah_cert_import "${AH_CONF_DIR}" "master" "${ca_sys_dir}" ${ca_sys_name} ${ca_password} + fi +} + ah_cert_trust () { dst_path=${1} src_path=${2} @@ -359,4 +456,4 @@ ah_transform_log_file () { rm ${log_path}/log4j2.xml.orig chown :arrowhead ${log_path}/log4j2.xml chmod 640 ${log_path}/log4j2.xml -} \ No newline at end of file +}