From 482261845ec352d4b8af011e4d64fc9279449cb2 Mon Sep 17 00:00:00 2001 From: Alberto Trevezani Date: Wed, 1 Nov 2023 11:25:51 -0300 Subject: [PATCH] =?UTF-8?q?Rotinas=20para=20gerar=20a=20libera=C3=A7=C3=A3?= =?UTF-8?q?o=20da=20utiliza=C3=A7=C3=A3o=20da=20empresa=20no=20sistema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 9 ++ .../web/utilerias/security/AESGSMHelper.java | 106 +++++++++++++ .../security/SecurityEmpresaToken.java | 140 ++++++++++++++++++ .../security/exception/SecurityException.java | 13 ++ .../tests/SecurityEmpresaTokenTest.java | 91 ++++++++++++ ...aTest.java => SerieEmbarcadaTest.java.old} | 0 6 files changed, 359 insertions(+) create mode 100644 src/java/com/rjconsultores/ventaboletos/web/utilerias/security/AESGSMHelper.java create mode 100644 src/java/com/rjconsultores/ventaboletos/web/utilerias/security/SecurityEmpresaToken.java create mode 100644 src/java/com/rjconsultores/ventaboletos/web/utilerias/security/exception/SecurityException.java create mode 100644 tests/com/rjconsultores/tests/SecurityEmpresaTokenTest.java rename tests/com/rjconsultores/tests/{SerieEmbarcadaTest.java => SerieEmbarcadaTest.java.old} (100%) diff --git a/pom.xml b/pom.xml index f2f0737be..5220d9f91 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,15 @@ + tests + + + tests + + **/*.java + + + maven-compiler-plugin diff --git a/src/java/com/rjconsultores/ventaboletos/web/utilerias/security/AESGSMHelper.java b/src/java/com/rjconsultores/ventaboletos/web/utilerias/security/AESGSMHelper.java new file mode 100644 index 000000000..bc7f43653 --- /dev/null +++ b/src/java/com/rjconsultores/ventaboletos/web/utilerias/security/AESGSMHelper.java @@ -0,0 +1,106 @@ +package com.rjconsultores.ventaboletos.web.utilerias.security; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; + +public class AESGSMHelper { + private final String SECRET_KEY = "RJ@2019#c0n5ul10r35"; + private final String SALT = "HrqoFr44GtkAhhYN+jP8Ag=="; + private final String ENCRYPT_ALGO = "AES/GCM/NoPadding"; + private final int TAG_LENGTH_BIT = 128; + private final int IV_LENGTH_BYTE = 12; + + private final Charset UTF_8 = StandardCharsets.UTF_8; + + public String encrypt(String value) throws Exception { + SecretKey secret = getAESKeyFromPassword(SECRET_KEY.toCharArray()); + + byte[] pText = value.getBytes(StandardCharsets.UTF_8); + byte[] iv = getRandomNonce(12); + byte[] cipherText = encrypt(pText, secret, iv); + + byte[] cipherTextWithIv = ByteBuffer.allocate(iv.length + cipherText.length) + .put(iv) + .put(cipherText) + .array(); + + return hex(cipherTextWithIv); + } + + private byte[] encrypt(byte[] pText, SecretKey secret, byte[] iv) throws Exception { + Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); + cipher.init(Cipher.ENCRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); + + byte[] encryptedText = cipher.doFinal(pText); + + return encryptedText; + + } + + public String decrypt(String value) throws Exception { + SecretKey secret = getAESKeyFromPassword(SECRET_KEY.toCharArray()); + + byte[] cText = unhex(value); + + ByteBuffer bb = ByteBuffer.wrap(cText); + + byte[] iv = new byte[IV_LENGTH_BYTE]; + bb.get(iv); + + byte[] cipherText = new byte[bb.remaining()]; + bb.get(cipherText); + + String plainText = decrypt(cipherText, secret, iv); + + return plainText; + } + + private String decrypt(byte[] cText, SecretKey secret, byte[] iv) throws Exception { + Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); + cipher.init(Cipher.DECRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); + + byte[] plainText = cipher.doFinal(cText); + + return new String(plainText, UTF_8); + } + + private byte[] getRandomNonce(int numBytes) { + byte[] nonce = new byte[numBytes]; + new SecureRandom().nextBytes(nonce); + + return nonce; + } + + private SecretKey getAESKeyFromPassword(char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + + KeySpec spec = new PBEKeySpec(password, SALT.getBytes(), 65536, 256); + SecretKeySpec secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); + + return secret; + } + + private String hex(byte[] bytes) { + char[] result = Hex.encodeHex(bytes); + return new String(result); + } + + private byte[] unhex(String hex) throws DecoderException { + return Hex.decodeHex(hex.toCharArray()); + } +} diff --git a/src/java/com/rjconsultores/ventaboletos/web/utilerias/security/SecurityEmpresaToken.java b/src/java/com/rjconsultores/ventaboletos/web/utilerias/security/SecurityEmpresaToken.java new file mode 100644 index 000000000..533537fa1 --- /dev/null +++ b/src/java/com/rjconsultores/ventaboletos/web/utilerias/security/SecurityEmpresaToken.java @@ -0,0 +1,140 @@ +package com.rjconsultores.ventaboletos.web.utilerias.security; + +import java.text.ParseException; +import java.time.Duration; +import java.util.Calendar; + +import javax.xml.bind.DatatypeConverter; + +import org.apache.log4j.Logger; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSObject; +import com.nimbusds.jose.Payload; +import com.nimbusds.jose.crypto.MACSigner; +import com.nimbusds.jwt.JWTClaimsSet; + +import net.minidev.json.JSONObject; + +public class SecurityEmpresaToken { + private static Logger log = Logger.getLogger(SecurityEmpresaToken.class); + + private String secret = "#KO&Fm4_k.sU9M8`6Mx'F\\\"H:*Qxu]6F4r,)JmZ2Jwafd)I.2[RET'1:)VQ6mG9,"; + private static final Duration ttl = Duration.ofHours(24); + + private Gson gson = new Gson(); + + public String bodyRequestGenerate(final Integer empresaId, final String cnpj) throws SecurityException { + try { + AESGSMHelper crypto = new AESGSMHelper(); + + JsonObject json = new JsonObject(); + json.addProperty("empresaId", empresaId); + json.addProperty("CNPJ", cnpj); + + return crypto.encrypt(json.toString()); + + } catch (Exception e) { + log.error("Erro ao gerar o body usado no request da licença: " + e.getMessage(), e); + + throw new SecurityException(e); + } + } + + public String licenseDefaultGenerate(final Integer empresaId, final String cnpj) throws SecurityException { + try { + AESGSMHelper crypto = new AESGSMHelper(); + + JsonObject json = new JsonObject(); + json.addProperty("empresaId", empresaId); + json.addProperty("CNPJ", cnpj); + json.addProperty("aprovado", 1); + + return crypto.encrypt(json.toString()); + + } catch (Exception e) { + log.error("Erro ao gerar a licença padrão para as empresas existentes: " + e.getMessage(), e); + + throw new SecurityException(e); + } + } + + public boolean licenseValidate(final String license, final Integer empresaId, final String cnpj) { + try { + AESGSMHelper crypto = new AESGSMHelper(); + + final String value = crypto.decrypt(license); + final JsonObject json = gson.fromJson(value, JsonObject.class); + + if (json.has("empresaId") && json.get("empresaId").getAsInt() == empresaId.intValue() + && json.has("CNPJ") && json.get("CNPJ").getAsString().equals(cnpj) + && json.has("aprovado")) { + log.debug("[empresaId=" + json.get("empresaId").getAsString() + ", CNPJ=" + json.get("CNPJ").getAsString() + ", aprovado=" + json.get("aprovado").getAsString() + "]"); + + return json.get("aprovado").getAsString().equals("1"); + } + } catch (Exception e) { + log.error("Erro ao gerar o body usado no request da licença: " + e.getMessage(), e); + } + + return false; + } + + public String requestGenerate(String licenseRequest) throws SecurityException { + return requestGenerate(licenseRequest, ttl); + } + + public String requestGenerate(String licenseRequest, Duration ttl) throws SecurityException { + try { + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MILLISECOND, (int) ttl.toMillis()); + + JWTClaimsSet claims = new JWTClaimsSet.Builder() + .expirationTime(cal.getTime()) + .claim("sub", licenseRequest) + .claim("userId", "adm") + .claim("role", "ROLE_TOKEN") + .build(); + + JWSObject jwsObject = new JWSObject(new JWSHeader(JWSAlgorithm.HS256), new Payload(claims.toJSONObject())); + + jwsObject.sign(new MACSigner(DatatypeConverter.parseBase64Binary(secret))); + + return jwsObject.serialize(); + } catch (Exception e) { + log.error("Erro ao gerar a request: " + e.getMessage(), e); + + throw new SecurityException(e); + } + } + + public String tokenValidate(final String token) throws SecurityException { + try { + JWSObject jwsObject = JWSObject.parse(token); + JSONObject jsonPayload = jwsObject.getPayload().toJSONObject(); + JWTClaimsSet claims = JWTClaimsSet.parse(jsonPayload); + + if (claims.getExpirationTime().compareTo(Calendar.getInstance().getTime()) < 0) { + throw new SecurityException("Token expirado"); + } + + return claims.getSubject(); + + } catch (SecurityException e) { + throw e; + + } catch (ParseException e) { + log.error("Erro no parser do token: " + e.getMessage(), e); + + throw new SecurityException(e); + + } catch (Exception e) { + log.error("Erro ao validar o token: " + e.getMessage(), e); + + throw new SecurityException(e); + } + } +} diff --git a/src/java/com/rjconsultores/ventaboletos/web/utilerias/security/exception/SecurityException.java b/src/java/com/rjconsultores/ventaboletos/web/utilerias/security/exception/SecurityException.java new file mode 100644 index 000000000..ca6b559f9 --- /dev/null +++ b/src/java/com/rjconsultores/ventaboletos/web/utilerias/security/exception/SecurityException.java @@ -0,0 +1,13 @@ +package com.rjconsultores.ventaboletos.web.utilerias.security.exception; + +public class SecurityException extends Exception { + private static final long serialVersionUID = 1801174850577435303L; + + public SecurityException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public SecurityException(String arg0) { + super(arg0); + } +} diff --git a/tests/com/rjconsultores/tests/SecurityEmpresaTokenTest.java b/tests/com/rjconsultores/tests/SecurityEmpresaTokenTest.java new file mode 100644 index 000000000..82de6e409 --- /dev/null +++ b/tests/com/rjconsultores/tests/SecurityEmpresaTokenTest.java @@ -0,0 +1,91 @@ +package com.rjconsultores.tests; + +import static org.junit.Assert.fail; + +import java.time.Duration; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.rjconsultores.ventaboletos.web.utilerias.security.SecurityEmpresaToken; + +public class SecurityEmpresaTokenTest { + private static final Logger log = LoggerFactory.getLogger(SecurityEmpresaTokenTest.class); + + @Before + public void initApplicationContext() { + log.info("[ BEGIN ]"); + } + + @After + public void closeApplicationContext() { + log.info("[ END ]"); + } + + @Test + public void test_EmpresaNova() throws Exception { + // license request -> token request -> token response -> license + + Integer empresaId = 1313; + String cnpj = "00073778000120"; + + SecurityEmpresaToken security = new SecurityEmpresaToken(); + + final String bodyRequest = security.bodyRequestGenerate(empresaId, cnpj); + final String request = security.requestGenerate(bodyRequest); + + final String license = security.tokenValidate(request); + final boolean valid = security.licenseValidate(license, empresaId, cnpj); + + log.info("Body Request: " + bodyRequest); + log.info("Request: " + request); + log.info("License: " + license); + log.info("Valid: " + Boolean.toString(valid)); + + if (valid) fail("Licença válida"); + } + + @Test + public void test_Licenca() throws Exception { + Integer empresaId = 1313; + String cnpj = "00073778000120"; + + SecurityEmpresaToken security = new SecurityEmpresaToken(); + + final String license = security.licenseDefaultGenerate(empresaId, cnpj); + final boolean valid = security.licenseValidate(license, empresaId, cnpj); + + log.info("License: " + license); + log.info("Valid: " + Boolean.toString(valid)); + + if (!valid) fail("Licença inválida"); + } + + @Test(expected = SecurityException.class) + public void test_TTL_expired() throws Exception { + Duration ttl = Duration.ofSeconds(5); + + Integer empresaId = 1313; + String cnpj = "00073778000120"; + + SecurityEmpresaToken security = new SecurityEmpresaToken(); + + final String bodyRequest = security.bodyRequestGenerate(empresaId, cnpj); + final String request = security.requestGenerate(bodyRequest, ttl); + + Thread.sleep(Duration.ofSeconds(10).toMillis()); + + final String license = security.tokenValidate(request); + final boolean valid = security.licenseValidate(license, empresaId, cnpj); + + log.info("Body Request: " + bodyRequest); + log.info("Request: " + request); + log.info("License: " + license); + log.info("Valid: " + Boolean.toString(valid)); + + if (valid) fail("Licença válida"); + } +} diff --git a/tests/com/rjconsultores/tests/SerieEmbarcadaTest.java b/tests/com/rjconsultores/tests/SerieEmbarcadaTest.java.old similarity index 100% rename from tests/com/rjconsultores/tests/SerieEmbarcadaTest.java rename to tests/com/rjconsultores/tests/SerieEmbarcadaTest.java.old