changeset 9999:c032da475287

8237990: Enhanced LDAP contexts Reviewed-by: dfuchs, robm, weijun, xyin, rhalade, ahgross, yan, bae
author abakhtin
date Mon, 22 Jun 2020 14:30:37 +0100
parents 7345953eea82
children 963d6ac6635a
files src/share/classes/com/sun/jndi/ldap/Connection.java src/share/classes/com/sun/jndi/ldap/LdapClient.java src/share/classes/com/sun/jndi/ldap/LdapCtx.java src/share/classes/com/sun/jndi/ldap/ext/StartTlsResponseImpl.java
diffstat 4 files changed, 162 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/com/sun/jndi/ldap/Connection.java	Wed Sep 23 13:38:26 2020 +0300
+++ b/src/share/classes/com/sun/jndi/ldap/Connection.java	Mon Jun 22 14:30:37 2020 +0100
@@ -158,6 +158,13 @@
 
     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();
 
@@ -735,6 +742,23 @@
         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
@@ -889,6 +913,11 @@
                     // 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;
@@ -916,6 +945,13 @@
                         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);
--- a/src/share/classes/com/sun/jndi/ldap/LdapClient.java	Wed Sep 23 13:38:26 2020 +0300
+++ b/src/share/classes/com/sun/jndi/ldap/LdapClient.java	Mon Jun 22 14:30:37 2020 +0100
@@ -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 @@
         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) {
--- a/src/share/classes/com/sun/jndi/ldap/LdapCtx.java	Wed Sep 23 13:38:26 2020 +0300
+++ b/src/share/classes/com/sun/jndi/ldap/LdapCtx.java	Mon Jun 22 14:30:37 2020 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2017, 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,13 +33,19 @@
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
 
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collections;
 import java.util.Locale;
+import java.util.Set;
 import java.util.Vector;
 import java.util.Hashtable;
+import java.util.HashSet;
 import java.util.List;
 import java.util.StringTokenizer;
 import java.util.Enumeration;
 
+
 import java.io.IOException;
 import java.io.OutputStream;
 
@@ -200,6 +206,27 @@
     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 +262,8 @@
     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 ------------------------
 
@@ -2669,6 +2697,82 @@
         ensureOpen();      // open or reauthenticated
     }
 
+    // Load 'mechsAllowedToSendCredentials' system property value
+    private static String getMechsAllowedToSendCredentials() {
+        return AccessController.doPrivileged(
+            new PrivilegedAction<String>() {
+                public String run() {
+                    return System.getProperty(ALLOWED_MECHS_SP);
+                }
+            }
+        );
+    }
+
+    // Get set of allowed authentication mechanism names from the property value
+    private static Set<String> getMechsFromPropertyValue(String propValue) {
+        if (propValue == null || propValue.isEmpty()) {
+            return Collections.emptySet();
+        }
+
+        Set<String> s = new HashSet<>();
+        for (String part : propValue.split("\\s*,\\s*")) {
+            if (!part.isEmpty()) {
+                s.add(part.toLowerCase(Locale.ROOT));
+            }
+        }
+        return s;
+    }
+
+    // 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);
     }
@@ -2770,6 +2874,9 @@
                     // 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;
@@ -2788,8 +2895,12 @@
                 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
 
@@ -3293,6 +3404,7 @@
                 String domainName = (String)
                     (envprops != null ? envprops.get(DOMAIN_NAME) : null);
                 ((StartTlsResponseImpl)er).setConnection(clnt.conn, domainName);
+                contextSeenStartTlsEnabled |= startTLS;
             }
             return er;
 
--- a/src/share/classes/com/sun/jndi/ldap/ext/StartTlsResponseImpl.java	Wed Sep 23 13:38:26 2020 +0300
+++ b/src/share/classes/com/sun/jndi/ldap/ext/StartTlsResponseImpl.java	Mon Jun 22 14:30:37 2020 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2011, 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
@@ -266,7 +266,7 @@
 
         // Replace SSL streams with the original streams
         ldapConnection.replaceStreams(
-                        originalInputStream, originalOutputStream);
+                originalInputStream, originalOutputStream, false);
 
         if (debug) {
             System.out.println("StartTLS: closing SSL Socket");
@@ -356,7 +356,7 @@
 
             // 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");
             }