diff options
author | Zipeng WU <zipeng.wu@sonarsource.com> | 2021-02-11 18:25:14 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-02-17 20:07:15 +0000 |
commit | 122edd4683e3019c8035c40c53c8813e855372f0 (patch) | |
tree | f69986d0d45f2ef08ffff760b223fff28b63f1dc /sonar-plugin-api-impl/src/main | |
parent | d90fced6c38073a22b76ef7b3c6b834ca21c7418 (diff) | |
download | sonarqube-122edd4683e3019c8035c40c53c8813e855372f0.tar.gz sonarqube-122edd4683e3019c8035c40c53c8813e855372f0.zip |
SONAR-14426 Add support for AES-GCM encryption
Diffstat (limited to 'sonar-plugin-api-impl/src/main')
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) { |