I've been examining the code relating to discovery, feel free to clarify
if I've missed some important detail.
River currently has two discovery protocols,
Discovery V2 and V1.
V1 dates from Jini 1.0, during the discovery process, registrar proxy's
are stored in java.rmi.MarshalledObject and transferred over the network
in serialized form.
Discovery V1 utilised MarshalledObject, I haven't figured out a way
prevent Denial of service attacks during unmarshalling of the registrar
proxy for V1.
It is important to remember there are two occasions when we're
susceptible to DOS attack, firstly during deserialization of
MarshalledObject and secondly when we unmarshal the registrar proxy from
the MarshalledObject.
The question is of course what to do with Discovery V1 going forward?
Should we deprecate it? If so, how should we ensure that V2 is used by
default. DiscoveryConstraints?
Discovery V2 (Unicast), uses MarshalledInstance serial form, there's a
lot more we can do with V2, it can utilise multiple implementations via
DiscoveryFormatProvider.
Discovery V2 supports constraints and has a number of pluggable
implementations. It has SSL and Kerberos implementations.
In LookupDiscovery, Multicast is used first to discover a registrar and
create a MulticastAnnouncement which contains the following fields:
public class MulticastAnnouncement {
/** The announcement sequence number. */
protected long sequenceNumber;
/** The lookup service host. */
protected String host;
/** The lookup service listen port. */
protected int port;
/** The lookup service member groups. */
protected String[] groups;
/** The lookup service ID. */
protected ServiceID serviceID;
The information gathered during Multicasting is then used to for Unicast
to get the Registrar proxy (from a serialized MarshalledInstance).
DNS-SD could be used to provide the same information as
MulticastAnnouncement prior to performing Unicast discovery, this will
allow services to be dynamically discovered over the internet proper.
Multicast uses datagram packets with a recommended limit of 512 bytes, I
can't think of much opportunity for an attack on the client, it's a
different story for the Join protocol and the registrar but since
multicast is only utilised on private networks this shouldn't be a
problem. DNS-SD allows a range of addresses and ports to be utilised
dynamically, multiple registrars for a group might assist to protect
against packet storms, remembering that Multicast packets can't be
utilised in a packet storm over the internet, because of TTL routers etc.
We will have to take DOS into account when designing a DNS-SD equivalent
to Multicast.
Currently Unicast can be subjected to DOS, Notice in the Discovery V2
Unicast implementation methods below, we're reading more than just the
MarshalledInstance. Since Unicast is not limited by packet size, an
attacker can feed an unending stream of bytes (very large group string
arrays etc) to protect against Denial of Service we need to run Unicast
Discovery within an Executor thread that handles StackOverflowError's,
until we can return a UnicastResponse.
Excerpt from com.sun.jini.discovery.internal.PlainText:
/**
* Reads unicast response according to the net.jini.discovery.plaintext
* format.
*/
public static UnicastResponse readUnicastResponse(
InputStream in,
ClassLoader defaultLoader,
boolean verifyCodebaseIntegrity,
ClassLoader verifierLoader,
Collection context)
throws IOException, ClassNotFoundException
{
try {
DataInput din = new DataInputStream(in);
// read LUS host
String host = din.readUTF();
// read LUS port
int port = din.readUnsignedShort();
// read LUS member groups
String[] groups = new String[din.readInt()];
for (int i = 0; i < groups.length; i++) {
groups[i] = din.readUTF();
}
// read LUS proxy
MarshalledInstance mi =
(MarshalledInstance) new ObjectInputStream(in).readObject();
/* We have the opportunity to protect against unmarshalling
* attacks, this is the place to do it.
*/
ServiceRegistrar reg = (ServiceRegistrar) mi.get(
defaultLoader,
verifyCodebaseIntegrity,
verifierLoader,
context);
return new UnicastResponse(host, port, groups, reg);
} catch (RuntimeException e) {
throw new DiscoveryProtocolException(null, e);
}
}
Excerpt from com.sun.jini.discovery.plaintext.Client:
// documentation inherited from UnicastDiscoveryClient
public UnicastResponse doUnicastDiscovery(
Socket socket,
InvocationConstraints constraints,
ClassLoader defaultLoader,
ClassLoader verifierLoader,
Collection context,
ByteBuffer sent,
ByteBuffer received)
throws IOException, ClassNotFoundException
{
Plaintext.checkConstraints(constraints);
return Plaintext.readUnicastResponse(
new BufferedInputStream(socket.getInputStream()),
defaultLoader,
false,
null,
context);
}
Excerpt from com.sun.jini.discovery.internal.EndpointBasedClient:
/**
* Provides an abstract endpoint-based UnicastDiscoveryClient
implementation,
* which serves as a superclass for client-side providers for the
* net.jini.discovery.ssl and net.jini.discovery.kerberos unicast discovery
* formats.
*/
public abstract class EndpointBasedClient
extends EndpointBasedProvider implements UnicastDiscoveryClient
{
/**
* Constructs instance with the given format name and object providing
* access to non-public endpoint operations.
*/
protected EndpointBasedClient(String formatName,
EndpointInternals endpointInternals)
{
super(formatName, endpointInternals);
}
// documentation inherited from UnicastDiscoveryClient
public void checkUnicastDiscoveryConstraints(
InvocationConstraints constraints)
throws UnsupportedConstraintException
{
if (constraints == null) {
constraints = InvocationConstraints.EMPTY;
}
ConnectionInfo ci = getConnectionInfo(null, constraints);
checkIntegrity(endpointInternals.getUnfulfilledConstraints(ci.handle));
}
// documentation inherited from UnicastDiscoveryClient
public UnicastResponse doUnicastDiscovery(
Socket socket,
InvocationConstraints constraints,
ClassLoader defaultLoader,
ClassLoader verifierLoader,
Collection context,
ByteBuffer sent,
ByteBuffer received)
throws IOException, ClassNotFoundException
{
if (socket == null || sent == null || received == null) {
throw new NullPointerException();
}
if (constraints == null) {
constraints = InvocationConstraints.EMPTY;
}
ConnectionInfo ci = getConnectionInfo(socket, constraints);
Connection conn = ci.endpoint.connect(ci.handle);
try {
boolean integrity =
checkIntegrity(conn.getUnfulfilledConstraints(ci.handle));
OutputStream out =
new BufferedOutputStream(conn.getOutputStream());
conn.writeRequestData(ci.handle, out);
out.write(calcHandshakeHash(sent, received));
out.flush();
InputStream in = new BufferedInputStream(conn.getInputStream());
IOException e = conn.readResponseData(ci.handle, in);
if (e != null) {
throw e;
}
return Plaintext.readUnicastResponse(
in, defaultLoader, integrity, verifierLoader, context);
} finally {
conn.close();
}
}
It looks as though Kerberos and SSL Discovery provides guarantees for
integrity and authentication. So is MarshalledInstance really subject
to DOS attacks during unmarshalling in this case? Can we use DNS-SD
with Secure protocols? For the registrar proxy, it would seem that it
isn't subject to DOS attacks if Kerberos or SSL Discovery is used.
However if we're using the Registrar to lookup other services, we might
not have the same trust relationship with them as we do the Registrar,
so in that case it justifies requiring a service authenticate prior to
unmarshalling it's proxy from MarshalledInstance.
There also seems to be a case still for Codebase services, we can
determine codebase service trust prior to downloading a codebase from
it. This wouldn't prevent Maven from being utilised for codebase
provisioning, although we still need to determine a suitable way to
utilise Maven.
If we're using Kerberos and SSL discovery, from the Jini Discovery and
Join Specification:
The net.jini.discovery.ssl Format
The net.jini.discovery.ssl format specifies an encoding in which unicast
response data is sent across a TLS/SSL (Transport Layer Security/Secure
Socket Layer) connection. Encryption, authentication, and/or integrity
protection may be provided by the underlying TLS/SSL connection,
depending on the selected cipher suite. This discovery format does not
apply to multicast requests or announcements. The discovery format ID
for this format is 1816474798606646324. TLS/SSL is specified in /RFC
2246 <http://tools.ietf.org/html/rfc2246>/.
Unicast Responses
In the net.jini.discovery.ssl format, transmission of unicast response
data involves three steps:
1. The discovering entity establishes a TLS/SSL connection between
itself and the lookup service on top of the TCP connection over
which it sent the unicast request.
2. The discovering entity transmits a hash of all of the data it has
sent and received over the connection so far.
3. The lookup service also computes a hash of all of the data it has
received and sent over the connection so far. If this hash value
matches that sent by the discovering entity, then the lookup
service sends the unicast response data encoded as described later
in this section. If the hash does not match, the lookup service
terminates the connection.
The hash sent by the discovering entity to the lookup service is
computed as follows: first, a sequence of bytes is assembled consisting
of the entire unicast request immediately followed by the portion of the
unicast response that is not discovery format data--the initial protocol
version followed by the selected format ID, as specified in Section
DJ.2.6.7, "Protocol Version 2 Response Format"
<http://www.jini.org/wiki/Jini_Discovery_and_Join_Specification#Protocol_Version_2_Response_Format>.
This sequence of bytes is then used as input to the SHA-1 hash function;
the 160-bit result is sent in its entirety in big-endian order as the
hash value. The SHA-1 hash function is specified in /Federal Information
Processing Standards Publication (FIPS PUB) 180-1/.
The unicast response data sent by the lookup service, if the hashes
match, consists of the following values encoded (on top of the secured
TLS/SSL connection) according to the net.jini.discovery.plaintext
discovery format for unicast responses, specified in Section DJ.3.1.3
<http://www.jini.org/wiki/Jini_Discovery_and_Join_Specification#Unicast_Responses>:
* Unicast discovery host
* Unicast discovery port
* Member groups
* Registrar proxy
The net.jini.discovery.kerberos Format
The net.jini.discovery.kerberos format specifies an encoding in which
unicast response data is sent across a connection secured using the
Kerberos Version 5 GSS-API Mechanism, defined in /RFC 1964
<http://tools.ietf.org/html/rfc1964>/. Kerberos provides authentication;
encryption and integrity protection of the transmitted data may also be
provided depending on the GSS-API context in use for the connection.
This discovery format does not apply to multicast requests or
announcements. The discovery format ID for this format is
5724038453852586603. The Kerberos network authentication protocol is
defined in /RFC 1510 <http://tools.ietf.org/html/rfc1510>/; the GSS-API
is defined in /RFC 2743 <http://tools.ietf.org/html/rfc2743>/.
Unicast Responses
Transmission of unicast response data in the net.jini.discovery.kerberos
discovery format is similar to the net.jini.discovery.ssl format, except
that the underlying connection is secured using the Kerberos Version 5
GSS-API Mechanism instead of TLS/SSL:
1. The discovering entity establishes a GSS-API context for
communicating with the lookup service, whose Kerberos principal it
knows in advance. It then uses GSS-API tokens obtained from the
established context to wrap all subsequent data sent to the lookup
service (over the TCP connection on which the unicast request was
originally transmitted). Also, all data subsequently received over
the connection is unwrapped using the GSS-API.
2. The discovering entity transmits a hash of all of the data it has
sent and received over the connection so far. Multiple GSS-API
tokens may be used to convey the hash. The hash is computed as
specified in Section DJ.3.4.1
<http://www.jini.org/wiki/Jini_Discovery_and_Join_Specification#Unicast_Responses_2>
3. The lookup service also computes a hash of all of the data it has
received and sent over the connection so far. If this hash value
matches that sent by the discovering entity, then the lookup
service sends the unicast response data, encoded as specified in
Section DJ.3.4.1
<http://www.jini.org/wiki/Jini_Discovery_and_Join_Specification#Unicast_Responses_2>,
over the TCP connection to the discovering entity, using GSS-API
tokens as described in step 1. Multiple GSS-API tokens may be used
to convey the unicast response data.
Cheers,
Peter.