From e5f874cd052cd2af5b9bb36a71323f9bff3e4dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20=C4=8Eurech?= Date: Fri, 11 Aug 2023 15:34:20 +0200 Subject: [PATCH 1/3] Fix #420: Use general encryptor for E2EE --- docs/RESTful-API-for-Spring.md | 22 +- .../annotation/PowerAuthEncryption.java | 12 +- .../PowerAuthEncryptionArgumentResolver.java | 27 ++- .../encryption/EciesEncryptionContext.java | 164 --------------- .../spring/encryption/EncryptionContext.java | 55 +++++ .../spring/encryption/EncryptionScope.java | 37 ++++ .../encryption/PowerAuthEciesEncryption.java | 174 ---------------- .../encryption/PowerAuthEncryptorData.java | 83 ++++++++ ...java => PowerAuthEncryptorParameters.java} | 8 +- .../filter/EncryptionResponseBodyAdvice.java | 55 ++--- .../PowerAuthAuthenticationProviderBase.java | 6 +- .../provider/PowerAuthEncryptionProvider.java | 6 +- .../PowerAuthEncryptionProviderBase.java | 197 +++++++++--------- .../controller/ActivationController.java | 14 +- .../spring/controller/UserInfoController.java | 14 +- .../api/spring/service/ActivationService.java | 14 +- .../api/spring/service/RecoveryService.java | 11 +- .../spring/service/SecureVaultService.java | 25 +-- .../rest/api/spring/service/TokenService.java | 19 +- .../api/spring/service/UpgradeService.java | 17 +- 20 files changed, 371 insertions(+), 589 deletions(-) delete mode 100644 powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EciesEncryptionContext.java create mode 100644 powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionContext.java create mode 100644 powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionScope.java delete mode 100644 powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEciesEncryption.java create mode 100644 powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEncryptorData.java rename powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/{PowerAuthEciesDecryptorParameters.java => PowerAuthEncryptorParameters.java} (80%) diff --git a/docs/RESTful-API-for-Spring.md b/docs/RESTful-API-for-Spring.md index 02f42ce5..81ff89e9 100644 --- a/docs/RESTful-API-for-Spring.md +++ b/docs/RESTful-API-for-Spring.md @@ -394,16 +394,16 @@ You can encrypt data in `application` scope (non-personalized) using following p public class EncryptedDataExchangeController { @RequestMapping(value = "application", method = RequestMethod.POST) - @PowerAuthEncryption(scope = EciesScope.APPLICATION_SCOPE) + @PowerAuthEncryption(scope = EncryptionScope.APPLICATION_SCOPE) public DataExchangeResponse exchangeInApplicationScope(@EncryptedRequestBody DataExchangeRequest request, - EciesEncryptionContext eciesContext) throws PowerAuthEncryptionException { + EncryptionContext encryptionContext) throws PowerAuthEncryptionException { - if (eciesContext == null) { + if (encryptionContext == null) { throw new PowerAuthEncryptionException(); } // Return a slightly different String containing original data in response - return new DataExchangeResponse("Server successfully decrypted signed data: " + (request == null ? "''" : request.getData()) + ", scope: " + eciesContext.getEciesScope()); + return new DataExchangeResponse("Server successfully decrypted signed data: " + (request == null ? "''" : request.getData()) + ", scope: " + encryptionContext.getEncryptionScope()); } } ``` @@ -422,16 +422,16 @@ You can encrypt data in `activation` scope (personalized) using following patter public class EncryptedDataExchangeController { @RequestMapping(value = "activation", method = RequestMethod.POST) - @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE) + @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE) public DataExchangeResponse exchangeInActivationScope(@EncryptedRequestBody DataExchangeRequest request, - EciesEncryptionContext eciesContext) throws PowerAuthEncryptionException { + EncryptionContext encryptionContext) throws PowerAuthEncryptionException { - if (eciesContext == null) { + if (encryptionContext == null) { throw new PowerAuthEncryptionException(); } // Return a slightly different String containing original data in response - return new DataExchangeResponse("Server successfully decrypted signed data: " + (request == null ? "''" : request.getData()) + ", scope: " + eciesContext.getEciesScope()); + return new DataExchangeResponse("Server successfully decrypted signed data: " + (request == null ? "''" : request.getData()) + ", scope: " + encryptionContext.getEncryptionScope()); } } ``` @@ -451,16 +451,16 @@ public class EncryptedDataExchangeController { @RequestMapping(value = "signed", method = RequestMethod.POST) @PowerAuth(resourceId = "/exchange/signed") - @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE) + @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE) public DataExchangeResponse exchangeSignedAndEncryptedData(@EncryptedRequestBody DataExchangeRequest request, - EciesEncryptionContext eciesContext, + EncryptionContext encryptionContext, PowerAuthApiAuthentication auth) throws PowerAuthAuthenticationException, PowerAuthEncryptionException { if (auth == null || auth.getUserId() == null) { throw new PowerAuthSignatureInvalidException(); } - if (eciesContext == null) { + if (encryptionContext == null) { throw new PowerAuthEncryptionException(); } diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/PowerAuthEncryption.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/PowerAuthEncryption.java index f186fda5..814c59fd 100644 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/PowerAuthEncryption.java +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/PowerAuthEncryption.java @@ -19,7 +19,8 @@ */ package io.getlime.security.powerauth.rest.api.spring.annotation; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesScope; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope; +import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionScope; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -27,7 +28,7 @@ import java.lang.annotation.Target; /** - * Annotation that simplifies end to end encryption. + * Annotation that simplifies end-to-end encryption. * * @author Roman Strobl, roman.strobl@wultra.com */ @@ -36,10 +37,9 @@ public @interface PowerAuthEncryption { /** - * Encryption scope, either EciesScope.ACTIVATION_SCOPE or EciesScope.APPLICATION_SCOPE. - * @see EciesScope + * Encryption scope, either {@link EncryptionScope#ACTIVATION_SCOPE} or {@link EncryptionScope#APPLICATION_SCOPE}. + * @see EncryptorScope * @return Encryption scope. */ - EciesScope scope() default EciesScope.ACTIVATION_SCOPE; - + EncryptionScope scope() default EncryptionScope.ACTIVATION_SCOPE; } diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/support/PowerAuthEncryptionArgumentResolver.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/support/PowerAuthEncryptionArgumentResolver.java index 346fc337..b729f080 100644 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/support/PowerAuthEncryptionArgumentResolver.java +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/support/PowerAuthEncryptionArgumentResolver.java @@ -23,9 +23,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import io.getlime.security.powerauth.rest.api.spring.annotation.EncryptedRequestBody; -import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption; -import io.getlime.security.powerauth.rest.api.spring.encryption.EciesEncryptionContext; -import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEciesEncryption; +import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext; +import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEncryptorData; import io.getlime.security.powerauth.rest.api.spring.model.PowerAuthRequestObjects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,8 +40,8 @@ import java.lang.reflect.Type; /** - * Argument resolver for {@link PowerAuthEciesEncryption} objects. It enables automatic - * parameter resolution for methods that are annotated via {@link PowerAuthEciesEncryption} annotation. + * Argument resolver for {@link PowerAuthEncryptorData} objects. It enables automatic + * parameter resolution for methods that are annotated via {@link PowerAuthEncryptorData} annotation. * * @author Roman Strobl, roman.strobl@wultra.com */ @@ -54,14 +53,14 @@ public class PowerAuthEncryptionArgumentResolver implements HandlerMethodArgumen @Override public boolean supportsParameter(@NonNull MethodParameter parameter) { - return parameter.hasMethodAnnotation(PowerAuthEncryption.class) - && (parameter.hasParameterAnnotation(EncryptedRequestBody.class) || EciesEncryptionContext.class.isAssignableFrom(parameter.getParameterType())); + return parameter.hasMethodAnnotation(io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption.class) + && (parameter.hasParameterAnnotation(EncryptedRequestBody.class) || EncryptionContext.class.isAssignableFrom(parameter.getParameterType())); } @Override public Object resolveArgument(@NonNull MethodParameter parameter, ModelAndViewContainer mavContainer, @NonNull NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { final HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); - final PowerAuthEciesEncryption eciesObject = (PowerAuthEciesEncryption) request.getAttribute(PowerAuthRequestObjects.ENCRYPTION_OBJECT); + final PowerAuthEncryptorData eciesObject = (PowerAuthEncryptorData) request.getAttribute(PowerAuthRequestObjects.ENCRYPTION_OBJECT); // Decrypted object is inserted into parameter annotated by @EncryptedRequestBody annotation if (parameter.hasParameterAnnotation(EncryptedRequestBody.class) && eciesObject != null && eciesObject.getDecryptedRequest() != null) { final Type requestType = parameter.getGenericParameterType(); @@ -81,11 +80,11 @@ public Object resolveArgument(@NonNull MethodParameter parameter, ModelAndViewCo } } // Ecies encryption object is inserted into parameter which is of type PowerAuthEciesEncryption - if (eciesObject != null && EciesEncryptionContext.class.isAssignableFrom(parameter.getParameterType())) { + if (eciesObject != null && EncryptionContext.class.isAssignableFrom(parameter.getParameterType())) { // Set ECIES scope in case it is specified by the @PowerAuthEncryption annotation - PowerAuthEncryption powerAuthEncryption = parameter.getMethodAnnotation(PowerAuthEncryption.class); + io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption powerAuthEncryption = parameter.getMethodAnnotation(io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption.class); if (powerAuthEncryption != null) { - EciesEncryptionContext eciesContext = eciesObject.getContext(); + EncryptionContext eciesContext = eciesObject.getContext(); boolean validScope = validateEciesScope(eciesContext); if (validScope) { return eciesContext; @@ -99,8 +98,8 @@ public Object resolveArgument(@NonNull MethodParameter parameter, ModelAndViewCo * Validate that encryption HTTP header contains correct values for given ECIES scope. * @param eciesContext ECIES context. */ - private boolean validateEciesScope(EciesEncryptionContext eciesContext) { - switch (eciesContext.getEciesScope()) { + private boolean validateEciesScope(EncryptionContext eciesContext) { + switch (eciesContext.getEncryptionScope()) { case ACTIVATION_SCOPE -> { if (eciesContext.getApplicationKey() == null || eciesContext.getApplicationKey().isEmpty()) { logger.warn("ECIES activation scope is invalid because of missing application key"); @@ -118,7 +117,7 @@ private boolean validateEciesScope(EciesEncryptionContext eciesContext) { } } default -> { - logger.warn("Unsupported ECIES scope: {}", eciesContext.getEciesScope()); + logger.warn("Unsupported ECIES scope: {}", eciesContext.getEncryptionScope()); return false; } } diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EciesEncryptionContext.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EciesEncryptionContext.java deleted file mode 100644 index 5104cb04..00000000 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EciesEncryptionContext.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * PowerAuth integration libraries for RESTful API applications, examples and - * related software components - * - * Copyright (C) 2018 Wultra s.r.o. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package io.getlime.security.powerauth.rest.api.spring.encryption; - -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesScope; -import io.getlime.security.powerauth.http.PowerAuthHttpHeader; - -/** - * Class for storing ECIES encryption context derived from HTTP headers. - * - * @author Roman Strobl, roman.strobl@wultra.com - * - */ -public class EciesEncryptionContext { - - private String applicationKey; - private String activationId; - private String version; - private EciesScope eciesScope; - private String ephemeralPublicKey; - private PowerAuthHttpHeader httpHeader; - - /** - * Default constructor. - */ - public EciesEncryptionContext() { - } - - /** - * Constructor with all context parameters. - * - * @param applicationKey Application key. - * @param activationId Activation ID. - * @param version PowerAuth protocol version. - * @param httpHeader HTTP header used to derive ECIES encryption context. - */ - public EciesEncryptionContext(String applicationKey, String activationId, String version, PowerAuthHttpHeader httpHeader) { - this.applicationKey = applicationKey; - this.activationId = activationId; - this.version = version; - this.httpHeader = httpHeader; - } - - /** - * Get application key. - * - * @return Application key. - */ - public String getApplicationKey() { - return applicationKey; - } - - /** - * Set application key. - * - * @param applicationKey Application key. - */ - public void setApplicationKey(String applicationKey) { - this.applicationKey = applicationKey; - } - - /** - * Get activation ID. - * - * @return Activation ID. - */ - public String getActivationId() { - return activationId; - } - - /** - * Set activation ID. - * - * @param activationId Activation ID. - */ - public void setActivationId(String activationId) { - this.activationId = activationId; - } - - /** - * Get PowerAuth protocol version. - * - * @return PowerAuth protocol version. - */ - public String getVersion() { - return version; - } - - /** - * Set PowerAuth protocol version. - * - * @param version PowerAuth protocol version. - */ - public void setVersion(String version) { - this.version = version; - } - - /** - * Get ECIES scope (application or activation). - * @return ECIES scope. - */ - public EciesScope getEciesScope() { - return eciesScope; - } - - /** - * Set ECIES scope (application or activation). - * @param eciesScope ECIES scope. - */ - public void setEciesScope(EciesScope eciesScope) { - this.eciesScope = eciesScope; - } - - /** - * Get ephemeral public key in Base64 encoding. - * @return Ephemeral public key. - */ - public String getEphemeralPublicKey() { - return ephemeralPublicKey; - } - - /** - * Set ephemeral public key bytes in Base64 encoding. - * @param ephemeralPublicKey Ephemeral public key bytes. - */ - public void setEphemeralPublicKey(String ephemeralPublicKey) { - this.ephemeralPublicKey = ephemeralPublicKey; - } - - /** - * Get PowerAuth HTTP header used for deriving ECIES encryption context. - * - * @return PowerAuth HTTP header used for deriving ECIES encryption context. - */ - public PowerAuthHttpHeader getHttpHeader() { - return httpHeader; - } - - /** - * Set PowerAuth HTTP header used for deriving ECIES encryption context. - * - * @param httpHeader PowerAuth HTTP header used for deriving ECIES encryption context. - */ - public void setHttpHeader(PowerAuthHttpHeader httpHeader) { - this.httpHeader = httpHeader; - } -} \ No newline at end of file diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionContext.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionContext.java new file mode 100644 index 00000000..c919dbea --- /dev/null +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionContext.java @@ -0,0 +1,55 @@ +/* + * PowerAuth integration libraries for RESTful API applications, examples and + * related software components + * + * Copyright (C) 2018 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.getlime.security.powerauth.rest.api.spring.encryption; + +import io.getlime.security.powerauth.http.PowerAuthHttpHeader; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Class for storing PowerAuth End-To-End encryption context derived from HTTP headers. + * + * @author Roman Strobl, roman.strobl@wultra.com + */ +@Getter +@AllArgsConstructor +public class EncryptionContext { + /** + * Application key. + */ + private final String applicationKey; + /** + * Activation ID. + */ + private final String activationId; + /** + * Protocol version. + */ + private final String version; + /** + * PowerAuth HTTP header used for deriving ECIES encryption context. + */ + private final PowerAuthHttpHeader httpHeader; + /** + * Scope of the encryption. + */ + private final EncryptionScope encryptionScope; +} \ No newline at end of file diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionScope.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionScope.java new file mode 100644 index 00000000..567336fd --- /dev/null +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionScope.java @@ -0,0 +1,37 @@ +/* + * PowerAuth integration libraries for RESTful API applications, examples and + * related software components + * + * Copyright (C) 2023 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.getlime.security.powerauth.rest.api.spring.encryption; + +/** + * Enumeration of application scopes for encryptor. + */ +public enum EncryptionScope { + + /** + * Application scope (non-personalized). + */ + APPLICATION_SCOPE, + + /** + * Activation scope (personalized). + */ + ACTIVATION_SCOPE +} diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEciesEncryption.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEciesEncryption.java deleted file mode 100644 index 0ba04007..00000000 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEciesEncryption.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * PowerAuth integration libraries for RESTful API applications, examples and - * related software components - * - * Copyright (C) 2018 Wultra s.r.o. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package io.getlime.security.powerauth.rest.api.spring.encryption; - -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesDecryptor; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesEncryptor; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesParameters; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesPayload; - -/** - * Class used for storing data used during ECIES decryption and encryption. A reference to an initialized ECIES decryptor - * is also stored so that response can be encrypted using same decryptor as used for request decryption. - *

- * Use the T parameter to specify the type of request object to be decrypted. - * - * @author Roman Strobl, roman.strobl@wultra.com - */ -public class PowerAuthEciesEncryption { - - private final EciesEncryptionContext context; - private EciesDecryptor eciesDecryptor; - private EciesEncryptor eciesEncryptor; - private EciesParameters requestParameters; - private byte[] encryptedRequest; - private byte[] decryptedRequest; - private byte[] associatedData; - private Object requestObject; - - /** - * Initialize ECIES encryption object from either encryption or signature HTTP header. - * - * @param context PowerAuth encryption context derived from either encryption or signature HTTP header. - */ - public PowerAuthEciesEncryption(EciesEncryptionContext context) { - this.context = context; - } - - /** - * Get ECIES encryption context. - * @return ECIES encryption context. - */ - public EciesEncryptionContext getContext() { - return context; - } - - /** - * Get ECIES decryptor. - * @return ECIES decryptor. - */ - public EciesDecryptor getEciesDecryptor() { - return eciesDecryptor; - } - - /** - * Set ECIES decryptor. - * @param eciesDecryptor ECIES decryptor. - */ - public void setEciesDecryptor(EciesDecryptor eciesDecryptor) { - this.eciesDecryptor = eciesDecryptor; - } - - /** - * Get ECIES encryptor. - * @return eciesEncryptor ECIES encryptor. - */ - public EciesEncryptor getEciesEncryptor() { - return eciesEncryptor; - } - - /** - * Set ECIES encryptor. - * @param eciesEncryptor ECIES encryptor. - */ - public void setEciesEncryptor(EciesEncryptor eciesEncryptor) { - this.eciesEncryptor = eciesEncryptor; - } - - /** - * Get encrypted request data. - * @return Encrypted request data. - */ - public byte[] getEncryptedRequest() { - return encryptedRequest; - } - - /** - * Set encrypted request data. - * @param encryptedRequest Encrypted request data. - */ - public void setEncryptedRequest(byte[] encryptedRequest) { - this.encryptedRequest = encryptedRequest; - } - - /** - * Get decrypted request data. - * @return Decrypted request data. - */ - public byte[] getDecryptedRequest() { - return decryptedRequest; - } - - /** - * Set decrypted request data. - * @param decryptedRequest Decrypted request data. - */ - public void setDecryptedRequest(byte[] decryptedRequest) { - this.decryptedRequest = decryptedRequest; - } - - /** - * Get ECIES data associated with request and response. - * @return ECIES data associated with request and response. - */ - public byte[] getAssociatedData() { - return associatedData; - } - - /** - * Set ECIES data associated with request and response. - * @param associatedData ECIES data associated with request and response. - */ - public void setAssociatedData(byte[] associatedData) { - this.associatedData = associatedData; - } - - /** - * Get decrypted request object. - * @return Decrypted request object. - */ - public Object getRequestObject() { - return requestObject; - } - - /** - * Set decrypted request object. - * @param requestObject Decrypted request object. - */ - public void setRequestObject(Object requestObject) { - this.requestObject = requestObject; - } - - /** - * Set ECIES parameters for request decryption. - * @param requestParameters ECIES parameters. - */ - public void setRequestParameters(EciesParameters requestParameters) { - this.requestParameters = requestParameters; - } - - /** - * Get ECIES parameters for request decryption. - * @return ECIES parameters. - */ - public EciesParameters getRequestParameters() { - return requestParameters; - } -} diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEncryptorData.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEncryptorData.java new file mode 100644 index 00000000..11caa171 --- /dev/null +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEncryptorData.java @@ -0,0 +1,83 @@ +/* + * PowerAuth integration libraries for RESTful API applications, examples and + * related software components + * + * Copyright (C) 2023 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.getlime.security.powerauth.rest.api.spring.encryption; + +import io.getlime.security.powerauth.crypto.lib.encryptor.ServerEncryptor; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptedRequest; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorId; +import lombok.Getter; +import lombok.Setter; + +/** + * Class used for storing data used during PowerAuth decryption and encryption. A reference to an initialized ServerEncryptor + * is also stored so that the response can be encrypted using the same object as used for request decryption. + */ +@Getter +@Setter +public class PowerAuthEncryptorData { + /** + * ECIES encryption context. + */ + private final EncryptionContext context; + /** + * {@link ServerEncryptor} implementation. + */ + private ServerEncryptor serverEncryptor; + /** + * Encrypted request data. + */ + private EncryptedRequest encryptedRequest; + /** + * Decrypted request data. + */ + private byte[] decryptedRequest; + /** + * Request object + */ + private Object requestObject; + + /** + * Initialize encryption object from either encryption or signature HTTP header. + * + * @param context PowerAuth encryption context derived from either encryption or signature HTTP header. + */ + public PowerAuthEncryptorData(EncryptionContext context) { + this.context = context; + } + + /** + * Get EncryptorId depending on scope of encryption. + * @return EncryptorId depending on scope of encryption. + */ + public EncryptorId getEncryptorId() { + switch (context.getEncryptionScope()) { + case ACTIVATION_SCOPE -> { + return EncryptorId.ACTIVATION_SCOPE_GENERIC; + } + case APPLICATION_SCOPE -> { + return EncryptorId.APPLICATION_SCOPE_GENERIC; + } + default -> { + throw new IllegalStateException("Unsupported scope " + this); + } + } + } +} diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEciesDecryptorParameters.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEncryptorParameters.java similarity index 80% rename from powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEciesDecryptorParameters.java rename to powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEncryptorParameters.java index 97d2a564..2bb54b80 100644 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEciesDecryptorParameters.java +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/PowerAuthEncryptorParameters.java @@ -20,11 +20,11 @@ package io.getlime.security.powerauth.rest.api.spring.encryption; /** - * Class used for storing ECIES decryptor parameters. + * Class used for storing encryptor parameters. * - * @param secretKey ECIES secret key - * @param sharedInfo2 Parameter sharedInfo2 for ECIES. + * @param secretKey Secret key. + * @param sharedInfo2 Parameter sharedInfo2 for ECIES (V3.x protocols). * @author Roman Strobl, roman.strobl@wultra.com */ -public record PowerAuthEciesDecryptorParameters(String secretKey, String sharedInfo2) { +public record PowerAuthEncryptorParameters(String secretKey, String sharedInfo2) { } diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/filter/EncryptionResponseBodyAdvice.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/filter/EncryptionResponseBodyAdvice.java index 0d810f53..6f7b90a7 100644 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/filter/EncryptionResponseBodyAdvice.java +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/filter/EncryptionResponseBodyAdvice.java @@ -20,13 +20,9 @@ package io.getlime.security.powerauth.rest.api.spring.filter; import com.fasterxml.jackson.databind.ObjectMapper; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesEncryptor; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesParameters; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesPayload; -import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptedResponse; import io.getlime.security.powerauth.rest.api.model.response.EciesEncryptedResponse; -import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption; -import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEciesEncryption; +import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEncryptorData; import io.getlime.security.powerauth.rest.api.spring.model.PowerAuthRequestObjects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,8 +49,6 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.Date; import java.util.List; /** @@ -90,7 +84,7 @@ public void setRequestMappingHandlerAdapter(@Lazy RequestMappingHandlerAdapter r */ @Override public boolean supports(@NonNull MethodParameter methodParameter, @NonNull Class> converterClass) { - return methodParameter.hasMethodAnnotation(PowerAuthEncryption.class) && + return methodParameter.hasMethodAnnotation(io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption.class) && (converterClass.isAssignableFrom(MappingJackson2HttpMessageConverter.class) || converterClass.isAssignableFrom(StringHttpMessageConverter.class) || converterClass.isAssignableFrom(ByteArrayHttpMessageConverter.class)); @@ -113,50 +107,33 @@ public Object beforeBodyWrite(Object response, @NonNull MethodParameter methodPa return null; } - // Extract ECIES encryption object from HTTP request + // Extract encryption object from HTTP request final HttpServletRequest httpServletRequest = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest(); - final PowerAuthEciesEncryption eciesEncryption = (PowerAuthEciesEncryption) httpServletRequest.getAttribute(PowerAuthRequestObjects.ENCRYPTION_OBJECT); - if (eciesEncryption == null) { + final PowerAuthEncryptorData encryption = (PowerAuthEncryptorData) httpServletRequest.getAttribute(PowerAuthRequestObjects.ENCRYPTION_OBJECT); + if (encryption == null || encryption.getServerEncryptor() == null) { return null; } // Convert response to JSON try { byte[] responseBytes = serializeResponseObject(response); - - // Encrypt response using encryptor and return ECIES cryptogram - final EciesEncryptor eciesEncryptor = eciesEncryption.getEciesEncryptor(); - final String version = eciesEncryption.getContext().getVersion(); - final EciesParameters eciesParameters; - final String nonce; - final Long timestamp; - if ("3.2".equals(version)) { - final byte[] associatedData = eciesEncryption.getAssociatedData(); - final byte[] nonceBytes = new KeyGenerator().generateRandomBytes(16); - nonce = Base64.getEncoder().encodeToString(nonceBytes); - timestamp = new Date().getTime(); - eciesParameters = EciesParameters.builder().nonce(nonceBytes).associatedData(associatedData).timestamp(timestamp).build(); - } else { - nonce = null; - timestamp = null; - eciesParameters = eciesEncryption.getRequestParameters(); - } - - final EciesPayload payload = eciesEncryptor.encrypt(responseBytes, eciesParameters); - final String encryptedDataBase64 = Base64.getEncoder().encodeToString(payload.getCryptogram().getEncryptedData()); - final String macBase64 = Base64.getEncoder().encodeToString(payload.getCryptogram().getMac()); - + final EncryptedResponse encryptedResponse = encryption.getServerEncryptor().encryptResponse(responseBytes); // Return encrypted response with type given by converter class - final EciesEncryptedResponse encryptedResponse = new EciesEncryptedResponse(encryptedDataBase64, macBase64, nonce, timestamp); + final EciesEncryptedResponse encryptedResponseObject = new EciesEncryptedResponse( + encryptedResponse.getEncryptedData(), + encryptedResponse.getMac(), + encryptedResponse.getNonce(), + encryptedResponse.getTimestamp() + ); if (converterClass.isAssignableFrom(MappingJackson2HttpMessageConverter.class)) { // Object conversion is done automatically using MappingJackson2HttpMessageConverter - return encryptedResponse; + return encryptedResponseObject; } else if (converterClass.isAssignableFrom(StringHttpMessageConverter.class)) { // Conversion to byte[] is done using first applicable configured HTTP message converter, corresponding String is returned - return new String(convertEncryptedResponse(encryptedResponse, mediaType), StandardCharsets.UTF_8); + return new String(convertEncryptedResponse(encryptedResponseObject, mediaType), StandardCharsets.UTF_8); } else { // Conversion to byte[] is done using first applicable configured HTTP message converter - return convertEncryptedResponse(encryptedResponse, mediaType); + return convertEncryptedResponse(encryptedResponseObject, mediaType); } } catch (Exception ex) { logger.warn("Encryption failed, error: {}", ex.getMessage()); diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthAuthenticationProviderBase.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthAuthenticationProviderBase.java index 6b4b0c73..ee04280c 100644 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthAuthenticationProviderBase.java +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthAuthenticationProviderBase.java @@ -21,7 +21,7 @@ import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureTypes; import io.getlime.security.powerauth.rest.api.spring.authentication.PowerAuthApiAuthentication; -import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEciesEncryption; +import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEncryptorData; import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthAuthenticationException; import io.getlime.security.powerauth.rest.api.spring.exception.authentication.PowerAuthRequestFilterException; import io.getlime.security.powerauth.rest.api.spring.model.PowerAuthRequestBody; @@ -198,8 +198,8 @@ public abstract class PowerAuthAuthenticationProviderBase { */ public @Nullable byte[] extractRequestBodyBytes(@Nonnull HttpServletRequest servletRequest) throws PowerAuthAuthenticationException { if (servletRequest.getAttribute(PowerAuthRequestObjects.ENCRYPTION_OBJECT) != null) { - // Implementation of sign-then-encrypt - in case the encryption object is present and signature is validate, use decrypted request data - PowerAuthEciesEncryption eciesEncryption = (PowerAuthEciesEncryption) servletRequest.getAttribute(PowerAuthRequestObjects.ENCRYPTION_OBJECT); + // Implementation of sign-then-encrypt - in case the encryption object is present and signature is valid, use decrypted request data + PowerAuthEncryptorData eciesEncryption = (PowerAuthEncryptorData) servletRequest.getAttribute(PowerAuthRequestObjects.ENCRYPTION_OBJECT); return eciesEncryption.getDecryptedRequest(); } else { // Request data was not encrypted - use regular PowerAuth request body for signature validation diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProvider.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProvider.java index d4706be6..9b04439d 100644 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProvider.java +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProvider.java @@ -22,7 +22,7 @@ import com.wultra.security.powerauth.client.PowerAuthClient; import com.wultra.security.powerauth.client.model.request.GetEciesDecryptorRequest; import com.wultra.security.powerauth.client.model.response.GetEciesDecryptorResponse; -import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEciesDecryptorParameters; +import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEncryptorParameters; import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthEncryptionException; import io.getlime.security.powerauth.rest.api.spring.service.HttpCustomizationService; import org.slf4j.Logger; @@ -59,7 +59,7 @@ public PowerAuthEncryptionProvider(PowerAuthClient powerAuthClient, HttpCustomiz } @Override - public @Nonnull PowerAuthEciesDecryptorParameters getEciesDecryptorParameters(@Nullable String activationId, @Nonnull String applicationKey, @Nonnull String ephemeralPublicKey, @Nonnull String version, String nonce, Long timestamp) throws PowerAuthEncryptionException { + public @Nonnull PowerAuthEncryptorParameters getEciesDecryptorParameters(@Nullable String activationId, @Nonnull String applicationKey, @Nonnull String ephemeralPublicKey, @Nonnull String version, String nonce, Long timestamp) throws PowerAuthEncryptionException { try { final GetEciesDecryptorRequest eciesDecryptorRequest = new GetEciesDecryptorRequest(); eciesDecryptorRequest.setActivationId(activationId); @@ -74,7 +74,7 @@ public PowerAuthEncryptionProvider(PowerAuthClient powerAuthClient, HttpCustomiz httpCustomizationService.getHttpHeaders() ); - return new PowerAuthEciesDecryptorParameters(eciesDecryptorResponse.getSecretKey(), eciesDecryptorResponse.getSharedInfo2()); + return new PowerAuthEncryptorParameters(eciesDecryptorResponse.getSecretKey(), eciesDecryptorResponse.getSharedInfo2()); } catch (Exception ex) { logger.warn("Get ECIES decryptor call failed, error: {}", ex.getMessage()); logger.debug(ex.getMessage(), ex); diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProviderBase.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProviderBase.java index f9e111e9..9108877c 100644 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProviderBase.java +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProviderBase.java @@ -23,16 +23,10 @@ import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesDecryptor; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesEncryptor; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesEnvelopeKey; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.EciesFactory; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesCryptogram; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesParameters; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesPayload; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesScope; -import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator; -import io.getlime.security.powerauth.crypto.lib.util.EciesUtils; +import io.getlime.security.powerauth.crypto.lib.encryptor.EncryptorFactory; +import io.getlime.security.powerauth.crypto.lib.encryptor.ServerEncryptor; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.*; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.v3.ServerEncryptorSecrets; import io.getlime.security.powerauth.http.PowerAuthEncryptionHttpHeader; import io.getlime.security.powerauth.http.PowerAuthSignatureHttpHeader; import io.getlime.security.powerauth.http.validator.InvalidPowerAuthHttpHeaderException; @@ -40,9 +34,10 @@ import io.getlime.security.powerauth.http.validator.PowerAuthSignatureHttpHeaderValidator; import io.getlime.security.powerauth.rest.api.model.request.EciesEncryptedRequest; import io.getlime.security.powerauth.rest.api.model.response.EciesEncryptedResponse; -import io.getlime.security.powerauth.rest.api.spring.encryption.EciesEncryptionContext; -import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEciesDecryptorParameters; -import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEciesEncryption; +import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext; +import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionScope; +import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEncryptorParameters; +import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEncryptorData; import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthEncryptionException; import io.getlime.security.powerauth.rest.api.spring.model.PowerAuthRequestBody; import io.getlime.security.powerauth.rest.api.spring.model.PowerAuthRequestObjects; @@ -55,7 +50,6 @@ import java.io.IOException; import java.lang.reflect.Type; import java.util.Base64; -import java.util.Date; /** * Abstract class for PowerAuth encryption provider with common HTTP header parsing logic. The class is available for @@ -69,7 +63,7 @@ public abstract class PowerAuthEncryptionProviderBase { private static final Logger logger = LoggerFactory.getLogger(PowerAuthEncryptionProviderBase.class); private final ObjectMapper objectMapper = new ObjectMapper(); - private final EciesFactory eciesFactory = new EciesFactory(); + private final EncryptorFactory encryptorFactory = new EncryptorFactory(); /** * Get ECIES decryptor parameters from PowerAuth server. @@ -84,20 +78,18 @@ public abstract class PowerAuthEncryptionProviderBase { * @throws PowerAuthEncryptionException In case PowerAuth server call fails. */ public abstract @Nonnull - PowerAuthEciesDecryptorParameters getEciesDecryptorParameters(@Nullable String activationId, @Nonnull String applicationKey, @Nonnull String ephemeralPublicKey, @Nonnull String version, String nonce, Long timestamp) throws PowerAuthEncryptionException; + PowerAuthEncryptorParameters getEciesDecryptorParameters(@Nullable String activationId, @Nonnull String applicationKey, @Nonnull String ephemeralPublicKey, @Nonnull String version, String nonce, Long timestamp) throws PowerAuthEncryptionException; /** * Decrypt HTTP request body and construct object with ECIES data. Use the requestType parameter to specify * the type of decrypted object. * - * @param request HTTP request. - * @param requestType Class of request object. - * @param eciesScope ECIES scope. - * @return Object with ECIES data. + * @param request HTTP request. + * @param requestType Class of request object. + * @param encryptionScope Encryption scope. * @throws PowerAuthEncryptionException In case request decryption fails. */ - public @Nonnull - PowerAuthEciesEncryption decryptRequest(@Nonnull HttpServletRequest request, @Nonnull Type requestType, @Nonnull EciesScope eciesScope) throws PowerAuthEncryptionException { + public void decryptRequest(@Nonnull HttpServletRequest request, @Nonnull Type requestType, @Nonnull EncryptionScope encryptionScope) throws PowerAuthEncryptionException { // Only POST HTTP method is supported for ECIES if (!"POST".equals(request.getMethod())) { logger.warn("Invalid HTTP method: {}", request.getMethod()); @@ -105,13 +97,10 @@ PowerAuthEciesEncryption decryptRequest(@Nonnull HttpServletRequest request, @No } // Resolve either signature or encryption HTTP header for ECIES - final EciesEncryptionContext encryptionContext = extractEciesEncryptionContext(request); + final EncryptionContext encryptionContext = extractEciesEncryptionContext(request, encryptionScope); // Construct ECIES encryption object from HTTP header - final PowerAuthEciesEncryption eciesEncryption = new PowerAuthEciesEncryption(encryptionContext); - - // Save ECIES scope in context - eciesEncryption.getContext().setEciesScope(eciesScope); + final PowerAuthEncryptorData encryptorData = new PowerAuthEncryptorData(encryptionContext); try { // Parse ECIES cryptogram from request body @@ -133,99 +122,106 @@ PowerAuthEciesEncryption decryptRequest(@Nonnull HttpServletRequest request, @No logger.debug(ex.getMessage(), ex); throw new PowerAuthEncryptionException(); } - if (eciesRequest == null) { logger.warn("Deserialization of request body bytes resulted in null value."); throw new PowerAuthEncryptionException(); } - // Prepare ephemeral public key - final String ephemeralPublicKey = eciesRequest.getEphemeralPublicKey(); - final String encryptedData = eciesRequest.getEncryptedData(); - final String mac = eciesRequest.getMac(); - final String nonce = eciesRequest.getNonce(); - final Long timestamp = eciesRequest.getTimestamp(); + // Extract useful properties in advance + final String version = encryptionContext.getVersion(); + final String applicationKey = encryptionContext.getApplicationKey(); + final String activationId = encryptionContext.getActivationId(); - // Verify ECIES request data. Nonce is required for protocol 3.1+ - if (ephemeralPublicKey == null || encryptedData == null || mac == null) { - logger.warn("Invalid ECIES request data"); + // Prepare and validate EncryptedRequest object + final EncryptedRequest encryptedRequest = new EncryptedRequest( + eciesRequest.getEphemeralPublicKey(), + eciesRequest.getEncryptedData(), + eciesRequest.getMac(), + eciesRequest.getNonce(), + eciesRequest.getTimestamp() + ); + if (!encryptorFactory.getRequestResponseValidator(version).validateEncryptedRequest(encryptedRequest)) { + logger.warn("Invalid encrypted request data"); throw new PowerAuthEncryptionException(); } - if (nonce == null && !"3.0".equals(encryptionContext.getVersion())) { - logger.warn("Missing nonce in ECIES request data"); + // Validate presence of activation id for activation scope. + if (encryptionScope == EncryptionScope.ACTIVATION_SCOPE && activationId == null) { + logger.warn("Activation ID is required for activation scope"); throw new PowerAuthEncryptionException(); } - if (timestamp == null && (!"3.0".equals(encryptionContext.getVersion()) && !"3.1".equals(encryptionContext.getVersion()))) { - logger.warn("Missing timestamp in ECIES request data"); - throw new PowerAuthEncryptionException(); - } - - // Save ephemeral public key in context - eciesEncryption.getContext().setEphemeralPublicKey(ephemeralPublicKey); - - final byte[] ephemeralPublicKeyBytes = Base64.getDecoder().decode(ephemeralPublicKey); - final byte[] encryptedDataBytes = Base64.getDecoder().decode(encryptedData); - final byte[] macBytes = Base64.getDecoder().decode(mac); - final byte[] nonceBytes = nonce != null ? Base64.getDecoder().decode(nonce) : null; - - final String applicationKey = eciesEncryption.getContext().getApplicationKey(); - final PowerAuthEciesDecryptorParameters decryptorParameters; - final PowerAuthEciesDecryptorParameters encryptorParameters; - // Obtain ECIES decryptor parameters from PowerAuth server - final byte[] associatedData; - switch (eciesScope) { - case ACTIVATION_SCOPE -> { - final String activationId = eciesEncryption.getContext().getActivationId(); - if (activationId == null) { - logger.warn("Activation ID is required in ECIES activation scope"); - throw new PowerAuthEncryptionException(); - } - decryptorParameters = getEciesDecryptorParameters(activationId, applicationKey, ephemeralPublicKey, encryptionContext.getVersion(), nonce, timestamp); - associatedData = "3.2".equals(encryptionContext.getVersion()) ? EciesUtils.deriveAssociatedData(EciesScope.ACTIVATION_SCOPE, encryptionContext.getVersion(), applicationKey, activationId) : null; - } - case APPLICATION_SCOPE -> { - decryptorParameters = getEciesDecryptorParameters(null, applicationKey, ephemeralPublicKey, encryptionContext.getVersion(), nonce, timestamp); - associatedData = "3.2".equals(encryptionContext.getVersion()) ? EciesUtils.deriveAssociatedData(EciesScope.APPLICATION_SCOPE, encryptionContext.getVersion(), applicationKey, null) : null; - } - default -> { - logger.warn("Unsupported ECIES scope: {}", eciesScope); - throw new PowerAuthEncryptionException(); - } - } + // Get encryptor parameters from the PowerAuth Server. + final PowerAuthEncryptorParameters encryptorParameters = getEciesDecryptorParameters( + activationId, + applicationKey, + encryptedRequest.getEphemeralPublicKey(), + version, + encryptedRequest.getNonce(), + encryptedRequest.getTimestamp() + ); + // Build server encryptor with obtained encryptor parameters + final byte[] secretKeyBytes = Base64.getDecoder().decode(encryptorParameters.secretKey()); + final byte[] sharedInfo2Base = Base64.getDecoder().decode(encryptorParameters.sharedInfo2()); + final ServerEncryptor serverEncryptor = encryptorFactory.getServerEncryptor( + encryptorData.getEncryptorId(), + new EncryptorParameters(version, applicationKey, activationId), + new ServerEncryptorSecrets(secretKeyBytes, sharedInfo2Base) + ); - // Prepare envelope key and sharedInfo2 parameter for decryptor - final byte[] secretKey = Base64.getDecoder().decode(decryptorParameters.secretKey()); - final EciesEnvelopeKey envelopeKey = new EciesEnvelopeKey(secretKey, ephemeralPublicKeyBytes); - final byte[] sharedInfo2 = Base64.getDecoder().decode(decryptorParameters.sharedInfo2()); + // Try to decrypt request data + final byte[] decryptedData = serverEncryptor.decryptRequest(encryptedRequest); - // Construct decryptor and encryptor - final EciesDecryptor eciesDecryptor = eciesFactory.getEciesDecryptor(envelopeKey, sharedInfo2); - final EciesEncryptor eciesEncryptor = eciesFactory.getEciesEncryptor(envelopeKey, sharedInfo2); - eciesEncryption.setEciesDecryptor(eciesDecryptor); - eciesEncryption.setEciesEncryptor(eciesEncryptor); + encryptorData.setEncryptedRequest(encryptedRequest); + encryptorData.setDecryptedRequest(decryptedData); + encryptorData.setServerEncryptor(serverEncryptor); - // Decrypt request data - final EciesParameters parameters = EciesParameters.builder().nonce(nonceBytes).associatedData(associatedData).timestamp(timestamp).build(); - final EciesCryptogram cryptogram = EciesCryptogram.builder().encryptedData(encryptedDataBytes).mac(macBytes).ephemeralPublicKey(ephemeralPublicKeyBytes).build(); - final EciesPayload payload = new EciesPayload(cryptogram, parameters); - final byte[] decryptedData = eciesDecryptor.decrypt(payload); - eciesEncryption.setEncryptedRequest(encryptedDataBytes); - eciesEncryption.setDecryptedRequest(decryptedData); - eciesEncryption.setAssociatedData(associatedData); - eciesEncryption.setRequestParameters(parameters); // Set the request object only in case when request data is sent if (decryptedData.length != 0) { - eciesEncryption.setRequestObject(deserializeRequestData(decryptedData, requestType)); + encryptorData.setRequestObject(deserializeRequestData(decryptedData, requestType)); } // Set encryption object in HTTP servlet request - request.setAttribute(PowerAuthRequestObjects.ENCRYPTION_OBJECT, eciesEncryption); + request.setAttribute(PowerAuthRequestObjects.ENCRYPTION_OBJECT, encryptorData); } catch (Exception ex) { logger.warn("Request decryption failed, error: " + ex.getMessage()); logger.debug(ex.getMessage(), ex); throw new PowerAuthEncryptionException(); } - return eciesEncryption; + } + + /** + * Encrypt response using End-To-End Encryptor. + * + * @param responseObject Response object which should be encrypted. + * @param encryption PowerAuth encryption object. + * @return ECIES encrypted response. + */ + public @Nullable + EciesEncryptedResponse encryptResponse(@Nonnull Object responseObject, @Nonnull PowerAuthEncryptorData encryption) { + try { + final EncryptionContext encryptionContext = encryption.getContext(); + final ServerEncryptor serverEncryptor = encryption.getServerEncryptor(); + if (encryptionContext == null) { + logger.warn("Encryption context is not prepared"); + throw new PowerAuthEncryptionException(); + } + if (serverEncryptor == null || serverEncryptor.canEncryptResponse()) { + logger.warn("Encryptor is not available or not prepared for encryption. Scope: {}", encryptionContext.getEncryptionScope()); + throw new PowerAuthEncryptionException(); + } + // Serialize response data + final byte[] responseData = serializeResponseData(responseObject); + // Encrypt response + final EncryptedResponse encryptedResponse = serverEncryptor.encryptResponse(responseData); + return new EciesEncryptedResponse( + encryptedResponse.getEncryptedData(), + encryptedResponse.getMac(), + encryptedResponse.getNonce(), + encryptedResponse.getTimestamp() + ); + } catch (Exception ex) { + logger.debug("Response encryption failed, error: " + ex.getMessage(), ex); + return null; + } } /** @@ -268,10 +264,11 @@ private byte[] serializeResponseData(Object responseObject) throws JsonProcessin * Extract context required for ECIES encryption from either encryption or signature HTTP header. * * @param request HTTP servlet request. + * @param encryptorScope Scope of encryption. * @return Context for ECIES encryption. * @throws PowerAuthEncryptionException Thrown when HTTP header with ECIES data is invalid. */ - private EciesEncryptionContext extractEciesEncryptionContext(HttpServletRequest request) throws PowerAuthEncryptionException { + private EncryptionContext extractEciesEncryptionContext(HttpServletRequest request, EncryptionScope encryptorScope) throws PowerAuthEncryptionException { final String encryptionHttpHeader = request.getHeader(PowerAuthEncryptionHttpHeader.HEADER_NAME); final String signatureHttpHeader = request.getHeader(PowerAuthSignatureHttpHeader.HEADER_NAME); @@ -299,7 +296,7 @@ private EciesEncryptionContext extractEciesEncryptionContext(HttpServletRequest final String applicationKey = header.getApplicationKey(); final String activationId = header.getActivationId(); final String version = header.getVersion(); - return new EciesEncryptionContext(applicationKey, activationId, version, header); + return new EncryptionContext(applicationKey, activationId, version, header, encryptorScope); } else { // Parse encryption HTTP header final PowerAuthEncryptionHttpHeader header = new PowerAuthEncryptionHttpHeader().fromValue(encryptionHttpHeader); @@ -317,7 +314,7 @@ private EciesEncryptionContext extractEciesEncryptionContext(HttpServletRequest final String applicationKey = header.getApplicationKey(); final String activationId = header.getActivationId(); final String version = header.getVersion(); - return new EciesEncryptionContext(applicationKey, activationId, version, header); + return new EncryptionContext(applicationKey, activationId, version, header, encryptorScope); } } } diff --git a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/ActivationController.java b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/ActivationController.java index 651a0ef6..9f51d488 100644 --- a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/ActivationController.java +++ b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/ActivationController.java @@ -21,10 +21,10 @@ 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.ecies.model.EciesScope; import io.getlime.security.powerauth.http.PowerAuthSignatureHttpHeader; import io.getlime.security.powerauth.rest.api.spring.authentication.PowerAuthApiAuthentication; -import io.getlime.security.powerauth.rest.api.spring.encryption.EciesEncryptionContext; +import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext; +import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionScope; import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthActivationException; import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthAuthenticationException; import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthRecoveryException; @@ -89,20 +89,20 @@ public void setAuthenticationProvider(PowerAuthAuthenticationProvider authentica /** * Create activation. * @param request Encrypted activation layer 1 request. - * @param eciesContext ECIES encryption context. + * @param context Encryption context. * @return Activation layer 1 response. * @throws PowerAuthActivationException In case activation fails. * @throws PowerAuthRecoveryException In case recovery PUK is invalid. */ @PostMapping("create") - @PowerAuthEncryption(scope = EciesScope.APPLICATION_SCOPE) + @PowerAuthEncryption(scope = EncryptionScope.APPLICATION_SCOPE) public ActivationLayer1Response createActivation(@EncryptedRequestBody ActivationLayer1Request request, - EciesEncryptionContext eciesContext) throws PowerAuthActivationException, PowerAuthRecoveryException { - if (request == null || eciesContext == null) { + EncryptionContext context) throws PowerAuthActivationException, PowerAuthRecoveryException { + if (request == null || context == null) { logger.warn("Invalid request in activation create"); throw new PowerAuthActivationException(); } - return activationServiceV3.createActivation(request, eciesContext); + return activationServiceV3.createActivation(request, context); } /** diff --git a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/UserInfoController.java b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/UserInfoController.java index 198723be..f922524b 100644 --- a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/UserInfoController.java +++ b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/UserInfoController.java @@ -19,11 +19,11 @@ */ package io.getlime.security.powerauth.rest.api.spring.controller; -import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesScope; import io.getlime.security.powerauth.rest.api.model.request.UserInfoRequest; import io.getlime.security.powerauth.rest.api.spring.annotation.EncryptedRequestBody; import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption; -import io.getlime.security.powerauth.rest.api.spring.encryption.EciesEncryptionContext; +import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext; +import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionScope; import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthEncryptionException; import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthUserInfoException; import io.getlime.security.powerauth.rest.api.spring.service.UserInfoService; @@ -66,21 +66,21 @@ public UserInfoController(UserInfoService userInfoService) { * Fetch user info. * * @param request Request with user info service. - * @param eciesContext PowerAuth ECIES encryption context. + * @param encryptionContext PowerAuth ECIES encryption context. * @return Encrypted user info claims. * @throws PowerAuthUserInfoException In case there is an error while fetching claims. * @throws PowerAuthEncryptionException In case of failed encryption. */ - @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE) + @PowerAuthEncryption(scope = EncryptionScope.ACTIVATION_SCOPE) @PostMapping("info") - public Map claims(@EncryptedRequestBody UserInfoRequest request, EciesEncryptionContext eciesContext) throws PowerAuthUserInfoException, PowerAuthEncryptionException { - if (eciesContext == null) { + public Map claims(@EncryptedRequestBody UserInfoRequest request, EncryptionContext encryptionContext) throws PowerAuthUserInfoException, PowerAuthEncryptionException { + if (encryptionContext == null) { logger.error("Encryption failed"); throw new PowerAuthEncryptionException("Encryption failed"); } return userInfoService.fetchUserClaimsByActivationId( - eciesContext.getActivationId() + encryptionContext.getActivationId() ); } diff --git a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/ActivationService.java b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/ActivationService.java index 773c3606..1d2b9cf0 100644 --- a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/ActivationService.java +++ b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/ActivationService.java @@ -37,7 +37,7 @@ import io.getlime.security.powerauth.rest.api.spring.application.PowerAuthApplicationConfiguration; import io.getlime.security.powerauth.rest.api.spring.authentication.PowerAuthApiAuthentication; import io.getlime.security.powerauth.rest.api.spring.converter.ActivationContextConverter; -import io.getlime.security.powerauth.rest.api.spring.encryption.EciesEncryptionContext; +import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext; import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthActivationException; import io.getlime.security.powerauth.rest.api.spring.exception.PowerAuthRecoveryException; import io.getlime.security.powerauth.rest.api.spring.exception.authentication.PowerAuthInvalidRequestException; @@ -126,7 +126,7 @@ public void setUserInfoProvider(UserInfoProvider userInfoProvider) { * @throws PowerAuthActivationException In case create activation fails. * @throws PowerAuthRecoveryException In case activation recovery fails. */ - public ActivationLayer1Response createActivation(ActivationLayer1Request request, EciesEncryptionContext eciesContext) throws PowerAuthActivationException, PowerAuthRecoveryException { + public ActivationLayer1Response createActivation(ActivationLayer1Request request, EncryptionContext eciesContext) throws PowerAuthActivationException, PowerAuthRecoveryException { try { final String applicationKey = eciesContext.getApplicationKey(); @@ -139,16 +139,6 @@ public ActivationLayer1Response createActivation(ActivationLayer1Request request final Map identity = request.getIdentityAttributes(); final Map customAttributes = (request.getCustomAttributes() != null) ? request.getCustomAttributes() : new HashMap<>(); - // Validate inner encryption - if (nonce == null && !"3.0".equals(eciesContext.getVersion())) { - logger.warn("Missing nonce in ECIES request data"); - throw new PowerAuthActivationException(); - } - if (timestamp == null && (!"3.0".equals(eciesContext.getVersion()) && !"3.1".equals(eciesContext.getVersion()))) { - logger.warn("Missing timestamp in ECIES request data"); - throw new PowerAuthActivationException(); - } - switch (request.getType()) { // Regular activation which uses "code" identity attribute case CODE -> { diff --git a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/RecoveryService.java b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/RecoveryService.java index 0044091a..0ea79a6f 100644 --- a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/RecoveryService.java +++ b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/RecoveryService.java @@ -76,8 +76,7 @@ public EciesEncryptedResponse confirmRecoveryCode(EciesEncryptedRequest request, final String activationId = authentication.getActivationContext().getActivationId(); final PowerAuthSignatureHttpHeader httpHeader = (PowerAuthSignatureHttpHeader) authentication.getHttpHeader(); final String applicationKey = httpHeader.getApplicationKey(); - if (activationId == null || applicationKey == null || request.getEphemeralPublicKey() == null - || request.getEncryptedData() == null || request.getMac() == null) { + if (activationId == null || applicationKey == null) { logger.warn("PowerAuth confirm recovery failed because of invalid request"); throw new PowerAuthInvalidRequestException(); } @@ -87,7 +86,6 @@ public EciesEncryptedResponse confirmRecoveryCode(EciesEncryptedRequest request, confirmRequest.setEphemeralPublicKey(request.getEphemeralPublicKey()); confirmRequest.setEncryptedData(request.getEncryptedData()); confirmRequest.setMac(request.getMac()); - confirmRequest.setEphemeralPublicKey(request.getEphemeralPublicKey()); confirmRequest.setNonce(request.getNonce()); confirmRequest.setProtocolVersion(httpHeader.getVersion()); confirmRequest.setTimestamp(request.getTimestamp()); @@ -100,8 +98,11 @@ public EciesEncryptedResponse confirmRecoveryCode(EciesEncryptedRequest request, logger.warn("PowerAuth confirm recovery failed because of invalid activation ID in response"); throw new PowerAuthInvalidRequestException(); } - return new EciesEncryptedResponse(paResponse.getEncryptedData(), paResponse.getMac(), - paResponse.getNonce(), paResponse.getTimestamp()); + return new EciesEncryptedResponse( + paResponse.getEncryptedData(), + paResponse.getMac(), + paResponse.getNonce(), + paResponse.getTimestamp()); } catch (Exception ex) { logger.warn("PowerAuth confirm recovery failed, error: {}", ex.getMessage()); logger.debug(ex.getMessage(), ex); diff --git a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/SecureVaultService.java b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/SecureVaultService.java index 595f5fdd..f0304ba6 100644 --- a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/SecureVaultService.java +++ b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/SecureVaultService.java @@ -100,13 +100,6 @@ public EciesEncryptedResponse vaultUnlock(PowerAuthSignatureHttpHeader header, final String signatureVersion = header.getVersion(); final String nonce = header.getNonce(); - // Fetch data from the request - final String ephemeralPublicKey = request.getEphemeralPublicKey(); - final String encryptedData = request.getEncryptedData(); - final String mac = request.getMac(); - final String eciesNonce = request.getNonce(); - final Long timestamp = request.getTimestamp(); - // Prepare data for signature to allow signature verification on PowerAuth server final byte[] requestBodyBytes = authenticationProvider.extractRequestBodyBytes(httpServletRequest); final String data = PowerAuthHttpBody.getSignatureBaseString("POST", "/pa/vault/unlock", Base64.getDecoder().decode(nonce), requestBodyBytes); @@ -119,12 +112,11 @@ public EciesEncryptedResponse vaultUnlock(PowerAuthSignatureHttpHeader header, unlockRequest.setSignatureType(signatureType); unlockRequest.setSignatureVersion(signatureVersion); unlockRequest.setSignedData(data); - unlockRequest.setEphemeralPublicKey(ephemeralPublicKey); - unlockRequest.setEncryptedData(encryptedData); - unlockRequest.setMac(mac); - unlockRequest.setEphemeralPublicKey(ephemeralPublicKey); - unlockRequest.setNonce(eciesNonce); - unlockRequest.setTimestamp(timestamp); + unlockRequest.setEphemeralPublicKey(request.getEphemeralPublicKey()); + unlockRequest.setEncryptedData(request.getEncryptedData()); + unlockRequest.setMac(request.getMac()); + unlockRequest.setNonce(request.getNonce()); + unlockRequest.setTimestamp(request.getTimestamp()); final VaultUnlockResponse paResponse = powerAuthClient.unlockVault( unlockRequest, httpCustomizationService.getQueryParams(), @@ -136,8 +128,11 @@ public EciesEncryptedResponse vaultUnlock(PowerAuthSignatureHttpHeader header, throw new PowerAuthSignatureInvalidException(); } - return new EciesEncryptedResponse(paResponse.getEncryptedData(), paResponse.getMac(), - paResponse.getNonce(), paResponse.getTimestamp()); + return new EciesEncryptedResponse( + paResponse.getEncryptedData(), + paResponse.getMac(), + paResponse.getNonce(), + paResponse.getTimestamp()); } catch (PowerAuthAuthenticationException ex) { throw ex; } catch (Exception ex) { diff --git a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/TokenService.java b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/TokenService.java index b4d1913e..f43693a9 100644 --- a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/TokenService.java +++ b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/TokenService.java @@ -84,13 +84,6 @@ public EciesEncryptedResponse createToken(EciesEncryptedRequest request, // Fetch activation ID and signature type final PowerAuthSignatureTypes signatureFactors = authentication.getAuthenticationContext().getSignatureType(); - // Fetch data from the request - final String ephemeralPublicKey = request.getEphemeralPublicKey(); - final String encryptedData = request.getEncryptedData(); - final String mac = request.getMac(); - final String nonce = request.getNonce(); - final Long timestamp = request.getTimestamp(); - // Prepare a signature type converter final SignatureTypeConverter converter = new SignatureTypeConverter(); final SignatureType signatureType = converter.convertFrom(signatureFactors); @@ -108,14 +101,13 @@ public EciesEncryptedResponse createToken(EciesEncryptedRequest request, final CreateTokenRequest tokenRequest = new CreateTokenRequest(); tokenRequest.setActivationId(activationId); tokenRequest.setApplicationKey(applicationKey); - tokenRequest.setEphemeralPublicKey(ephemeralPublicKey); - tokenRequest.setEncryptedData(encryptedData); - tokenRequest.setMac(mac); - tokenRequest.setEphemeralPublicKey(ephemeralPublicKey); - tokenRequest.setNonce(nonce); + tokenRequest.setEphemeralPublicKey(request.getEphemeralPublicKey()); + tokenRequest.setEncryptedData(request.getEncryptedData()); + tokenRequest.setMac(request.getMac()); + tokenRequest.setNonce(request.getNonce()); tokenRequest.setSignatureType(signatureType); tokenRequest.setProtocolVersion(httpHeader.getVersion()); - tokenRequest.setTimestamp(timestamp); + tokenRequest.setTimestamp(request.getTimestamp()); final CreateTokenResponse token = powerAuthClient.createToken( tokenRequest, httpCustomizationService.getQueryParams(), @@ -128,6 +120,7 @@ public EciesEncryptedResponse createToken(EciesEncryptedRequest request, response.setEncryptedData(token.getEncryptedData()); response.setNonce(token.getNonce()); response.setMac(token.getMac()); + response.setTimestamp(token.getTimestamp()); return response; } catch (Exception ex) { logger.warn("Creating PowerAuth token failed, error: {}", ex.getMessage()); diff --git a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/UpgradeService.java b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/UpgradeService.java index a3492afd..c8bfa948 100644 --- a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/UpgradeService.java +++ b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/service/UpgradeService.java @@ -89,13 +89,6 @@ public EciesEncryptedResponse upgradeStart(EciesEncryptedRequest request, PowerA throws PowerAuthUpgradeException { try { - // Fetch data from the request - final String ephemeralPublicKey = request.getEphemeralPublicKey(); - final String encryptedData = request.getEncryptedData(); - final String mac = request.getMac(); - final String nonce = request.getNonce(); - final Long timestamp = request.getTimestamp(); - // Get ECIES headers final String activationId = header.getActivationId(); final String applicationKey = header.getApplicationKey(); @@ -104,12 +97,12 @@ public EciesEncryptedResponse upgradeStart(EciesEncryptedRequest request, PowerA final StartUpgradeRequest upgradeRequest = new StartUpgradeRequest(); upgradeRequest.setActivationId(activationId); upgradeRequest.setApplicationKey(applicationKey); - upgradeRequest.setEphemeralPublicKey(ephemeralPublicKey); - upgradeRequest.setEncryptedData(encryptedData); - upgradeRequest.setMac(mac); - upgradeRequest.setNonce(nonce); + upgradeRequest.setEphemeralPublicKey(request.getEphemeralPublicKey()); + upgradeRequest.setEncryptedData(request.getEncryptedData()); + upgradeRequest.setMac(request.getMac()); + upgradeRequest.setNonce(request.getNonce()); upgradeRequest.setProtocolVersion(header.getVersion()); - upgradeRequest.setTimestamp(timestamp); + upgradeRequest.setTimestamp(request.getTimestamp()); final StartUpgradeResponse upgradeResponse = powerAuthClient.startUpgrade( upgradeRequest, httpCustomizationService.getQueryParams(), From 66d0a1dfdbf08a4e8d3f7fd4c62859a08f396267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20=C4=8Eurech?= Date: Fri, 18 Aug 2023 12:17:06 +0200 Subject: [PATCH 2/3] Improve encryption header validation --- .../spring/encryption/EncryptionScope.java | 22 ++++++++++++++++++- .../PowerAuthEncryptionProviderBase.java | 2 +- .../spring/controller/UpgradeController.java | 3 ++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionScope.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionScope.java index 567336fd..36e945e4 100644 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionScope.java +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/encryption/EncryptionScope.java @@ -20,6 +20,8 @@ package io.getlime.security.powerauth.rest.api.spring.encryption; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope; + /** * Enumeration of application scopes for encryptor. */ @@ -33,5 +35,23 @@ public enum EncryptionScope { /** * Activation scope (personalized). */ - ACTIVATION_SCOPE + ACTIVATION_SCOPE; + + /** + * Translate this scope object into {@link EncryptorScope} used by low level PowerAuth Crypto library. + * @return Translated {@link EncryptorScope}. + */ + public EncryptorScope toEncryptorScope() { + switch (this) { + case APPLICATION_SCOPE -> { + return EncryptorScope.APPLICATION_SCOPE; + } + case ACTIVATION_SCOPE -> { + return EncryptorScope.ACTIVATION_SCOPE; + } + default -> { + throw new IllegalArgumentException("Unsupported scope " + this); + } + } + } } diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProviderBase.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProviderBase.java index 9108877c..5376a3da 100644 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProviderBase.java +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/provider/PowerAuthEncryptionProviderBase.java @@ -303,7 +303,7 @@ private EncryptionContext extractEciesEncryptionContext(HttpServletRequest reque // Validate the encryption HTTP header try { - PowerAuthEncryptionHttpHeaderValidator.validate(header); + PowerAuthEncryptionHttpHeaderValidator.validate(header, encryptorScope.toEncryptorScope()); } catch (InvalidPowerAuthHttpHeaderException ex) { logger.warn("Encryption validation failed, error: {}", ex.getMessage()); logger.debug(ex.getMessage(), ex); diff --git a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/UpgradeController.java b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/UpgradeController.java index df86489a..7e97a224 100644 --- a/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/UpgradeController.java +++ b/powerauth-restful-security-spring/src/main/java/io/getlime/security/powerauth/rest/api/spring/controller/UpgradeController.java @@ -20,6 +20,7 @@ package io.getlime.security.powerauth.rest.api.spring.controller; import io.getlime.core.rest.model.base.response.Response; +import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptorScope; import io.getlime.security.powerauth.http.PowerAuthEncryptionHttpHeader; import io.getlime.security.powerauth.http.PowerAuthSignatureHttpHeader; import io.getlime.security.powerauth.http.validator.InvalidPowerAuthHttpHeaderException; @@ -88,7 +89,7 @@ public EciesEncryptedResponse upgradeStart(@RequestBody EciesEncryptedRequest re // Validate the encryption header try { - PowerAuthEncryptionHttpHeaderValidator.validate(header); + PowerAuthEncryptionHttpHeaderValidator.validate(header, EncryptorScope.ACTIVATION_SCOPE); } catch (InvalidPowerAuthHttpHeaderException ex) { logger.warn("Encryption validation failed, error: {}", ex.getMessage()); logger.debug(ex.getMessage(), ex); From 5ee0d755336f53fd3aa04d573511e19c355eaa03 Mon Sep 17 00:00:00 2001 From: Lubos Racansky Date: Thu, 24 Aug 2023 11:18:08 +0200 Subject: [PATCH 3/3] Replace fully qualified names by imports --- .../support/PowerAuthEncryptionArgumentResolver.java | 7 ++++--- .../api/spring/filter/EncryptionResponseBodyAdvice.java | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/support/PowerAuthEncryptionArgumentResolver.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/support/PowerAuthEncryptionArgumentResolver.java index b729f080..e90bc9cd 100644 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/support/PowerAuthEncryptionArgumentResolver.java +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/annotation/support/PowerAuthEncryptionArgumentResolver.java @@ -23,9 +23,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import io.getlime.security.powerauth.rest.api.spring.annotation.EncryptedRequestBody; +import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption; import io.getlime.security.powerauth.rest.api.spring.encryption.EncryptionContext; import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEncryptorData; import io.getlime.security.powerauth.rest.api.spring.model.PowerAuthRequestObjects; +import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.MethodParameter; @@ -35,7 +37,6 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; -import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.reflect.Type; @@ -53,7 +54,7 @@ public class PowerAuthEncryptionArgumentResolver implements HandlerMethodArgumen @Override public boolean supportsParameter(@NonNull MethodParameter parameter) { - return parameter.hasMethodAnnotation(io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption.class) + return parameter.hasMethodAnnotation(PowerAuthEncryption.class) && (parameter.hasParameterAnnotation(EncryptedRequestBody.class) || EncryptionContext.class.isAssignableFrom(parameter.getParameterType())); } @@ -82,7 +83,7 @@ public Object resolveArgument(@NonNull MethodParameter parameter, ModelAndViewCo // Ecies encryption object is inserted into parameter which is of type PowerAuthEciesEncryption if (eciesObject != null && EncryptionContext.class.isAssignableFrom(parameter.getParameterType())) { // Set ECIES scope in case it is specified by the @PowerAuthEncryption annotation - io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption powerAuthEncryption = parameter.getMethodAnnotation(io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption.class); + final PowerAuthEncryption powerAuthEncryption = parameter.getMethodAnnotation(io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption.class); if (powerAuthEncryption != null) { EncryptionContext eciesContext = eciesObject.getContext(); boolean validScope = validateEciesScope(eciesContext); diff --git a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/filter/EncryptionResponseBodyAdvice.java b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/filter/EncryptionResponseBodyAdvice.java index 6f7b90a7..5de8617c 100644 --- a/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/filter/EncryptionResponseBodyAdvice.java +++ b/powerauth-restful-security-spring-annotation/src/main/java/io/getlime/security/powerauth/rest/api/spring/filter/EncryptionResponseBodyAdvice.java @@ -22,8 +22,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.getlime.security.powerauth.crypto.lib.encryptor.model.EncryptedResponse; import io.getlime.security.powerauth.rest.api.model.response.EciesEncryptedResponse; +import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption; import io.getlime.security.powerauth.rest.api.spring.encryption.PowerAuthEncryptorData; import io.getlime.security.powerauth.rest.api.spring.model.PowerAuthRequestObjects; +import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -44,7 +46,6 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; -import jakarta.servlet.http.HttpServletRequest; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -84,7 +85,7 @@ public void setRequestMappingHandlerAdapter(@Lazy RequestMappingHandlerAdapter r */ @Override public boolean supports(@NonNull MethodParameter methodParameter, @NonNull Class> converterClass) { - return methodParameter.hasMethodAnnotation(io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption.class) && + return methodParameter.hasMethodAnnotation(PowerAuthEncryption.class) && (converterClass.isAssignableFrom(MappingJackson2HttpMessageConverter.class) || converterClass.isAssignableFrom(StringHttpMessageConverter.class) || converterClass.isAssignableFrom(ByteArrayHttpMessageConverter.class));