8237990: Enhanced LDAP contexts

Mon, 22 Jun 2020 14:30:37 +0100

author
abakhtin
date
Mon, 22 Jun 2020 14:30:37 +0100
changeset 14211
ccf97104b8ea
parent 14210
0da7153533e6
child 14212
eca1dcb470d8

8237990: Enhanced LDAP contexts
Reviewed-by: dfuchs, robm, weijun, xyin, rhalade, ahgross, mbalao, andrew

src/share/classes/com/sun/jndi/ldap/Connection.java file | annotate | diff | comparison | revisions
src/share/classes/com/sun/jndi/ldap/LdapClient.java file | annotate | diff | comparison | revisions
src/share/classes/com/sun/jndi/ldap/LdapCtx.java file | annotate | diff | comparison | revisions
src/share/classes/com/sun/jndi/ldap/ext/StartTlsResponseImpl.java file | annotate | diff | comparison | revisions
     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              }

mercurial