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

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


The following commit(s) were added to refs/heads/branch_9_0 by this push:
     new 6402d9f  SOLR-15961: Only consider the number in the SolrAuth header a 
timestamp if it's between a valid range of digits (#575)
6402d9f is described below

commit 6402d9fe5ff99856863deda2374d275fa566158b
Author: Tomas Fernandez Lobbe <[email protected]>
AuthorDate: Fri Jan 28 15:48:21 2022 -0800

    SOLR-15961: Only consider the number in the SolrAuth header a timestamp if 
it's between a valid range of digits (#575)
---
 solr/CHANGES.txt                                   |  3 +
 .../solr/security/PKIAuthenticationPlugin.java     | 16 ++++-
 .../solr/security/TestPKIAuthenticationPlugin.java | 76 ++++++++++++++++++++++
 3 files changed, 93 insertions(+), 2 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index c23e073..0b5a1a4 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -536,6 +536,9 @@ Bug Fixes
 
 * SOLR-14595: Consistent overrequest across different facet methods for 
`sort:index` JSON Facet field (Michael Gibney, hossman)
 
+* SOLR-15961: Fix bug in PKIAuthenticationPlugin that can cause a request to 
fail with 401 Unauthorized instead
+ of re-fetching expired remote keys from other nodes. (Tomás Fernández Löbbe)
+
 ==================  8.11.2 ==================
 
 Bug Fixes
diff --git 
a/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java 
b/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java
index 5ec1d55..8a1e1e0 100644
--- a/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java
@@ -30,6 +30,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 
+import com.google.common.annotations.VisibleForTesting;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpException;
 import org.apache.http.HttpHeaders;
@@ -75,6 +76,15 @@ public class PKIAuthenticationPlugin extends 
AuthenticationPlugin implements Htt
   }
 
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  /**
+   * If a number has less than this number of digits, it'll not be considered 
a timestamp.
+   */
+  private static final int MIN_TIMESTAMP_DIGITS = 10; // a timestamp of 
9999999999 is year 1970
+  /**
+   * If a number has more than this number of digits, it'll not be considered 
a timestamp.
+   */
+  private static final int MAX_TIMESTAMP_DIGITS = 13; // a timestamp of 
9999999999999 is year 2286
   private final Map<String, PublicKey> keyCache = new ConcurrentHashMap<>();
   private final PublicKeyHandler publicKeyHandler;
   private final CoreContainer cores;
@@ -177,7 +187,8 @@ public class PKIAuthenticationPlugin extends 
AuthenticationPlugin implements Htt
     }
   }
 
-  private static PKIHeaderData parseCipher(String cipher, PublicKey key) {
+  @VisibleForTesting
+  static PKIHeaderData parseCipher(String cipher, PublicKey key) {
     byte[] bytes;
     try {
       bytes = CryptoKeys.decryptRSA(Base64.getDecoder().decode(cipher), key);
@@ -187,7 +198,8 @@ public class PKIAuthenticationPlugin extends 
AuthenticationPlugin implements Htt
     }
     String s = new String(bytes, UTF_8).trim();
     int splitPoint = s.lastIndexOf(' ');
-    if (splitPoint == -1) {
+    int timestampDigits = s.length() - 1 - splitPoint;
+    if (splitPoint == -1 || timestampDigits < MIN_TIMESTAMP_DIGITS || 
timestampDigits > MAX_TIMESTAMP_DIGITS) {
       log.warn("Invalid cipher {} deciphered data {}", cipher, s);
       return null;
     }
diff --git 
a/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java 
b/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java
index 75a97e9..a742e54 100644
--- 
a/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java
+++ 
b/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java
@@ -19,8 +19,11 @@ package org.apache.solr.security;
 import javax.servlet.FilterChain;
 import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServletRequest;
+import java.nio.ByteBuffer;
 import java.security.Principal;
 import java.security.PublicKey;
+import java.time.Instant;
+import java.util.Base64;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.http.Header;
@@ -34,6 +37,7 @@ import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.util.CryptoKeys;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -73,6 +77,8 @@ public class TestPKIAuthenticationPlugin extends 
SolrTestCaseJ4 {
   final FilterChain filterChain = (servletRequest, servletResponse) -> 
wrappedRequestByFilter.set(servletRequest);
   final String nodeName = "node_x_233";
 
+  final CryptoKeys.RSAKeyPair aKeyPair = new CryptoKeys.RSAKeyPair();
+
   final LocalSolrQueryRequest localSolrQueryRequest = new 
LocalSolrQueryRequest(null, new ModifiableSolrParams()) {
     @Override
     public Principal getUserPrincipal() {
@@ -155,6 +161,76 @@ public class TestPKIAuthenticationPlugin extends 
SolrTestCaseJ4 {
     mock1.close();
   }
 
+  public void testParseCipher() {
+    for (String validUser: new String[]{"user1", "$", "some user","some 123"}) 
{
+      for (long validTimestamp: new long[]{Instant.now().toEpochMilli(), 
99999999999L, 9999999999999L}) {
+        String s = validUser + " " + validTimestamp;
+        byte[] payload = s.getBytes(UTF_8);
+        byte[] payloadCipher = aKeyPair.encrypt(ByteBuffer.wrap(payload));
+        String base64Cipher = 
Base64.getEncoder().encodeToString(payloadCipher);
+        PKIAuthenticationPlugin.PKIHeaderData header = 
PKIAuthenticationPlugin.parseCipher(base64Cipher, aKeyPair.getPublicKey());
+        assertNotNull("Expecting valid header for user " + validUser + " and 
timestamp " + validTimestamp, header);
+        assertEquals(validUser, header.userName);
+        assertEquals(validTimestamp, header.timestamp);
+      }
+    }
+  }
+
+  public void testParseCipherInvalidTimestampTooSmall() {
+    long timestamp = 999999999L;
+    String s = "user1 " + timestamp;
+
+    byte[] payload = s.getBytes(UTF_8);
+    byte[] payloadCipher = aKeyPair.encrypt(ByteBuffer.wrap(payload));
+    String base64Cipher = Base64.getEncoder().encodeToString(payloadCipher);
+    assertNull(PKIAuthenticationPlugin.parseCipher(base64Cipher, 
aKeyPair.getPublicKey()));
+  }
+
+  public void testParseCipherInvalidTimestampTooBig() {
+    long timestamp = 10000000000000L;
+    String s = "user1 " + timestamp;
+
+    byte[] payload = s.getBytes(UTF_8);
+    byte[] payloadCipher = aKeyPair.encrypt(ByteBuffer.wrap(payload));
+    String base64Cipher = Base64.getEncoder().encodeToString(payloadCipher);
+    assertNull(PKIAuthenticationPlugin.parseCipher(base64Cipher, 
aKeyPair.getPublicKey()));
+  }
+
+  public void testParseCipherInvalidKey() {
+    String s = "user1 " + Instant.now().toEpochMilli();
+    byte[] payload = s.getBytes(UTF_8);
+    byte[] payloadCipher = aKeyPair.encrypt(ByteBuffer.wrap(payload));
+    String base64Cipher = Base64.getEncoder().encodeToString(payloadCipher);
+    assertNull(PKIAuthenticationPlugin.parseCipher(base64Cipher, new 
CryptoKeys.RSAKeyPair().getPublicKey()));
+  }
+
+  public void testParseCipherNoSpace() {
+    String s = "user1" + Instant.now().toEpochMilli(); // missing space
+
+    byte[] payload = s.getBytes(UTF_8);
+    byte[] payloadCipher = aKeyPair.encrypt(ByteBuffer.wrap(payload));
+    String base64Cipher = Base64.getEncoder().encodeToString(payloadCipher);
+    assertNull(PKIAuthenticationPlugin.parseCipher(base64Cipher, 
aKeyPair.getPublicKey()));
+  }
+
+  public void testParseCipherNoTimestamp() {
+    String s = "user1 aaaaaaaaaa";
+
+    byte[] payload = s.getBytes(UTF_8);
+    byte[] payloadCipher = aKeyPair.encrypt(ByteBuffer.wrap(payload));
+    String base64Cipher = Base64.getEncoder().encodeToString(payloadCipher);
+    assertNull(PKIAuthenticationPlugin.parseCipher(base64Cipher, 
aKeyPair.getPublicKey()));
+  }
+
+  public void testParseCipherInvalidKeyExample() {
+    /*
+    This test shows a case with an invalid public key for which the decrypt 
will return an output that triggers SOLR-15961.
+     */
+    String base64Cipher = 
"A8tEkMfmA5m5+wVG9xSI46Lhg8MqDFkjPVqXc6Tf6LT/EVIpW3DUrkIygIjk9tSCCAxhHwSvKfVJeujaBtxr19ajmpWjtZKgZOXkynF5aPbDuI+mnvCiTmhLuZYExvnmeYxag6A4Fu2TpA/Wo97S4cIkRgfyag/ZOYM0pZwVAtNoJgTpmODDGrH4W16BXSZ6xm+EV4vrfUqpuuO7U7YiU5fd1tv22Au0ZaY6lPbxAHjeFyD8WrkPPIkEoM14K0G5vAg4wUxpRF/eVlnzhULoPgKFErz7cKVxuvxSsYpVw5oko+ldzyfsnMrC1brqUKA7NxhpdpJzp7bmd8W8/mvZEw==";
+    String publicKey = 
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsJu1O+A/gGikFSeLGYdgNPrz3ef/tqJP1sRqzkVjnBcdyI2oXMmAWF+yDe0Zmya+HevyOI8YN2Yaq6aCLjbHnT364Rno/urhKvR5PmaH/PqXrh3Dl+vn08B74iLVZxZro/v34FGjX8fkiasZggC4AnyLjFkU7POsHhJKSXGslsWe0dq7yaaA2AES/bFwJ3r3FNxUsE+kWEtZG1RKMq8P8wlx/HLDzjYKaGnyApAltBHVx60XHiOC9Oatu5HZb/eKU3jf7sKibrzrRsqwb+iE4ZxxtXkgATuLOl/2ks5Mnkk4u7bPEAgEpEuzQBB4AahMC7r+R5AzRnB4+xx69FP1IwIDAQAB";
+    assertNull(PKIAuthenticationPlugin.parseCipher(base64Cipher, 
CryptoKeys.deserializeX509PublicKey(publicKey)));
+  }
+
   private HttpServletRequest createMockRequest(final AtomicReference<Header> 
header) {
     HttpServletRequest mockReq = mock(HttpServletRequest.class);
     when(mockReq.getHeader(any(String.class))).then(invocation -> {

Reply via email to