diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity-scan.yml new file mode 100644 index 00000000..8a887f33 --- /dev/null +++ b/.github/workflows/coverity-scan.yml @@ -0,0 +1,16 @@ +name: Run Coverity scan and upload results + +on: + workflow_dispatch: + schedule: + - cron: '0 10 1 * *' # monthly + + +jobs: + coverity-scan: + uses: wultra/wultra-infrastructure/.github/workflows/coverity-scan.yml@develop + secrets: inherit + with: + project-name: ${{ github.event.repository.name }} + version: ${{ github.sha }} + description: ${{ github.ref }} diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml new file mode 100644 index 00000000..58607393 --- /dev/null +++ b/.github/workflows/maven-deploy.yml @@ -0,0 +1,52 @@ +name: Deploy with Maven + +on: + workflow_dispatch: + branches: + - 'develop' + - 'master' + - 'releases/**' + - 'test/ci' + inputs: + release_type: + type: choice + description: releasing to snapshot or release + default: snapshot + options: + - snapshot + - release + environment: + type: environment + default: internal-publish + description: internal or external repository + push: + branches: + - 'develop' + - 'test/ci' + + + +jobs: + maven-deploy-jfrog: + if: ${{ github.event_name == 'push' }} + name: Deploy to jfrog + uses: wultra/wultra-infrastructure/.github/workflows/maven-deploy.yml@develop + with: + environment: internal-publish + release_type: snapshot + secrets: + username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + + maven-deploy-manual: + if: ${{ github.event_name == 'workflow_dispatch' }} + name: Deploy by parameter + uses: wultra/wultra-infrastructure/.github/workflows/maven-deploy.yml@develop + with: + environment: ${{ inputs.environment }} + release_type: ${{ inputs.release_type }} + secrets: + username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + gpg_passphrase: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} + gpg_key: ${{ secrets.OSSRH_GPG_SECRET_KEY }} \ No newline at end of file diff --git a/.github/workflows/maven-test.yml b/.github/workflows/maven-test.yml new file mode 100644 index 00000000..4bba5877 --- /dev/null +++ b/.github/workflows/maven-test.yml @@ -0,0 +1,20 @@ +name: Test with Maven + +on: + workflow_dispatch: + push: + branches: + - 'develop' + - 'master' + - 'releases/**' + - 'test/ci' + pull_request: + branches: + - 'develop' + - 'master' + - 'releases/**' + +jobs: + maven-tests: + uses: wultra/wultra-infrastructure/.github/workflows/maven-test.yml@develop + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/owas-dependecy-check.yml b/.github/workflows/owas-dependecy-check.yml new file mode 100644 index 00000000..c79178b2 --- /dev/null +++ b/.github/workflows/owas-dependecy-check.yml @@ -0,0 +1,12 @@ +name: Run OWASP Dependency Check +on: + workflow_dispatch: + + push: + branches: + - 'develop' + +jobs: + owasp-check: + uses: wultra/wultra-infrastructure/.github/workflows/owasp-dependency-check.yml@develop + secrets: inherit \ No newline at end of file diff --git a/docs/Readme.md b/docs/Readme.md index 916b37f6..8e0914a8 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -361,6 +361,23 @@ java -jar powerauth-java-cmd.jar \ The commit upgrade request is sent to the server including a version `3.0` signature. The server verifies the request signature and commits the upgrade of activation to version `3`. +## Compute Offline Signature + +Use this method to compute offline PowerAuth signature. + +```bash +java -jar powerauth-java-cmd.jar \ + --status-file "/tmp/pa_status.json" \ + --config-file "/tmp/pamk.json" \ + --method "compute-offline-signature" \ + --qr-code-data "c68dc57f-ee5f-497c-8c92-338439426e76\nApprove Login\nPlease confirm the login request.\nA2\nB\nETIK4iFz1E9u6vABKSbytg==\n1MEYCIQCnQqFFzS589auwdMRZ9Aq5qFxso21oxd2sng9Vp7gCUgIhAITaJ9L3fP2tov63mcIgU2e/37h9EXyAMhzrCXXDNJZE" \ + --password "1234" +``` + +The `qr-code-data` parameter is taken from QR code generated by PowerAuth RESTful services. Note that the QR code is signed, the signature is verified during offline signature computation. The method unlocks the knowledge related signing key using `1234` as a password. + +The method does not execute any server calls due to its offline nature. The computed offline signature is used as an OTP and it is available from the output of the command in decimal format, e.g.: `"offlineSignature" : "99961544-80193814"`. + ## Basic Usage PowerAuth Reference Client is called as any Java application that is packaged as a JAR file and it uses following command-line arguments. diff --git a/pom.xml b/pom.xml index f74b6a01..14f46cef 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ io.getlime.security powerauth-cmd-parent - 1.3.0 + 1.4.0 pom 2016 @@ -76,21 +76,42 @@ 1.8 1.8 3.2.1 - 3.0.0-M2 - 3.4.0 - 2.6.8 - 1.70 - 2.13.3 + 3.0.0 + 3.4.1 + 2.6.14 + 1.72 1.5.0 2.11.0 1.1.1 - 1.3.0 - 1.5.0 - 1.18.24 - 5.8.2 + 1.4.0 + 1.6.0 2.22.2 + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + io.getlime.security + powerauth-java-cmd-lib + ${project.version} + + + + org.bouncycastle + bcprov-jdk18on + ${bc.version} + + + + @@ -157,6 +178,66 @@ + + internal-repository + + + useInternalRepo + true + + + + + + + jfrog-central + Wultra Artifactory-releases + https://wultra.jfrog.io/artifactory/internal-maven-repository + + + jfrog-central + Wultra Artifactory-snapshots + https://wultra.jfrog.io/artifactory/internal-maven-repository + + + + + jfrog-central + Wultra Artifactory-releases + https://wultra.jfrog.io/artifactory/internal-maven-repository + + + ossrh-snapshots + https://oss.sonatype.org/content/repositories/snapshots/ + + false + + + true + + + + + + public-repository + + + !useInternalRepo + + + + + + + ossrh-snapshots-distribution + https://oss.sonatype.org/content/repositories/snapshots/ + + + ossrh-staging-distribution + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + @@ -172,15 +253,4 @@ - - - ossrh-snapshots-distribution - https://oss.sonatype.org/content/repositories/snapshots/ - - - ossrh-staging-distribution - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - diff --git a/powerauth-java-cmd-lib/pom.xml b/powerauth-java-cmd-lib/pom.xml index af00f3e7..1c3fbd2b 100644 --- a/powerauth-java-cmd-lib/pom.xml +++ b/powerauth-java-cmd-lib/pom.xml @@ -6,20 +6,17 @@ 4.0.0 powerauth-java-cmd-lib PowerAuth Command-line Utility - Java Library - 1.3.0 powerauth-cmd-parent io.getlime.security - 1.3.0 - ../pom.xml + 1.4.0 com.fasterxml.jackson.core jackson-databind - ${jackson.version} commons-cli @@ -60,15 +57,13 @@ org.bouncycastle - bcprov-jdk15on - ${bc.version} + bcprov-jdk18on provided org.springframework.boot spring-boot-starter - ${spring-boot.version} log4j-to-slf4j @@ -79,7 +74,6 @@ org.springframework.boot spring-boot-configuration-processor - ${spring-boot.version} true @@ -91,7 +85,6 @@ org.projectlombok lombok - ${lombok.version} diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/consts/PowerAuthStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/consts/PowerAuthStep.java index 65d6ef6b..45833dce 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/consts/PowerAuthStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/consts/PowerAuthStep.java @@ -73,6 +73,11 @@ public enum PowerAuthStep { */ SIGN_ENCRYPT("sign-encrypt", "Sign and Encrypt Request", "sign-encrypt"), + /** + * Compute an offline signature + */ + SIGNATURE_OFFLINE_COMPUTE("signature-offline-compute", "Compute Offline Signature", "compute-offline-signature"), + /** * Verifying a signed request */ diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/AbstractBaseStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/AbstractBaseStep.java index a4c46f02..776bd54c 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/AbstractBaseStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/AbstractBaseStep.java @@ -142,7 +142,10 @@ public final JSONObject execute(StepLogger stepLogger, Map conte null ); - StepContext stepContext = prepareStepContext(stepLogger, context); + final StepContext stepContext = prepareStepContext(stepLogger, context); + if (stepContext == null) { + return null; + } try { ResponseContext responseContext = callServer(stepContext); @@ -295,6 +298,15 @@ protected void logDryRun(StepLogger stepLogger) { * Calls the server and prepares response context with the response data */ private @Nullable ResponseContext callServer(StepContext stepContext) throws Exception { + if (stepContext == null) { + return null; + } + + final ParameterizedTypeReference responseTypeReference = getResponseTypeReference(); + if (responseTypeReference == null) { + return null; + } + M model = stepContext.getModel(); RequestContext requestContext = stepContext.getRequestContext(); @@ -328,9 +340,9 @@ protected void logDryRun(StepLogger stepLogger) { try { // Call the right method with the REST client if (HttpMethod.GET.equals(requestContext.getHttpMethod())) { - responseEntity = restClient.get(requestContext.getUri(), null, MapUtil.toMultiValueMap(headers), ParameterizedTypeReference.forType(getResponseTypeReference().getType())); + responseEntity = restClient.get(requestContext.getUri(), null, MapUtil.toMultiValueMap(headers), responseTypeReference); } else { - responseEntity = restClient.post(requestContext.getUri(), requestBytes, null, MapUtil.toMultiValueMap(headers), ParameterizedTypeReference.forType(getResponseTypeReference().getType())); + responseEntity = restClient.post(requestContext.getUri(), requestBytes, null, MapUtil.toMultiValueMap(headers), responseTypeReference); } } catch (RestClientException ex) { stepContext.getStepLogger().writeServerCallError(step.id() + "-error-server-call", ex.getStatusCode().value(), ex.getResponse(), HttpUtil.flattenHttpHeaders(ex.getResponseHeaders())); diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/ComputeOfflineSignatureStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/ComputeOfflineSignatureStep.java new file mode 100644 index 00000000..b8a29b80 --- /dev/null +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/ComputeOfflineSignatureStep.java @@ -0,0 +1,233 @@ +/* + * PowerAuth Command-line utility + * Copyright 2022 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getlime.security.powerauth.lib.cmd.steps; + +import com.google.common.io.BaseEncoding; +import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureFormat; +import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator; +import io.getlime.security.powerauth.crypto.lib.util.KeyConvertor; +import io.getlime.security.powerauth.crypto.lib.util.SignatureUtils; +import io.getlime.security.powerauth.http.PowerAuthHttpBody; +import io.getlime.security.powerauth.lib.cmd.consts.BackwardCompatibilityConst; +import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthStep; +import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthVersion; +import io.getlime.security.powerauth.lib.cmd.logging.StepLogger; +import io.getlime.security.powerauth.lib.cmd.logging.StepLoggerFactory; +import io.getlime.security.powerauth.lib.cmd.status.ResultStatusService; +import io.getlime.security.powerauth.lib.cmd.steps.context.RequestContext; +import io.getlime.security.powerauth.lib.cmd.steps.context.StepContext; +import io.getlime.security.powerauth.lib.cmd.steps.model.ComputeOfflineSignatureStepModel; +import io.getlime.security.powerauth.lib.cmd.steps.pojo.ResultStatusObject; +import io.getlime.security.powerauth.lib.cmd.util.CounterUtil; +import io.getlime.security.powerauth.lib.cmd.util.EncryptedStorageUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.io.Console; +import java.nio.charset.StandardCharsets; +import java.security.interfaces.ECPublicKey; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Step for computing offline PowerAuth signature. + * + *

PowerAuth protocol versions: + *

    + *
  • 2.0
  • + *
  • 2.1
  • + *
  • 3.0
  • + *
  • 3.1
  • + *
+ * + * @author Roman Strobl, roman.strobl@wultra.com + */ +@Component +public class ComputeOfflineSignatureStep extends AbstractBaseStep { + + private static final KeyGenerator KEY_GENERATOR = new KeyGenerator(); + private static final KeyConvertor KEY_CONVERTOR = new KeyConvertor(); + private static final SignatureUtils SIGNATURE_UTILS = new SignatureUtils(); + + /** + * Constructor + * @param resultStatusService Result status service + * @param stepLoggerFactory Step logger factory + */ + @Autowired + public ComputeOfflineSignatureStep( + ResultStatusService resultStatusService, + StepLoggerFactory stepLoggerFactory) { + super(PowerAuthStep.SIGNATURE_OFFLINE_COMPUTE, PowerAuthVersion.ALL_VERSIONS, resultStatusService, stepLoggerFactory); + } + + /** + * Constructor for backward compatibility + */ + public ComputeOfflineSignatureStep() { + this( + BackwardCompatibilityConst.RESULT_STATUS_SERVICE, + BackwardCompatibilityConst.STEP_LOGGER_FACTORY + ); + } + + @Override + public ParameterizedTypeReference getResponseTypeReference() { + // No response type, server is not called due to offline nature of the step + return null; + } + + @Override + public StepContext prepareStepContext(StepLogger stepLogger, Map context) throws Exception { + final ComputeOfflineSignatureStepModel model = new ComputeOfflineSignatureStepModel(); + model.fromMap(context); + + final RequestContext requestContext = RequestContext.builder() + .uri(model.getUriString()) + .build(); + + final StepContext stepContext = + buildStepContext(stepLogger, model, requestContext); + + if (model.getQrCodeData() == null) { + stepLogger.writeError(getStep().id() + "-error-missing-qr-code-data", "Missing offline signature data", "Specify offline signature data which is encoded in QR code"); + stepLogger.writeDoneFailed(getStep().id() + "-failed"); + return null; + } + + final String offlineData = unescape(model.getQrCodeData()); + final Map inputMap = new HashMap<>(); + inputMap.put("qrCodeData", offlineData); + + stepLogger.writeItem( + getStep().id() + "-start", + "Offline Signature Computation Started", + null, + "OK", + inputMap + ); + + // Ask for the password to unlock knowledge factor key + final char[] password; + if (model.getPassword() == null) { + Console console = System.console(); + password = console.readPassword("Enter your password to unlock the knowledge related key: "); + } else { + password = model.getPassword().toCharArray(); + } + + final String offlineSignature = calculateOfflineSignature(offlineData, stepLogger, model.getResultStatus(), password); + if (offlineSignature == null) { + return null; + } + + final Map resultMap = new HashMap<>(); + resultMap.put("offlineSignature", offlineSignature); + + stepLogger.writeItem( + getStep().id() + "-finished", + "Offline Signature Computation Finished", + null, + "OK", + resultMap + ); + + incrementCounter(stepContext.getModel()); + + return stepContext; + } + + private String unescape(String text) { + return text.replace("\\n", "\n"); + } + + private String calculateOfflineSignature(final String offlineData, final StepLogger stepLogger, + final ResultStatusObject resultStatusObject, final char[] password) { + // Split the offline data into individual lines, see: https://github.com/wultra/powerauth-webflow/blob/develop/docs/Off-line-Signatures-QR-Code.md + final String[] parts = offlineData.split("\n"); + if (parts.length < 7) { + stepLogger.writeError(getStep().id() + "-error-invalid-qr-code-data", "Invalid QR code data", "Invalid QR code, expected 7 lines of data or more"); + stepLogger.writeDoneFailed(getStep().id() + "-failed"); + return null; + } + final String operationId = parts[0]; + final String operationData = parts[3]; + final String nonce = parts[5]; + final String signatureLine = parts[parts.length - 1]; + + // 1 = KEY_SERVER_PRIVATE was used to sign data (personalized offline signature), otherwise return error + final String signatureType = signatureLine.substring(0, 1); + if (!"1".equals(signatureType)) { + stepLogger.writeError(getStep().id() + "-error-invalid-signature-type", "Invalid signature type", "Personalized offline signature expected, however other signature type is used"); + stepLogger.writeDoneFailed(getStep().id() + "-failed"); + return null; + } + + try { + // Verify ECDSA signature from the offline data, return error in case of invalid signature + final String ecdsaSignature = signatureLine.substring(1); + final byte[] serverPublicKeyBytes = BaseEncoding.base64().decode(resultStatusObject.getServerPublicKey()); + final ECPublicKey serverPublicKey = (ECPublicKey) KEY_CONVERTOR.convertBytesToPublicKey(serverPublicKeyBytes); + final String offlineDataWithoutSignature = offlineData.substring(0, offlineData.length() - ecdsaSignature.length()); + final boolean dataSignatureValid = SIGNATURE_UTILS.validateECDSASignature( + offlineDataWithoutSignature.getBytes(StandardCharsets.UTF_8), + BaseEncoding.base64().decode(ecdsaSignature), + serverPublicKey); + if (!dataSignatureValid) { + stepLogger.writeError(getStep().id() + "-error-invalid-signature", "Invalid signature", "Invalid signature of offline data"); + stepLogger.writeDoneFailed(getStep().id() + "-failed"); + return null; + } + + // Prepare data for PowerAuth offline signature calculation + final String dataForSignature = operationId + "&" + operationData; + final String signatureBaseString = PowerAuthHttpBody.getSignatureBaseString( + "POST", + "/operation/authorize/offline", + BaseEncoding.base64().decode(nonce), + dataForSignature.getBytes(StandardCharsets.UTF_8)); + + // Prepare keys for PowerAuth offline signature calculation + final byte[] signaturePossessionKeyBytes = BaseEncoding.base64().decode(resultStatusObject.getSignaturePossessionKey()); + final byte[] signatureKnowledgeKeySalt = BaseEncoding.base64().decode(resultStatusObject.getSignatureKnowledgeKeySalt()); + final byte[] signatureKnowledgeKeyEncryptedBytes = BaseEncoding.base64().decode(resultStatusObject.getSignatureKnowledgeKeyEncrypted()); + final SecretKey signaturePossessionKey = KEY_CONVERTOR.convertBytesToSharedSecretKey(signaturePossessionKeyBytes); + final SecretKey signatureKnowledgeKey = EncryptedStorageUtil.getSignatureKnowledgeKey( + password, + signatureKnowledgeKeyEncryptedBytes, + signatureKnowledgeKeySalt, + KEY_GENERATOR); + final List signatureKeys = new ArrayList<>(); + signatureKeys.add(signaturePossessionKey); + signatureKeys.add(signatureKnowledgeKey); + + // Calculate signature of normalized signature base string with 'offline' constant used as application secret + return SIGNATURE_UTILS.computePowerAuthSignature((signatureBaseString + "&offline").getBytes(StandardCharsets.UTF_8), + signatureKeys, + CounterUtil.getCtrData(resultStatusObject, stepLogger), + PowerAuthSignatureFormat.DECIMAL); + } catch (Exception ex) { + stepLogger.writeError(getStep().id() + "-error-cryptography", "Cryptography error", ex.getMessage()); + stepLogger.writeDoneFailed(getStep().id() + "-failed"); + return null; + } + } +} diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/VerifyTokenStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/VerifyTokenStep.java index d6e2463a..126eb792 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/VerifyTokenStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/VerifyTokenStep.java @@ -102,8 +102,8 @@ public StepContext> prepareStepContext buildStepContext(stepLogger, model, requestContext); Map map = new HashMap<>(); - map.put("TOKEN_ID", model.getTokenId()); - map.put("TOKEN_SECRET", model.getTokenSecret()); + map.put("tokenId", model.getTokenId()); + map.put("tokenSecret", model.getTokenSecret()); stepLogger.writeItem( "token-validate-start", "Token Digest Validation Started", diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/model/ComputeOfflineSignatureStepModel.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/model/ComputeOfflineSignatureStepModel.java new file mode 100644 index 00000000..506eec69 --- /dev/null +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/model/ComputeOfflineSignatureStepModel.java @@ -0,0 +1,72 @@ +/* + * PowerAuth Command-line utility + * Copyright 2022 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getlime.security.powerauth.lib.cmd.steps.model; + +import io.getlime.security.powerauth.lib.cmd.steps.model.feature.DryRunCapable; +import io.getlime.security.powerauth.lib.cmd.steps.model.feature.ResultStatusChangeable; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Map; + +/** + * Model representing parameters of the step for computing offline signatures. + * + * @author Roman Strobl, roman.strobl@wultra.com + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ComputeOfflineSignatureStepModel extends BaseStepModel implements ResultStatusChangeable, DryRunCapable { + + /** + * File name of the file with stored activation status. + */ + private String statusFileName; + + /** + * QR code data. + */ + private String qrCodeData; + + /** + * Knowledge key password (PIN). + */ + private String password; + + @Override + public Map toMap() { + Map context = super.toMap(); + context.put("STATUS_FILENAME", statusFileName); + context.put("QR_CODE_DATA", qrCodeData); + context.put("PASSWORD", password); + return context; + } + + @Override + public void fromMap(Map context) { + super.fromMap(context); + setStatusFileName((String) context.get("STATUS_FILENAME")); + setQrCodeData((String) context.get("QR_CODE_DATA")); + setPassword((String) context.get("PASSWORD")); + } + + @Override + public boolean isDryRun() { + return true; + } + +} diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v2/PrepareActivationStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v2/PrepareActivationStep.java index 47d57810..2839a82d 100755 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v2/PrepareActivationStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v2/PrepareActivationStep.java @@ -78,7 +78,7 @@ public class PrepareActivationStep extends AbstractBaseStepV2 { */ @Autowired public PrepareActivationStep(StepLogger stepLogger) { - super(PowerAuthStep.ACTIVATION_CREATE, PowerAuthVersion.VERSION_2, stepLogger); + super(PowerAuthStep.ACTIVATION_CREATE, PowerAuthVersion.VERSION_2, Objects.requireNonNull(stepLogger, "stepLogger must not be null")); } /** @@ -110,11 +110,9 @@ public ResultStatusObject execute(Map context) throws Exception Pattern p = Pattern.compile("^[A-Z2-7]{5}-[A-Z2-7]{5}-[A-Z2-7]{5}-[A-Z2-7]{5}(#.*)?$"); Matcher m = p.matcher(model.getActivationCode()); if (!m.find()) { - if (stepLogger != null) { - stepLogger.writeError("activation-create-error-activation-code", "Activation failed", "Activation code has invalid format"); - stepLogger.writeDoneFailed("activation-create-failed"); - return null; - } + stepLogger.writeError("activation-create-error-activation-code", "Activation failed", "Activation code has invalid format"); + stepLogger.writeDoneFailed("activation-create-failed"); + return null; } String activationIdShort = model.getActivationCode().substring(0, 11); String activationOTP = model.getActivationCode().substring(12, 23); diff --git a/powerauth-java-cmd/pom.xml b/powerauth-java-cmd/pom.xml index a4f99d1e..89ce4b04 100644 --- a/powerauth-java-cmd/pom.xml +++ b/powerauth-java-cmd/pom.xml @@ -22,7 +22,6 @@ 4.0.0 powerauth-java-cmd - 1.3.0 powerauth-java-cmd PowerAuth Reference Client Application connected to PowerAuth Standard RESTful API @@ -31,15 +30,13 @@ io.getlime.security powerauth-cmd-parent - 1.3.0 - ../pom.xml + 1.4.0 io.getlime.security powerauth-java-cmd-lib - 1.3.0 log4j-to-slf4j @@ -50,13 +47,15 @@ org.junit.jupiter junit-jupiter-engine - ${junit.version} test org.bouncycastle - bcprov-jdk15on - ${bc.version} + bcprov-jdk18on + + + ch.qos.logback + logback-classic @@ -78,13 +77,6 @@ io.getlime.security.powerauth.app.cmd.Application - - org.apache.maven.plugins - maven-deploy-plugin - - true - - maven-surefire-plugin ${maven-surefire-plugin.version} @@ -92,4 +84,28 @@ + + + public-repository + + + !useInternalRepo + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + diff --git a/powerauth-java-cmd/src/main/java/io/getlime/security/powerauth/app/cmd/Application.java b/powerauth-java-cmd/src/main/java/io/getlime/security/powerauth/app/cmd/Application.java index db5af222..0b9ddc84 100755 --- a/powerauth-java-cmd/src/main/java/io/getlime/security/powerauth/app/cmd/Application.java +++ b/powerauth-java-cmd/src/main/java/io/getlime/security/powerauth/app/cmd/Application.java @@ -104,6 +104,7 @@ public static void main(String[] args) { options.addOption("R", "recovery-code", true, "Recovery code to be confirmed."); options.addOption("P", "platform", true, "User device platform."); options.addOption("D", "device-info", true, "Information about user device."); + options.addOption("q", "qr-code-data", true, "Data for offline signature encoded in QR code."); options.addOption("v", "version", true, "PowerAuth protocol version."); Option httpHeaderOption = Option.builder("H") @@ -173,6 +174,11 @@ public static void main(String[] args) { deviceInfo = "cmd-tool"; } + String qrCodeData = null; + if (cmd.hasOption("q")) { + qrCodeData = cmd.getOptionValue("q"); + } + // Read values String method = cmd.getOptionValue("m"); String uriString = cmd.getOptionValue("u"); @@ -448,6 +454,7 @@ public static void main(String[] args) { model.setData(dataFileBytes); stepExecutionService.execute(powerAuthStep, version, model); + break; } case UPGRADE_START: { StartUpgradeStepModel model = new StartUpgradeStepModel(); @@ -520,6 +527,20 @@ public static void main(String[] args) { model.setVersion(version); stepExecutionService.execute(powerAuthStep, version, model); + break; + } + + case SIGNATURE_OFFLINE_COMPUTE: { + + ComputeOfflineSignatureStepModel model = new ComputeOfflineSignatureStepModel(); + model.setStatusFileName(statusFileName); + model.setQrCodeData(qrCodeData); + model.setPassword(cmd.getOptionValue("p")); + model.setResultStatus(resultStatusObject); + model.setVersion(version); + + stepExecutionService.execute(powerAuthStep, version, model); + break; } default: