diff --git a/README.md b/README.md index 4a07449ca..2a21e347d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Applications under apps/ depend on shared code under libs/. test-coverage/ is us    ├── onboarding-sdk-common    ├── onboarding-sdk-azure-storage    ├── onboarding-sdk-product +    ├── onboarding-sdk-crypto └── test-coverage ``` @@ -30,6 +31,12 @@ The [`.container_apps/`] sub folder contains terraform files for deploying infra The [`.github/`] sub folder contains a self-contained ci-stack for building the monorepo with Github Actions. +## Usage + +```shell script +mvn clean package install +``` + ## Maven basic actions for monorep Maven is really not a monorepo-*native* build tool (e.g. lacks diff --git a/apps/onboarding-functions/README.md b/apps/onboarding-functions/README.md index 5fa9af379..fa1abdfbd 100644 --- a/apps/onboarding-functions/README.md +++ b/apps/onboarding-functions/README.md @@ -7,6 +7,11 @@ These functions handle all asynchronous activities related to preparing and comp It is triggered by http request at GET or POST `/api/StartOnboardingOrchestration?onboardingId={onboardingId}` where onboardingId is a reference to onboarding which you want to process. +### Contract Signature + +You can enable the signature inside contracts when there are builded setting PAGOPA_SIGNATURE_SOURCE env (default value is `disabled`) as `local` if you want to use Pkcs7HashSignService or `aruba` for ArubaPkcs7HashSignService. Look at this [README](https://github.com/pagopa/selfcare-onboarding/tree/develop/libs/onboarding-sdk-crypto#readme) for more informations. + + ## Running locally @@ -27,7 +32,7 @@ Before running you must set these properties as environment variables. ### Storage emulator: Azurite -Use the Azurite emulator for local Azure Storage development. Once installed, you must create `selc-d-contracts-blob` and `selc-product` container. +Use the Azurite emulator for local Azure Storage development. Once installed, you must create `selc-d-contracts-blob` and `selc-d-product` container. Inside last one you have to put products.json file. ([guide](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio)) diff --git a/apps/onboarding-functions/pom.xml b/apps/onboarding-functions/pom.xml index 9ac1ee5c8..5f28711c3 100644 --- a/apps/onboarding-functions/pom.xml +++ b/apps/onboarding-functions/pom.xml @@ -13,12 +13,12 @@ 3.11.0 - 11 + 17 UTF-8 UTF-8 quarkus-bom io.quarkus.platform - 3.3.3 + 3.5.1 true 3.1.2 0.1.0 @@ -80,6 +80,11 @@ onboarding-sdk-common ${onboarding-sdk.version} + + it.pagopa.selfcare + onboarding-sdk-crypto + ${onboarding-sdk.version} + org.apache.commons commons-text diff --git a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/config/OnboardingFunctionConfig.java b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/config/OnboardingFunctionConfig.java index c78a0fefd..530eda610 100644 --- a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/config/OnboardingFunctionConfig.java +++ b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/config/OnboardingFunctionConfig.java @@ -6,18 +6,26 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.quarkus.arc.DefaultBean; +import io.quarkus.arc.properties.IfBuildProperty; import io.vertx.core.json.jackson.DatabindCodec; import it.pagopa.selfcare.azurestorage.AzureBlobClientDefault; import it.pagopa.selfcare.azurestorage.AzureBlobClient; +import it.pagopa.selfcare.onboarding.crypto.*; import it.pagopa.selfcare.onboarding.exception.GenericOnboardingException; import it.pagopa.selfcare.product.service.ProductService; import it.pagopa.selfcare.product.service.ProductServiceDefault; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; +import java.io.IOException; +import java.io.InputStream; + @ApplicationScoped public class OnboardingFunctionConfig { + + @Produces public ObjectMapper objectMapper(){ ObjectMapper mapper = DatabindCodec.mapper(); @@ -48,4 +56,31 @@ public ProductService productService(AzureStorageConfig azureStorageConfig){ } } + @Produces + @IfBuildProperty(name = "onboarding-functions.pagopa-signature.source", stringValue = "aruba") + public Pkcs7HashSignService arubaPkcs7HashSignService(){ + return new ArubaPkcs7HashSignServiceImpl(new ArubaSignServiceImpl()); + } + + + @Produces + @IfBuildProperty(name = "onboarding-functions.pagopa-signature.source", stringValue = "disabled") + public Pkcs7HashSignService disabledPkcs7HashSignService(){ + return new Pkcs7HashSignService(){ + @Override + public byte[] sign(InputStream inputStream) throws IOException { + return new byte[0]; + } + }; + } + @Produces + @DefaultBean + public Pkcs7HashSignService pkcs7HashSignService(){ + return new Pkcs7HashSignServiceImpl(); + } + @Produces + public PadesSignService padesSignService(Pkcs7HashSignService pkcs7HashSignService){ + return new PadesSignServiceImpl(pkcs7HashSignService); + } + } diff --git a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/config/PagoPaSignatureConfig.java b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/config/PagoPaSignatureConfig.java new file mode 100644 index 000000000..a9e68bb51 --- /dev/null +++ b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/config/PagoPaSignatureConfig.java @@ -0,0 +1,24 @@ +package it.pagopa.selfcare.onboarding.config; + + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "onboarding-functions.pagopa-signature") +public interface PagoPaSignatureConfig { + + String source(); + + String signer(); + + String location(); + + boolean applyOnboardingEnabled(); + + String applyOnboardingTemplateReason(); + + boolean verifyEnabled(); + + String euListOfTrustedListsURL(); + + String euOfficialJournalUrl(); +} diff --git a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractServiceDefault.java b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractServiceDefault.java index 682b4e391..9ad66642c 100644 --- a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractServiceDefault.java +++ b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractServiceDefault.java @@ -5,12 +5,14 @@ import it.pagopa.selfcare.azurestorage.AzureBlobClient; import it.pagopa.selfcare.onboarding.common.InstitutionType; import it.pagopa.selfcare.onboarding.config.AzureStorageConfig; +import it.pagopa.selfcare.onboarding.config.PagoPaSignatureConfig; +import it.pagopa.selfcare.onboarding.crypto.PadesSignService; +import it.pagopa.selfcare.onboarding.crypto.entity.SignatureInformation; import it.pagopa.selfcare.onboarding.entity.Institution; import it.pagopa.selfcare.onboarding.entity.Onboarding; import it.pagopa.selfcare.onboarding.exception.GenericOnboardingException; import it.pagopa.selfcare.onboarding.utils.ClassPathStream; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; import org.apache.commons.text.StringSubstitutor; import org.jsoup.Jsoup; import org.jsoup.helper.W3CDom; @@ -41,14 +43,23 @@ public class ContractServiceDefault implements ContractService { private static final Logger log = LoggerFactory.getLogger(ContractServiceDefault.class); public static final String PDF_FORMAT_FILENAME = "%s.pdf"; + public static final String PAGOPA_SIGNATURE_DISABLED = "disabled"; private final AzureStorageConfig azureStorageConfig; private final AzureBlobClient azureBlobClient; - public ContractServiceDefault(AzureStorageConfig azureStorageConfig, AzureBlobClient azureBlobClient) { + private final PadesSignService padesSignService; + + private final PagoPaSignatureConfig pagoPaSignatureConfig; + + public ContractServiceDefault(AzureStorageConfig azureStorageConfig, + AzureBlobClient azureBlobClient, PadesSignService padesSignService, + PagoPaSignatureConfig pagoPaSignatureConfig) { this.azureStorageConfig = azureStorageConfig; this.azureBlobClient = azureBlobClient; + this.padesSignService = padesSignService; + this.pagoPaSignatureConfig = pagoPaSignatureConfig; } /** @@ -77,7 +88,7 @@ public File createContractPDF(String contractTemplatePath, Onboarding onboarding // Read the content of the contract template file. String contractTemplateText = azureBlobClient.getFileAsText(contractTemplatePath); // Create a temporary PDF file to store the contract. - Path files = Files.createTempFile(builder, ".pdf"); + Path temporaryPdfFile = Files.createTempFile(builder, ".pdf"); // Prepare common data for the contract document. Map data = setUpCommonData(validManager, users, institution, onboarding.getBilling(), List.of()); @@ -95,20 +106,45 @@ public File createContractPDF(String contractTemplatePath, Onboarding onboarding setupSAProdInteropData(data, institution); } log.debug("data Map for PDF: {}", data); - getPDFAsFile(files, contractTemplateText, data); + fillPDFAsFile(temporaryPdfFile, contractTemplateText, data); // Define the filename and path for storage. - /* return signContract(institution, request, files.toFile()); */ final String filename = String.format(PDF_FORMAT_FILENAME, onboarding.getOnboardingId()); final String path = String.format("%s%s", azureStorageConfig.contractPath(), onboarding.getOnboardingId()); - azureBlobClient.uploadFile(path, filename, Files.readAllBytes(files)); - return files.toFile(); + File signedPath = signPdf(temporaryPdfFile.toFile(), institution.getDescription(), productId); + azureBlobClient.uploadFile(path, filename, Files.readAllBytes(signedPath.toPath())); + + return signedPath; } catch (IOException e) { throw new GenericOnboardingException(String.format("Can not create contract PDF, message: %s", e.getMessage())); } } + private File signPdf(File pdf, String institutionDescription, String productId) throws IOException { + if(PAGOPA_SIGNATURE_DISABLED.equals(pagoPaSignatureConfig.source())) { + log.info("Skipping PagoPA contract pdf sign due to global disabling"); + return pdf; + } + + String signReason = pagoPaSignatureConfig.applyOnboardingTemplateReason() + .replace("${institutionName}", institutionDescription) + .replace("${productName}", productId); + + log.info("Signing input file {} using reason {}", pdf.getName(), signReason); + Path signedPdf = Files.createTempFile("signed", ".pdf"); + padesSignService.padesSign(pdf, signedPdf.toFile(), buildSignatureInfo(signReason)); + return signedPdf.toFile(); + } + + private SignatureInformation buildSignatureInfo(String signReason) { + return new SignatureInformation( + pagoPaSignatureConfig.signer(), + pagoPaSignatureConfig.location(), + signReason + ); + } + @Override public File loadContractPDF(String contractTemplatePath, String onboardingId) { try { @@ -124,7 +160,7 @@ public File loadContractPDF(String contractTemplatePath, String onboardingId) { } } - private void getPDFAsFile(Path files, String contractTemplate, Map data) { + private void fillPDFAsFile(Path file, String contractTemplate, Map data) { log.debug("Getting PDF for HTML template..."); String html = StringSubstitutor.replace(contractTemplate, data); PdfRendererBuilder builder = new PdfRendererBuilder(); @@ -144,7 +180,7 @@ private void getPDFAsFile(Path files, String contractTemplate, Maplocal | | -| crypto.key.private | The certificate (PEM) used when the pkcs7 hash signature source is local | | +| CRYPTO_PRIVATE_KEY | The private key (PEM) used when the pkcs7 hash signature source is local | | +| CRYPTO_CERT | The certificate (PEM) used when the pkcs7 hash signature source is local | | @@ -36,17 +36,17 @@ for integration and documentation details The integration towards Aruba is configurable through the following environment variables: -| ENV | Description | Default | -|-------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| -| aruba.sign-service.baseUrl | The URL of the webService | https://arss.demo.firma-automatica.it:443/ArubaSignService/ArubaSignService | -| aruba.sign-service.connectTimeoutMs | The timeout configured to establish the connection. If 0, no timeout will be configured | 0 | -| aruba.sign-service.requestTimeoutMs | The timeout configured for the request. If 0, no timeout will be configured | 0 | -| aruba.sign-service.auth.typeOtpAuth | The string identifying the automatic signature domain indicated when ARSS is installed | typeOtpAuth | -| aruba.sign-service.auth.otpPwd | The string identifying the automatic signature transactions defined when the ARSS server is installed (it is normally known by the administrator of the IT infrastructure network on which users are working) | otpPwd | -| aruba.sign-service.auth.user | The string containing the signature user's username | user | -| aruba.sign-service.auth.delegatedUser | The string containing the username for the delegated user | delegatedUser | -| aruba.sign-service.auth.delegatedPassword | The String containing the delegated user's password | delegatedPassword | -| aruba.sign-service.auth.delegatedDomain | The delegated user's domain | delegatedDomain | +| ENV | Description | Default | +|------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| ARUBA_SIGN_SERVICE_BASE_URL | The URL of the webService | https://arss.demo.firma-automatica.it:443/ArubaSignService/ArubaSignService | +| ARUBA_SIGN_SERVICE_CONNECT_TIMEOUT_MS | The timeout configured to establish the connection. If 0, no timeout will be configured | 0 | +| ARUBA_SIGN_SERVICE_REQUEST_TIMEOUT_MS | The timeout configured for the request. If 0, no timeout will be configured | 0 | +| ARUBA_SIGN_SERVICE_IDENTITY_TYPE_OTP_AUTH | The string identifying the automatic signature domain indicated when ARSS is installed | typeOtpAuth | +| ARUBA_SIGN_SERVICE_IDENTITY_OTP_PWD | The string identifying the automatic signature transactions defined when the ARSS server is installed (it is normally known by the administrator of the IT infrastructure network on which users are working) | otpPwd | +| ARUBA_SIGN_SERVICE_IDENTITY_USER | The string containing the signature user's username | user | +| ARUBA_SIGN_SERVICE_IDENTITY_DELEGATED_USER | The string containing the username for the delegated user | delegatedUser | +| ARUBA_SIGN_SERVICE_IDENTITY_DELEGATED_PASSWORD | The String containing the delegated user's password | delegatedPassword | +| ARUBA_SIGN_SERVICE_IDENTITY_DELEGATED_DOMAIN | The delegated user's domain | delegatedDomain | ## Installation diff --git a/libs/onboarding-sdk-crypto/pom.xml b/libs/onboarding-sdk-crypto/pom.xml index 624d26ce9..b88658f89 100644 --- a/libs/onboarding-sdk-crypto/pom.xml +++ b/libs/onboarding-sdk-crypto/pom.xml @@ -68,6 +68,13 @@ org.apache.commons commons-lang3 + + + uk.org.webcompere + system-stubs-core + 2.1.3 + test + diff --git a/libs/onboarding-sdk-crypto/src/main/java/it/pagopa/selfcare/onboarding/crypto/config/ArubaInitializer.java b/libs/onboarding-sdk-crypto/src/main/java/it/pagopa/selfcare/onboarding/crypto/config/ArubaInitializer.java index ed013396a..7575056ce 100644 --- a/libs/onboarding-sdk-crypto/src/main/java/it/pagopa/selfcare/onboarding/crypto/config/ArubaInitializer.java +++ b/libs/onboarding-sdk-crypto/src/main/java/it/pagopa/selfcare/onboarding/crypto/config/ArubaInitializer.java @@ -11,21 +11,20 @@ private ArubaInitializer() {} public static ArubaSignConfig initializeConfig() { ArubaSignConfig config = new ArubaSignConfig(); - config.setConnectTimeoutMs(Optional.ofNullable(System.getProperty("aruba.sign-service.connectTimeoutMs")) + config.setConnectTimeoutMs(Optional.ofNullable(System.getenv("ARUBA_SIGN_SERVICE_CONNECT_TIMEOUT_MS")) .map(Integer::parseInt).orElse(0)); - config.setRequestTimeoutMs(Optional.ofNullable(System.getProperty("aruba.sign-service.requestTimeoutMs")) + config.setRequestTimeoutMs(Optional.ofNullable(System.getenv("ARUBA_SIGN_SERVICE_REQUEST_TIMEOUT_MS")) .map(Integer::parseInt).orElse(0)); - config.setBaseUrl(Optional.ofNullable(System.getProperty("aruba.sign-service.baseUrl")) + config.setBaseUrl(Optional.ofNullable(System.getenv("ARUBA_SIGN_SERVICE_BASE_URL")) .orElse("https://arss.demo.firma-automatica.it:443/ArubaSignService/ArubaSignService")); Auth auth = new Auth(); - auth.setTypeOtpAuth(System.getProperty("aruba.sign-service.auth.typeOtpAuth")); - auth.setDelegatedDomain(System.getProperty("aruba.sign-service.auth.delegatedDomain")); - auth.setOtpPwd(System.getProperty("aruba.sign-service.auth.otpPwd")); - auth.setUser(System.getProperty("aruba.sign-service.auth.user")); - auth.setDelegatedUser(System.getProperty("aruba.sign-service.auth.delegatedUser")); - auth.setDelegatedPassword(System.getProperty("aruba.sign-service.auth.delegatedPassword")); - auth.setDelegatedDomain(System.getProperty("aruba.sign-service.auth.delegatedDomain")); + auth.setTypeOtpAuth(System.getenv("ARUBA_SIGN_SERVICE_IDENTITY_TYPE_OTP_AUTH")); + auth.setOtpPwd(System.getenv("ARUBA_SIGN_SERVICE_IDENTITY_OTP_PWD")); + auth.setUser(System.getenv("ARUBA_SIGN_SERVICE_IDENTITY_USER")); + auth.setDelegatedUser(System.getenv("ARUBA_SIGN_SERVICE_IDENTITY_DELEGATED_USER")); + auth.setDelegatedPassword(System.getenv("ARUBA_SIGN_SERVICE_IDENTITY_DELEGATED_PASSWORD")); + auth.setDelegatedDomain(System.getenv("ARUBA_SIGN_SERVICE_IDENTITY_DELEGATED_DOMAIN")); auth.setTypeHSM("COSIGN"); config.setAuth(auth); diff --git a/libs/onboarding-sdk-crypto/src/main/java/it/pagopa/selfcare/onboarding/crypto/config/LocalCryptoInitializer.java b/libs/onboarding-sdk-crypto/src/main/java/it/pagopa/selfcare/onboarding/crypto/config/LocalCryptoInitializer.java index 8c3e5ff13..4d352f5ee 100644 --- a/libs/onboarding-sdk-crypto/src/main/java/it/pagopa/selfcare/onboarding/crypto/config/LocalCryptoInitializer.java +++ b/libs/onboarding-sdk-crypto/src/main/java/it/pagopa/selfcare/onboarding/crypto/config/LocalCryptoInitializer.java @@ -2,26 +2,31 @@ import it.pagopa.selfcare.onboarding.crypto.utils.CryptoUtils; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LocalCryptoInitializer { + private final static Logger log = LoggerFactory.getLogger(LocalCryptoInitializer.class); + private LocalCryptoInitializer(){} public static LocalCryptoConfig initializeConfig() { LocalCryptoConfig config = new LocalCryptoConfig(); - String cert = System.getProperty("crypto.key.cert"); - String pKey = System.getProperty("crypto.key.private"); + String cert = System.getenv("CRYPTO_CERT"); + String pKey = System.getenv("CRYPTO_PRIVATE_KEY"); try { - if(StringUtils.isBlank(cert) || StringUtils.isBlank(pKey)){ - throw new IllegalStateException("Define private and cert values in order to perform locally sign operations"); - } + if (StringUtils.isBlank(cert) || StringUtils.isBlank(pKey)) { + log.error("Define private and cert values in order to perform locally sign operations"); + } else { - config.setCertificate(CryptoUtils.getCertificate(cert)); - config.setPrivateKey(CryptoUtils.getPrivateKey(pKey)); + config.setCertificate(CryptoUtils.getCertificate(cert)); + config.setPrivateKey(CryptoUtils.getPrivateKey(pKey)); + } } catch (Exception e) { - throw new IllegalStateException("Something gone wrong while loading crypto private and public keys", e); + log.error("Something gone wrong while loading crypto private and public keys", e); } return config; diff --git a/libs/onboarding-sdk-crypto/src/test/java/it/pagopa/selfcare/onboarding/crypto/Pkcs7HashSignServiceTest.java b/libs/onboarding-sdk-crypto/src/test/java/it/pagopa/selfcare/onboarding/crypto/Pkcs7HashSignServiceTest.java index 6059cec79..490b284ff 100644 --- a/libs/onboarding-sdk-crypto/src/test/java/it/pagopa/selfcare/onboarding/crypto/Pkcs7HashSignServiceTest.java +++ b/libs/onboarding-sdk-crypto/src/test/java/it/pagopa/selfcare/onboarding/crypto/Pkcs7HashSignServiceTest.java @@ -1,14 +1,15 @@ package it.pagopa.selfcare.onboarding.crypto; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Path; import java.util.Base64; +import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariables; + class Pkcs7HashSignServiceTest { private static final String testCert = "-----BEGIN CERTIFICATE-----\n" + @@ -65,14 +66,23 @@ class Pkcs7HashSignServiceTest { private Pkcs7HashSignService service; - @BeforeEach - void setup(){ - System.setProperty("crypto.key.cert", testCert); - System.setProperty("crypto.key.private", testPrivateKey); - service = new Pkcs7HashSignServiceImpl(); + private static EnvironmentVariables environmentVariables = new EnvironmentVariables(); + + @BeforeAll + static void setup() throws Exception { + environmentVariables.set("CRYPTO_CERT", testCert); + environmentVariables.set("CRYPTO_PRIVATE_KEY", testPrivateKey); + environmentVariables.setup(); + } + + @AfterAll + static void afterAll() throws Exception { + environmentVariables.teardown(); } @Test void shouldAssertEncoding() throws IOException { + + service = new Pkcs7HashSignServiceImpl(); try (FileInputStream fis = new FileInputStream(Path.of("src/test/resources/signTest.pdf").toFile())) { byte[] result = service.sign(fis); Assertions.assertNotNull(result);