Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8279842: HTTPS Channel Binding support for Java GSS/Kerberos #7065

Closed
wants to merge 16 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/java.base/share/classes/java/net/doc-files/net-properties.html
Original file line number Diff line number Diff line change
@@ -214,6 +214,19 @@ <H2>Misc HTTP URL stream protocol handler properties</H2>
property is defined, then its value will be used as the domain
name.</P>
</OL>
<LI><P><B>{@systemProperty jdk.https.negotiate.cbt}</B> (default: &lt;never&gt;)<BR>
This controls the generation and sending of TLS channel binding tokens (CBT) when Kerberos
or the Negotiate authentication scheme using Kerberos are employed over HTTPS with
{@code HttpURLConnection}. There are three possible settings:</P>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be {@code HttpsURLConnection}?
(BTW - can we use {@code } here ? Would be worth checking the generated doc)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right HttpsURLConnection is better. {@code} works here.

<OL>
<LI><P>"never". This is also the default value if the property is not set. In this case,
CBT's are never sent.</P>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo, "CBTs"?

<LI><P>"always". CBTs are sent for all Kerberos authentication attempts over HTTPS.</P>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we remove "Kerberos"? Or we can use "Kerberos or Negotiate".

<LI><P>"domain:&lt;comma separated domain list&gt;" Each domain in the list specifies destination
host or hosts for which a CBT is sent. Domains can be single hosts like foo, or foo.com,
or wildcards like *.foo.com which matches all hosts under foo.com and its sub-domains.
CBTs are not sent to any destinations that don't match one of the list entries</P>
</OL>
</UL>
<P>All these properties are checked only once at startup.</P>
<a id="AddressCache"></a>
10 changes: 8 additions & 2 deletions src/java.base/share/classes/sun/net/www/http/HttpClient.java
Original file line number Diff line number Diff line change
@@ -144,7 +144,7 @@ private static int getDefaultPort(String proto) {
// Traffic capture tool, if configured. See HttpCapture class for info
private HttpCapture capture = null;

/* "jdk.spnego.cbt" property can be set to "always" (always sent), "never" (never sent) or
/* "jdk.https.negotiate.cbt" property can be set to "always" (always sent), "never" (never sent) or
* "domain:a,c.d,*.e.f" (sent to host a, or c.d or to the domain e.f and any of its subdomains). This is
* a comma separated list of arbitrary length with no white-space allowed.
* If enabled (for a particular destination) then SPNEGO authentication requests will include

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously Negotiate was used, now SPNEGO?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Negotiate" is the name of the HTTP authentication scheme which uses the SPNEGO mechanism. Possibly makes more sense to refer to Negotiate here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I know. That's the point. Clearly differentiate between GSS-API mech and HTTP auth scheme.

@@ -160,6 +160,11 @@ private static void logFinest(String msg) {
logger.finest(msg);
}
}
private static void logError(String msg) {
if (logger.isLoggable(PlatformLogger.Level.SEVERE)) {
logger.severe(msg);
}
}

protected volatile String authenticatorKey;

@@ -180,6 +185,7 @@ static String normalizeCBT(String s) {
s.equals("never") || s.startsWith("domain:"))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess there's a ! missing in front of s.startsWith("domain:") here?

Copy link
Member Author

@Michael-Mc-Mahon Michael-Mc-Mahon Jan 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what was intended (equivalent)

if (s ==null || (s!="always" && s!="never" && !s.startsWith("domain")))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argh - you're right I missed the fact that the 3 expressions where included in parenthesis. I read it as

! (s.equals("always")) || ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we log a message if the value is not one of the 3 forms?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually malformed values are just ignored - and the property takes its default value. But yes - s.n.w.h.HttpClient has a logger so it wouldn't be much effort to log it as a DEBUG trace for better diagnostic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will log it using the same debug/logging mechanism already in the same source file..

return "never";
} else {
logError("Unexpected value for \"jdk.https.negotiate.cbt\" system property");
return s;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return either "always" or "never" instead? It seems that junk values will be treated as "always". It would be better to make it clear here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was being handled elsewhere as "never". But, I agree it would be clearer to normalise it to "never" here.

Copy link
Member Author

@Michael-Mc-Mahon Michael-Mc-Mahon Jan 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I should have checked back to the source rather than the snippet quoted. The problem is that the logError call is in the wrong place. It should be before line 186. Though some other adjustments are also required

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! Yes - I was also bitten again by the negation in the if too.
The presence of logError in the body of the if will make it clearer :-)

}
}
@@ -191,7 +197,7 @@ static String normalizeCBT(String s) {
String cacheNTLM = props.getProperty("jdk.ntlm.cache");
String cacheSPNEGO = props.getProperty("jdk.spnego.cache");

String s = props.getProperty("jdk.spnego.cbt");
String s = props.getProperty("jdk.https.negotiate.cbt");
spnegoCBT = normalizeCBT(s);

if (keepAlive != null) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2022, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package sun.security.util;

/**
* Thrown by TlsChannelBinding if an error occurs
*/
public class ChannelBindingException extends Exception {
Copy link
Member

@dfuch dfuch Jan 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this extend GeneralSecurityException instead? Or should we just remove this class and throw plain GeneralSecurityException in TlsChannelBinding ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a distinct exception is necessary. I don't have a strong opinion on whether it should extend GeneralSecurityException.


@java.io.Serial
private static final long serialVersionUID = -5021387249782788460L;

/**
* Constructs a ChannelBindingException with no detail message. A detail
* message is a String that describes this particular exception.
*/
public ChannelBindingException() {
super();
}

/**
* Constructs a ChannelBindingException with a detail message and
* specified cause.
*/
public ChannelBindingException(String msg, Exception e) {
super(msg, e);
}

/**
* Constructs a ChannelBindingException with a detail message
*/
public ChannelBindingException(String msg) {
super(msg);
}
}
Original file line number Diff line number Diff line change
@@ -22,10 +22,9 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.jndi.ldap.sasl;

import javax.naming.NamingException;
import javax.security.sasl.SaslException;
package sun.security.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
@@ -84,14 +83,14 @@ public String getName() {
* @param cbType
* @return TLS Channel Binding type or null if
* "com.sun.jndi.ldap.tls.cbtype" property has not been set.
* @throws NamingException
* @throws ChannelBindingException
*/
public static TlsChannelBindingType parseType(String cbType) throws NamingException {
public static TlsChannelBindingType parseType(String cbType) throws ChannelBindingException {
if (cbType != null) {
if (cbType.equals(TlsChannelBindingType.TLS_SERVER_END_POINT.getName())) {
return TlsChannelBindingType.TLS_SERVER_END_POINT;
} else {
throw new NamingException("Illegal value for " +
throw new ChannelBindingException("Illegal value for " +
CHANNEL_BINDING_TYPE + " property.");
}
}
@@ -104,9 +103,9 @@ public static TlsChannelBindingType parseType(String cbType) throws NamingExcept
/**
* Construct tls-server-end-point Channel Binding data
* @param serverCertificate
* @throws SaslException
* @throws ChannelBindingException
*/
public static TlsChannelBinding create(X509Certificate serverCertificate) throws SaslException {
public static TlsChannelBinding create(X509Certificate serverCertificate) throws ChannelBindingException {
try {
final byte[] prefix =
TlsChannelBindingType.TLS_SERVER_END_POINT.getName().concat(":").getBytes();
@@ -127,7 +126,7 @@ public static TlsChannelBinding create(X509Certificate serverCertificate) throws
System.arraycopy(hash, 0, cbData, prefix.length, hash.length);
return new TlsChannelBinding(TlsChannelBindingType.TLS_SERVER_END_POINT, cbData);
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
throw new SaslException("Cannot create TLS channel binding data", e);
throw new ChannelBindingException("Cannot create TLS channel binding data", e);
}
}

20 changes: 15 additions & 5 deletions src/java.naming/share/classes/com/sun/jndi/ldap/sasl/LdapSasl.java
Original file line number Diff line number Diff line change
@@ -42,7 +42,9 @@
import com.sun.jndi.ldap.Connection;
import com.sun.jndi.ldap.LdapClient;
import com.sun.jndi.ldap.LdapResult;
import com.sun.jndi.ldap.sasl.TlsChannelBinding.TlsChannelBindingType;
import sun.security.util.ChannelBindingException;
import sun.security.util.TlsChannelBinding;
import sun.security.util.TlsChannelBinding.TlsChannelBindingType;

/**
* Handles SASL support.
@@ -123,15 +125,23 @@ public static LdapResult saslBind(LdapClient clnt, Connection conn,
try {
// Prepare TLS Channel Binding data
if (conn.isTlsConnection()) {
TlsChannelBindingType cbType =
TlsChannelBinding.parseType(
TlsChannelBindingType cbType;
try {
cbType = TlsChannelBinding.parseType(
(String)env.get(TlsChannelBinding.CHANNEL_BINDING_TYPE));
} catch (ChannelBindingException e) {
throw new SaslException(e.getMessage());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about setting e as cause of new exception? In TlsChannelBinding.java the when the original exception was thrown (the 2nd throws) there was a cause.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

}
if (cbType == TlsChannelBindingType.TLS_SERVER_END_POINT) {
// set tls-server-end-point channel binding
X509Certificate cert = conn.getTlsServerCertificate();
if (cert != null) {
TlsChannelBinding tlsCB =
TlsChannelBinding.create(cert);
TlsChannelBinding tlsCB;
try {
tlsCB = TlsChannelBinding.create(cert);
} catch (ChannelBindingException e) {
throw new SaslException(e.getMessage());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a difference compared to line 133?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that was a mistake.

}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we call initCause here and above? I see that we do call initCause in NegotiatorImpl.java.
On the one hand it's better for diagnostic. On the other hand it exposes a module-internal exception class which is not great. Or maybe we should set the cause of the CBE as the cause of NamingException.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as the spec has not dictated what the cause should be, I don't care if the exception type is internal or not.

envProps = (Hashtable<String, Object>) env.clone();
envProps.put(TlsChannelBinding.CHANNEL_BINDING, tlsCB.getData());
} else {
3 changes: 0 additions & 3 deletions src/java.naming/share/classes/module-info.java
Original file line number Diff line number Diff line change
@@ -127,9 +127,6 @@
jdk.naming.dns,
jdk.naming.rmi;

exports com.sun.jndi.ldap.sasl to
java.security.jgss;

uses javax.naming.ldap.StartTlsResponse;
uses javax.naming.spi.InitialContextFactory;
uses javax.naming.ldap.spi.LdapDnsProvider;
Original file line number Diff line number Diff line change
@@ -27,7 +27,6 @@

import java.io.IOException;
import java.security.cert.Certificate;
import javax.security.sasl.SaslException;

import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSException;
@@ -43,7 +42,8 @@
import sun.security.jgss.GSSUtil;
import sun.security.jgss.HttpCaller;
import sun.security.jgss.krb5.internal.TlsChannelBindingImpl;
import com.sun.jndi.ldap.sasl.TlsChannelBinding;
import sun.security.util.ChannelBindingException;
import sun.security.util.TlsChannelBinding;

/**
* This class encapsulates all JAAS and JGSS API calls in a separate class
@@ -69,7 +69,7 @@ public class NegotiatorImpl extends Negotiator {
* <li>Creating GSSContext
* <li>A first call to initSecContext</ul>
*/
private void init(HttpCallerInfo hci) throws GSSException, SaslException {
private void init(HttpCallerInfo hci) throws GSSException, ChannelBindingException {
final Oid oid;

if (hci.scheme.equalsIgnoreCase("Kerberos")) {
@@ -122,7 +122,7 @@ private void init(HttpCallerInfo hci) throws GSSException, SaslException {
public NegotiatorImpl(HttpCallerInfo hci) throws IOException {
try {
init(hci);
} catch (GSSException | SaslException e) {
} catch (GSSException | ChannelBindingException e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two spaces before "e".

if (DEBUG) {
System.out.println("Negotiate support not initiated, will " +
"fallback to other scheme if allowed. Reason:");