Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5e8aadb
Add the DTLS 1.3 ack content type (RFC 9147 7.1), relates to github #…
mondain Sep 11, 2026
92a868c
Add DTLS 1.3 record number encryption mask primitive for AES and ChaC…
mondain Sep 11, 2026
db62603
Add DTLS 1.3 record protection to TlsAEADCipher via a new TlsDTLS13Ci…
mondain Sep 11, 2026
dda1d91
Add DTLS 1.3 unified header codec and sequence number reconstruction …
mondain Sep 11, 2026
d5a8bd2
Add DTLS 1.3 record layer send/receive paths and epoch switching (RFC…
mondain Sep 11, 2026
405626e
Use the "dtls13" HKDF-Expand-Label prefix for the DTLS 1.3 key schedu…
JonathanLennox Sep 14, 2026
5643cc6
Add the DTLS 1.3 reliable handshake and pack handshake flights into d…
mondain Sep 12, 2026
7c9e771
Offer DTLS 1.3 from the DTLS client, relates to github #1468.
mondain Sep 12, 2026
a46bc93
Complete the client half of a DTLS 1.3 handshake, relates to github #…
mondain Sep 12, 2026
57c8d20
Hash DTLS 1.3 handshake transcripts without the DTLS message header, …
mondain Sep 12, 2026
ca35039
Fail loudly if a DTLS handshake transcript is read while undecided, r…
mondain Sep 12, 2026
9408d20
Complete the server half of a DTLS 1.3 handshake, relates to github #…
mondain Sep 12, 2026
f9ddbff
Retain the DTLS 1.3 handshake epoch after the handshake completes, re…
mondain Sep 12, 2026
f415e49
Add an end-to-end DTLS 1.3 handshake test, relates to github #1468.
mondain Sep 12, 2026
8c3899d
Bound the DTLS 1.3 epochs retained after the handshake, relates to gi…
mondain Sep 12, 2026
c8ed172
Answer only a retransmission of the peer's final flight with an ACK, …
mondain Sep 12, 2026
af96b84
Test the DTLS 1.3 retransmitted-ACK path with a reordered final fligh…
mondain Sep 12, 2026
b440ece
Re-arm the DTLS 1.3 retained-epoch application data test, relates to …
mondain Sep 12, 2026
3d00eff
Add the DTLS 1.3 HelloRetryRequest and its cookie, relates to github …
mondain Sep 12, 2026
74b9e36
Refuse a DTLS 1.2 HelloVerifyRequest on a DTLS 1.3 handshake, relates…
mondain Sep 12, 2026
58122ec
Support DTLS 1.3 client authentication during the handshake, relates …
mondain Sep 12, 2026
f53fca4
Cover the DTLS 1.3 exporter, use_srtp and version fallback, relates t…
mondain Sep 12, 2026
f15ff71
Restore the DTLS 1.3 no-CertificateRequest and authenticated-flight c…
mondain Sep 12, 2026
696b934
Drop the DTLS 1.3 authenticated flight deterministically and prove it…
mondain Sep 12, 2026
db273e0
Gate the DTLS secure renegotiation notification on the selected versi…
mondain Sep 12, 2026
d92f674
Assert the HelloRetryRequest synthetic transcript bytes directly, rel…
mondain Sep 12, 2026
a7d4c99
Derive the retained handshake epoch and document the DTLS 1.3 epoch r…
mondain Sep 12, 2026
f22a101
Refuse to negotiate DTLS 1.3 behind a HelloVerifyRequest front end, r…
mondain Sep 12, 2026
e2098c6
Leave generate13EncryptedExtensions without an unused state parameter…
mondain Sep 12, 2026
49d7433
Assert the server's own refusal and the second ClientHello's message_…
mondain Sep 12, 2026
4437623
Complete the handshake in the DTLS 1.3 record layer test harness, rel…
mondain Sep 12, 2026
53b3b8c
Let TlsCryptoParameters resolve security parameters after the handsha…
mondain Sep 12, 2026
aa1581b
Route TlsCryptoParameters and Tls13NullCipher through their own acces…
mondain Sep 12, 2026
6b28cc6
Resolve DTLS 1.3 read epochs from one ordered live set, relates to gi…
mondain Sep 12, 2026
250c34a
Hold a derived DTLS 1.3 write epoch before installing it, relates to …
mondain Sep 12, 2026
be481b5
Own DTLS 1.3 post-handshake messages and acknowledgements, relates to…
mondain Sep 12, 2026
3246e5c
Install a new DTLS 1.3 read epoch on a peer KeyUpdate and retire the …
mondain Sep 12, 2026
50c19ff
Gate the new DTLS 1.3 write epoch on the KeyUpdate acknowledgement, r…
mondain Sep 12, 2026
bf8dc9c
Drive a DTLS 1.3 key update through the real client and server, relat…
mondain Sep 12, 2026
9225114
Correct the peer-keyed epoch reasoning and apply the branch review, r…
mondain Sep 12, 2026
b274dc3
State the peer-keyed nonce reuse precisely and document the lock orde…
mondain Sep 12, 2026
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
11 changes: 8 additions & 3 deletions tls/src/main/java/org/bouncycastle/tls/AbstractTlsContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,9 @@ else if (!TlsUtils.isValidUint16(context.length))
TlsHash exporterHash = getCrypto().createHash(cryptoHashAlgorithm);
byte[] emptyTranscriptHash = exporterHash.calculateHash();

TlsSecret exporterSecret = TlsUtils.deriveSecret(getSecurityParametersConnection(), secret, asciiLabel,
emptyTranscriptHash);
SecurityParameters sp = getSecurityParametersConnection();

TlsSecret exporterSecret = TlsUtils.deriveSecret(sp, secret, asciiLabel, emptyTranscriptHash);

byte[] exporterContext = emptyTranscriptHash;
if (context.length > 0)
Expand All @@ -299,8 +300,12 @@ else if (!TlsUtils.isValidUint16(context.length))
exporterContext = exporterHash.calculateHash();
}

// RFC 9147 5.9. DTLS 1.3 derives with the "dtls13" label prefix rather than TLS 1.3's "tls13 ".
boolean isDTLS = sp.getNegotiatedVersion().isDTLS();

return TlsCryptoUtils
.hkdfExpandLabel(exporterSecret, cryptoHashAlgorithm, "exporter", exporterContext, length).extract();
.hkdfExpandLabel(exporterSecret, cryptoHashAlgorithm, "exporter", exporterContext, length, isDTLS)
.extract();
}
catch (IOException e)
{
Expand Down
6 changes: 5 additions & 1 deletion tls/src/main/java/org/bouncycastle/tls/ContentType.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public class ContentType
public static final short application_data = 23;
public static final short heartbeat = 24;
public static final short tls12_cid = 25;
/** RFC 9147 7.1 */
public static final short ack = 26;

public static String getName(short contentType)
{
Expand All @@ -27,7 +29,9 @@ public static String getName(short contentType)
case heartbeat:
return "heartbeat";
case tls12_cid:
return "tls12_cid";
return "tls12_cid";
case ack:
return "ack";
default:
return "UNKNOWN";
}
Expand Down
162 changes: 162 additions & 0 deletions tls/src/main/java/org/bouncycastle/tls/DTLS13FlightTracker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package org.bouncycastle.tls;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

/**
* RFC 9147 7.2. Tracks which record carried which handshake fragment of the current outbound flight, so
* that an ACK retires exactly the fragments it covers and a retransmission resends only what is left.
* <p>
* A fragment may be registered more than once, under a different record number each time it is sent. It
* is acknowledged as soon as any one of those records is acknowledged.
* </p>
*/
class DTLS13FlightTracker
{
/** One handshake fragment of the current outbound flight. */
static final class Fragment
{
private final int messageSeq;
private final int fragmentOffset;
private final int fragmentLength;

/*
* RFC 9147 5.8.1. The epoch the fragment was first sent at, which is the epoch it must be retransmitted
* at: a DTLS 1.3 flight straddles an epoch change, and once the handshake completes the write epoch has
* moved on past the keys a peer that is still retransmitting can read. Negative until a record number
* is registered, which means nothing was actually written.
*/
private int epoch = -1;

boolean acknowledged = false;

Fragment(int messageSeq, int fragmentOffset, int fragmentLength)
{
this.messageSeq = messageSeq;
this.fragmentOffset = fragmentOffset;
this.fragmentLength = fragmentLength;
}

int getEpoch()
{
return epoch;
}

int getMessageSeq()
{
return messageSeq;
}

int getFragmentOffset()
{
return fragmentOffset;
}

int getFragmentLength()
{
return fragmentLength;
}

private String key()
{
return messageSeq + ":" + fragmentOffset + ":" + fragmentLength;
}
}

// record number -> Fragment
private Hashtable carriers = new Hashtable();
// fragment key -> Fragment, so the same fragment sent twice is one entry
private Hashtable fragments = new Hashtable();
// fragments in registration order, for deterministic retransmission
private Vector order = new Vector();

void reset()
{
carriers = new Hashtable();
fragments = new Hashtable();
order = new Vector();
}

void register(DTLSRecordNumber recordNumber, int messageSeq, int fragmentOffset, int fragmentLength)
{
Fragment fragment = new Fragment(messageSeq, fragmentOffset, fragmentLength);
String key = fragment.key();

Fragment existing = (Fragment)fragments.get(key);
if (null == existing)
{
fragments.put(key, fragment);
order.addElement(fragment);
existing = fragment;
}

if (null != recordNumber)
{
carriers.put(recordNumber, existing);

if (existing.epoch < 0)
{
existing.epoch = (int)recordNumber.getEpoch();
}
}
}

void acknowledge(Vector recordNumbers)
{
for (int i = 0; i < recordNumbers.size(); ++i)
{
Fragment fragment = (Fragment)carriers.get(recordNumbers.elementAt(i));
if (null != fragment)
{
fragment.acknowledged = true;
}
}
}

boolean isEmpty()
{
return order.isEmpty();
}

/**
* @return true if fragments were registered and every one of them has been acknowledged.
*/
boolean isComplete()
{
if (order.isEmpty())
{
return false;
}

Enumeration e = order.elements();
while (e.hasMoreElements())
{
if (!((Fragment)e.nextElement()).acknowledged)
{
return false;
}
}
return true;
}

/**
* @return the fragments not yet acknowledged, in the order they were first registered.
*/
Vector getOutstanding()
{
Vector outstanding = new Vector();

Enumeration e = order.elements();
while (e.hasMoreElements())
{
Fragment fragment = (Fragment)e.nextElement();
if (!fragment.acknowledged)
{
outstanding.addElement(fragment);
}
}

return outstanding;
}
}
Loading