All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.luminis.quic.packet.RetryPacket Maven / Gradle / Ivy

There is a newer version: 0.9.1
Show newest version
/*
 * 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);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy