Mon, 22 Jun 2020 14:30:37 +0100
8237990: Enhanced LDAP contexts
Reviewed-by: dfuchs, robm, weijun, xyin, rhalade, ahgross, mbalao, andrew
1.1 --- a/src/share/classes/com/sun/jndi/ldap/Connection.java Fri Aug 07 11:02:19 2020 +0000 1.2 +++ b/src/share/classes/com/sun/jndi/ldap/Connection.java Mon Jun 22 14:30:37 2020 +0100 1.3 @@ -158,6 +158,13 @@ 1.4 1.5 int readTimeout; 1.6 int connectTimeout; 1.7 + 1.8 + // Is connection upgraded to SSL via STARTTLS extended operation 1.9 + private volatile boolean isUpgradedToStartTls; 1.10 + 1.11 + // Lock to maintain isUpgradedToStartTls state 1.12 + final Object startTlsLock = new Object(); 1.13 + 1.14 private static final boolean IS_HOSTNAME_VERIFICATION_DISABLED 1.15 = hostnameVerificationDisabledValue(); 1.16 1.17 @@ -714,6 +721,23 @@ 1.18 outStream = newOut; 1.19 } 1.20 1.21 + /* 1.22 + * Replace streams and set isUpdradedToStartTls flag to the provided value 1.23 + */ 1.24 + synchronized public void replaceStreams(InputStream newIn, OutputStream newOut, boolean isStartTls) { 1.25 + synchronized (startTlsLock) { 1.26 + replaceStreams(newIn, newOut); 1.27 + isUpgradedToStartTls = isStartTls; 1.28 + } 1.29 + } 1.30 + 1.31 + /* 1.32 + * Returns true if connection was upgraded to SSL with STARTTLS extended operation 1.33 + */ 1.34 + public boolean isUpgradedToStartTls() { 1.35 + return isUpgradedToStartTls; 1.36 + } 1.37 + 1.38 /** 1.39 * Used by Connection thread to read inStream into a local variable. 1.40 * This ensures that there is no contention between the main thread 1.41 @@ -868,6 +892,11 @@ 1.42 // is equal to & 0x80 (i.e. length byte with high bit off). 1.43 if ((seqlen & 0x80) == 0x80) { 1.44 seqlenlen = seqlen & 0x7f; // number of length bytes 1.45 + // Check the length of length field, since seqlen is int 1.46 + // the number of bytes can't be greater than 4 1.47 + if (seqlenlen > 4) { 1.48 + throw new IOException("Length coded with too many bytes: " + seqlenlen); 1.49 + } 1.50 1.51 bytesread = 0; 1.52 eos = false; 1.53 @@ -895,6 +924,13 @@ 1.54 offset += bytesread; 1.55 } 1.56 1.57 + if (seqlenlen > bytesread) { 1.58 + throw new IOException("Unexpected EOF while reading length"); 1.59 + } 1.60 + 1.61 + if (seqlen < 0) { 1.62 + throw new IOException("Length too big: " + (((long) seqlen) & 0xFFFFFFFFL)); 1.63 + } 1.64 // read in seqlen bytes 1.65 byte[] left = readFully(in, seqlen); 1.66 inbuf = Arrays.copyOf(inbuf, offset + left.length);
2.1 --- a/src/share/classes/com/sun/jndi/ldap/LdapClient.java Fri Aug 07 11:02:19 2020 +0000 2.2 +++ b/src/share/classes/com/sun/jndi/ldap/LdapClient.java Mon Jun 22 14:30:37 2020 +0100 2.3 @@ -1,5 +1,5 @@ 2.4 /* 2.5 - * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved. 2.6 + * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. 2.7 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 2.8 * 2.9 * This code is free software; you can redistribute it and/or modify it 2.10 @@ -396,6 +396,12 @@ 2.11 return (conn.inStream instanceof SaslInputStream); 2.12 } 2.13 2.14 + // Returns true if client connection was upgraded 2.15 + // with STARTTLS extended operation on the server side 2.16 + boolean isUpgradedToStartTls() { 2.17 + return conn.isUpgradedToStartTls(); 2.18 + } 2.19 + 2.20 synchronized void incRefCount() { 2.21 ++referenceCount; 2.22 if (debug > 1) {
3.1 --- a/src/share/classes/com/sun/jndi/ldap/LdapCtx.java Fri Aug 07 11:02:19 2020 +0000 3.2 +++ b/src/share/classes/com/sun/jndi/ldap/LdapCtx.java Mon Jun 22 14:30:37 2020 +0100 3.3 @@ -1,5 +1,5 @@ 3.4 /* 3.5 - * Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved. 3.6 + * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. 3.7 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3.8 * 3.9 * This code is free software; you can redistribute it and/or modify it 3.10 @@ -33,13 +33,19 @@ 3.11 import javax.naming.ldap.LdapName; 3.12 import javax.naming.ldap.Rdn; 3.13 3.14 +import java.security.AccessController; 3.15 +import java.security.PrivilegedAction; 3.16 +import java.util.Collections; 3.17 import java.util.Locale; 3.18 +import java.util.Set; 3.19 import java.util.Vector; 3.20 import java.util.Hashtable; 3.21 +import java.util.HashSet; 3.22 import java.util.List; 3.23 import java.util.StringTokenizer; 3.24 import java.util.Enumeration; 3.25 3.26 + 3.27 import java.io.IOException; 3.28 import java.io.OutputStream; 3.29 3.30 @@ -200,6 +206,27 @@ 3.31 private static final String REPLY_QUEUE_SIZE = 3.32 "com.sun.jndi.ldap.search.replyQueueSize"; 3.33 3.34 + // System and environment property name to control allowed list of 3.35 + // authentication mechanisms: "all" or "" or "mech1,mech2,...,mechN" 3.36 + // "all": allow all mechanisms, 3.37 + // "": allow none 3.38 + // or comma separated list of allowed authentication mechanisms 3.39 + // Note: "none" or "anonymous" are always allowed. 3.40 + private static final String ALLOWED_MECHS_SP = 3.41 + "jdk.jndi.ldap.mechsAllowedToSendCredentials"; 3.42 + 3.43 + // System property value 3.44 + private static final String ALLOWED_MECHS_SP_VALUE = 3.45 + getMechsAllowedToSendCredentials(); 3.46 + 3.47 + // Set of authentication mechanisms allowed by the system property 3.48 + private static final Set<String> MECHS_ALLOWED_BY_SP = 3.49 + getMechsFromPropertyValue(ALLOWED_MECHS_SP_VALUE); 3.50 + 3.51 + // The message to use in NamingException if the transmission of plain credentials are not allowed 3.52 + private static final String UNSECURED_CRED_TRANSMIT_MSG = 3.53 + "Transmission of credentials over unsecured connection is not allowed"; 3.54 + 3.55 // ----------------- Fields that don't change ----------------------- 3.56 private static final NameParser parser = new LdapNameParser(); 3.57 3.58 @@ -235,7 +262,8 @@ 3.59 Name currentParsedDN; // DN of this context 3.60 Vector<Control> respCtls = null; // Response controls read 3.61 Control[] reqCtls = null; // Controls to be sent with each request 3.62 - 3.63 + // Used to track if context was seen to be secured with STARTTLS extended operation 3.64 + volatile boolean contextSeenStartTlsEnabled; 3.65 3.66 // ------------- Private instance variables ------------------------ 3.67 3.68 @@ -2669,6 +2697,77 @@ 3.69 ensureOpen(); // open or reauthenticated 3.70 } 3.71 3.72 + // Load 'mechsAllowedToSendCredentials' system property value 3.73 + private static String getMechsAllowedToSendCredentials() { 3.74 + PrivilegedAction<String> pa = () -> System.getProperty(ALLOWED_MECHS_SP); 3.75 + return System.getSecurityManager() == null ? pa.run() : AccessController.doPrivileged(pa); 3.76 + } 3.77 + 3.78 + // Get set of allowed authentication mechanism names from the property value 3.79 + private static Set<String> getMechsFromPropertyValue(String propValue) { 3.80 + if (propValue == null || propValue.isEmpty()) { 3.81 + return Collections.emptySet(); 3.82 + } 3.83 + 3.84 + Set<String> s = new HashSet<>(); 3.85 + for (String part : propValue.trim().split("\\s*,\\s*")) { 3.86 + if (!part.isEmpty()) { 3.87 + s.add(part); 3.88 + } 3.89 + } 3.90 + return Collections.unmodifiableSet(s); 3.91 + } 3.92 + 3.93 + // Returns true if TLS connection opened using "ldaps" scheme, or using "ldap" and then upgraded with 3.94 + // startTLS extended operation, and startTLS is still active. 3.95 + private boolean isConnectionEncrypted() { 3.96 + return hasLdapsScheme || clnt.isUpgradedToStartTls(); 3.97 + } 3.98 + 3.99 + // Ensure connection and context are in a safe state to transmit credentials 3.100 + private void ensureCanTransmitCredentials(String authMechanism) throws NamingException { 3.101 + 3.102 + // "none" and "anonumous" authentication mechanisms are allowed unconditionally 3.103 + if ("none".equalsIgnoreCase(authMechanism) || "anonymous".equalsIgnoreCase(authMechanism)) { 3.104 + return; 3.105 + } 3.106 + 3.107 + // Check environment first 3.108 + String allowedMechanismsOrTrue = (String) envprops.get(ALLOWED_MECHS_SP); 3.109 + boolean useSpMechsCache = false; 3.110 + boolean anyPropertyIsSet = ALLOWED_MECHS_SP_VALUE != null || allowedMechanismsOrTrue != null; 3.111 + 3.112 + // If current connection is not encrypted, and context seen to be secured with STARTTLS 3.113 + // or 'mechsAllowedToSendCredentials' is set to any value via system/context environment properties 3.114 + if (!isConnectionEncrypted() && (contextSeenStartTlsEnabled || anyPropertyIsSet)) { 3.115 + // First, check if security principal is provided in context environment for "simple" 3.116 + // authentication mechanism. There is no check for other SASL mechanisms since the credentials 3.117 + // can be specified via other properties 3.118 + if ("simple".equalsIgnoreCase(authMechanism) && !envprops.containsKey(SECURITY_PRINCIPAL)) { 3.119 + return; 3.120 + } 3.121 + 3.122 + // If null - will use mechanism name cached from system property 3.123 + if (allowedMechanismsOrTrue == null) { 3.124 + useSpMechsCache = true; 3.125 + allowedMechanismsOrTrue = ALLOWED_MECHS_SP_VALUE; 3.126 + } 3.127 + 3.128 + // If the property value (system or environment) is 'all': 3.129 + // any kind of authentication is allowed unconditionally - no check is needed 3.130 + if ("all".equalsIgnoreCase(allowedMechanismsOrTrue)) { 3.131 + return; 3.132 + } 3.133 + 3.134 + // Get the set with allowed authentication mechanisms and check current mechanism 3.135 + Set<String> allowedAuthMechs = useSpMechsCache ? 3.136 + MECHS_ALLOWED_BY_SP : getMechsFromPropertyValue(allowedMechanismsOrTrue); 3.137 + if (!allowedAuthMechs.contains(authMechanism)) { 3.138 + throw new NamingException(UNSECURED_CRED_TRANSMIT_MSG); 3.139 + } 3.140 + } 3.141 + } 3.142 + 3.143 private void ensureOpen() throws NamingException { 3.144 ensureOpen(false); 3.145 } 3.146 @@ -2771,6 +2870,9 @@ 3.147 // Required for SASL client identity 3.148 envprops); 3.149 3.150 + // Mark current context as secure if the connection is acquired 3.151 + // from the pool and it is secure. 3.152 + contextSeenStartTlsEnabled |= clnt.isUpgradedToStartTls(); 3.153 3.154 /** 3.155 * Pooled connections are preauthenticated; 3.156 @@ -2789,8 +2891,12 @@ 3.157 ldapVersion = LdapClient.LDAP_VERSION3; 3.158 } 3.159 3.160 - LdapResult answer = clnt.authenticate(initial, 3.161 - user, passwd, ldapVersion, authMechanism, bindCtls, envprops); 3.162 + LdapResult answer; 3.163 + synchronized (clnt.conn.startTlsLock) { 3.164 + ensureCanTransmitCredentials(authMechanism); 3.165 + answer = clnt.authenticate(initial, user, passwd, ldapVersion, 3.166 + authMechanism, bindCtls, envprops); 3.167 + } 3.168 3.169 respCtls = answer.resControls; // retrieve (bind) response controls 3.170 3.171 @@ -3294,6 +3400,7 @@ 3.172 String domainName = (String) 3.173 (envprops != null ? envprops.get(DOMAIN_NAME) : null); 3.174 ((StartTlsResponseImpl)er).setConnection(clnt.conn, domainName); 3.175 + contextSeenStartTlsEnabled |= startTLS; 3.176 } 3.177 return er; 3.178
4.1 --- a/src/share/classes/com/sun/jndi/ldap/ext/StartTlsResponseImpl.java Fri Aug 07 11:02:19 2020 +0000 4.2 +++ b/src/share/classes/com/sun/jndi/ldap/ext/StartTlsResponseImpl.java Mon Jun 22 14:30:37 2020 +0100 4.3 @@ -1,5 +1,5 @@ 4.4 /* 4.5 - * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. 4.6 + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. 4.7 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4.8 * 4.9 * This code is free software; you can redistribute it and/or modify it 4.10 @@ -268,7 +268,7 @@ 4.11 4.12 // Replace SSL streams with the original streams 4.13 ldapConnection.replaceStreams( 4.14 - originalInputStream, originalOutputStream); 4.15 + originalInputStream, originalOutputStream, false); 4.16 4.17 if (debug) { 4.18 System.out.println("StartTLS: closing SSL Socket"); 4.19 @@ -358,7 +358,7 @@ 4.20 4.21 // Replace original streams with the new SSL streams 4.22 ldapConnection.replaceStreams(sslSocket.getInputStream(), 4.23 - sslSocket.getOutputStream()); 4.24 + sslSocket.getOutputStream(), true); 4.25 if (debug) { 4.26 System.out.println("StartTLS: Replaced IO Streams"); 4.27 }