summaryrefslogtreecommitdiffstats
path: root/PolicyEngineUtils
diff options
context:
space:
mode:
authorJorge Hernandez <jorge.hernandez-herrero@att.com>2019-04-03 02:01:34 +0000
committerGerrit Code Review <gerrit@onap.org>2019-04-03 02:01:34 +0000
commitd5f95d08ffd3f2e16b67aadae7007c7a51dfae19 (patch)
treea9388d004af2bf679b66524edbc53571ce06d8e1 /PolicyEngineUtils
parent338796c4d56d2e95aa2903d80589b3b35b5a3dd2 (diff)
parentc1b69dfb1297365d35f2ada8690f13f787d38b4f (diff)
Merge "Enhancement to use the common CryptoUtils"
Diffstat (limited to 'PolicyEngineUtils')
-rw-r--r--PolicyEngineUtils/src/main/java/org/onap/policy/utils/CryptoUtils.java256
-rw-r--r--PolicyEngineUtils/src/main/java/org/onap/policy/utils/PeCryptoUtils.java102
-rw-r--r--PolicyEngineUtils/src/test/java/org/onap/policy/utils/PeCryptoUtilsTest.java64
-rw-r--r--PolicyEngineUtils/src/test/java/org/onap/policy/utils/test/CryptoUtilsTest.java128
4 files changed, 166 insertions, 384 deletions
diff --git a/PolicyEngineUtils/src/main/java/org/onap/policy/utils/CryptoUtils.java b/PolicyEngineUtils/src/main/java/org/onap/policy/utils/CryptoUtils.java
deleted file mode 100644
index 15a93bdab..000000000
--- a/PolicyEngineUtils/src/main/java/org/onap/policy/utils/CryptoUtils.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * PolicyEngineUtils
- * ================================================================================
- * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-package org.onap.policy.utils;
-
-import java.nio.charset.StandardCharsets;
-import java.security.AlgorithmParameters;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Key;
-import java.util.Base64;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.SecretKeySpec;
-import javax.xml.bind.DatatypeConverter;
-
-import org.onap.policy.common.logging.flexlogger.FlexLogger;
-import org.onap.policy.common.logging.flexlogger.Logger;
-
-public class CryptoUtils {
- private static final Logger LOGGER = FlexLogger.getLogger(CryptoUtils.class);
- private static final String CIPHER_TYPE = "AES/CBC/PKCS5Padding";
- private static Key mKey = null;
- private static AlgorithmParameters mAlgParm = null;
-
- static {
- //the hadcoded key is to be removed in a future iteration
- try {
- String kval = "bmpybWJrbGN4dG9wbGF3Zg==";
- String algp = "BBBpbml0VmVjVGhpc0lzVGhl";
-
- byte[] kvalb = DatatypeConverter.parseBase64Binary(kval);
- byte[] algb = DatatypeConverter.parseBase64Binary(algp);
-
- mKey = new SecretKeySpec(kvalb, "AES");
-
- mAlgParm = AlgorithmParameters.getInstance("AES");
- mAlgParm.init(algb, "ASN.1");
-
- } catch (Exception ex) {
- throw new ExceptionInInitializerError(ex);
- }
- }
-
- private CryptoUtils() {
- // Private Constructor
- }
-
- /**
- * Decrypt txt.
- *
- * @param encryptedTxt
- * text to be decrypted, Base 64 UrlEncoded
- * @return the byte[]
- * @throws NoSuchAlgorithmException
- * the no such algorithm exception
- * @throws NoSuchPaddingException
- * the no such padding exception
- * @throws InvalidAlgorithmParameterException
- * the invalid algorithm parameter exception
- * @throws InvalidKeyException
- * the invalid key exception
- * @throws IllegalBlockSizeException
- * the illegal block size exception
- * @throws BadPaddingException
- * the bad padding exception
- */
- public static byte[] decryptTxt(String encryptedTxt)
- throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException,
- InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
- Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
- cipher.init(Cipher.DECRYPT_MODE, mKey, mAlgParm);
-
- return cipher.doFinal(Base64.getUrlDecoder().decode(encryptedTxt.getBytes(StandardCharsets.UTF_8)));
- }
-
- /**
- * Decrypt txt.
- *
- * @param encryptedTxt
- * text to be decrypted, Base 64 UrlEncoded
- * @param mKey
- * the key as Base 64
- * @return the byte[]
- * @throws NoSuchAlgorithmException
- * the no such algorithm exception
- * @throws NoSuchPaddingException
- * the no such padding exception
- * @throws InvalidAlgorithmParameterException
- * the invalid algorithm parameter exception
- * @throws InvalidKeyException
- * the invalid key exception
- * @throws IllegalBlockSizeException
- * the illegal block size exception
- * @throws BadPaddingException
- * the bad padding exception
- */
- public static byte[] decryptTxt(String encryptedTxt, String base64BinaryKey)
- throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException,
- InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
-
- byte[] keyValueByte = DatatypeConverter.parseBase64Binary(base64BinaryKey);
- Key paramKey = new SecretKeySpec(keyValueByte, "AES");
- Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
- cipher.init(Cipher.DECRYPT_MODE, paramKey, mAlgParm);
-
- return cipher.doFinal(Base64.getUrlDecoder().decode(encryptedTxt.getBytes(StandardCharsets.UTF_8)));
- }
-
- /**
- * Decrypt txt, no exceptions thrown.
- *
- * @param encryptedTxt
- * text to be decrypted, Base 64 UrlEncoded
- * @return the decrypted text, or the original text if it could not be
- * decrypted
- */
- public static byte[] decryptTxtNoEx(String encryptedTxt) {
-
- try {
- if (encryptedTxt == null || encryptedTxt.isEmpty()) {
- LOGGER.info("decryptTxtNoEx: Input param encryptedTxt is empty");
- return new byte[0];
- }
- return decryptTxt(encryptedTxt);
- } catch (Exception e) {
- try {
- LOGGER.info("decryptTxtNoEx: Exception while decrypting : " + e);
- return (encryptedTxt != null) ? encryptedTxt.getBytes(StandardCharsets.UTF_8) : new byte[0];
- } catch (Exception e1) {
- LOGGER.warn("decryptTxtNoEx: Exception on sending default : " + e1);
- return new byte[0];
- }
- }
- }
-
- /**
- * Decrypt txt, no exceptions thrown.
- *
- * @param encryptedTxt
- * text to be decrypted, Base 64 UrlEncoded
- * @return the decrypted text, or the original text if it could not be
- * decrypted
- */
- public static String decryptTxtNoExStr(String encryptedTxt) {
- return new String(decryptTxtNoEx(encryptedTxt), StandardCharsets.UTF_8);
- }
-
- /**
- * Encrypt txt.
- *
- * @param plainTxt
- * the plain txt
- * @return the encrypted string
- * @throws NoSuchPaddingException
- * the no such padding exception
- * @throws InvalidAlgorithmParameterException
- * the invalid algorithm parameter exception
- * @throws NoSuchAlgorithmException
- * the no such algorithm exception
- * @throws InvalidKeyException
- * the invalid key exception
- * @throws IllegalBlockSizeException
- * the illegal block size exception
- * @throws BadPaddingException
- * the bad padding exception
- */
- public static String encryptTxt(byte[] plainTxt)
- throws NoSuchPaddingException, InvalidAlgorithmParameterException,
- NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
-
- Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
- cipher.init(Cipher.ENCRYPT_MODE, mKey, mAlgParm);
-
- byte[] encryption = cipher.doFinal(plainTxt);
- return new String(Base64.getUrlEncoder().encode(encryption), StandardCharsets.UTF_8);
- }
-
- /**
- * Encrypt txt.
- *
- * @param plainTxt
- * the plain txt to be encrypted
- * @param base64BinaryKey
- * the key as lexical representation of Base64 Binary
- * @return the encrypted string
- * @throws NoSuchPaddingException
- * the no such padding exception
- * @throws InvalidAlgorithmParameterException
- * the invalid algorithm parameter exception
- * @throws NoSuchAlgorithmException
- * the no such algorithm exception
- * @throws InvalidKeyException
- * the invalid key exception
- * @throws IllegalBlockSizeException
- * the illegal block size exception
- * @throws BadPaddingException
- * the bad padding exception
- */
- public static String encryptTxt(byte[] plainTxt, String base64BinaryKey)
- throws NoSuchPaddingException, InvalidAlgorithmParameterException,
- NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
-
- byte[] keyValueByte = DatatypeConverter.parseBase64Binary(base64BinaryKey);
- Key paramKey = new SecretKeySpec(keyValueByte, "AES");
- Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
- cipher.init(Cipher.ENCRYPT_MODE, paramKey, mAlgParm);
-
- byte[] encryption = cipher.doFinal(plainTxt);
- return new String(Base64.getMimeEncoder().encode(encryption), StandardCharsets.UTF_8);
- }
-
- /**
- * Encrypt txt, no exceptions thrown
- *
- * @param plainTxt
- * the plain txt to be encrypted
- * @return the encrypted String , or the original text if it could not be
- * encrypted
- */
- public static String encryptTxtNoEx(byte[] plainTxt) {
-
- if (plainTxt == null || plainTxt.length == 0) {
- LOGGER.error("encryptTxtNoEx: Input param plainTxt is not valid");
- return "";
- }
-
- try {
- return encryptTxt(plainTxt);
- } catch (Exception e) {
- LOGGER.error("encryptTxtNoEx: Exception while decryption : " + e);
- return new String(plainTxt, StandardCharsets.UTF_8);
- }
- }
-
-} \ No newline at end of file
diff --git a/PolicyEngineUtils/src/main/java/org/onap/policy/utils/PeCryptoUtils.java b/PolicyEngineUtils/src/main/java/org/onap/policy/utils/PeCryptoUtils.java
new file mode 100644
index 000000000..9863f03ad
--- /dev/null
+++ b/PolicyEngineUtils/src/main/java/org/onap/policy/utils/PeCryptoUtils.java
@@ -0,0 +1,102 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP Policy Engine
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.utils;
+
+import java.security.GeneralSecurityException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.commons.lang3.StringUtils;
+import org.onap.policy.common.logging.flexlogger.FlexLogger;
+import org.onap.policy.common.logging.flexlogger.Logger;
+import org.onap.policy.common.utils.security.CryptoUtils;
+
+public class PeCryptoUtils {
+
+ private static Logger logger = FlexLogger.getLogger(PeCryptoUtils.class);
+ private static final String PROP_AES_KEY = "org.onap.policy.encryption.aes.key";
+ private static CryptoUtils cryptoUtils = null;
+ private static String secretKey = System.getenv("AES_ENCRYPTION_KEY");
+ private static final Map<String, String> decryptCache = new ConcurrentHashMap<>();
+ private static final Map<String, String> encryptCache = new ConcurrentHashMap<>();
+
+
+ private PeCryptoUtils() {}
+
+ /**
+ * Inits the aes key.
+ *
+ * @param theSecretKey the the secret key
+ */
+ public static synchronized void initAesKey(String theSecretKey) {
+ String secKey = theSecretKey;
+ if (cryptoUtils == null) {
+ if (StringUtils.isBlank(secKey)) {
+ secKey = System.getProperty(PROP_AES_KEY);
+ }
+ if (StringUtils.isBlank(secKey)) {
+ secKey = secretKey;
+ }
+ cryptoUtils = new CryptoUtils(secKey);
+ }
+ }
+
+ /**
+ * Encrypt a value based on the Policy Encryption Key.
+ *
+ * @param value The plain text string
+ * @return The encrypted String
+ */
+ public static String encrypt(String value) {
+
+ if (cryptoUtils == null || StringUtils.isBlank(value)) {
+ return value;
+ }
+
+ return encryptCache.computeIfAbsent(value, k -> {
+ try {
+ return cryptoUtils.encrypt(k);
+ } catch (GeneralSecurityException e) {
+ logger.error("Could not decrypt value - exception: ", e);
+ return value;
+ }
+ });
+ }
+
+ /**
+ * Decrypt a value based on the Policy Encryption Key if string begin with 'enc:'.
+ *
+ * @param value The encrypted string that must be decrypted using the Policy Encryption Key
+ * @return The String decrypted if string begin with 'enc:'
+ */
+ public static String decrypt(String value) {
+ if (cryptoUtils == null || StringUtils.isBlank(value)) {
+ return value;
+ }
+ return decryptCache.computeIfAbsent(value, k -> {
+ try {
+ return cryptoUtils.decrypt(k);
+ } catch (GeneralSecurityException e) {
+ logger.error("Could not decrypt value - exception: ", e);
+ return value;
+ }
+ });
+ }
+}
diff --git a/PolicyEngineUtils/src/test/java/org/onap/policy/utils/PeCryptoUtilsTest.java b/PolicyEngineUtils/src/test/java/org/onap/policy/utils/PeCryptoUtilsTest.java
new file mode 100644
index 000000000..3765ff2e3
--- /dev/null
+++ b/PolicyEngineUtils/src/test/java/org/onap/policy/utils/PeCryptoUtilsTest.java
@@ -0,0 +1,64 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP-REST
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import java.security.GeneralSecurityException;
+import org.junit.Before;
+import org.junit.Test;
+import org.powermock.reflect.Whitebox;
+
+
+public class PeCryptoUtilsTest {
+ private final String pass = "policy_user";
+ private final String secretKey = "bmpybWJrbGN4dG9wbGF3Zg==";
+ private final String encryptedPass = "enc:5ID9PoqWIzBaut+KQcAFBtci9CKDRcCNRHRjdBnXM5U=";
+ private static final String PROP_AES_KEY = "org.onap.policy.encryption.aes.key";
+
+ @Before
+ public void reset() {
+ Whitebox.setInternalState( PeCryptoUtils.class, "cryptoUtils", (PeCryptoUtils)null);
+
+ }
+
+ @Test
+ public void testEncrypt() throws GeneralSecurityException {
+ assertEquals(pass, PeCryptoUtils.encrypt(pass));
+ PeCryptoUtils.initAesKey(secretKey);
+ System.out.println("original value : " + pass + " encrypted value: " + PeCryptoUtils.encrypt(pass));
+ assertNotNull(PeCryptoUtils.encrypt(pass));
+ }
+
+ @Test
+ public void testDecrypt() throws Exception {
+ assertEquals(pass, PeCryptoUtils.decrypt(pass));
+ System.setProperty(PROP_AES_KEY, secretKey);
+ PeCryptoUtils.initAesKey(null);
+ System.clearProperty(PROP_AES_KEY);
+ assertEquals(pass, PeCryptoUtils.decrypt(encryptedPass));
+ Whitebox.setInternalState( PeCryptoUtils.class, "cryptoUtils", (PeCryptoUtils)null);
+ Whitebox.setInternalState( PeCryptoUtils.class, "secretKey", secretKey);
+ PeCryptoUtils.initAesKey(" ");
+ assertEquals(pass, PeCryptoUtils.decrypt(pass));
+ }
+
+}
diff --git a/PolicyEngineUtils/src/test/java/org/onap/policy/utils/test/CryptoUtilsTest.java b/PolicyEngineUtils/src/test/java/org/onap/policy/utils/test/CryptoUtilsTest.java
deleted file mode 100644
index e2ca78a06..000000000
--- a/PolicyEngineUtils/src/test/java/org/onap/policy/utils/test/CryptoUtilsTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * PolicyEngineUtils
- * ================================================================================
- * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-package org.onap.policy.utils.test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertArrayEquals;
-
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-
-import org.junit.Test;
-import org.onap.policy.utils.CryptoUtils;
-
-public class CryptoUtilsTest {
-
- @Test
- public final void testDecryptTxt() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
- InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
- {
- String decryptedTxt = new String(CryptoUtils.decryptTxt("g0uHKXCLyzJ6wSbpphNGsA=="), StandardCharsets.UTF_8);
- assertEquals("mypass", decryptedTxt);
- }
-
- @Test
- public final void testDecryptTxtWithKey() throws InvalidKeyException, NoSuchAlgorithmException,
- NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
- {
- String decryptedTxt = new String(CryptoUtils.decryptTxt("g0uHKXCLyzJ6wSbpphNGsA==", "bmpybWJrbGN4dG9wbGF3Zg=="),
- StandardCharsets.UTF_8);
- assertEquals("mypass", decryptedTxt);
- }
-
- @Test
- public final void testDecryptTxtNoEx() {
- String decryptedTxt = new String(CryptoUtils.decryptTxtNoEx("g0uHKXCLyzJ6wSbpphNGsA=="),
- StandardCharsets.UTF_8);
- assertEquals("mypass", decryptedTxt);
-
- }
-
- @Test
- public final void testDecryptTxtNoExStr() {
- assertEquals("mypass", CryptoUtils.decryptTxtNoExStr("g0uHKXCLyzJ6wSbpphNGsA=="));
- }
- @Test
- public final void testDecryptTxtNoExInvalidInput() {
- assertArrayEquals(new byte[0], CryptoUtils.decryptTxtNoEx(null));
- assertArrayEquals(new byte[0], CryptoUtils.decryptTxtNoEx(""));
- // ensure backward compatibility
- assertEquals("bogus", new String(CryptoUtils.decryptTxtNoEx("bogus"), StandardCharsets.UTF_8));
- assertEquals("admin123", CryptoUtils.decryptTxtNoExStr("admin123"));
- assertEquals("password", CryptoUtils.decryptTxtNoExStr("password"));
- }
-
- @Test(expected = IllegalArgumentException.class)
- public final void testDecryptTxtInvalidInput() throws InvalidKeyException, NoSuchAlgorithmException,
- NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
- {
- CryptoUtils.decryptTxt("bogus");
- }
-
- @Test
- public final void testEncryptTxt() throws InvalidKeyException, NoSuchPaddingException,
- InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException,
- BadPaddingException {
- String txtStr = "mypass";
- byte[] txt = txtStr.getBytes(StandardCharsets.UTF_8);
- assertEquals("g0uHKXCLyzJ6wSbpphNGsA==", CryptoUtils.encryptTxt(txt));
- }
-
- @Test
- public final void testEncryptTxtWithKey() throws InvalidKeyException,
- NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
- IllegalBlockSizeException, BadPaddingException {
- String txtStr = "mypass";
- byte[] txt = txtStr.getBytes(StandardCharsets.UTF_8);
- assertEquals("g0uHKXCLyzJ6wSbpphNGsA==", CryptoUtils.encryptTxt(txt, "bmpybWJrbGN4dG9wbGF3Zg=="));
- }
-
- @Test
- public final void testEncryptTxtNoEx() {
- String txtStr = "mypass";
- byte[] txt = txtStr.getBytes(StandardCharsets.UTF_8);
- assertEquals("g0uHKXCLyzJ6wSbpphNGsA==", CryptoUtils.encryptTxtNoEx(txt));
- }
-
- @Test
- public final void testEncryptTxtNoExInvalidInput() {
- String txtStr = "";
- byte[] txt = txtStr.getBytes(StandardCharsets.UTF_8);
- assertEquals("", CryptoUtils.encryptTxtNoEx(txt));
- assertEquals("", CryptoUtils.encryptTxtNoEx(null));
- }
-
- @Test(expected = InvalidKeyException.class)
- public final void testEncryptTxtWithKeyInvalid() throws InvalidKeyException,
- NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
- IllegalBlockSizeException, BadPaddingException {
- String txtStr = "mypass";
- byte[] txt = txtStr.getBytes(StandardCharsets.UTF_8);
- CryptoUtils.encryptTxt(txt, "mykey");
- }
-
-
-} \ No newline at end of file