Skip to content

Commit

Permalink
DTLS: Initial implementation of HelloVerifyRequest support
Browse files Browse the repository at this point in the history
  • Loading branch information
peterdettman committed Feb 18, 2019
1 parent 013b518 commit 94e6acf
Show file tree
Hide file tree
Showing 16 changed files with 617 additions and 156 deletions.
163 changes: 163 additions & 0 deletions tls/src/main/java/org/bouncycastle/tls/ClientHello.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package org.bouncycastle.tls;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;

import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.io.TeeInputStream;

public class ClientHello
{
private final ProtocolVersion clientVersion;
private final byte[] random;
private final byte[] sessionID;
private final byte[] cookie;
private final int[] cipherSuites;
private final Hashtable extensions;

public ClientHello(ProtocolVersion clientVersion, byte[] random, byte[] sessionID, byte[] cookie,
int[] cipherSuites, Hashtable extensions)
{
this.clientVersion = clientVersion;
this.random = random;
this.sessionID = sessionID;
this.cookie = cookie;
this.cipherSuites = cipherSuites;
this.extensions = extensions;
}

public int[] getCipherSuites()
{
return cipherSuites;
}

public ProtocolVersion getClientVersion()
{
return clientVersion;
}

public byte[] getCookie()
{
return cookie;
}

public Hashtable getExtensions()
{
return extensions;
}

public byte[] getRandom()
{
return random;
}

public byte[] getSessionID()
{
return sessionID;
}

/**
* Encode this {@link ClientHello} to an {@link OutputStream}.
*
* @param output
* the {@link OutputStream} to encode to.
* @throws IOException
*/
public void encode(TlsContext context, OutputStream output) throws IOException
{
TlsUtils.writeVersion(clientVersion, output);

output.write(random);

TlsUtils.writeOpaque8(sessionID, output);

if (null != cookie)
{
TlsUtils.writeOpaque8(cookie, output);
}

TlsUtils.writeUint16ArrayWithUint16Length(cipherSuites, output);

TlsUtils.writeUint8ArrayWithUint8Length(new short[]{ CompressionMethod._null }, output);

TlsProtocol.writeExtensions(output, extensions);
}

/**
* Parse a {@link ClientHello} from a {@link ByteArrayInputStream}.
*
* @param messageInput
* the {@link ByteArrayInputStream} to parse from.
* @param dtlsOutput
* for DTLS this should be non-null; the input is copied to this
* {@link OutputStream}, minus the cookie field.
* @return a {@link Certificate} object.
* @throws IOException
*/
public static ClientHello parse(ByteArrayInputStream messageInput, OutputStream dtlsOutput)
throws IOException
{
InputStream input = messageInput;
if (null != dtlsOutput)
{
input = new TeeInputStream(input, dtlsOutput);
}

ProtocolVersion clientVersion = TlsUtils.readVersion(input);

byte[] random = TlsUtils.readFully(32, input);

byte[] sessionID = TlsUtils.readOpaque8(input, 0, 32);

byte[] cookie = null;
if (null != dtlsOutput)
{
/*
* RFC 6347 This specification increases the cookie size limit to 255 bytes for greater
* future flexibility. The limit remains 32 for previous versions of DTLS.
*/
int maxCookieLength = ProtocolVersion.DTLSv12.isEqualOrEarlierVersionOf(clientVersion) ? 255 : 32;

cookie = TlsUtils.readOpaque8(messageInput, 0, maxCookieLength);
}

int cipher_suites_length = TlsUtils.readUint16(input);
if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0)
{
throw new TlsFatalAlert(AlertDescription.decode_error);
}

/*
* NOTE: "If the session_id field is not empty (implying a session resumption request) this
* vector must include at least the cipher_suite from that session."
*/
int[] cipherSuites = TlsUtils.readUint16Array(cipher_suites_length / 2, input);

int compression_methods_length = TlsUtils.readUint8(input);
if (compression_methods_length < 1)
{
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
}

short[] compressionMethods = TlsUtils.readUint8Array(compression_methods_length, input);
if (!Arrays.contains(compressionMethods, CompressionMethod._null))
{
throw new TlsFatalAlert(AlertDescription.handshake_failure);
}

Hashtable extensions = null;
if (messageInput.available() > 0)
{
byte[] extBytes = TlsUtils.readOpaque16(input);

TlsProtocol.assertEmpty(messageInput);

extensions = TlsProtocol.readExtensionsData(extBytes);
}

return new ClientHello(clientVersion, random, sessionID, cookie, cipherSuites, extensions);
}
}
24 changes: 6 additions & 18 deletions tls/src/main/java/org/bouncycastle/tls/DTLSClientProtocol.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ protected DTLSTransport clientHandshake(ClientHandshakeState state, DTLSRecordLa
throws IOException
{
SecurityParameters securityParameters = state.clientContext.getSecurityParametersHandshake();
DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.clientContext, recordLayer);
DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.clientContext, recordLayer, null);

byte[] clientHelloBody = generateClientHello(state);

Expand Down Expand Up @@ -119,7 +119,7 @@ protected DTLSTransport clientHandshake(ClientHandshakeState state, DTLSRecordLa
byte[] cookie = processHelloVerifyRequest(state, serverMessage.getBody());
byte[] patched = patchClientHelloWithCookie(clientHelloBody, cookie);

handshake.resetHandshakeMessagesDigest();
handshake.resetAfterHelloVerifyRequest();
handshake.sendMessage(HandshakeType.client_hello, patched);

serverMessage = handshake.receiveMessage();
Expand Down Expand Up @@ -471,23 +471,11 @@ protected byte[] generateClientHello(ClientHandshakeState state)



ByteArrayOutputStream buf = new ByteArrayOutputStream();

TlsUtils.writeVersion(legacy_version, buf);

buf.write(securityParameters.getClientRandom());

TlsUtils.writeOpaque8(session_id, buf);

// Cookie
TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf);

TlsUtils.writeUint16ArrayWithUint16Length(state.offeredCipherSuites, buf);

TlsUtils.writeUint8ArrayWithUint8Length(new short[]{ CompressionMethod._null }, buf);

TlsProtocol.writeExtensions(buf, state.clientExtensions);
ClientHello clientHello = new ClientHello(legacy_version, securityParameters.getClientRandom(), session_id,
TlsUtils.EMPTY_BYTES, state.offeredCipherSuites, state.clientExtensions);

ByteArrayOutputStream buf = new ByteArrayOutputStream();
clientHello.encode(state.clientContext, buf);
return buf.toByteArray();
}

Expand Down
82 changes: 78 additions & 4 deletions tls/src/main/java/org/bouncycastle/tls/DTLSRecordLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import org.bouncycastle.tls.crypto.TlsCipher;
import org.bouncycastle.tls.crypto.TlsNullNullCipher;
import org.bouncycastle.util.Arrays;

class DTLSRecordLayer
implements DatagramTransport
Expand All @@ -13,6 +14,60 @@ class DTLSRecordLayer
private static final long TCP_MSL = 1000L * 60 * 2;
private static final long RETRANSMIT_TIMEOUT = TCP_MSL * 2;

static byte[] receiveClientHelloRecord(byte[] data, int dataOff, int dataLen) throws IOException
{
if (dataLen < RECORD_HEADER_LENGTH)
{
return null;
}

short contentType = TlsUtils.readUint8(data, dataOff + 0);
if (ContentType.handshake != contentType)
{
return null;
}

ProtocolVersion version = TlsUtils.readVersion(data, dataOff + 1);
if (!ProtocolVersion.DTLSv10.isEqualOrEarlierVersionOf(version))
{
return null;
}

int epoch = TlsUtils.readUint16(data, dataOff + 3);
if (0 != epoch)
{
return null;
}

// TODO Are there any constraints on this?
long sequenceNumber = TlsUtils.readUint48(data, dataOff + 5);
System.out.println("Record sequence in ClientHello: " + sequenceNumber);

int length = TlsUtils.readUint16(data, dataOff + 11);
if (dataLen != RECORD_HEADER_LENGTH + length)
{
return null;
}

return Arrays.copyOfRange(data, dataOff + RECORD_HEADER_LENGTH, dataOff + dataLen);
}

static void sendHelloVerifyRequestRecord(DatagramSender sender, byte[] message) throws IOException
{
TlsUtils.checkUint16(message.length);

byte[] record = new byte[RECORD_HEADER_LENGTH + message.length];
TlsUtils.writeUint8(ContentType.handshake, record, 0);
TlsUtils.writeVersion(ProtocolVersion.DTLSv10, record, 1);
TlsUtils.writeUint16(0, record, 3);
TlsUtils.writeUint48(0, record, 5);
TlsUtils.writeUint16(message.length, record, 11);

System.arraycopy(message, 0, record, RECORD_HEADER_LENGTH, message.length);

sender.send(record, 0, record.length);
}

private final DatagramTransport transport;
private final TlsPeer peer;

Expand Down Expand Up @@ -46,6 +101,11 @@ class DTLSRecordLayer
setPlaintextLimit(MAX_FRAGMENT_LENGTH);
}

void resetAfterHelloVerifyRequest()
{
currentEpoch.getReplayWindow().reset();
}

void setPlaintextLimit(int plaintextLimit)
{
this.plaintextLimit = plaintextLimit;
Expand Down Expand Up @@ -218,12 +278,26 @@ else if (type == ContentType.handshake && retransmitEpoch != null

if (readVersion != null && !readVersion.equals(version))
{
continue;
/*
* Special-case handling for retransmitted ClientHello records.
*
* TODO Revisit how 'readVersion' works, since this is quite awkward.
*/
boolean isClientHelloFragment =
getReadEpoch() == 0
&& length > 0
&& ContentType.handshake == type
&& HandshakeType.client_hello == TlsUtils.readUint8(record, RECORD_HEADER_LENGTH);

if (!isClientHelloFragment)
{
continue;
}
}

byte[] plaintext = recordEpoch.getCipher().decodeCiphertext(
getMacSequenceNumber(recordEpoch.getEpoch(), seq), type, record, RECORD_HEADER_LENGTH,
received - RECORD_HEADER_LENGTH);
long macSeqNo = getMacSequenceNumber(recordEpoch.getEpoch(), seq);
byte[] plaintext = recordEpoch.getCipher().decodeCiphertext(macSeqNo, type, record,
RECORD_HEADER_LENGTH, length);

recordEpoch.getReplayWindow().reportAuthenticated(seq);

Expand Down
Loading

0 comments on commit 94e6acf

Please sign in to comment.