diff --git a/docs/Readme.md b/docs/Readme.md index 85dcd82b..826d6769 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -78,7 +78,8 @@ This file is automatically created by the utility after you call the `create` me ## Specifying PowerAuth Protocol Version Command-line tool supports following PowerAuth protocol versions: -- Version `3.2` (default) +- Version `3.3` (default) +- Version `3.2` - Version `3.1` - Version `3.0` @@ -297,6 +298,7 @@ Use this method to send encrypted data to the server. ```bash java -jar powerauth-java-cmd.jar \ --url "http://localhost:8080/enrollment-server/exchange" \ + --base-url "http://localhost:8080/enrollment-server" \ --config-file "config.json" \ --method "encrypt" \ --data-file "request.json" \ @@ -304,7 +306,7 @@ java -jar powerauth-java-cmd.jar \ ``` Uses the `encrypt` method to encrypt data in `request.json` file using ECIES encryption. The encryption uses `application` scope, you can use the `activation` option to switch to activation scope. -The encrypted data is sent to specified endpoint URL. The endpoint which receives encrypted data needs to decrypt the data and return encrypted response back to the client. The cmd line tool receives the encrypted response from server, decrypts it and prints it into the command line. +The encrypted data is sent to specified endpoint URL. The base URL is used for PowerAuth Standard RESTful API requests, e.g. to request temporary encryption keys. The endpoint which receives encrypted data needs to decrypt the data and return encrypted response back to the client. The cmd line tool receives the encrypted response from server, decrypts it and prints it into the command line. ### Send Signed and Encrypted Data to Server @@ -313,6 +315,7 @@ Use this method to send signed and encrypted data to the server. ```bash java -jar powerauth-java-cmd.jar \ --url "http://localhost:8080/enrollment-server/exchange/v3/signed" \ + --base-url "http://localhost:8080/enrollment-server" \ --status-file "pa_status.json" \ --config-file "config.json" \ --method "sign-encrypt" \ @@ -324,7 +327,7 @@ java -jar powerauth-java-cmd.jar \ ``` The data in `request.json` file is signed and encrypted using ECIES encryption. See chapter [Validate the Signature](#validate-the-signature) which describes signature parameters. -The encrypted data is sent to specified endpoint URL. The endpoint which receives encrypted data needs to decrypt the data, verify data signature and return encrypted response back to the client. The cmd line tool receives the encrypted response from server, decrypts it and prints it into the command line. +The encrypted data is sent to specified endpoint URL. The base URL is used for PowerAuth Standard RESTful API requests, e.g. to request temporary encryption keys. The endpoint which receives encrypted data needs to decrypt the data, verify data signature and return encrypted response back to the client. The cmd line tool receives the encrypted response from server, decrypts it and prints it into the command line. ### Send Encrypted Data with Token Validation to Server @@ -333,6 +336,7 @@ Use this method to send encrypted data with token validation to the server. ```bash java -jar powerauth-java-cmd.jar \ --url "http://localhost:8080/enrollment-server/exchange/v3/token" \ + --base-url "http://localhost:8080/enrollment-server" \ --status-file "pa_status.json" \ --config-file "config.json" \ --method "token-encrypt" \ @@ -343,7 +347,7 @@ java -jar powerauth-java-cmd.jar \ ``` The data in `request.json` file is encrypted using ECIES encryption and token authentication is computed. -The encrypted data is sent to specified endpoint URL. The endpoint which receives encrypted data needs to decrypt the data, validate the token and return encrypted response back to the client. The cmd line tool receives the encrypted response from server, decrypts it and prints it into the command line. +The encrypted data is sent to specified endpoint URL. The base URL is used for PowerAuth Standard RESTful API requests, e.g. to request temporary encryption keys. The endpoint which receives encrypted data needs to decrypt the data, validate the token and return encrypted response back to the client. The cmd line tool receives the encrypted response from server, decrypts it and prints it into the command line. ### Start Upgrade @@ -365,7 +369,7 @@ Use this method to commit upgrade of a version `2` activation to version `3`. ``` java -jar powerauth-java-cmd.jar \ - --url "http://localhost:8080/powerauth-webflow" \ + --url "http://localhost:8080/enrollment-server" \ --status-file "pa_status.json" \ --config-file "config.json" \ --method "commit-upgrade" @@ -399,6 +403,7 @@ usage: java -jar powerauth-java-cmd.jar -a,--activation-code In case a specified method is 'create', this field contains the activation key (a concatenation of a short activation ID and activation OTP). + -b,--base-url Base URL of the PowerAuth Standard RESTful API. -A,--activation-otp In case a specified method is 'create', this field contains additional activation OTP (PA server 0.24+) -c,--config-file Specifies a path to the config file with Base64 encoded server @@ -445,7 +450,7 @@ usage: java -jar powerauth-java-cmd.jar 'token-encrypt', this field specifies a HTTP method, as specified in PowerAuth signature process. -T,--token-id Token ID (UUID4), in case of 'token-validate' method. - -u,--url Base URL of the PowerAuth Standard RESTful API. + -u,--url URL used for the request. -v,--version PowerAuth protocol version. -y,--dry-run In case a specified method is 'sign', 'sign-encrypt', 'validate-token' or 'token-encrypt' and this attribute is diff --git a/pom.xml b/pom.xml index b9f6328d..373bcb6f 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ io.getlime.security powerauth-cmd-parent - 1.8.0 + 1.9.0 pom 2016 @@ -78,19 +78,19 @@ 3.13.0 3.3.1 - 3.1.2 - 3.8.0 + 3.1.3 + 3.10.1 3.5.0 - 3.3.1 - 3.3.2 - 1.8.0 - 2.16.1 + 3.5.1 + 3.3.4 + 1.9.0 + 2.17.0 1.1.1 - 1.8.0 - 1.8.0 - 1.10.0 + 1.9.0 + 1.9.0 + 1.11.0 diff --git a/powerauth-java-cmd-lib/pom.xml b/powerauth-java-cmd-lib/pom.xml index 73a4117f..bfba1dda 100644 --- a/powerauth-java-cmd-lib/pom.xml +++ b/powerauth-java-cmd-lib/pom.xml @@ -10,7 +10,7 @@ powerauth-cmd-parent io.getlime.security - 1.8.0 + 1.9.0 @@ -54,6 +54,10 @@ powerauth-java-http ${powerauth-crypto.version} + + org.springframework.security + spring-security-oauth2-jose + org.springframework.boot diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/consts/PowerAuthVersion.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/consts/PowerAuthVersion.java index 6f8997fb..7f0d532b 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/consts/PowerAuthVersion.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/consts/PowerAuthVersion.java @@ -37,9 +37,14 @@ public enum PowerAuthVersion { V3_1(3, "3.1"), /** - * Version 3.1 + * Version 3.2 */ - V3_2(3, "3.2"); + V3_2(3, "3.2"), + + /** + * Version 3.3 + */ + V3_3(3, "3.3"); /** * All supported versions @@ -49,12 +54,12 @@ public enum PowerAuthVersion { /** * Default version */ - public static final PowerAuthVersion DEFAULT = V3_2; + public static final PowerAuthVersion DEFAULT = V3_3; /** * All versions belonging to major version 3 */ - public static final List VERSION_3 = List.of(V3_0, V3_1, V3_2); + public static final List VERSION_3 = List.of(V3_0, V3_1, V3_2, V3_3); /** * Major version value @@ -62,7 +67,7 @@ public enum PowerAuthVersion { private final int majorVersion; /** - * Version string value ("3.0", "3.1", "3.2", ...) + * Version string value ("3.0", "3.1", "3.2", "3.3", ...) */ private final String value; @@ -94,7 +99,7 @@ public boolean useIv() { * @return Flag whether decryption uses different non-zero initialization vector. */ public boolean useDifferentIvForResponse() { - return majorVersion >= 3 && V3_2.equals(this); + return majorVersion >= 3 && !V3_0.equals(this) && !V3_1.equals(this); } /** @@ -107,6 +112,16 @@ public boolean useTimestamp() { return majorVersion >= 3 && !V3_0.equals(this) && !V3_1.equals(this); } + /** + * Provides flag whether encryption uses temporary keys. + *

This feature is supported only for protocol V3.3.+

+ * + * @return Flag whether encryption uses temporary keys + */ + public boolean useTemporaryKeys() { + return majorVersion >= 3 && !V3_0.equals(this) && !V3_1.equals(this) && !V3_2.equals(this); + } + /** * @return Version string value */ diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/AbstractActivationStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/AbstractActivationStep.java index 47dfe79e..b178b142 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/AbstractActivationStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/AbstractActivationStep.java @@ -22,10 +22,7 @@ import io.getlime.security.powerauth.crypto.client.vault.PowerAuthClientVault; import io.getlime.security.powerauth.crypto.lib.encryptor.ClientEncryptor; import io.getlime.security.powerauth.crypto.lib.encryptor.EncryptorFactory; -import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptedRequest; -import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptedResponse; -import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId; -import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorParameters; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.*; import io.getlime.security.powerauth.crypto.lib.encryptor.model.v3.ClientEncryptorSecrets; import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator; import io.getlime.security.powerauth.crypto.lib.util.KeyConvertor; @@ -57,6 +54,9 @@ import java.security.PublicKey; import java.util.*; +import static io.getlime.security.powerauth.lib.cmd.util.TemporaryKeyUtil.TEMPORARY_KEY_ID; +import static io.getlime.security.powerauth.lib.cmd.util.TemporaryKeyUtil.TEMPORARY_PUBLIC_KEY; + /** * Abstract step with common parts used in activations steps * @@ -135,7 +135,6 @@ public void processResponse(StepContext stepContext) public ResultStatusObject processResponse(EciesEncryptedResponse encryptedResponseL1, StepContext context) throws Exception { M model = context.getModel(); - final PowerAuthVersion version = model.getVersion(); final ActivationSecurityContext securityContext = (ActivationSecurityContext) context.getSecurityContext(); // Decrypt activation layer 1 response @@ -252,17 +251,22 @@ protected ParameterizedTypeReference getResponseTypeRefe */ protected void addEncryptedRequest(StepContext stepContext) throws Exception { M model = stepContext.getModel(); + fetchTemporaryKey(stepContext, EncryptorScope.APPLICATION_SCOPE); + final String temporaryPublicKey = (String) stepContext.getAttributes().get(TEMPORARY_PUBLIC_KEY); + final PublicKey encryptionPublicKey = temporaryPublicKey == null ? + model.getMasterPublicKey() : + KEY_CONVERTOR.convertBytesToPublicKey(Base64.getDecoder().decode(temporaryPublicKey)); // Get activation key and secret ClientEncryptor clientEncryptorL1 = ENCRYPTOR_FACTORY.getClientEncryptor( EncryptorId.APPLICATION_SCOPE_GENERIC, - new EncryptorParameters(model.getVersion().value(), model.getApplicationKey(), null), - new ClientEncryptorSecrets(model.getMasterPublicKey(), model.getApplicationSecret()) + new EncryptorParameters(model.getVersion().value(), model.getApplicationKey(), null, (String) stepContext.getAttributes().get(TEMPORARY_KEY_ID)), + new ClientEncryptorSecrets(encryptionPublicKey, model.getApplicationSecret()) ); ClientEncryptor clientEncryptorL2 = ENCRYPTOR_FACTORY.getClientEncryptor( EncryptorId.ACTIVATION_LAYER_2, - new EncryptorParameters(model.getVersion().value(), model.getApplicationKey(), null), - new ClientEncryptorSecrets(model.getMasterPublicKey(), model.getApplicationSecret()) + new EncryptorParameters(model.getVersion().value(), model.getApplicationKey(), null, (String) stepContext.getAttributes().get(TEMPORARY_KEY_ID)), + new ClientEncryptorSecrets(encryptionPublicKey, model.getApplicationSecret()) ); KeyPair deviceKeyPair = ACTIVATION.generateDeviceKeyPair(); 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 02b5c662..549afc0a 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 @@ -21,11 +21,10 @@ import com.wultra.core.rest.client.base.RestClientException; import io.getlime.security.powerauth.crypto.lib.encryptor.ClientEncryptor; import io.getlime.security.powerauth.crypto.lib.encryptor.EncryptorFactory; -import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptedRequest; -import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptedResponse; -import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId; -import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorParameters; +import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.exception.EciesException; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.*; import io.getlime.security.powerauth.crypto.lib.encryptor.model.v3.ClientEncryptorSecrets; +import io.getlime.security.powerauth.crypto.lib.util.KeyConvertor; 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.DisabledStepLogger; @@ -54,8 +53,12 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import java.security.PublicKey; import java.util.*; +import static io.getlime.security.powerauth.lib.cmd.util.TemporaryKeyUtil.TEMPORARY_KEY_ID; +import static io.getlime.security.powerauth.lib.cmd.util.TemporaryKeyUtil.TEMPORARY_PUBLIC_KEY; + /** * Abstract step with common execution patterns and methods * @@ -66,6 +69,7 @@ public abstract class AbstractBaseStep implements BaseStep { private static final Logger logger = LoggerFactory.getLogger(AbstractBaseStep.class); + private static final KeyConvertor KEY_CONVERTOR = new KeyConvertor(); /** * Corresponding PowerAuth step @@ -147,8 +151,15 @@ public final ResultStatusObject execute(StepLogger stepLogger, Map stepContext = prepareStepContext(stepLogger, context); - if (stepContext == null) { + final StepContext stepContext; + try { + stepContext = prepareStepContext(stepLogger, context); + if (stepContext == null) { + return null; + } + } catch (EciesException e) { + stepLogger.writeError(getStep().id() + "-error-encryption", e); + stepLogger.writeDoneFailed(getStep().id() + "-failed"); return null; } @@ -184,18 +195,26 @@ public final ResultStatusObject execute(StepLogger stepLogger, Map stepContext, String applicationKey, String applicationSecret, EncryptorId encryptorId, byte[] data) throws Exception { + public void addEncryptedRequest(StepContext stepContext, String applicationKey, String applicationSecret, EncryptorId encryptorId, byte[] data, EncryptorScope scope) throws Exception { M model = stepContext.getModel(); final SimpleSecurityContext securityContext = (SimpleSecurityContext) stepContext.getSecurityContext(); final ResultStatusObject resultStatusObject = model.getResultStatus(); + fetchTemporaryKey(stepContext, scope); + final ClientEncryptor encryptor; if (securityContext == null) { + final String temporaryKeyId = (String) stepContext.getAttributes().get(TEMPORARY_KEY_ID); + final String temporaryPublicKey = (String) stepContext.getAttributes().get(TEMPORARY_PUBLIC_KEY); + final PublicKey encryptionPublicKey = temporaryKeyId == null ? + resultStatusObject.getServerPublicKeyObject() : + KEY_CONVERTOR.convertBytesToPublicKey(Base64.getDecoder().decode(temporaryPublicKey)); final byte[] transportMasterKeyBytes = Base64.getDecoder().decode(resultStatusObject.getTransportMasterKey()); - final EncryptorParameters encryptorParameters = new EncryptorParameters(model.getVersion().value(), applicationKey, resultStatusObject.getActivationId()); - final ClientEncryptorSecrets encryptorSecrets = new ClientEncryptorSecrets(resultStatusObject.getServerPublicKeyObject(), applicationSecret, transportMasterKeyBytes); + final EncryptorParameters encryptorParameters = new EncryptorParameters(model.getVersion().value(), applicationKey, resultStatusObject.getActivationId(), temporaryKeyId); + final ClientEncryptorSecrets encryptorSecrets = new ClientEncryptorSecrets(encryptionPublicKey, applicationSecret, transportMasterKeyBytes); encryptor = ENCRYPTOR_FACTORY.getClientEncryptor(encryptorId, encryptorParameters, encryptorSecrets); stepContext.setSecurityContext(SimpleSecurityContext.builder() .encryptor(encryptor) @@ -232,6 +251,16 @@ public void addEncryptedRequest(StepContext stepContext, ClientEncryptor e stepContext.getRequestContext().setRequestObject(requestObject); } + /** + * Fetch temporary key for current request, if applicable. + * @param stepContext Step context. + * @param scope ECIES scope. + * @throws Exception In case request fails. + */ + public void fetchTemporaryKey(StepContext stepContext, EncryptorScope scope) throws Exception { + TemporaryKeyUtil.fetchTemporaryKey(getStep(), stepContext, scope); + } + /** * Decrypts an object from a response * 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 index 8ac195c6..34094557 100644 --- 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 @@ -55,6 +55,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Roman Strobl, roman.strobl@wultra.com diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/VerifySignatureStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/VerifySignatureStep.java index a7ab2a68..8d97ea36 100755 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/VerifySignatureStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/VerifySignatureStep.java @@ -43,6 +43,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com 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 95414488..671a82bd 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 @@ -42,6 +42,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/model/BaseStepModel.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/model/BaseStepModel.java index f60f4c2d..1a704ba0 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/model/BaseStepModel.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/model/BaseStepModel.java @@ -39,10 +39,15 @@ public class BaseStepModel implements BaseStepData { private Map headers; /** - * Base URI of PowerAuth Standard RESTful API + * URL used for the request. */ private String uriString; + /** + * Base URI of PowerAuth Standard RESTful API. + */ + private String baseUriString; + /** * Activation status object */ @@ -109,6 +114,7 @@ public Map toMap() { Map context = new HashMap<>(); context.put("HTTP_HEADERS", headers); context.put("URI_STRING", uriString); + context.put("BASE_URI_STRING", baseUriString); context.put("STATUS_OBJECT", resultStatusObject); context.put("VERSION", version); return context; @@ -123,6 +129,7 @@ public Map toMap() { public void fromMap(Map context) { setHeaders((Map) context.get("HTTP_HEADERS")); setUriString((String) context.get("URI_STRING")); + setBaseUriString((String) context.get("BASE_URI_STRING")); Object statusObject = context.get("STATUS_OBJECT"); if (statusObject instanceof JSONObject) { setResultStatus(ResultStatusObject.fromJsonObject((JSONObject) statusObject)); diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/ActivationRecoveryStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/ActivationRecoveryStep.java index 18ba62c9..3c57a5c3 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/ActivationRecoveryStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/ActivationRecoveryStep.java @@ -43,6 +43,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CommitUpgradeStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CommitUpgradeStep.java index 9c3dc90a..d42b614f 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CommitUpgradeStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CommitUpgradeStep.java @@ -43,6 +43,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/ConfirmRecoveryCodeStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/ConfirmRecoveryCodeStep.java index 264f67c0..96fa0adc 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/ConfirmRecoveryCodeStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/ConfirmRecoveryCodeStep.java @@ -17,6 +17,7 @@ package io.getlime.security.powerauth.lib.cmd.steps.v3; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope; import io.getlime.security.powerauth.lib.cmd.consts.BackwardCompatibilityConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthStep; @@ -48,6 +49,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com @@ -110,7 +112,7 @@ public StepContext prepare // Encrypt the request final byte[] requestBytesPayload = RestClientConfiguration.defaultMapper().writeValueAsBytes(confirmRequestPayload); - addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.CONFIRM_RECOVERY_CODE, requestBytesPayload); + addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.CONFIRM_RECOVERY_CODE, requestBytesPayload, EncryptorScope.ACTIVATION_SCOPE); powerAuthHeaderFactory.getHeaderProvider(model).addHeader(stepContext); diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CreateActivationStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CreateActivationStep.java index 78265862..8bf03a85 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CreateActivationStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CreateActivationStep.java @@ -43,6 +43,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CreateTokenStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CreateTokenStep.java index 662a670a..a5a765c4 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CreateTokenStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/CreateTokenStep.java @@ -17,6 +17,7 @@ package io.getlime.security.powerauth.lib.cmd.steps.v3; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope; import io.getlime.security.powerauth.lib.cmd.consts.BackwardCompatibilityConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthStep; @@ -45,6 +46,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com @@ -99,7 +101,7 @@ public StepContext prepareStepCont StepContext stepContext = buildStepContext(stepLogger, model, requestContext); - addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.CREATE_TOKEN, PowerAuthConst.EMPTY_JSON_BYTES); + addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.CREATE_TOKEN, PowerAuthConst.EMPTY_JSON_BYTES, EncryptorScope.ACTIVATION_SCOPE); powerAuthHeaderFactory.getHeaderProvider(model).addHeader(stepContext); diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/EncryptStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/EncryptStep.java index 22d81f1b..cccedfe0 100755 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/EncryptStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/EncryptStep.java @@ -20,8 +20,9 @@ import io.getlime.security.powerauth.crypto.lib.encryptor.EncryptorFactory; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorParameters; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope; import io.getlime.security.powerauth.crypto.lib.encryptor.model.v3.ClientEncryptorSecrets; -import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator; +import io.getlime.security.powerauth.crypto.lib.util.KeyConvertor; import io.getlime.security.powerauth.http.PowerAuthEncryptionHttpHeader; import io.getlime.security.powerauth.lib.cmd.consts.BackwardCompatibilityConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthConst; @@ -42,8 +43,12 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.stereotype.Component; +import java.security.PublicKey; import java.util.Map; +import static io.getlime.security.powerauth.lib.cmd.util.TemporaryKeyUtil.TEMPORARY_KEY_ID; +import static io.getlime.security.powerauth.lib.cmd.util.TemporaryKeyUtil.TEMPORARY_PUBLIC_KEY; + /** * Encrypt step encrypts request data using ECIES encryption in application or activation scope. * @@ -52,6 +57,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com @@ -61,7 +67,7 @@ public class EncryptStep extends AbstractBaseStep { private static final EncryptorFactory ENCRYPTOR_FACTORY = new EncryptorFactory(); - private static final KeyGenerator KEY_GENERATOR = new KeyGenerator(); + private static final KeyConvertor KEY_CONVERTOR = new KeyConvertor(); /** * Constructor @@ -115,36 +121,57 @@ public StepContext prepareStepContext( requestDataBytes ); - final ClientEncryptor encryptor; + final EncryptorScope scope = switch (model.getScope()) { + case "activation": + yield EncryptorScope.ACTIVATION_SCOPE; + case "application": + yield EncryptorScope.APPLICATION_SCOPE; + default: + yield null; + }; + if (scope == null) { + stepLogger.writeError("encrypt-error-scope", "Encrypt Request Failed", "Unsupported encryption scope: " + model.getScope()); + stepLogger.writeDoneFailed("encrypt-failed"); + return null; + } + fetchTemporaryKey(stepContext, scope); + final String temporaryKeyId = (String) stepContext.getAttributes().get(TEMPORARY_KEY_ID); + final String temporaryPublicKey = (String) stepContext.getAttributes().get(TEMPORARY_PUBLIC_KEY); // Prepare the encryption header final EncryptorId encryptorId; + final ClientEncryptor encryptor; final PowerAuthEncryptionHttpHeader header; - switch (model.getScope()) { - case "application" -> { + switch (scope) { + case APPLICATION_SCOPE -> { + final PublicKey encryptionPublicKey = temporaryPublicKey == null ? + model.getMasterPublicKey() : + KEY_CONVERTOR.convertBytesToPublicKey(java.util.Base64.getDecoder().decode(temporaryPublicKey)); // Prepare ECIES encryptor with sharedInfo1 = /pa/generic/application encryptorId = EncryptorId.APPLICATION_SCOPE_GENERIC; - final EncryptorParameters encryptorParameters = new EncryptorParameters(model.getVersion().value(), model.getApplicationKey(), null); - final ClientEncryptorSecrets encryptorSecrets = new ClientEncryptorSecrets(model.getMasterPublicKey(), model.getApplicationSecret()); + final EncryptorParameters encryptorParameters = new EncryptorParameters(model.getVersion().value(), model.getApplicationKey(), null, temporaryKeyId); + final ClientEncryptorSecrets encryptorSecrets = new ClientEncryptorSecrets(encryptionPublicKey, model.getApplicationSecret()); encryptor = ENCRYPTOR_FACTORY.getClientEncryptor(encryptorId, encryptorParameters, encryptorSecrets); header = new PowerAuthEncryptionHttpHeader(model.getApplicationKey(), model.getVersion().value()); } - case "activation" -> { - ResultStatusObject resultStatusObject = model.getResultStatus(); + case ACTIVATION_SCOPE -> { + final ResultStatusObject resultStatusObject = model.getResultStatus(); + final PublicKey encryptionPublicKey = temporaryPublicKey == null ? + resultStatusObject.getServerPublicKeyObject() : + KEY_CONVERTOR.convertBytesToPublicKey(java.util.Base64.getDecoder().decode(temporaryPublicKey)); encryptorId = EncryptorId.ACTIVATION_SCOPE_GENERIC; encryptor = ENCRYPTOR_FACTORY.getClientEncryptor( encryptorId, - new EncryptorParameters(model.getVersion().value(), model.getApplicationKey(), resultStatusObject.getActivationId()), - new ClientEncryptorSecrets(resultStatusObject.getServerPublicKeyObject(), model.getApplicationSecret(), Base64.decode(resultStatusObject.getTransportMasterKey())) + new EncryptorParameters(model.getVersion().value(), model.getApplicationKey(), resultStatusObject.getActivationId(), temporaryKeyId), + new ClientEncryptorSecrets(encryptionPublicKey, model.getApplicationSecret(), Base64.decode(resultStatusObject.getTransportMasterKey())) ); // Prepare ECIES encryptor with sharedInfo1 = /pa/generic/activation final String activationId = model.getResultStatus().getActivationId(); header = new PowerAuthEncryptionHttpHeader(model.getApplicationKey(), activationId, model.getVersion().value()); } default -> { - stepLogger.writeError("encrypt-error-scope", "Encrypt Request Failed", "Unsupported encryption scope: " + model.getScope()); - stepLogger.writeDoneFailed("encrypt-failed"); - return null; + encryptor = null; + header = null; } } diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/GetStatusStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/GetStatusStep.java index 081503a1..65c33867 100755 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/GetStatusStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/GetStatusStep.java @@ -51,6 +51,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/PrepareActivationStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/PrepareActivationStep.java index e67f63cb..eefa2c33 100755 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/PrepareActivationStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/PrepareActivationStep.java @@ -47,6 +47,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/RemoveStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/RemoveStep.java index 8d7433d0..b62da044 100755 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/RemoveStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/RemoveStep.java @@ -43,6 +43,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/RemoveTokenStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/RemoveTokenStep.java index 76556e9f..aed6eff6 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/RemoveTokenStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/RemoveTokenStep.java @@ -46,6 +46,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/SignAndEncryptStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/SignAndEncryptStep.java index d371b475..d8a1f620 100755 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/SignAndEncryptStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/SignAndEncryptStep.java @@ -17,6 +17,7 @@ package io.getlime.security.powerauth.lib.cmd.steps.v3; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope; import io.getlime.security.powerauth.lib.cmd.consts.BackwardCompatibilityConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthStep; @@ -47,6 +48,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com @@ -140,7 +142,7 @@ public StepContext prepareStep // Encrypt the request - addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.ACTIVATION_SCOPE_GENERIC, requestDataBytes); + addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.ACTIVATION_SCOPE_GENERIC, requestDataBytes, EncryptorScope.ACTIVATION_SCOPE); incrementCounter(model); diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/StartUpgradeStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/StartUpgradeStep.java index c6313051..a8a1ac1c 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/StartUpgradeStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/StartUpgradeStep.java @@ -16,6 +16,7 @@ package io.getlime.security.powerauth.lib.cmd.steps.v3; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope; import io.getlime.security.powerauth.lib.cmd.consts.BackwardCompatibilityConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthStep; @@ -44,6 +45,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com @@ -97,7 +99,7 @@ public StepContext prepareStepCon StepContext stepContext = buildStepContext(stepLogger, model, requestContext); - addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.UPGRADE, PowerAuthConst.EMPTY_JSON_BYTES); + addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.UPGRADE, PowerAuthConst.EMPTY_JSON_BYTES, EncryptorScope.ACTIVATION_SCOPE); powerAuthHeaderFactory.getHeaderProvider(model).addHeader(stepContext); diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/TokenAndEncryptStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/TokenAndEncryptStep.java index 0873eda8..23eb293b 100755 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/TokenAndEncryptStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/TokenAndEncryptStep.java @@ -17,6 +17,7 @@ package io.getlime.security.powerauth.lib.cmd.steps.v3; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope; import io.getlime.security.powerauth.lib.cmd.consts.BackwardCompatibilityConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthStep; @@ -46,6 +47,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Roman Strobl, roman.strobl@wultra.com @@ -139,7 +141,7 @@ public StepContext prepareStep // Encrypt the request - addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.ACTIVATION_SCOPE_GENERIC, requestDataBytes); + addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.ACTIVATION_SCOPE_GENERIC, requestDataBytes, EncryptorScope.ACTIVATION_SCOPE); powerAuthHeaderFactory.getHeaderProvider(model).addHeader(stepContext); diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/VaultUnlockStep.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/VaultUnlockStep.java index 6709157f..f107ac6d 100755 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/VaultUnlockStep.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/steps/v3/VaultUnlockStep.java @@ -18,6 +18,7 @@ import io.getlime.security.powerauth.crypto.client.keyfactory.PowerAuthClientKeyFactory; import io.getlime.security.powerauth.crypto.client.vault.PowerAuthClientVault; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope; import io.getlime.security.powerauth.crypto.lib.util.KeyConvertor; import io.getlime.security.powerauth.lib.cmd.consts.BackwardCompatibilityConst; import io.getlime.security.powerauth.lib.cmd.consts.PowerAuthConst; @@ -55,6 +56,7 @@ *
  • 3.0
  • *
  • 3.1
  • *
  • 3.2
  • + *
  • 3.3
  • * * * @author Lukas Lukovsky, lukas.lukovsky@wultra.com @@ -121,7 +123,7 @@ public StepContext prepareStepCont final byte[] requestBytesPayload = RestClientConfiguration.defaultMapper().writeValueAsBytes(requestPayload); - addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.VAULT_UNLOCK, requestBytesPayload); + addEncryptedRequest(stepContext, model.getApplicationKey(), model.getApplicationSecret(), EncryptorId.VAULT_UNLOCK, requestBytesPayload, EncryptorScope.ACTIVATION_SCOPE); powerAuthHeaderFactory.getHeaderProvider(model).addHeader(stepContext); diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/util/SecurityUtil.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/util/SecurityUtil.java index 63893c69..4643cd53 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/util/SecurityUtil.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/util/SecurityUtil.java @@ -68,6 +68,7 @@ public static EciesEncryptedRequest createEncryptedRequest(EncryptedRequest encr request.setMac(encryptedRequest.getMac()); request.setNonce(encryptedRequest.getNonce()); request.setTimestamp(encryptedRequest.getTimestamp()); + request.setTemporaryKeyId(encryptedRequest.getTemporaryKeyId()); return request; } diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/util/TemporaryKeyUtil.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/util/TemporaryKeyUtil.java new file mode 100644 index 00000000..eece9b0c --- /dev/null +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/util/TemporaryKeyUtil.java @@ -0,0 +1,216 @@ +/* + * PowerAuth Command-line utility + * Copyright 2024 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.util; + +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.util.Base64URL; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import com.wultra.core.rest.client.base.RestClient; +import com.wultra.core.rest.client.base.RestClientException; +import io.getlime.core.rest.model.base.request.ObjectRequest; +import io.getlime.core.rest.model.base.response.ObjectResponse; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope; +import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator; +import io.getlime.security.powerauth.crypto.lib.util.HMACHashUtilities; +import io.getlime.security.powerauth.crypto.lib.util.KeyConvertor; +import io.getlime.security.powerauth.crypto.lib.util.SignatureUtils; +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.steps.context.StepContext; +import io.getlime.security.powerauth.lib.cmd.steps.model.BaseStepModel; +import io.getlime.security.powerauth.lib.cmd.steps.model.data.BaseStepData; +import io.getlime.security.powerauth.rest.api.model.request.TemporaryKeyRequest; +import io.getlime.security.powerauth.rest.api.model.response.TemporaryKeyResponse; +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.DLSequence; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; + +import javax.crypto.SecretKey; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; + +/** + * Helper class for fetching temporary keys. + * + * @author Roman Strobl, roman.strobl@wultra.com + */ +public class TemporaryKeyUtil { + + private TemporaryKeyUtil() { + } + + /** + * Temporary key ID constant. + */ + public static final String TEMPORARY_KEY_ID = "temporaryKeyId"; + /** + * Temporary public key constant. + */ + public static final String TEMPORARY_PUBLIC_KEY = "temporaryPublicKey"; + + private static final KeyGenerator KEY_GENERATOR = new KeyGenerator(); + private static final KeyConvertor KEY_CONVERTOR = new KeyConvertor(); + private static final SignatureUtils SIGNATURE_UTILS = new SignatureUtils(); + + /** + * Fetch temporary key for encryption from the server and store it into the step context. + * @param step Current step. + * @param stepContext Step context. + * @param scope Encryption scope. + * @throws Exception Thrown in case temporary key fetch fails. + */ + public static void fetchTemporaryKey(PowerAuthStep step, StepContext stepContext, EncryptorScope scope) throws Exception { + final PowerAuthVersion version = stepContext.getModel().getVersion(); + if (!version.useTemporaryKeys() || stepContext.getAttributes().containsKey(TEMPORARY_KEY_ID)) { + return; + } + final RestClient restClient = RestClientFactory.getRestClient(); + if (restClient == null) { + stepContext.getStepLogger().writeError(step.id() + "-error-rest-client", "Unable to prepare a REST client"); + return; + } + sendTemporaryKeyRequest(step, stepContext, scope); + } + + private static Map prepareHeaders() { + Map headers = new HashMap<>(); + headers.put(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE); + headers.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + return headers; + } + + private static String createJwtRequest(StepContext stepContext, BaseStepModel model, EncryptorScope scope, String challenge) throws Exception { + final Instant now = Instant.now(); + final String activationId = scope == EncryptorScope.ACTIVATION_SCOPE ? model.getResultStatus().getActivationId() : null; + final JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder() + .claim("applicationKey", stepContext.getModel().toMap().get("APPLICATION_KEY")) + .claim("activationId", activationId) + .claim("challenge", challenge) + .issueTime(Date.from(now)) + .expirationTime(Date.from(now.plus(5, ChronoUnit.MINUTES))) + .build(); + final byte[] secretKey = getSecretKey(stepContext, model, scope); + return signJwt(jwtClaims, secretKey); + } + + private static byte[] getSecretKey(StepContext stepContext, BaseStepModel model, EncryptorScope scope) throws Exception { + final String appSecret = (String) stepContext.getModel().toMap().get("APPLICATION_SECRET"); + if (scope == EncryptorScope.APPLICATION_SCOPE) { + return Base64.getDecoder().decode(appSecret); + } else if (scope == EncryptorScope.ACTIVATION_SCOPE) { + final byte[] appSecretBytes = Base64.getDecoder().decode(appSecret); + final SecretKey transportMasterKey = model.getResultStatus().getTransportMasterKeyObject(); + final SecretKey secretKeyBytes = KEY_GENERATOR.deriveSecretKeyHmac(transportMasterKey, appSecretBytes); + return KEY_CONVERTOR.convertSharedSecretKeyToBytes(secretKeyBytes); + } + return null; + } + + private static String signJwt(JWTClaimsSet jwtClaims, byte[] secretKey) throws Exception { + final JWSHeader jwsHeader = new JWSHeader(JWSAlgorithm.HS256); + final byte[] payloadBytes = jwtClaims.toPayload().toBytes(); + final Base64URL encodedHeader = jwsHeader.toBase64URL(); + final Base64URL encodedPayload = Base64URL.encode(payloadBytes); + final String signingInput = encodedHeader + "." + encodedPayload; + final byte[] hash = new HMACHashUtilities().hash(secretKey, signingInput.getBytes(StandardCharsets.UTF_8)); + final Base64URL signature = Base64URL.encode(hash); + return encodedHeader + "." + encodedPayload + "." + signature; + } + + private static void sendTemporaryKeyRequest(PowerAuthStep step, StepContext stepContext, EncryptorScope scope) throws Exception { + final BaseStepModel model = (BaseStepModel) stepContext.getModel(); + final Map headers = prepareHeaders(); + String baseUri = model.getBaseUriString(); + if (!StringUtils.hasText(baseUri)) { + baseUri = model.getUriString(); + if (!StringUtils.hasText(baseUri)) { + stepContext.getStepLogger().writeError(step.id() + "-error-missing-base-uri-string", "Base URI string is required for fetching temporary keys"); + return; + } + } + final String uri = baseUri + "/pa/v3/keystore/create"; + final byte[] challengeBytes = KEY_GENERATOR.generateRandomBytes(18); + final String challenge = Base64.getEncoder().encodeToString(challengeBytes); + final String requestData = createJwtRequest(stepContext, model, scope, challenge); + final TemporaryKeyRequest jwtData = new TemporaryKeyRequest(); + jwtData.setJwt(requestData); + final ObjectRequest request = new ObjectRequest<>(jwtData); + final RestClient restClient = RestClientFactory.getRestClient(); + try { + final ObjectResponse response = Objects.requireNonNull(restClient).postObject(uri, request, null, MapUtil.toMultiValueMap(headers), TemporaryKeyResponse.class); + stepContext.getStepLogger().writeItem(step.id() + "-temporary-key-fetched", "Temporary key fetched", "Temporary key was fetched from the server", "OK", null); + handleTemporaryKeyResponse(step, stepContext, response, scope); + } catch (RestClientException ex) { + stepContext.getStepLogger().writeServerCallError(step.id() + "-error-server-call", ex.getStatusCode().value(), ex.getResponse(), HttpUtil.flattenHttpHeaders(ex.getResponseHeaders())); + } + } + + private static void handleTemporaryKeyResponse(PowerAuthStep step, StepContext stepContext, ObjectResponse response, EncryptorScope scope) throws Exception { + final String jwtResponse = response.getResponseObject().getJwt(); + final SignedJWT decodedJWT = SignedJWT.parse(jwtResponse); + final ECPublicKey publicKey = switch (scope) { + case ACTIVATION_SCOPE -> (ECPublicKey) stepContext.getModel().getResultStatus().getServerPublicKeyObject(); + case APPLICATION_SCOPE -> (ECPublicKey) stepContext.getModel().toMap().get("MASTER_PUBLIC_KEY"); + }; + if (!validateJwtSignature(decodedJWT, publicKey)) { + stepContext.getStepLogger().writeError(step.id() + "-error-signature-invalid", "JWT signature is invalid"); + return; + } + final String temporaryKeyId = (String) decodedJWT.getJWTClaimsSet().getClaim("sub"); + final String temporaryPublicKey = (String) decodedJWT.getJWTClaimsSet().getClaim("publicKey"); + stepContext.getAttributes().put(TEMPORARY_KEY_ID, temporaryKeyId); + stepContext.getAttributes().put(TEMPORARY_PUBLIC_KEY, temporaryPublicKey); + } + + private static boolean validateJwtSignature(SignedJWT jwt, PublicKey publicKey) throws Exception { + final Base64URL[] jwtParts = jwt.getParsedParts(); + final Base64URL encodedHeader = jwtParts[0]; + final Base64URL encodedPayload = jwtParts[1]; + final Base64URL encodedSignature = jwtParts[2]; + final String signingInput = encodedHeader + "." + encodedPayload; + final byte[] signatureBytes = convertRawSignatureToDER(encodedSignature.decode()); + return SIGNATURE_UTILS.validateECDSASignature(signingInput.getBytes(StandardCharsets.UTF_8), signatureBytes, publicKey); + } + + private static byte[] convertRawSignatureToDER(byte[] rawSignature) throws Exception { + if (rawSignature.length % 2 != 0) { + throw new IllegalArgumentException("Invalid ECDSA signature format"); + } + int len = rawSignature.length / 2; + byte[] rBytes = new byte[len]; + byte[] sBytes = new byte[len]; + System.arraycopy(rawSignature, 0, rBytes, 0, len); + System.arraycopy(rawSignature, len, sBytes, 0, len); + BigInteger r = new BigInteger(1, rBytes); + BigInteger s = new BigInteger(1, sBytes); + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new ASN1Integer(r)); + v.add(new ASN1Integer(s)); + return new DLSequence(v).getEncoded(); + } + +} \ No newline at end of file diff --git a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/util/config/DataWriter.java b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/util/config/DataWriter.java index e161eaa5..1f070f68 100644 --- a/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/util/config/DataWriter.java +++ b/powerauth-java-cmd-lib/src/main/java/io/getlime/security/powerauth/lib/cmd/util/config/DataWriter.java @@ -73,6 +73,7 @@ public interface DataWriter { * streams. For example, if count value is lesser than 128, then just * one byte is serialized. * @param count Count value to write. + * @return whether write succeeded */ boolean writeCount(int count); diff --git a/powerauth-java-cmd/pom.xml b/powerauth-java-cmd/pom.xml index 70910082..ce3645f5 100644 --- a/powerauth-java-cmd/pom.xml +++ b/powerauth-java-cmd/pom.xml @@ -30,7 +30,7 @@ io.getlime.security powerauth-cmd-parent - 1.8.0 + 1.9.0 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 fc15dcfa..ce352875 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 @@ -83,7 +83,8 @@ public static void main(String[] args) { options.addOption("h", "help", false, "Print this help manual."); options.addOption("hs", "help-steps", false, "PowerAuth supported steps and versions."); options.addOption("hv", "help-versions", false, "PowerAuth supported versions and steps."); - options.addOption("u", "url", true, "Base URL of the PowerAuth Standard RESTful API."); + options.addOption("u", "url", true, "URL used for the request."); + options.addOption("b", "base-url", true, "Base URL of the PowerAuth Standard RESTful API."); options.addOption("m", "method", true, "What API method to call, available names are 'create', 'status', 'remove', 'sign', 'unlock', 'create-custom', 'create-token', 'validate-token', 'remove-token', 'encrypt', 'sign-encrypt', 'token-encrypt', 'start-upgrade', 'commit-upgrade', 'create-recovery' and 'confirm-recovery-code'."); options.addOption("c", "config-file", true, "Specifies a path to the config file with Base64 encoded server master public key, application ID and application secret."); options.addOption("s", "status-file", true, "Path to the file with the activation status, serving as the data persistence."); @@ -184,6 +185,7 @@ public static void main(String[] args) { // Read values String method = cmd.getOptionValue("m"); String uriString = cmd.getOptionValue("u"); + String baseUriString = cmd.getOptionValue("b"); String statusFileName = cmd.getOptionValue("s"); String configFileName = cmd.getOptionValue("c"); String reason = cmd.getOptionValue("r"); @@ -411,6 +413,7 @@ public static void main(String[] args) { model.setResultStatus(resultStatusObject); model.setScope(cmd.getOptionValue("o")); model.setUriString(uriString); + model.setBaseUriString(baseUriString); model.setVersion(version); // Read the file with request data @@ -432,6 +435,7 @@ public static void main(String[] args) { model.setSignatureType(PowerAuthSignatureTypes.getEnumFromString(cmd.getOptionValue("l"))); model.setStatusFileName(statusFileName); model.setUriString(uriString); + model.setBaseUriString(baseUriString); model.setVersion(version); // Read the file with request data @@ -452,6 +456,7 @@ public static void main(String[] args) { model.setHeaders(httpHeaders); model.setResultStatus(resultStatusObject); model.setUriString(uriString); + model.setBaseUriString(baseUriString); model.setVersion(version); // Read the file with request data