aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-plugin-api-impl/src/main
diff options
context:
space:
mode:
authorZipeng WU <zipeng.wu@sonarsource.com>2021-02-11 18:25:14 +0100
committersonartech <sonartech@sonarsource.com>2021-02-17 20:07:15 +0000
commit122edd4683e3019c8035c40c53c8813e855372f0 (patch)
treef69986d0d45f2ef08ffff760b223fff28b63f1dc /sonar-plugin-api-impl/src/main
parentd90fced6c38073a22b76ef7b3c6b834ca21c7418 (diff)
downloadsonarqube-122edd4683e3019c8035c40c53c8813e855372f0.tar.gz
sonarqube-122edd4683e3019c8035c40c53c8813e855372f0.zip
SONAR-14426 Add support for AES-GCM encryption
Diffstat (limited to 'sonar-plugin-api-impl/src/main')
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesCipher.java37
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesECBCipher.java67
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesGCMCipher.java79
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/Encryption.java21
4 files changed, 164 insertions, 40 deletions
diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesCipher.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesCipher.java
index 3df06736202..57541227658 100644
--- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesCipher.java
+++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesCipher.java
@@ -21,7 +21,6 @@ package org.sonar.api.config.internal;
import java.io.File;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.SecureRandom;
import javax.annotation.Nullable;
@@ -34,8 +33,9 @@ import org.apache.commons.lang.StringUtils;
import org.sonar.api.CoreProperties;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.sonar.api.CoreProperties.ENCRYPTION_SECRET_KEY_PATH;
-final class AesCipher implements Cipher {
+abstract class AesCipher implements Cipher {
static final int KEY_SIZE_IN_BITS = 256;
private static final String CRYPTO_KEY = "AES";
@@ -46,33 +46,6 @@ final class AesCipher implements Cipher {
this.pathToSecretKey = pathToSecretKey;
}
- @Override
- public String encrypt(String clearText) {
- try {
- javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_KEY);
- cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, loadSecretFile());
- return Base64.encodeBase64String(cipher.doFinal(clearText.getBytes(StandardCharsets.UTF_8.name())));
- } catch (RuntimeException e) {
- throw e;
- } catch (Exception e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public String decrypt(String encryptedText) {
- try {
- javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_KEY);
- cipher.init(javax.crypto.Cipher.DECRYPT_MODE, loadSecretFile());
- byte[] cipherData = cipher.doFinal(Base64.decodeBase64(StringUtils.trim(encryptedText)));
- return new String(cipherData, StandardCharsets.UTF_8);
- } catch (RuntimeException e) {
- throw e;
- } catch (Exception e) {
- throw new IllegalStateException(e);
- }
- }
-
/**
* This method checks the existence of the file, but not the validity of the contained key.
*/
@@ -85,18 +58,18 @@ final class AesCipher implements Cipher {
return false;
}
- private Key loadSecretFile() throws IOException {
+ protected Key loadSecretFile() throws IOException {
String path = getPathToSecretKey();
return loadSecretFileFromFile(path);
}
Key loadSecretFileFromFile(@Nullable String path) throws IOException {
if (StringUtils.isBlank(path)) {
- throw new IllegalStateException("Secret key not found. Please set the property " + CoreProperties.ENCRYPTION_SECRET_KEY_PATH);
+ throw new IllegalStateException("Secret key not found. Please set the property " + ENCRYPTION_SECRET_KEY_PATH);
}
File file = new File(path);
if (!file.exists() || !file.isFile()) {
- throw new IllegalStateException("The property " + CoreProperties.ENCRYPTION_SECRET_KEY_PATH + " does not link to a valid file: " + path);
+ throw new IllegalStateException("The property " + ENCRYPTION_SECRET_KEY_PATH + " does not link to a valid file: " + path);
}
String s = FileUtils.readFileToString(file, UTF_8);
if (StringUtils.isBlank(s)) {
diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesECBCipher.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesECBCipher.java
new file mode 100644
index 00000000000..ff8aada450e
--- /dev/null
+++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesECBCipher.java
@@ -0,0 +1,67 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.api.config.internal;
+
+import java.nio.charset.StandardCharsets;
+import javax.annotation.Nullable;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @deprecated since 8.7.0
+ */
+@Deprecated
+final class AesECBCipher extends AesCipher {
+
+ private static final String CRYPTO_ALGO = "AES";
+
+ AesECBCipher(@Nullable String pathToSecretKey) {
+ super(pathToSecretKey);
+ }
+
+ @Override
+ public String encrypt(String clearText) {
+ try {
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_ALGO);
+ cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, loadSecretFile());
+ byte[] cipherData = cipher.doFinal(clearText.getBytes(StandardCharsets.UTF_8.name()));
+ return Base64.encodeBase64String(cipherData);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public String decrypt(String encryptedText) {
+ try {
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_ALGO);
+ cipher.init(javax.crypto.Cipher.DECRYPT_MODE, loadSecretFile());
+ byte[] cipherData = cipher.doFinal(Base64.decodeBase64(StringUtils.trim(encryptedText)));
+ return new String(cipherData, StandardCharsets.UTF_8);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+}
diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesGCMCipher.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesGCMCipher.java
new file mode 100644
index 00000000000..2f8f86d8d3f
--- /dev/null
+++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/AesGCMCipher.java
@@ -0,0 +1,79 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.api.config.internal;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
+import javax.annotation.Nullable;
+import javax.crypto.spec.GCMParameterSpec;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.StringUtils;
+
+final class AesGCMCipher extends AesCipher {
+ private static final int GCM_TAG_LENGTH_IN_BITS = 128;
+ private static final int GCM_IV_LENGTH_IN_BYTES = 12;
+
+ private static final String CRYPTO_ALGO = "AES/GCM/NoPadding";
+
+ AesGCMCipher(@Nullable String pathToSecretKey) {
+ super(pathToSecretKey);
+ }
+
+ @Override
+ public String encrypt(String clearText) {
+ try {
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_ALGO);
+ byte[] iv = new byte[GCM_IV_LENGTH_IN_BYTES];
+ new SecureRandom().nextBytes(iv);
+ cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, loadSecretFile(), new GCMParameterSpec(GCM_TAG_LENGTH_IN_BITS, iv));
+ byte[] encryptedText = cipher.doFinal(clearText.getBytes(StandardCharsets.UTF_8.name()));
+ return Base64.encodeBase64String(
+ ByteBuffer.allocate(GCM_IV_LENGTH_IN_BYTES + encryptedText.length)
+ .put(iv)
+ .put(encryptedText)
+ .array());
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public String decrypt(String encryptedText) {
+ try {
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_ALGO);
+ ByteBuffer byteBuffer = ByteBuffer.wrap(Base64.decodeBase64(StringUtils.trim(encryptedText)));
+ byte[] iv = new byte[GCM_IV_LENGTH_IN_BYTES];
+ byteBuffer.get(iv);
+ byte[] cipherText = new byte[byteBuffer.remaining()];
+ byteBuffer.get(cipherText);
+ cipher.init(javax.crypto.Cipher.DECRYPT_MODE, loadSecretFile(), new GCMParameterSpec(GCM_TAG_LENGTH_IN_BITS, iv));
+ byte[] cipherData = cipher.doFinal(cipherText);
+ return new String(cipherData, StandardCharsets.UTF_8);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/Encryption.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/Encryption.java
index c5542301678..23c38aa88aa 100644
--- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/Encryption.java
+++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/config/internal/Encryption.java
@@ -32,29 +32,34 @@ import javax.annotation.Nullable;
public final class Encryption {
private static final String BASE64_ALGORITHM = "b64";
+ private static final String AES_ECB_ALGORITHM = "aes";
+ private static final String AES_GCM_ALGORITHM = "aes-gcm";
- private static final String AES_ALGORITHM = "aes";
- private final AesCipher aesCipher;
+ private final AesECBCipher aesECBCipher;
+ private final AesGCMCipher aesGCMCipher;
private final Map<String, Cipher> ciphers;
private static final Pattern ENCRYPTED_PATTERN = Pattern.compile("\\{(.*?)\\}(.*)");
public Encryption(@Nullable String pathToSecretKey) {
- aesCipher = new AesCipher(pathToSecretKey);
+ aesECBCipher = new AesECBCipher(pathToSecretKey);
+ aesGCMCipher = new AesGCMCipher(pathToSecretKey);
ciphers = new HashMap<>();
ciphers.put(BASE64_ALGORITHM, new Base64Cipher());
- ciphers.put(AES_ALGORITHM, aesCipher);
+ ciphers.put(AES_ECB_ALGORITHM, aesECBCipher);
+ ciphers.put(AES_GCM_ALGORITHM, aesGCMCipher);
}
public void setPathToSecretKey(@Nullable String pathToSecretKey) {
- aesCipher.setPathToSecretKey(pathToSecretKey);
+ aesECBCipher.setPathToSecretKey(pathToSecretKey);
+ aesGCMCipher.setPathToSecretKey(pathToSecretKey);
}
/**
* Checks the availability of the secret key, that is required to encrypt and decrypt.
*/
public boolean hasSecretKey() {
- return aesCipher.hasSecretKey();
+ return aesGCMCipher.hasSecretKey();
}
public boolean isEncrypted(String value) {
@@ -62,7 +67,7 @@ public final class Encryption {
}
public String encrypt(String clearText) {
- return encrypt(AES_ALGORITHM, clearText);
+ return encrypt(AES_GCM_ALGORITHM, clearText);
}
public String scramble(String clearText) {
@@ -70,7 +75,7 @@ public final class Encryption {
}
public String generateRandomSecretKey() {
- return aesCipher.generateRandomSecretKey();
+ return aesGCMCipher.generateRandomSecretKey();
}
public String decrypt(String encryptedText) {