net.luminis.quic.packet.RetryPacket Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kwik Show documentation
Show all versions of kwik Show documentation
A QUIC implementation in Java
/*
* Copyright © 2019, 2020, 2021, 2022, 2023 Peter Doornbosch
*
* This file is part of Kwik, an implementation of the QUIC protocol in Java.
*
* Kwik is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Kwik 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 Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
package net.luminis.quic.packet;
import net.luminis.quic.crypto.Aead;
import net.luminis.quic.log.Logger;
import net.luminis.quic.core.*;
import net.luminis.tls.util.ByteUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.Arrays;
/**
* See https://tools.ietf.org/html/draft-ietf-quic-transport-25#section-17.2.5
*/
public class RetryPacket extends QuicPacket {
// https://www.rfc-editor.org/rfc/rfc9000.html#name-retry-packet
// "a Retry packet uses a long packet header with a type value of 0x03."
private static int V1_type = 3;
// https://www.rfc-editor.org/rfc/rfc9369.html#name-long-header-packet-types
// "Retry: 0b00"
private static int V2_type = 0;
private static final int RETRY_INTEGRITY_TAG_LENGTH = 16; // The Retry Integrity Tag is 128 bits.
// https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.8
// "The secret key, K, is 128 bits equal to 0xccce187ed09a09d05728155a6cb96be1."
private static final byte[] SECRET_KEY = new byte[] {
(byte) 0xcc, (byte) 0xce, (byte) 0x18, (byte) 0x7e, (byte) 0xd0, (byte) 0x9a, (byte) 0x09, (byte) 0xd0,
(byte) 0x57, (byte) 0x28, (byte) 0x15, (byte) 0x5a, (byte) 0x6c, (byte) 0xb9, (byte) 0x6b, (byte) 0xe1 };
// https://www.rfc-editor.org/rfc/rfc9001.html#name-retry-packet-integrity
// "The secret key, K, is 128 bits equal to 0xbe0c690b9f66575a1d766b54e368c84e."
private static final byte[] SECRET_KEY_V1 = new byte[] {
(byte) 0xbe, (byte) 0x0c, (byte) 0x69, (byte) 0x0b, (byte) 0x9f, (byte) 0x66, (byte) 0x57, (byte) 0x5a,
(byte) 0x1d, (byte) 0x76, (byte) 0x6b, (byte) 0x54, (byte) 0xe3, (byte) 0x68, (byte) 0xc8, (byte) 0x4e };
// https://www.rfc-editor.org/rfc/rfc9369.html#name-retry-integrity-tag
// "The key and nonce used for the Retry Integrity Tag (Section 5.8 of [QUIC-TLS]) change to:
// (...)
// key = 0x8fb4b01b56ac48e260fbcbcead7ccc92
private static final byte[] SECRET_KEY_V2 = new byte[] {
(byte) 0x8f, (byte) 0xb4, (byte) 0xb0, (byte) 0x1b, (byte) 0x56, (byte) 0xac, (byte) 0x48, (byte) 0xe2,
(byte) 0x60, (byte) 0xfb, (byte) 0xcb, (byte) 0xce, (byte) 0xad, (byte) 0x7c, (byte) 0xcc, (byte) 0x92 };
// https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.8
// "The nonce, N, is 96 bits equal to 0xe54930f97f2136f0530a8c1c."
private static final byte[] NONCE = new byte[] {
(byte) 0xe5, (byte) 0x49, (byte) 0x30, (byte) 0xf9, (byte) 0x7f, (byte) 0x21, (byte) 0x36, (byte) 0xf0,
(byte) 0x53, (byte) 0x0a, (byte) 0x8c, (byte) 0x1c };
// https://www.rfc-editor.org/rfc/rfc9001.html#name-retry-packet-integrity
// "The nonce, N, is 96 bits equal to 0x461599d35d632bf2239825bb."
private static final byte[] NONCE_V1 = new byte[] {
(byte) 0x46, (byte) 0x15, (byte) 0x99, (byte) 0xd3, (byte) 0x5d, (byte) 0x63, (byte) 0x2b, (byte) 0xf2,
(byte) 0x23, (byte) 0x98, (byte) 0x25, (byte) 0xbb };
// https://www.rfc-editor.org/rfc/rfc9369.html#name-retry-integrity-tag
// "The key and nonce used for the Retry Integrity Tag (Section 5.8 of [QUIC-TLS]) change to:
// (...)
// nonce = 0xd86969bc2d7c6d9990efb04a
private static final byte[] NONCE_V2 = new byte[] {
(byte) 0xd8, (byte) 0x69, (byte) 0x69, (byte) 0xbc, (byte) 0x2d, (byte) 0x7c, (byte) 0x6d, (byte) 0x99,
(byte) 0x90, (byte) 0xef, (byte) 0xb0, (byte) 0x4a };
// Minimal length for a valid packet: type version dcid len dcid scid len scid retry-integrety-tag
private static int MIN_PACKET_LENGTH = 1 + 4 + 1 + 0 + 1 + 0 + 16;
private byte[] sourceConnectionId;
private byte[] originalDestinationConnectionId;
private byte[] retryToken;
private byte[] rawPacketData;
private byte[] retryIntegrityTag;
public static boolean isRetry(int type, Version quicVersion) {
if (quicVersion.isV2()) {
return type == 0;
}
else {
return type == 3;
}
}
public RetryPacket(Version quicVersion) {
this.quicVersion = quicVersion;
}
public RetryPacket(Version quicVersion, byte[] sourceConnectionId, byte[] destinationConnectionId, byte[] originalDestinationConnectionId, byte[] retryToken) {
this.quicVersion = quicVersion;
this.sourceConnectionId = sourceConnectionId;
this.destinationConnectionId = destinationConnectionId;
this.originalDestinationConnectionId = originalDestinationConnectionId;
this.retryToken = retryToken;
this.rawPacketData = new byte[1 + 4 + 1 + destinationConnectionId.length + 1 + sourceConnectionId.length +
retryToken.length + RETRY_INTEGRITY_TAG_LENGTH];
}
@Override
public void parse(ByteBuffer buffer, Aead aead, long largestPacketNumber, Logger log, int sourceConnectionIdLength) throws DecryptionException, InvalidPacketException {
log.debug("Parsing " + this.getClass().getSimpleName());
if (buffer.remaining() < MIN_PACKET_LENGTH) {
throw new InvalidPacketException();
}
packetSize = buffer.remaining();
rawPacketData = new byte[packetSize];
buffer.mark();
buffer.get(rawPacketData);
buffer.reset();
byte flags = buffer.get();
boolean matchingVersion = Version.parse(buffer.getInt()).equals(this.quicVersion);
if (! matchingVersion) {
// https://tools.ietf.org/html/draft-ietf-quic-transport-27#section-5.2
// "... packets are discarded if they indicate a different protocol version than that of the connection..."
throw new InvalidPacketException();
}
int dstConnIdLength = buffer.get();
if (buffer.remaining() < dstConnIdLength + 1 + RETRY_INTEGRITY_TAG_LENGTH) {
throw new InvalidPacketException();
}
destinationConnectionId = new byte[dstConnIdLength];
buffer.get(destinationConnectionId);
int srcConnIdLength = buffer.get();
if (buffer.remaining() < srcConnIdLength) {
throw new InvalidPacketException();
}
sourceConnectionId = new byte[srcConnIdLength];
buffer.get(sourceConnectionId);
log.debug("Destination connection id", destinationConnectionId);
log.debug("Source connection id", sourceConnectionId);
if (buffer.remaining() < RETRY_INTEGRITY_TAG_LENGTH) {
throw new InvalidPacketException();
}
int retryTokenLength = buffer.remaining() - RETRY_INTEGRITY_TAG_LENGTH;
retryToken = new byte[retryTokenLength];
buffer.get(retryToken);
retryIntegrityTag = new byte[RETRY_INTEGRITY_TAG_LENGTH];
buffer.get(retryIntegrityTag);
}
/**
* Validates the Retry Integrity Tag that is carried by this packet.
* @param originalDestinationConnectionId
* @return
*/
public boolean validateIntegrityTag(byte[] originalDestinationConnectionId) {
return Arrays.equals(computeIntegrityTag(originalDestinationConnectionId), retryIntegrityTag);
}
@Override
public EncryptionLevel getEncryptionLevel() {
return EncryptionLevel.Initial;
}
@Override
public PnSpace getPnSpace() {
return null;
}
@Override
public Long getPacketNumber() {
// Retry Packet doesn't have a packet number
return null;
}
@Override
public int estimateLength(int additionalPayload) {
throw new NotYetImplementedException();
}
@Override
public PacketProcessor.ProcessResult accept(PacketProcessor processor, Instant time) {
return processor.process(this, time);
}
@Override
public byte[] generatePacketBytes(Aead aead) {
packetSize = 1 + 4 + 1 + destinationConnectionId.length + 1 + sourceConnectionId.length + retryToken.length + 16;
ByteBuffer buffer = ByteBuffer.allocate(packetSize);
byte flags = (byte) (0b1100_0000 | (getPacketType() << 4));
buffer.put(flags);
buffer.put(quicVersion.getBytes());
buffer.put((byte) destinationConnectionId.length);
buffer.put(destinationConnectionId);
buffer.put((byte) sourceConnectionId.length);
buffer.put(sourceConnectionId);
buffer.put(retryToken);
rawPacketData = buffer.array();
buffer.put(computeIntegrityTag(originalDestinationConnectionId));
return buffer.array();
}
private int getPacketType() {
if (quicVersion.isV2()) {
return (byte) V2_type;
}
else {
return (byte) V1_type;
}
}
private byte[] computeIntegrityTag(byte[] originalDestinationConnectionId) {
ByteBuffer pseudoPacket = ByteBuffer.allocate(1 + originalDestinationConnectionId.length + 1 + 4 +
1 + destinationConnectionId.length + 1 + sourceConnectionId.length + retryToken.length);
pseudoPacket.put((byte) originalDestinationConnectionId.length);
pseudoPacket.put(originalDestinationConnectionId);
pseudoPacket.put(rawPacketData, 0, rawPacketData.length - RETRY_INTEGRITY_TAG_LENGTH);
try {
// https://tools.ietf.org/html/draft-ietf-quic-tls-25#section-5.8
// "The Retry Integrity Tag is a 128-bit field that is computed as the output of AEAD_AES_128_GCM [AEAD]..."
SecretKeySpec secretKey = new SecretKeySpec(quicVersion.isV1()? SECRET_KEY_V1: quicVersion.isV2()? SECRET_KEY_V2: SECRET_KEY, "AES");
String AES_GCM_NOPADDING = "AES/GCM/NoPadding";
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, quicVersion.isV1()? NONCE_V1: quicVersion.isV2()? NONCE_V2: NONCE);
Cipher aeadCipher = Cipher.getInstance(AES_GCM_NOPADDING);
aeadCipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
// https://tools.ietf.org/html/draft-ietf-quic-tls-25#section-5.8
// "The associated data, A, is the contents of the Retry Pseudo-Packet"
aeadCipher.updateAAD(pseudoPacket.array());
// https://tools.ietf.org/html/draft-ietf-quic-tls-25#section-5.8
// "The plaintext, P, is empty."
byte[] cipherText = aeadCipher.doFinal(new byte[0]);
return cipherText;
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
// Inappropriate runtime environment
throw new QuicRuntimeException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
// Programming error
throw new RuntimeException();
}
}
@Override
public boolean canBeAcked() {
// https://tools.ietf.org/html/draft-ietf-quic-transport-18#section-17.2.5
// "A Retry packet does not include a packet number and cannot be explicitly acknowledged by a client."
return false;
}
@Override
public boolean isInflightPacket() {
return false;
}
@Override
public boolean isAckEliciting() {
return false;
}
@Override
public boolean isAckOnly() {
return false;
}
public byte[] getRetryToken() {
return retryToken;
}
public byte[] getSourceConnectionId() {
return sourceConnectionId;
}
@Override
public String toString() {
return "Packet "
+ getEncryptionLevel().name().charAt(0) + "|"
+ "-" + "|"
+ "R" + "|"
+ packetSize + "|"
+ " Retry Token (" + retryToken.length + "): " + ByteUtils.bytesToHex(retryToken);
}
}