/*
* Copyright © 2019, 2020, 2021, 2022, 2023 Peter Doornbosch
*
* This file is part of Agent15, an implementation of TLS 1.3 in Java.
*
* Agent15 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.
*
* Agent15 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.tls.handshake;
import net.luminis.tls.*;
import net.luminis.tls.alert.DecodeErrorException;
import net.luminis.tls.alert.IllegalParameterAlert;
import net.luminis.tls.extension.Extension;
import java.nio.ByteBuffer;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ServerHello extends HandshakeMessage {
static byte[] HelloRetryRequest_SHA256 = new byte[] {
(byte) 0xCF, (byte) 0x21, (byte) 0xAD, (byte) 0x74, (byte) 0xE5, (byte) 0x9A, (byte) 0x61, (byte) 0x11,
(byte) 0xBE, (byte) 0x1D, (byte) 0x8C, (byte) 0x02, (byte) 0x1E, (byte) 0x65, (byte) 0xB8, (byte) 0x91,
(byte) 0xC2, (byte) 0xA2, (byte) 0x11, (byte) 0x16, (byte) 0x7A, (byte) 0xBB, (byte) 0x8C, (byte) 0x5E,
(byte) 0x07, (byte) 0x9E, (byte) 0x09, (byte) 0xE2, (byte) 0xC8, (byte) 0xA8, (byte) 0x33, (byte) 0x9C
};
private static final int MINIMAL_MESSAGE_LENGTH = 1 + 3 + 2 + 32 + 1 + 2 + 1 + 2;
private static SecureRandom secureRandom= new SecureRandom();
private byte[] raw;
private byte[] random;
private TlsConstants.CipherSuite cipherSuite;
private PublicKey serverSharedKey;
private short tlsVersion;
private List extensions = Collections.emptyList();
public ServerHello() {
}
public ServerHello(TlsConstants.CipherSuite cipher) {
this(cipher, Collections.emptyList());
}
public ServerHello(TlsConstants.CipherSuite cipher, List extensions) {
random = new byte[32];
secureRandom.nextBytes(random);
cipherSuite = cipher;
this.extensions = extensions;
int extensionsSize = extensions.stream().mapToInt(extension -> extension.getBytes().length).sum();
raw = new byte[1 + 3 + 2 + 32 + 1 + 2 + 1 + 2 + extensionsSize];
ByteBuffer buffer = ByteBuffer.wrap(raw);
// https://tools.ietf.org/html/rfc8446#section-4
// "uint24 length; /* remaining bytes in message */"
buffer.putInt((raw.length - 4) | 0x02000000);
buffer.putShort((short) 0x0303);
buffer.put(random);
buffer.put((byte) 0);
buffer.putShort(cipher.value);
buffer.put((byte) 0);
buffer.putShort((short) extensionsSize);
extensions.stream().forEach(extension -> buffer.put(extension.getBytes()));
}
@Override
public TlsConstants.HandshakeType getType() {
return TlsConstants.HandshakeType.server_hello;
}
public ServerHello parse(ByteBuffer buffer, int length) throws TlsProtocolException {
if (buffer.remaining() < MINIMAL_MESSAGE_LENGTH) {
throw new DecodeErrorException("Message too short");
}
buffer.getInt(); // Skip message type and 3 bytes length
int versionHigh = buffer.get();
int versionLow = buffer.get();
if (versionHigh != 3 || versionLow != 3)
throw new IllegalParameterAlert("Invalid version number (should be 0x0303)");
random = new byte[32];
buffer.get(random);
if (Arrays.equals(random, HelloRetryRequest_SHA256)) {
Logger.debug("HelloRetryRequest!");
}
int sessionIdLength = buffer.get() & 0xff;
if (sessionIdLength > 32) {
throw new DecodeErrorException("session id length exceeds 32");
}
byte[] legacySessionIdEcho = new byte[sessionIdLength];
buffer.get(legacySessionIdEcho); // TODO: must match, see 4.1.3
int cipherSuiteCode = buffer.getShort();
Arrays.stream(TlsConstants.CipherSuite.values())
.filter(item -> item.value == cipherSuiteCode)
.findFirst()
// https://tools.ietf.org/html/rfc8446#section-4.1.2
// "If the list contains cipher suites that the server does not recognize, support, or wish to use,
// the server MUST ignore those cipher suites and process the remaining ones as usual."
.ifPresent(item -> cipherSuite = item);
if (cipherSuite == null) {
throw new DecodeErrorException("Unknown cipher suite (" + cipherSuiteCode + ")");
}
int legacyCompressionMethod = buffer.get();
if (legacyCompressionMethod != 0) {
// https://www.davidwong.fr/tls13/#section-4.1.3
// "legacy_compression_method: A single byte which MUST have the value 0."
throw new DecodeErrorException("Legacy compression method must have the value 0");
}
extensions = EncryptedExtensions.parseExtensions(buffer, TlsConstants.HandshakeType.server_hello);
// Update state.
raw = new byte[length];
buffer.rewind();
buffer.get(raw);
return this;
}
@Override
public byte[] getBytes() {
return raw;
}
public byte[] getRandom() {
return random;
}
public TlsConstants.CipherSuite getCipherSuite() {
return cipherSuite;
}
public List getExtensions() {
return extensions;
}
}