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 -> {