From 9b58b56828e346b0d9ad5764442ef820c66580c5 Mon Sep 17 00:00:00 2001 From: Bogdan Mocanu Date: Wed, 28 Apr 2021 16:44:34 +0300 Subject: [PATCH 1/2] Make the iText license file path optional. Make the private key password optional --- .gitignore | 2 + .../client/common/PropertiesLoader.java | 25 ++++++--- .../client/config/AisClientConfiguration.java | 7 ++- .../ais/itext7/client/impl/AisClientImpl.java | 25 +++++---- .../ais/itext7/client/model/UserData.java | 5 +- .../client/rest/RestClientConfiguration.java | 20 ++++---- .../client/rest/SignatureRestClientImpl.java | 51 ++++++++++++++----- 7 files changed, 88 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index a4dfd21..3042e85 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ run.sh AIS-Client-Prod-IAM_2014-2017* mycert-preprod* *~* +target +sign-pdf.properties # IDE # ################### diff --git a/src/main/java/com/swisscom/ais/itext7/client/common/PropertiesLoader.java b/src/main/java/com/swisscom/ais/itext7/client/common/PropertiesLoader.java index 5bb4beb..ad3fd65 100644 --- a/src/main/java/com/swisscom/ais/itext7/client/common/PropertiesLoader.java +++ b/src/main/java/com/swisscom/ais/itext7/client/common/PropertiesLoader.java @@ -25,6 +25,7 @@ import java.util.Properties; import java.util.function.Function; +@SuppressWarnings("unused") public abstract class PropertiesLoader { private static final String ENV_VARIABLE_PREFIX = "${"; @@ -63,16 +64,23 @@ public Properties loadPropertiesFromClasspathFile(Class clazz, String filepat } } - public String extractStringProperty(ConfigurationProvider provider, String propertyName) { - return extractProperty(provider, propertyName); + public String extractStringProperty(ConfigurationProvider provider, String propertyName, boolean mandatory) { + return extractProperty(provider, propertyName, mandatory); } - public int extractIntProperty(ConfigurationProvider provider, String propertyName) { - return Integer.parseInt(extractProperty(provider, propertyName)); + public Integer extractIntProperty(ConfigurationProvider provider, String propertyName, boolean mandatory) { + String property = extractProperty(provider, propertyName, mandatory); + if (property == null && !mandatory) { + return null; + } + return Integer.parseInt(property); } - public String extractSecretProperty(ConfigurationProvider provider, String propertyName) { - String property = extractProperty(provider, propertyName); + public String extractSecretProperty(ConfigurationProvider provider, String propertyName, boolean mandatory) { + String property = extractProperty(provider, propertyName, mandatory); + if (property == null && !mandatory) { + return null; + } return shouldExtractFromEnvVariable(property) ? System.getenv(extractEnvPropertyName(property)) : property; } @@ -89,8 +97,11 @@ public E extractProperty(ConfigurationProvider provider, String propertyName return StringUtils.isNotBlank(propertyValue) ? mapperFunction.apply(propertyValue) : defaultValue; } - private String extractProperty(ConfigurationProvider provider, String propertyName) { + private String extractProperty(ConfigurationProvider provider, String propertyName, boolean mandatory) { String propertyValue = provider.getProperty(propertyName); + if (StringUtils.isBlank(propertyValue) && !mandatory) { + return null; + } validateProperty(propertyName, propertyValue); return propertyValue; } diff --git a/src/main/java/com/swisscom/ais/itext7/client/config/AisClientConfiguration.java b/src/main/java/com/swisscom/ais/itext7/client/config/AisClientConfiguration.java index d9bd06c..9a161ad 100644 --- a/src/main/java/com/swisscom/ais/itext7/client/config/AisClientConfiguration.java +++ b/src/main/java/com/swisscom/ais/itext7/client/config/AisClientConfiguration.java @@ -56,9 +56,9 @@ public String getLicenseFilePath() { @Override protected AisClientConfiguration.Builder fromConfigurationProvider(ConfigurationProvider provider) { return builder() - .withSignaturePollingIntervalInSeconds(extractIntProperty(provider, "client.poll.intervalInSeconds")) - .withSignaturePollingRounds(extractIntProperty(provider, "client.poll.rounds")) - .withLicenseFilePath(extractSecretProperty(provider, "license.file")); + .withSignaturePollingIntervalInSeconds(extractIntProperty(provider, "client.poll.intervalInSeconds", true)) + .withSignaturePollingRounds(extractIntProperty(provider, "client.poll.rounds", true)) + .withLicenseFilePath(extractSecretProperty(provider, "license.file", false)); } private void validate() { @@ -66,7 +66,6 @@ private void validate() { "The signaturePollingIntervalInSeconds parameter of the AIS client configuration must be between 1 and 300 seconds"); ValidationUtils.between(signaturePollingIntervalInSeconds, 1, 100, "The signaturePollingRounds parameter of the AIS client configuration must be between 1 and 100 seconds"); - ValidationUtils.notBlank(licenseFilePath, "The iText license file path can not be blank."); } public static class Builder { diff --git a/src/main/java/com/swisscom/ais/itext7/client/impl/AisClientImpl.java b/src/main/java/com/swisscom/ais/itext7/client/impl/AisClientImpl.java index 136be8e..eb796ff 100644 --- a/src/main/java/com/swisscom/ais/itext7/client/impl/AisClientImpl.java +++ b/src/main/java/com/swisscom/ais/itext7/client/impl/AisClientImpl.java @@ -39,9 +39,10 @@ import com.swisscom.ais.itext7.client.rest.model.signresp.ScExtendedSignatureObject; import com.swisscom.ais.itext7.client.rest.model.signresp.SignResponse; import com.swisscom.ais.itext7.client.rest.model.signresp.SignatureObject; -import com.swisscom.ais.itext7.client.utils.RequestUtils; import com.swisscom.ais.itext7.client.utils.AisObjectUtils; +import com.swisscom.ais.itext7.client.utils.RequestUtils; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,15 +70,17 @@ public AisClientImpl(AisClientConfiguration configuration, SignatureRestClient r } private void initialize() { - try { - LicenseKey.loadLicenseFile(configuration.getLicenseFilePath()); - LicenseKey.scheduledCheck(null); - String[] licenseeInfo = LicenseKey.getLicenseeInfo(); - clientLogger.info("Successfully load the {} iText license granted for company {}, with name {}, email {}, having version {} and " - + "producer line {}. Is license expired: {}.", licenseeInfo[8], licenseeInfo[2], licenseeInfo[0], licenseeInfo[1], - licenseeInfo[6], licenseeInfo[4], licenseeInfo[7]); - } catch (LicenseKeyException e) { - clientLogger.error("Failed to load the iText license: {}", e.getMessage()); + if (StringUtils.isNotBlank(configuration.getLicenseFilePath())) { + try { + LicenseKey.loadLicenseFile(configuration.getLicenseFilePath()); + LicenseKey.scheduledCheck(null); + String[] licenseeInfo = LicenseKey.getLicenseeInfo(); + clientLogger.info("Successfully load the {} iText license granted for company {}, with name {}, email {}, having version {} and " + + "producer line {}. Is license expired: {}.", licenseeInfo[8], licenseeInfo[2], licenseeInfo[0], licenseeInfo[1], + licenseeInfo[6], licenseeInfo[4], licenseeInfo[7]); + } catch (LicenseKeyException e) { + clientLogger.error("Failed to load the iText license: {}", e.getMessage()); + } } } @@ -117,7 +120,7 @@ private SignatureResult performSigning(SignatureMode signatureMode, SignatureTyp try { List additionalProfiles = prepareAdditionalProfiles(profiles, documents); AISSignRequest signRequest = RequestUtils.buildAisSignRequest(documents, signatureMode, signatureType, userData, additionalProfiles, - signWithStepUp, signWithCertificateRequest); + signWithStepUp, signWithCertificateRequest); AISSignResponse signResponse = restClient.requestSignature(signRequest, trace); if (signWithStepUp && !ResponseUtils.isResponseAsyncPending(signResponse)) { diff --git a/src/main/java/com/swisscom/ais/itext7/client/model/UserData.java b/src/main/java/com/swisscom/ais/itext7/client/model/UserData.java index 61c3ef0..6d8fc14 100644 --- a/src/main/java/com/swisscom/ais/itext7/client/model/UserData.java +++ b/src/main/java/com/swisscom/ais/itext7/client/model/UserData.java @@ -143,13 +143,13 @@ public String getSignatureContactInfo() { @Override protected UserData.Builder fromConfigurationProvider(ConfigurationProvider provider) { return builder() - .withClaimedIdentityName(extractStringProperty(provider, "signature.claimedIdentityName")) + .withClaimedIdentityName(extractStringProperty(provider, "signature.claimedIdentityName", true)) .withClaimedIdentityKey(provider.getProperty("signature.claimedIdentityKey")) .withStepUpLanguage(provider.getProperty("signature.stepUp.language")) .withStepUpMsisdn(provider.getProperty("signature.stepUp.msisdn")) .withStepUpMessage(provider.getProperty("signature.stepUp.message")) .withStepUpSerialNumber(provider.getProperty("signature.stepUp.serialNumber")) - .withDistinguishedName(extractStringProperty(provider, "signature.distinguishedName")) + .withDistinguishedName(extractStringProperty(provider, "signature.distinguishedName", true)) .withSignatureName(provider.getProperty("signature.name")) .withSignatureReason(provider.getProperty("signature.reason")) .withSignatureLocation(provider.getProperty("signature.location")) @@ -181,6 +181,7 @@ public void validatePropertiesForSignature(SignatureMode signatureMode, Trace tr } } + @SuppressWarnings("unused") public static class Builder { private String transactionId = IdGenerator.generateId(); diff --git a/src/main/java/com/swisscom/ais/itext7/client/rest/RestClientConfiguration.java b/src/main/java/com/swisscom/ais/itext7/client/rest/RestClientConfiguration.java index 66f1a0f..d75fc4a 100644 --- a/src/main/java/com/swisscom/ais/itext7/client/rest/RestClientConfiguration.java +++ b/src/main/java/com/swisscom/ais/itext7/client/rest/RestClientConfiguration.java @@ -105,16 +105,16 @@ public int getResponseTimeoutInSec() { @Override protected RestClientConfiguration.Builder fromConfigurationProvider(ConfigurationProvider provider) { return builder() - .withServiceSignUrl(extractStringProperty(provider, "server.rest.signUrl")) - .withServicePendingUrl(extractStringProperty(provider, "server.rest.pendingUrl")) - .withClientKeyFile(extractStringProperty(provider, "client.auth.keyFile")) - .withClientKeyPassword(extractSecretProperty(provider, "client.auth.keyPassword")) - .withClientCertificateFile(extractStringProperty(provider, "client.cert.file")) - .withServerCertificateFile(extractStringProperty(provider, "server.cert.file")) - .withMaxTotalConnections(extractIntProperty(provider, "client.http.maxTotalConnections")) - .withMaxConnectionsPerRoute(extractIntProperty(provider, "client.http.maxConnectionsPerRoute")) - .withConnectionTimeoutInSec(extractIntProperty(provider, "client.http.connectionTimeoutInSeconds")) - .withResponseTimeoutInSec(extractIntProperty(provider, "client.http.responseTimeoutInSeconds")); + .withServiceSignUrl(extractStringProperty(provider, "server.rest.signUrl", true)) + .withServicePendingUrl(extractStringProperty(provider, "server.rest.pendingUrl", true)) + .withClientKeyFile(extractStringProperty(provider, "client.auth.keyFile", true)) + .withClientKeyPassword(extractSecretProperty(provider, "client.auth.keyPassword", false)) + .withClientCertificateFile(extractStringProperty(provider, "client.cert.file", true)) + .withServerCertificateFile(extractStringProperty(provider, "server.cert.file", true)) + .withMaxTotalConnections(extractIntProperty(provider, "client.http.maxTotalConnections", true)) + .withMaxConnectionsPerRoute(extractIntProperty(provider, "client.http.maxConnectionsPerRoute", true)) + .withConnectionTimeoutInSec(extractIntProperty(provider, "client.http.connectionTimeoutInSeconds", true)) + .withResponseTimeoutInSec(extractIntProperty(provider, "client.http.responseTimeoutInSeconds", true)); } private void validate() { diff --git a/src/main/java/com/swisscom/ais/itext7/client/rest/SignatureRestClientImpl.java b/src/main/java/com/swisscom/ais/itext7/client/rest/SignatureRestClientImpl.java index 37dfc89..1734903 100644 --- a/src/main/java/com/swisscom/ais/itext7/client/rest/SignatureRestClientImpl.java +++ b/src/main/java/com/swisscom/ais/itext7/client/rest/SignatureRestClientImpl.java @@ -44,6 +44,7 @@ import org.apache.hc.core5.ssl.PrivateKeyStrategy; import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.hc.core5.ssl.SSLContexts; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMDecryptorProvider; import org.bouncycastle.openssl.PEMEncryptedKeyPair; @@ -54,9 +55,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedReader; import java.io.FileInputStream; +import java.io.FileReader; import java.io.IOException; -import java.io.InputStreamReader; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Security; @@ -244,20 +246,43 @@ private KeyStore produceTheTrustStore(RestClientConfiguration config) { } } - public PrivateKey getPrivateKey(String filename, String keyPassword) throws IOException { - PEMParser pemParser = new PEMParser(new InputStreamReader(new FileInputStream(filename))); - PEMKeyPair keyPair = retrieveKeyFromParser(keyPassword, pemParser); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); - return converter.getPrivateKey(keyPair.getPrivateKeyInfo()); - } + public static PrivateKey getPrivateKey(String fileName, String keyPassword) { + try { + BufferedReader br = new BufferedReader(new FileReader(fileName)); + // if we read a X509 key we will get immediately a PrivateKeyInfo + // if the key is a RSA key it is necessary to create a PEMKeyPair first + PrivateKeyInfo privateKeyInfo; + PEMParser pemParser; + try { + pemParser = new PEMParser(br); + privateKeyInfo = (PrivateKeyInfo) pemParser.readObject(); + } catch (Exception ignored) { + br.close(); + br = new BufferedReader(new FileReader(fileName)); + pemParser = new PEMParser(br); + Object pemKeyPair = pemParser.readObject(); + if (pemKeyPair instanceof PEMEncryptedKeyPair) { + if (StringUtils.isBlank(keyPassword)) { + throw new AisClientException("The client private key is encrypted but there is no key password provided " + + "(check field 'client.auth.keyPassword' from the config.properties or from " + + "the REST client configuration)"); + } + PEMDecryptorProvider decryptionProv = new JcePEMDecryptorProviderBuilder().build(keyPassword.toCharArray()); + PEMKeyPair decryptedKeyPair = ((PEMEncryptedKeyPair) pemKeyPair).decryptKeyPair(decryptionProv); + privateKeyInfo = decryptedKeyPair.getPrivateKeyInfo(); + } else { + privateKeyInfo = ((PEMKeyPair) pemKeyPair).getPrivateKeyInfo(); + } + } - private PEMKeyPair retrieveKeyFromParser(String keyPassword, PEMParser pemParser) throws IOException { - if (StringUtils.isBlank(keyPassword)) { - return (PEMKeyPair) pemParser.readObject(); + pemParser.close(); + br.close(); + + JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter(); + return jcaPEMKeyConverter.getPrivateKey(privateKeyInfo); + } catch (Exception e) { + throw new AisClientException("Failed to initialize the client private key", e); } - PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) pemParser.readObject(); - PEMDecryptorProvider decryptorProvider = new JcePEMDecryptorProviderBuilder().setProvider("BC").build(keyPassword.toCharArray()); - return encryptedKeyPair.decryptKeyPair(decryptorProvider); } private PrivateKeyStrategy producePrivateKeyStrategy() { From cca0ff432280a00b2f91c786dadbe905ca8055b1 Mon Sep 17 00:00:00 2001 From: Bogdan Mocanu Date: Wed, 28 Apr 2021 16:46:56 +0300 Subject: [PATCH 2/2] Bump the version to 1.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 890bf0a..32299b6 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.swisscom.ais itext7-ais - 1.0.0 + 1.1.0 jar itext7-ais