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

net.luminis.tls.handshake.CertificateMessage Maven / Gradle / Ivy

/*
 * Copyright © 2018, 2019, 2020, 2021, 2022, 2023, 2024 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.alert.BadCertificateAlert;
import net.luminis.tls.alert.DecodeErrorException;
import net.luminis.tls.TlsConstants;

import java.io.ByteArrayInputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

// https://tools.ietf.org/html/rfc8446#section-4.4.2
public class CertificateMessage extends HandshakeMessage {

    private static final int MINIMUM_MESSAGE_SIZE = 1 + 3 + 1 + 3 + 3 + 2;
    private byte[] requestContext;
    private X509Certificate endEntityCertificate;
    private List certificateChain = new ArrayList<>();
    private byte[] raw;

    public CertificateMessage(X509Certificate certificate) {
        this.requestContext = new byte[0];
        endEntityCertificate = certificate;
        if (certificate != null) {
            certificateChain = List.of(certificate);
        }
        else {
            certificateChain = Collections.emptyList();
        }

        serialize();
    }

    /**
     * @param certificateChain     The server certificate must be the first in the list
     */
    public CertificateMessage(List certificateChain) {
        Objects.requireNonNull(certificateChain);
        if (certificateChain.size() < 1) {
            throw new IllegalArgumentException();
        }
        this.requestContext = new byte[0];
        endEntityCertificate = certificateChain.get(0);
        this.certificateChain = certificateChain;

        serialize();
    }

    public CertificateMessage(byte[] requestContext, X509Certificate certificate) {
        Objects.requireNonNull(certificate);
        this.requestContext = requestContext;
        endEntityCertificate = certificate;
        certificateChain = List.of(certificate);

        serialize();
    }

    public CertificateMessage() {
    }

    @Override
    public TlsConstants.HandshakeType getType() {
        return TlsConstants.HandshakeType.certificate;
    }

    public CertificateMessage parse(ByteBuffer buffer) throws DecodeErrorException, BadCertificateAlert {
        int startPosition = buffer.position();
        int remainingLength = parseHandshakeHeader(buffer, TlsConstants.HandshakeType.certificate, MINIMUM_MESSAGE_SIZE);

        try {
            int certificateRequestContextSize = buffer.get() & 0xff;
            if (certificateRequestContextSize > 0) {
                requestContext = new byte[certificateRequestContextSize];
                buffer.get(requestContext);
            }
            else {
                requestContext = new byte[0];
            }
            parseCertificateEntries(buffer);

            // Update state.
            raw = new byte[4 + remainingLength];
            buffer.position(startPosition);
            buffer.get(raw);

            return this;
        }
        catch (BufferUnderflowException notEnoughBytes) {
            throw new DecodeErrorException("message underflow");
        }
    }

    private int parseCertificateEntries(ByteBuffer buffer) throws BadCertificateAlert {
        int certificateListSize = ((buffer.get() & 0xff) << 16) | ((buffer.get() & 0xff) << 8) | (buffer.get() & 0xff);
        int remainingCertificateBytes = certificateListSize;
        int certCount = 0;

        while (remainingCertificateBytes > 0) {
            int certSize = ((buffer.get() & 0xff) << 16) | ((buffer.get() & 0xff) << 8) | (buffer.get() & 0xff);
            byte[] certificateData = new byte[certSize];
            buffer.get(certificateData);

            if (certSize > 0) {
                // https://tools.ietf.org/html/rfc8446#section-4.4.2
                // "If the corresponding certificate type extension ("server_certificate_type" or "client_certificate_type")
                // was not negotiated in EncryptedExtensions, or the X.509 certificate type was negotiated, then each
                // CertificateEntry contains a DER-encoded X.509 certificate."
                // This implementation does not support raw-public-key certificates, so the only type supported is X509.
                try {
                    CertificateFactory cf = CertificateFactory.getInstance("X.509");
                    X509Certificate certificate = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certificateData));
                    if (certCount == 0) {
                        // https://tools.ietf.org/html/rfc8446#section-4.4.2
                        // "The sender's certificate MUST come in the first CertificateEntry in the list. "
                        endEntityCertificate = certificate;
                    }
                    certificateChain.add(certificate);
                } catch (CertificateException e) {
                    throw new BadCertificateAlert("could not parse certificate");
                }
            }

            remainingCertificateBytes -= (3 + certSize);
            certCount++;
            int extensionsSize = buffer.getShort() & 0xffff;
            // https://tools.ietf.org/html/rfc8446#section-4.4.2
            // "Valid extensions for server certificates at present include the OCSP Status extension [RFC6066]
            // and the SignedCertificateTimestamp extension [RFC6962];..."
            // None of them is (yet) supported by this implementation.
            byte[] extensionData = new byte[extensionsSize];
            buffer.get(extensionData);
            remainingCertificateBytes -= (2 + extensionsSize);
        }
        return certCount;
    }

    private void serialize() {
        int nrOfCerts = certificateChain.size();
        List encodedCerts = certificateChain.stream()
                .map(cert -> encode(cert))
                .collect(Collectors.toList());

        int msgSize = 4 + 1 + 3 + nrOfCerts * (3 + 2) + encodedCerts.stream().mapToInt(bytes -> bytes.length).sum();
        ByteBuffer buffer = ByteBuffer.allocate(msgSize);

        buffer.putInt((TlsConstants.HandshakeType.certificate.value << 24) | (msgSize - 4));
        // cert request context size
        buffer.put((byte) 0x00);
        // certificate_list size (3 bytes)
        buffer.put((byte) 0); // assuming < 65535
        buffer.putShort((short) (msgSize - 4 - 1 - 3));

        encodedCerts.forEach(encodedCert -> {
            if (encodedCert.length > 0xfff0) {
                throw new RuntimeException("Certificate size not supported");
            }
            // certificate size
            buffer.put((byte) 0);
            buffer.putShort((short) encodedCert.length);
            // certificate
            buffer.put(encodedCert);
            // extensions size
            buffer.putShort((short) 0);
        });
        raw = buffer.array();
    }

    byte[] encode(X509Certificate certificate) {
        try {
            return certificate.getEncoded();
        } catch (CertificateEncodingException e) {
            // Impossible with valid certificate
            throw new RuntimeException(e);
        }
    }

    @Override
    public byte[] getBytes() {
        return raw;
    }

    public byte[] getRequestContext() {
        return requestContext;
    }

    public X509Certificate getEndEntityCertificate() {
        return endEntityCertificate;
    }

    public List getCertificateChain() {
        return certificateChain;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy