8237990: Enhanced LDAP contexts

Reviewed-by: dfuchs, robm, weijun, xyin, rhalade, ahgross
This commit is contained in:
Aleksei Efimov 2020-06-22 14:30:37 +01:00 committed by Henry Jen
parent d149dcdbd5
commit 7eda1196c6
4 changed files with 155 additions and 8 deletions

View File

@ -169,6 +169,13 @@ public final class Connection implements Runnable, HandshakeCompletedListener {
int readTimeout;
int connectTimeout;
// Is connection upgraded to SSL via STARTTLS extended operation
private volatile boolean isUpgradedToStartTls;
// Lock to maintain isUpgradedToStartTls state
final Object startTlsLock = new Object();
private static final boolean IS_HOSTNAME_VERIFICATION_DISABLED
= hostnameVerificationDisabledValue();
@ -697,6 +704,23 @@ public final class Connection implements Runnable, HandshakeCompletedListener {
outStream = newOut;
}
/*
* Replace streams and set isUpdradedToStartTls flag to the provided value
*/
synchronized public void replaceStreams(InputStream newIn, OutputStream newOut, boolean isStartTls) {
synchronized (startTlsLock) {
replaceStreams(newIn, newOut);
isUpgradedToStartTls = isStartTls;
}
}
/*
* Returns true if connection was upgraded to SSL with STARTTLS extended operation
*/
public boolean isUpgradedToStartTls() {
return isUpgradedToStartTls;
}
/**
* Used by Connection thread to read inStream into a local variable.
* This ensures that there is no contention between the main thread
@ -851,6 +875,11 @@ public final class Connection implements Runnable, HandshakeCompletedListener {
// is equal to & 0x80 (i.e. length byte with high bit off).
if ((seqlen & 0x80) == 0x80) {
seqlenlen = seqlen & 0x7f; // number of length bytes
// Check the length of length field, since seqlen is int
// the number of bytes can't be greater than 4
if (seqlenlen > 4) {
throw new IOException("Length coded with too many bytes: " + seqlenlen);
}
bytesread = 0;
eos = false;
@ -878,6 +907,13 @@ public final class Connection implements Runnable, HandshakeCompletedListener {
offset += bytesread;
}
if (seqlenlen > bytesread) {
throw new IOException("Unexpected EOF while reading length");
}
if (seqlen < 0) {
throw new IOException("Length too big: " + (((long) seqlen) & 0xFFFFFFFFL));
}
// read in seqlen bytes
byte[] left = readFully(in, seqlen);
inbuf = Arrays.copyOf(inbuf, offset + left.length);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -396,6 +396,12 @@ public final class LdapClient implements PooledConnection {
return (conn.inStream instanceof SaslInputStream);
}
// Returns true if client connection was upgraded
// with STARTTLS extended operation on the server side
boolean isUpgradedToStartTls() {
return conn.isUpgradedToStartTls();
}
synchronized void incRefCount() {
++referenceCount;
if (debug > 1) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -33,12 +33,19 @@ import javax.naming.ldap.*;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import java.util.Vector;
import java.util.Hashtable;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Enumeration;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.io.IOException;
import java.io.OutputStream;
@ -200,6 +207,27 @@ final public class LdapCtx extends ComponentDirContext
private static final String REPLY_QUEUE_SIZE =
"com.sun.jndi.ldap.search.replyQueueSize";
// System and environment property name to control allowed list of
// authentication mechanisms: "all" or "" or "mech1,mech2,...,mechN"
// "all": allow all mechanisms,
// "": allow none
// or comma separated list of allowed authentication mechanisms
// Note: "none" or "anonymous" are always allowed.
private static final String ALLOWED_MECHS_SP =
"jdk.jndi.ldap.mechsAllowedToSendCredentials";
// System property value
private static final String ALLOWED_MECHS_SP_VALUE =
getMechsAllowedToSendCredentials();
// Set of authentication mechanisms allowed by the system property
private static final Set<String> MECHS_ALLOWED_BY_SP =
getMechsFromPropertyValue(ALLOWED_MECHS_SP_VALUE);
// The message to use in NamingException if the transmission of plain credentials are not allowed
private static final String UNSECURED_CRED_TRANSMIT_MSG =
"Transmission of credentials over unsecured connection is not allowed";
// ----------------- Fields that don't change -----------------------
private static final NameParser parser = new LdapNameParser();
@ -235,7 +263,8 @@ final public class LdapCtx extends ComponentDirContext
Name currentParsedDN; // DN of this context
Vector<Control> respCtls = null; // Response controls read
Control[] reqCtls = null; // Controls to be sent with each request
// Used to track if context was seen to be secured with STARTTLS extended operation
volatile boolean contextSeenStartTlsEnabled;
// ------------- Private instance variables ------------------------
@ -2670,6 +2699,73 @@ final public class LdapCtx extends ComponentDirContext
ensureOpen(); // open or reauthenticated
}
// Load 'mechsAllowedToSendCredentials' system property value
private static String getMechsAllowedToSendCredentials() {
PrivilegedAction<String> pa = () -> System.getProperty(ALLOWED_MECHS_SP);
return System.getSecurityManager() == null ? pa.run() : AccessController.doPrivileged(pa);
}
// Get set of allowed authentication mechanism names from the property value
private static Set<String> getMechsFromPropertyValue(String propValue) {
if (propValue == null || propValue.isBlank()) {
return Collections.emptySet();
}
return Arrays.stream(propValue.split(","))
.map(String::trim)
.filter(Predicate.not(String::isBlank))
.collect(Collectors.toUnmodifiableSet());
}
// Returns true if TLS connection opened using "ldaps" scheme, or using "ldap" and then upgraded with
// startTLS extended operation, and startTLS is still active.
private boolean isConnectionEncrypted() {
return hasLdapsScheme || clnt.isUpgradedToStartTls();
}
// Ensure connection and context are in a safe state to transmit credentials
private void ensureCanTransmitCredentials(String authMechanism) throws NamingException {
// "none" and "anonumous" authentication mechanisms are allowed unconditionally
if ("none".equalsIgnoreCase(authMechanism) || "anonymous".equalsIgnoreCase(authMechanism)) {
return;
}
// Check environment first
String allowedMechanismsOrTrue = (String) envprops.get(ALLOWED_MECHS_SP);
boolean useSpMechsCache = false;
boolean anyPropertyIsSet = ALLOWED_MECHS_SP_VALUE != null || allowedMechanismsOrTrue != null;
// If current connection is not encrypted, and context seen to be secured with STARTTLS
// or 'mechsAllowedToSendCredentials' is set to any value via system/context environment properties
if (!isConnectionEncrypted() && (contextSeenStartTlsEnabled || anyPropertyIsSet)) {
// First, check if security principal is provided in context environment for "simple"
// authentication mechanism. There is no check for other SASL mechanisms since the credentials
// can be specified via other properties
if ("simple".equalsIgnoreCase(authMechanism) && !envprops.containsKey(SECURITY_PRINCIPAL)) {
return;
}
// If null - will use mechanism name cached from system property
if (allowedMechanismsOrTrue == null) {
useSpMechsCache = true;
allowedMechanismsOrTrue = ALLOWED_MECHS_SP_VALUE;
}
// If the property value (system or environment) is 'all':
// any kind of authentication is allowed unconditionally - no check is needed
if ("all".equalsIgnoreCase(allowedMechanismsOrTrue)) {
return;
}
// Get the set with allowed authentication mechanisms and check current mechanism
Set<String> allowedAuthMechs = useSpMechsCache ?
MECHS_ALLOWED_BY_SP : getMechsFromPropertyValue(allowedMechanismsOrTrue);
if (!allowedAuthMechs.contains(authMechanism)) {
throw new NamingException(UNSECURED_CRED_TRANSMIT_MSG);
}
}
}
private void ensureOpen() throws NamingException {
ensureOpen(false);
}
@ -2772,6 +2868,10 @@ final public class LdapCtx extends ComponentDirContext
// Required for SASL client identity
envprops);
// Mark current context as secure if the connection is acquired
// from the pool and it is secure.
contextSeenStartTlsEnabled |= clnt.isUpgradedToStartTls();
/**
* Pooled connections are preauthenticated;
* newly created ones are not.
@ -2789,8 +2889,12 @@ final public class LdapCtx extends ComponentDirContext
ldapVersion = LdapClient.LDAP_VERSION3;
}
LdapResult answer = clnt.authenticate(initial,
user, passwd, ldapVersion, authMechanism, bindCtls, envprops);
LdapResult answer;
synchronized (clnt.conn.startTlsLock) {
ensureCanTransmitCredentials(authMechanism);
answer = clnt.authenticate(initial, user, passwd, ldapVersion,
authMechanism, bindCtls, envprops);
}
respCtls = answer.resControls; // retrieve (bind) response controls
@ -3294,6 +3398,7 @@ final public class LdapCtx extends ComponentDirContext
String domainName = (String)
(envprops != null ? envprops.get(DOMAIN_NAME) : null);
((StartTlsResponseImpl)er).setConnection(clnt.conn, domainName);
contextSeenStartTlsEnabled |= startTLS;
}
return er;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -268,7 +268,7 @@ final public class StartTlsResponseImpl extends StartTlsResponse {
// Replace SSL streams with the original streams
ldapConnection.replaceStreams(
originalInputStream, originalOutputStream);
originalInputStream, originalOutputStream, false);
if (debug) {
System.out.println("StartTLS: closing SSL Socket");
@ -359,7 +359,7 @@ final public class StartTlsResponseImpl extends StartTlsResponse {
// Replace original streams with the new SSL streams
ldapConnection.replaceStreams(sslSocket.getInputStream(),
sslSocket.getOutputStream());
sslSocket.getOutputStream(), true);
if (debug) {
System.out.println("StartTLS: Replaced IO Streams");
}