This is an automated email from the ASF dual-hosted git repository.

jdyer pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new 88bde09e22e SOLR-15484 JWTAuthPluginIntegrationTest - dynamically 
generate the self-signed certificate using the localhost loopback address 
(#2099)
88bde09e22e is described below

commit 88bde09e22ef1f7e413f09fe03145220720a8a8e
Author: James Dyer <[email protected]>
AuthorDate: Mon Dec 11 08:02:59 2023 -0600

    SOLR-15484 JWTAuthPluginIntegrationTest - dynamically generate the 
self-signed certificate using the localhost loopback address (#2099)
    
    SOLR-15484: dynamically generate the self-signed certificate using the 
localhost loopback address
---
 .../randomization/policies/solr-tests.policy       |   4 +
 solr/modules/jwt-auth/build.gradle                 |   2 +
 .../apache/solr/security/jwt/JWTIssuerConfig.java  |  17 +--
 .../security/jwt/JWTAuthPluginIntegrationTest.java |  11 +-
 .../solr/security/jwt/KeystoreGenerator.java       | 116 +++++++++++++++++++++
 5 files changed, 143 insertions(+), 7 deletions(-)

diff --git a/gradle/testing/randomization/policies/solr-tests.policy 
b/gradle/testing/randomization/policies/solr-tests.policy
index 276532b519d..86871e72613 100644
--- a/gradle/testing/randomization/policies/solr-tests.policy
+++ b/gradle/testing/randomization/policies/solr-tests.policy
@@ -162,6 +162,10 @@ grant {
   permission javax.security.auth.AuthPermission "createLoginContext.Server";
   permission javax.security.auth.AuthPermission "createLoginContext.Client";
 
+  // Needed by BouncyCastle in jwt-auth tests
+  permission java.security.SecurityPermission "putProviderProperty.BC";
+  permission java.security.SecurityPermission 
"getProperty.org.bouncycastle.x509.allow_non-der_tbscert";
+
   // may only be necessary with Java 7?
   permission javax.security.auth.PrivateCredentialPermission 
"javax.security.auth.kerberos.KeyTab * \"*\"", "read";
   permission javax.security.auth.PrivateCredentialPermission 
"sun.security.jgss.krb5.Krb5Util$KeysFromKeyTab * \"*\"", "read";
diff --git a/solr/modules/jwt-auth/build.gradle 
b/solr/modules/jwt-auth/build.gradle
index 1b420899e33..17886099e18 100644
--- a/solr/modules/jwt-auth/build.gradle
+++ b/solr/modules/jwt-auth/build.gradle
@@ -61,6 +61,8 @@ dependencies {
   testImplementation 'com.fasterxml.jackson.core:jackson-databind'
   permitTestUnusedDeclared 'com.fasterxml.jackson.core:jackson-databind'
 
+  testImplementation 'org.bouncycastle:bcpkix-jdk15on'
+  testImplementation 'org.bouncycastle:bcprov-jdk15on'
   testImplementation 'com.nimbusds:nimbus-jose-jwt'
   testImplementation 'com.squareup.okhttp3:mockwebserver'
   testImplementation 'com.squareup.okhttp3:okhttp'
diff --git 
a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java
 
b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java
index 947d040da8b..b7e650c1401 100644
--- 
a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java
+++ 
b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java
@@ -22,6 +22,7 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.invoke.MethodHandles;
+import java.net.InetAddress;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.nio.charset.Charset;
@@ -423,6 +424,14 @@ public class JWTIssuerConfig {
     return jwkConfigured > 0;
   }
 
+  private static void disableHostVerificationIfLocalhost(URL url, Get httpGet) 
{
+    InetAddress loopbackAddress = InetAddress.getLoopbackAddress();
+    if (loopbackAddress.getCanonicalHostName().equals(url.getHost())
+        || loopbackAddress.getHostName().equals(url.getHost())) {
+      httpGet.setHostnameVerifier((hostname, session) -> true);
+    }
+  }
+
   public void setTrustedCerts(Collection<X509Certificate> trustedCerts) {
     this.trustedCerts = trustedCerts;
   }
@@ -472,9 +481,7 @@ public class JWTIssuerConfig {
       if (trustedCerts != null) {
         Get getWithCustomTrust = new Get();
         getWithCustomTrust.setTrustedCertificates(trustedCerts);
-        if ("localhost".equals(jwksUrl.getHost())) {
-          getWithCustomTrust.setHostnameVerifier((hostname, session) -> true);
-        }
+        disableHostVerificationIfLocalhost(jwksUrl, getWithCustomTrust);
         httpsJkws.setSimpleHttpGet(getWithCustomTrust);
       }
       return httpsJkws;
@@ -525,9 +532,7 @@ public class JWTIssuerConfig {
           Get httpGet = new Get();
           if (trustedCerts != null) {
             httpGet.setTrustedCertificates(trustedCerts);
-            if ("localhost".equals(url.getHost())) {
-              httpGet.setHostnameVerifier((hostname, session) -> true);
-            }
+            disableHostVerificationIfLocalhost(url, httpGet);
           }
           SimpleResponse resp = httpGet.get(url.toString());
           return parse(new 
ByteArrayInputStream(resp.getBody().getBytes(StandardCharsets.UTF_8)));
diff --git 
a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
 
b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
index 7a0700946c8..ef7fe35d4f5 100644
--- 
a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
+++ 
b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
@@ -25,6 +25,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
+import java.net.InetAddress;
 import java.net.URL;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
@@ -102,6 +103,14 @@ public class JWTAuthPluginIntegrationTest extends 
SolrCloudAuthTestCase {
     pemFilePath = 
JWT_TEST_PATH().resolve("security").resolve("jwt_plugin_idp_cert.pem");
     wrongPemFilePath = 
JWT_TEST_PATH().resolve("security").resolve("jwt_plugin_idp_wrongcert.pem");
 
+    Path tempDir = 
Files.createTempDirectory(JWTAuthPluginIntegrationTest.class.getSimpleName());
+    tempDir.toFile().deleteOnExit();
+    Path modifiedP12Cert = tempDir.resolve(p12Cert.getFileName());
+    new KeystoreGenerator()
+        .generateKeystore(
+            p12Cert, modifiedP12Cert, 
InetAddress.getLoopbackAddress().getCanonicalHostName());
+    p12Cert = modifiedP12Cert;
+
     mockOAuth2Server = createMockOAuthServer(p12Cert, "secret");
     mockOAuth2Server.start();
     mockOAuthToken =
@@ -112,7 +121,7 @@ public class JWTAuthPluginIntegrationTest extends 
SolrCloudAuthTestCase {
   }
 
   @AfterClass
-  public static void afterClass() throws Exception {
+  public static void afterClass() {
     if (mockOAuth2Server != null) {
       mockOAuth2Server.shutdown();
     }
diff --git 
a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/KeystoreGenerator.java
 
b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/KeystoreGenerator.java
new file mode 100644
index 00000000000..6d2d6c9c23a
--- /dev/null
+++ 
b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/KeystoreGenerator.java
@@ -0,0 +1,116 @@
+/*
+ * 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.solr.security.jwt;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Date;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+public class KeystoreGenerator {
+
+  private static final String PASS_PHRASE = "secret";
+
+  public void generateKeystore(Path existingKeystore, Path newKeystore, String 
cn) {
+    KeyStore ks = null;
+    try (FileInputStream fis = new FileInputStream(existingKeystore.toFile())) 
{
+      ks = KeyStore.getInstance(KeyStore.getDefaultType());
+      ks.load(fis, PASS_PHRASE.toCharArray());
+      ks.setCertificateEntry(cn, selfSignCertificate(cn));
+
+    } catch (KeyStoreException | CertificateException | 
NoSuchAlgorithmException | IOException e) {
+      throw new RuntimeException(e);
+    }
+    try (OutputStream fos = new FileOutputStream(newKeystore.toFile())) {
+      ks.store(fos, PASS_PHRASE.toCharArray());
+    } catch (CertificateException | KeyStoreException | IOException | 
NoSuchAlgorithmException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private X509Certificate selfSignCertificate(String commonName) {
+    try {
+      KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+      kpg.initialize(1024);
+      KeyPair kp = kpg.generateKeyPair();
+      Provider bcp = new BouncyCastleProvider();
+      Security.addProvider(bcp);
+
+      X500Name cn =
+          new X500Name(
+              new RDN[] {
+                new RDN(new AttributeTypeAndValue(BCStyle.CN, new 
DERUTF8String(commonName)))
+              });
+      X500Name issuer =
+          new X500Name(
+              new RDN[] {
+                new RDN(new AttributeTypeAndValue(BCStyle.CN, new 
DERUTF8String("Solr Root CA")))
+              });
+
+      Instant yesterday =
+          
LocalDate.now(ZoneOffset.UTC).minusDays(1).atStartOfDay().toInstant(ZoneOffset.UTC);
+      Instant oneYear = ZonedDateTime.ofInstant(yesterday, 
ZoneOffset.UTC).plusYears(1).toInstant();
+      BigInteger serial = new 
BigInteger(String.valueOf(yesterday.toEpochMilli()));
+
+      JcaX509v3CertificateBuilder certBuilder =
+          new JcaX509v3CertificateBuilder(
+              issuer, serial, Date.from(yesterday), Date.from(oneYear), cn, 
kp.getPublic());
+      BasicConstraints basicConstraints = new BasicConstraints(true);
+      certBuilder.addExtension(Extension.basicConstraints, true, 
basicConstraints);
+
+      ContentSigner cs = new 
JcaContentSignerBuilder("SHA256WithRSA").build(kp.getPrivate());
+      return new JcaX509CertificateConverter()
+          .setProvider(bcp)
+          .getCertificate(certBuilder.build(cs));
+    } catch (CertificateException
+        | CertIOException
+        | NoSuchAlgorithmException
+        | OperatorCreationException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}

Reply via email to