# HG changeset patch # User abakhtin # Date 1592832637 -3600 # Node ID c032da475287c84eafd3778af03031de5d8c8ca3 # Parent 7345953eea829eb24d49f5bb2293f05eb6808c5b 8237990: Enhanced LDAP contexts Reviewed-by: dfuchs, robm, weijun, xyin, rhalade, ahgross, yan, bae diff -r 7345953eea82 -r c032da475287 src/share/classes/com/sun/jndi/ldap/Connection.java --- 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); diff -r 7345953eea82 -r c032da475287 src/share/classes/com/sun/jndi/ldap/LdapClient.java --- 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) { diff -r 7345953eea82 -r c032da475287 src/share/classes/com/sun/jndi/ldap/LdapCtx.java --- 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 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 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() { + public String run() { + return System.getProperty(ALLOWED_MECHS_SP); + } + } + ); + } + + // Get set of allowed authentication mechanism names from the property value + private static Set getMechsFromPropertyValue(String propValue) { + if (propValue == null || propValue.isEmpty()) { + return Collections.emptySet(); + } + + Set 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 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; diff -r 7345953eea82 -r c032da475287 src/share/classes/com/sun/jndi/ldap/ext/StartTlsResponseImpl.java --- 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"); }