From b4d63fb1e7aa2cac82990a0e2c3c45e216f9b857 Mon Sep 17 00:00:00 2001 From: exceptionfactory Date: Sat, 5 Apr 2025 11:39:34 -0500 Subject: [PATCH 1/4] Removed Bouncy Castle dependency usage from PemUtils - Added PEM format parsing in PemUtils - Added unit test for PemUtils - Removed Bouncy Castle Provider dependency from service common module - Removed Bouncy Castle Provider dependency from quarkus service module - Removed Bouncy Castle references from LICENSE and NOTICE files --- gradle/libs.versions.toml | 1 - quarkus/admin/distribution/NOTICE | 9 -- quarkus/server/distribution/LICENSE | 6 - quarkus/server/distribution/NOTICE | 9 -- quarkus/service/build.gradle.kts | 2 - service/common/build.gradle.kts | 2 - .../apache/polaris/service/auth/PemUtils.java | 38 ++++- .../polaris/service/auth/PemUtilsTest.java | 130 ++++++++++++++++++ 8 files changed, 163 insertions(+), 34 deletions(-) create mode 100644 service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 88c23180d..be8081718 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,7 +41,6 @@ assertj-core = { module = "org.assertj:assertj-core", version = "3.27.3" } auth0-jwt = { module = "com.auth0:java-jwt", version = "4.5.0" } awssdk-bom = { module = "software.amazon.awssdk:bom", version = "2.31.11" } azuresdk-bom = { module = "com.azure:azure-sdk-bom", version = "1.2.33" } -bouncycastle-bcprov = { module = "org.bouncycastle:bcprov-jdk18on", version = "1.80" } caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version = "3.2.0" } commons-codec1 = { module = "commons-codec:commons-codec", version = "1.18.0" } commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.17.0" } diff --git a/quarkus/admin/distribution/NOTICE b/quarkus/admin/distribution/NOTICE index 2d86302fe..e2abe465d 100644 --- a/quarkus/admin/distribution/NOTICE +++ b/quarkus/admin/distribution/NOTICE @@ -682,15 +682,6 @@ NOTICE: | * HOMEPAGE: | * https://github.com/google/protobuf | -| This product optionally depends on 'Bouncy Castle Crypto APIs' to generate -| a temporary self-signed X.509 certificate when the JVM does not provide the -| equivalent functionality. It can be obtained at: -| -| * LICENSE: -| * license/LICENSE.bouncycastle.txt (MIT License) -| * HOMEPAGE: -| * https://www.bouncycastle.org/ -| | This product optionally depends on 'Snappy', a compression library produced | by Google Inc, which can be obtained at: | diff --git a/quarkus/server/distribution/LICENSE b/quarkus/server/distribution/LICENSE index e7fb1ab2e..82030a51c 100644 --- a/quarkus/server/distribution/LICENSE +++ b/quarkus/server/distribution/LICENSE @@ -2050,12 +2050,6 @@ License (from POM): Apache License 2.0 - https://www.apache.org/licenses/LICENSE -------------------------------------------------------------------------------- -Group: org.bouncycastle Name: bcprov-jdk18on Version: 1.80 -Project URL (from POM): https://www.bouncycastle.org/download/bouncy-castle-java/ -License (from POM): Bouncy Castle Licence - https://www.bouncycastle.org/licence.html - --------------------------------------------------------------------------------- - Group: org.checkerframework Name: checker-qual Version: 3.49.0 Project URL (from POM): https://checkerframework.org/ License (from POM): MIT License - http://opensource.org/licenses/MIT diff --git a/quarkus/server/distribution/NOTICE b/quarkus/server/distribution/NOTICE index f94d64499..e0b06d508 100644 --- a/quarkus/server/distribution/NOTICE +++ b/quarkus/server/distribution/NOTICE @@ -370,15 +370,6 @@ NOTICE: | * HOMEPAGE: | * https://github.com/google/protobuf | -| This product optionally depends on 'Bouncy Castle Crypto APIs' to generate -| a temporary self-signed X.509 certificate when the JVM does not provide the -| equivalent functionality. It can be obtained at: -| -| * LICENSE: -| * license/LICENSE.bouncycastle.txt (MIT License) -| * HOMEPAGE: -| * https://www.bouncycastle.org/ -| | This product optionally depends on 'Snappy', a compression library produced | by Google Inc, which can be obtained at: | diff --git a/quarkus/service/build.gradle.kts b/quarkus/service/build.gradle.kts index 025a8156f..d0f277278 100644 --- a/quarkus/service/build.gradle.kts +++ b/quarkus/service/build.gradle.kts @@ -71,8 +71,6 @@ dependencies { implementation(libs.auth0.jwt) - implementation(libs.bouncycastle.bcprov) - compileOnly(libs.jakarta.annotation.api) compileOnly(libs.spotbugs.annotations) diff --git a/service/common/build.gradle.kts b/service/common/build.gradle.kts index ca00893e0..6c8f477a3 100644 --- a/service/common/build.gradle.kts +++ b/service/common/build.gradle.kts @@ -75,8 +75,6 @@ dependencies { implementation(libs.auth0.jwt) - implementation(libs.bouncycastle.bcprov) - implementation(platform(libs.google.cloud.storage.bom)) implementation("com.google.cloud:google-cloud-storage") diff --git a/service/common/src/main/java/org/apache/polaris/service/auth/PemUtils.java b/service/common/src/main/java/org/apache/polaris/service/auth/PemUtils.java index fd4a0be62..6e3786fa4 100644 --- a/service/common/src/main/java/org/apache/polaris/service/auth/PemUtils.java +++ b/service/common/src/main/java/org/apache/polaris/service/auth/PemUtils.java @@ -20,6 +20,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; +import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.IOException; @@ -36,8 +37,6 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; -import org.bouncycastle.util.io.pem.PemObject; -import org.bouncycastle.util.io.pem.PemReader; public class PemUtils { @@ -46,9 +45,38 @@ private static byte[] parsePEMFile(Path pemPath) throws IOException { throw new FileNotFoundException( String.format("The file '%s' doesn't exist.", pemPath.toAbsolutePath())); } - try (PemReader reader = new PemReader(Files.newBufferedReader(pemPath, UTF_8))) { - PemObject pemObject = reader.readPemObject(); - return pemObject.getContent(); + try (BufferedReader reader = Files.newBufferedReader(pemPath, UTF_8)) { + final StringBuilder encodedBuilder = new StringBuilder(); + + boolean headerFound = false; + boolean footerFound = false; + + String line = reader.readLine(); + while (line != null) { + if (line.startsWith("-----BEGIN")) { + headerFound = true; + } else if (line.startsWith("-----END")) { + footerFound = true; + } else if (!line.isBlank()) { + encodedBuilder.append(line); + } + + line = reader.readLine(); + } + + final byte[] parsed; + if (headerFound) { + if (footerFound) { + final String encoded = encodedBuilder.toString(); + parsed = Base64.getMimeDecoder().decode(encoded); + } else { + throw new IOException("PEM Footer not found"); + } + } else { + throw new IOException("PEM Header not found"); + } + + return parsed; } } diff --git a/service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java b/service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java new file mode 100644 index 000000000..49b6fc0a3 --- /dev/null +++ b/service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.polaris.service.auth; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Base64; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class PemUtilsTest { + private static final String RSA_ALGORITHM = "RSA"; + + private static final String PUBLIC_KEY_HEADER = "-----BEGIN PUBLIC KEY-----"; + + private static final String PUBLIC_KEY_FOOTER = "-----END PUBLIC KEY-----"; + + private static final String PRIVATE_KEY_HEADER = "-----BEGIN PRIVATE KEY-----"; + + private static final String PRIVATE_KEY_FOOTER = "-----END PRIVATE KEY-----"; + + private static final String LINE_SEPARATOR = System.lineSeparator(); + + private static final String RSA_PUBLIC_KEY_FILE = "rsa-public-key.pem"; + + private static final String RSA_PRIVATE_KEY_FILE = "rsa-private-key.pem"; + + private static final Base64.Encoder encoder = Base64.getMimeEncoder(); + + @TempDir private static Path tempDir; + + private static Path rsaRublicKeyPath; + + private static PublicKey rsaPublicKey; + + private static Path rsaPrivateKeyPath; + + private static PrivateKey rsaPrivateKey; + + @BeforeAll + public static void setKeyPair() throws NoSuchAlgorithmException, IOException { + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM); + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); + rsaPublicKey = keyPair.getPublic(); + rsaPrivateKey = keyPair.getPrivate(); + + final String publicKeyEncoded = getPublicKeyEncoded(rsaPublicKey); + final String privateKeyEncoded = getPrivateKeyEncoded(rsaPrivateKey); + + rsaRublicKeyPath = tempDir.resolve(RSA_PUBLIC_KEY_FILE); + Files.writeString(rsaRublicKeyPath, publicKeyEncoded); + + rsaPrivateKeyPath = tempDir.resolve(RSA_PRIVATE_KEY_FILE); + Files.writeString(rsaPrivateKeyPath, privateKeyEncoded); + } + + @Test + public void testReadPublicKeyFromFileRSA() throws IOException { + final PublicKey publicKeyRead = PemUtils.readPublicKeyFromFile(rsaRublicKeyPath, RSA_ALGORITHM); + + assertEquals(rsaPublicKey, publicKeyRead); + } + + @Test + public void testReadPrivateKeyFromFileRSA() throws IOException { + final PrivateKey privateKeyRead = + PemUtils.readPrivateKeyFromFile(rsaPrivateKeyPath, RSA_ALGORITHM); + + assertEquals(rsaPrivateKey, privateKeyRead); + } + + private static String getPublicKeyEncoded(final PublicKey publicKey) { + final StringBuilder builder = new StringBuilder(); + + builder.append(PUBLIC_KEY_HEADER); + builder.append(LINE_SEPARATOR); + + final byte[] publicKeyEncoded = publicKey.getEncoded(); + final String encoded = encoder.encodeToString(publicKeyEncoded); + builder.append(encoded); + builder.append(LINE_SEPARATOR); + + builder.append(PUBLIC_KEY_FOOTER); + builder.append(LINE_SEPARATOR); + + return builder.toString(); + } + + private static String getPrivateKeyEncoded(final PrivateKey privateKey) { + final StringBuilder builder = new StringBuilder(); + + builder.append(PRIVATE_KEY_HEADER); + builder.append(LINE_SEPARATOR); + + final byte[] privateKeyEncoded = privateKey.getEncoded(); + final String encoded = encoder.encodeToString(privateKeyEncoded); + builder.append(encoded); + builder.append(LINE_SEPARATOR); + + builder.append(PRIVATE_KEY_FOOTER); + builder.append(LINE_SEPARATOR); + + return builder.toString(); + } +} From d0923374bf736a71c2f82b7d8ea3c67354c5c058 Mon Sep 17 00:00:00 2001 From: exceptionfactory Date: Mon, 7 Apr 2025 07:44:47 -0500 Subject: [PATCH 2/4] Reverted LICENSE and NOTICE changes to retain Bouncy Castle --- quarkus/admin/distribution/NOTICE | 9 +++++++++ quarkus/server/distribution/LICENSE | 6 ++++++ quarkus/server/distribution/NOTICE | 9 +++++++++ 3 files changed, 24 insertions(+) diff --git a/quarkus/admin/distribution/NOTICE b/quarkus/admin/distribution/NOTICE index e2abe465d..2d86302fe 100644 --- a/quarkus/admin/distribution/NOTICE +++ b/quarkus/admin/distribution/NOTICE @@ -682,6 +682,15 @@ NOTICE: | * HOMEPAGE: | * https://github.com/google/protobuf | +| This product optionally depends on 'Bouncy Castle Crypto APIs' to generate +| a temporary self-signed X.509 certificate when the JVM does not provide the +| equivalent functionality. It can be obtained at: +| +| * LICENSE: +| * license/LICENSE.bouncycastle.txt (MIT License) +| * HOMEPAGE: +| * https://www.bouncycastle.org/ +| | This product optionally depends on 'Snappy', a compression library produced | by Google Inc, which can be obtained at: | diff --git a/quarkus/server/distribution/LICENSE b/quarkus/server/distribution/LICENSE index 82030a51c..e7fb1ab2e 100644 --- a/quarkus/server/distribution/LICENSE +++ b/quarkus/server/distribution/LICENSE @@ -2050,6 +2050,12 @@ License (from POM): Apache License 2.0 - https://www.apache.org/licenses/LICENSE -------------------------------------------------------------------------------- +Group: org.bouncycastle Name: bcprov-jdk18on Version: 1.80 +Project URL (from POM): https://www.bouncycastle.org/download/bouncy-castle-java/ +License (from POM): Bouncy Castle Licence - https://www.bouncycastle.org/licence.html + +-------------------------------------------------------------------------------- + Group: org.checkerframework Name: checker-qual Version: 3.49.0 Project URL (from POM): https://checkerframework.org/ License (from POM): MIT License - http://opensource.org/licenses/MIT diff --git a/quarkus/server/distribution/NOTICE b/quarkus/server/distribution/NOTICE index e0b06d508..f94d64499 100644 --- a/quarkus/server/distribution/NOTICE +++ b/quarkus/server/distribution/NOTICE @@ -370,6 +370,15 @@ NOTICE: | * HOMEPAGE: | * https://github.com/google/protobuf | +| This product optionally depends on 'Bouncy Castle Crypto APIs' to generate +| a temporary self-signed X.509 certificate when the JVM does not provide the +| equivalent functionality. It can be obtained at: +| +| * LICENSE: +| * license/LICENSE.bouncycastle.txt (MIT License) +| * HOMEPAGE: +| * https://www.bouncycastle.org/ +| | This product optionally depends on 'Snappy', a compression library produced | by Google Inc, which can be obtained at: | From c1aa0bba107ef2161bec79e62373031a986d0352 Mon Sep 17 00:00:00 2001 From: exceptionfactory Date: Mon, 7 Apr 2025 07:52:58 -0500 Subject: [PATCH 3/4] Added tests for empty file and multiple PEM objects --- .../apache/polaris/service/auth/PemUtils.java | 2 ++ .../polaris/service/auth/PemUtilsTest.java | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/service/common/src/main/java/org/apache/polaris/service/auth/PemUtils.java b/service/common/src/main/java/org/apache/polaris/service/auth/PemUtils.java index 6e3786fa4..375599013 100644 --- a/service/common/src/main/java/org/apache/polaris/service/auth/PemUtils.java +++ b/service/common/src/main/java/org/apache/polaris/service/auth/PemUtils.java @@ -57,6 +57,8 @@ private static byte[] parsePEMFile(Path pemPath) throws IOException { headerFound = true; } else if (line.startsWith("-----END")) { footerFound = true; + // Stop reading after finding footer + break; } else if (!line.isBlank()) { encodedBuilder.append(line); } diff --git a/service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java b/service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java index 49b6fc0a3..c84114bc8 100644 --- a/service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java +++ b/service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java @@ -19,6 +19,7 @@ package org.apache.polaris.service.auth; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; import java.nio.file.Files; @@ -50,6 +51,10 @@ public class PemUtilsTest { private static final String RSA_PRIVATE_KEY_FILE = "rsa-private-key.pem"; + private static final String RSA_PUBLIC_KEY_AND_PRIVATE_KEY_FILE = "rsa-public-key-and-private-key.pem"; + + private static final String EMPTY_FILE = "empty.pem"; + private static final Base64.Encoder encoder = Base64.getMimeEncoder(); @TempDir private static Path tempDir; @@ -62,6 +67,10 @@ public class PemUtilsTest { private static PrivateKey rsaPrivateKey; + private static Path rsaPublicKeyAndPrivateKeyPath; + + private static Path emptyFilePath; + @BeforeAll public static void setKeyPair() throws NoSuchAlgorithmException, IOException { final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM); @@ -77,6 +86,13 @@ public static void setKeyPair() throws NoSuchAlgorithmException, IOException { rsaPrivateKeyPath = tempDir.resolve(RSA_PRIVATE_KEY_FILE); Files.writeString(rsaPrivateKeyPath, privateKeyEncoded); + + rsaPublicKeyAndPrivateKeyPath = tempDir.resolve(RSA_PUBLIC_KEY_AND_PRIVATE_KEY_FILE); + final String rsaPublicKeyAndPrivateKey = publicKeyEncoded + LINE_SEPARATOR + privateKeyEncoded; + Files.writeString(rsaPublicKeyAndPrivateKeyPath, rsaPublicKeyAndPrivateKey); + + emptyFilePath = tempDir.resolve(EMPTY_FILE); + Files.write(emptyFilePath, new byte[0]); } @Test @@ -94,6 +110,18 @@ public void testReadPrivateKeyFromFileRSA() throws IOException { assertEquals(rsaPrivateKey, privateKeyRead); } + @Test + public void testReadPublicKeyFromFileRSAWithPrivateKeyIgnored() throws IOException { + final PublicKey publicKeyRead = PemUtils.readPublicKeyFromFile(rsaPublicKeyAndPrivateKeyPath, RSA_ALGORITHM); + + assertEquals(rsaPublicKey, publicKeyRead); + } + + @Test + public void testReadEmptyFIle() { + assertThrows(IOException.class, () -> PemUtils.readPublicKeyFromFile(emptyFilePath, RSA_ALGORITHM)); + } + private static String getPublicKeyEncoded(final PublicKey publicKey) { final StringBuilder builder = new StringBuilder(); From cf06a5e0af8231ff34ba4e0b2f6fd4dad1c23d39 Mon Sep 17 00:00:00 2001 From: exceptionfactory Date: Mon, 7 Apr 2025 08:44:36 -0500 Subject: [PATCH 4/4] Applied spotless formatting --- .../org/apache/polaris/service/auth/PemUtilsTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java b/service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java index c84114bc8..36e62ceae 100644 --- a/service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java +++ b/service/common/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java @@ -51,7 +51,7 @@ public class PemUtilsTest { private static final String RSA_PRIVATE_KEY_FILE = "rsa-private-key.pem"; - private static final String RSA_PUBLIC_KEY_AND_PRIVATE_KEY_FILE = "rsa-public-key-and-private-key.pem"; + private static final String RSA_PUBLIC_KEY_AND_PRIVATE_KEY_FILE = "rsa-public-key-pair.pem"; private static final String EMPTY_FILE = "empty.pem"; @@ -112,14 +112,16 @@ public void testReadPrivateKeyFromFileRSA() throws IOException { @Test public void testReadPublicKeyFromFileRSAWithPrivateKeyIgnored() throws IOException { - final PublicKey publicKeyRead = PemUtils.readPublicKeyFromFile(rsaPublicKeyAndPrivateKeyPath, RSA_ALGORITHM); + final PublicKey publicKeyRead = + PemUtils.readPublicKeyFromFile(rsaPublicKeyAndPrivateKeyPath, RSA_ALGORITHM); assertEquals(rsaPublicKey, publicKeyRead); } @Test public void testReadEmptyFIle() { - assertThrows(IOException.class, () -> PemUtils.readPublicKeyFromFile(emptyFilePath, RSA_ALGORITHM)); + assertThrows( + IOException.class, () -> PemUtils.readPublicKeyFromFile(emptyFilePath, RSA_ALGORITHM)); } private static String getPublicKeyEncoded(final PublicKey publicKey) {