aboutsummaryrefslogtreecommitdiff
/*
 * InspIRCd -- Internet Relay Chat Daemon
 *
 *   Copyright (C) 2026 Sadie Powell <sadie@witchery.services>
 *
 * This file is part of InspIRCd.  InspIRCd is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation, version 2.
 *
 * This program 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 General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


#pragma once

#include "iohook.h"

namespace TLS
{
	class API;
	class APIBase;
	class Certificate;
	class IOHook;

	/** A shared pointer to a TLS certificate. */
	using CertificatePtr = std::shared_ptr<Certificate>;

	/** A list of TLS certificate fingerprints. */
	using FingerprintList = std::vector<std::string>;

	/** If a socket contains a TLS hook then returns the hook; otherwise, returns nullptr. */
	inline IOHook* GetHook(StreamSocket* sock);
}

/** Encapsulates information about a TLS certificate. */
class TLS::Certificate
	: public std::enable_shared_from_this<TLS::Certificate>
{
protected:
	Certificate() = default;

	/** A list of zero or more TLS certificate fingerprints. */
	TLS::FingerprintList fingerprints;

	/** The Distinguished Name of the TLS certificate. */
	std::string dn;

	/** If non-empty then the error which occurred whilst creating this \ref TLS::Certificate. */
	std::string error;

	/** The Distinguished Name of the TLS certificate's issuer. */
	std::string issuer_dn;

	/** The time at which the TLS certificate was activated. */
	time_t activation = 0;

	/** The time at which the TLS certificate will expire. */
	time_t expiration = 0;

	/** Whether the signer of the TLS certificate is known. */
	bool known_signer:1 = false;

	/* Whether the TLS certificate has been revoked. */
	bool revoked:1 = false;

	/** Whether the TLS certificate is trusted by the system certificate store. */
	bool trusted:1 = false;

	/** Whether the TLS certificate is valid. */
	bool valid:1 = false;

public:
	/** Retrieves the time at which the TLS certificate was activated, or 0 if the peer did not
	 * provide a TLS certificate.
	 */
	inline auto GetActivationTime() const { return this->activation; }

	/** Retrieves the Distinguished Name of the TLS certificate. */
	inline const auto& GetDN() const { return this->dn; }

	/** If an error occurred whilst creating this \ref TLS::Certificate then the error; otherwise,
	 * an empty string. Call \ref HasError instead if you just want to check if an error occurred.
	 */
	inline const auto& GetError() const { return this->error; }

	/** Retrieves the time at which the TLS certificate will expire, or 0 if the peer did not
	 * provide a TLS certificate.
	 */
	inline auto GetExpirationTime() const { return this->expiration; }

	/** Retrieves all available fingerprints. */
	inline const auto& GetFingerprints() const { return this->fingerprints; }

	/** Retrieves the primary fingerprint. */
	inline auto GetFingerprint() const
	{
		return GetFingerprints().empty() ? "" : GetFingerprints().front();
	}

	/** Retrieves the Distinguished Name of the TLS certificate's issuer. */
	inline const auto& GetIssuerDN() const { return this->issuer_dn; }

	/** Whether an error occurred whilst creating this \ref TLS::Certificate. */
	inline auto HasError() const { return !this->error.empty(); }

	/** Determines whether the signer of the TLS certificate is known. */
	inline auto IsKnownSigner() const { return this->known_signer; }

	/* Determines whether the TLS certificate has been revoked. */
	inline auto IsRevoked() const { return this->revoked; }

	/** Determines whether the TLS certificate is trusted by the system certificate store. */
	inline auto IsTrusted() const { return this->trusted; }

	/** Determines whether the TLS certificate is valid. */
	inline auto IsValid() const { return this->valid; }

	/** Determines whether this TLS certificate is usable.
	 * @param strict Whether to consider self-signed TLS certificates as invalid.
	 */
	inline auto IsUsable(bool strict = false) const
	{
		if (strict && (!IsTrusted() || !IsKnownSigner()))
			return false;

		return !HasError() && !IsRevoked() && IsValid();
	}
};

class TLS::APIBase
	: public Service::SimpleProvider
{
public:
	APIBase(const WeakModulePtr& mod)
		: Service::SimpleProvider(mod, "tlsapi")
	{
	}

	/** Retrieves the TLS certificate of a user.
	 * @param user The user to retrieve the TLS certificate of.
	 * @return The TLS certificate of the user or a null pointer if they are not using TLS.
	 */
	virtual Certificate* GetCertificate(User* user) const = 0;

	/** Retrieves the primary TLS fingerprint of a user.
	 * @param user The user to retrieve the primary TLS fingerprint of.
	 * @param strict Whether to consider self-signed TLS certificates as invalid.
	 * @return The primary TLS fingerprint of the user or an empty string if \p user did not provide
	 *         a TLS certificate.
	 */
	inline auto GetFingerprint(User* user, bool strict = false) const
	{
		const auto* cert = GetCertificate(user);
		if (cert && cert->IsUsable(strict)) [[likely]]
			return cert->GetFingerprint();
		return std::string();
	}

	/** Retrieves all available TLS fingerprint of a user.
	 * @param user The user to retrieve all available TLS fingerprints of.
	 * @param strict Whether to consider self-signed TLS certificates as invalid.
	 * @return A list of TLS fingerprints or an empty list if \p user did not provide a TLS
	 *         certificate.
	 */
	inline auto GetFingerprints(User* user, bool strict = false) const
	{
		const auto* cert = GetCertificate(user);
		if (cert && cert->IsUsable(strict)) [[likely]]
			return cert->GetFingerprints();
		return TLS::FingerprintList();
	}

	/** Determines whether the specified user is connected securely. A secure connection is defined
	 * as using TLS or connecting locally to the server.
	 * @return True if the user is connected securely; otherwise, false.
	 */
	virtual bool IsSecure(User* user) const = 0;

	/** Sets the TLS certificate of a user.
	 * @param user The user whose TLS certificate to set.
	 * @param cert The TLS certificate to set for the user.
	 */
	virtual void SetCertificate(User* user, const TLS::CertificatePtr& cert) const = 0;
};

class TLS::API final
	: public dynamic_reference<TLS::APIBase>
{
public:
	API(const WeakModulePtr& mod)
		: dynamic_reference<TLS::APIBase>(mod, "tlsapi")
	{
	}
};

class TLS::IOHook
	: public ::IOHook
{
protected:
	/** An enumeration of possible TLS socket states. */
	enum Status
		: uint8_t
	{
		/** The TLS socket has just been opened or has been closed. */
		STATUS_NONE,

		/** The TLS socket is currently handshaking. */
		STATUS_HANDSHAKING,

		/** The TLS handshake has completed and data can be sent. */
		STATUS_OPEN
	};

	/** The TLS certificate of the peer. */
	TLS::CertificatePtr certificate;

	/** The status of the TLS connection. */
	Status status = STATUS_NONE;

public:
	IOHook(const std::shared_ptr<IOHookProvider>& hookprov)
		: ::IOHook(hookprov)
	{
	}

	/** Retrieves the TLS certificate of the peer. */
	const auto& GetCertificate() const
	{
		return this->certificate;
	}

	/** Retrieves the ciphersuite that was negotiated with the peer.
	 * @param out The location that the ciphersuite will be stored.
	 * @return True if the ciphersuite was retrieved; otherwise, false.
	 */
	virtual bool GetCiphersuite(std::string& out) const = 0;

	/** Retrieves the hostname that was sent by the peer using SNI.
	 * @param out The location that the server name will be stored.
	 * @return True if the server name was retrieved; otherwise, false.
	 */
	virtual bool GetServerName(std::string& out) const = 0;

	/** @copydoc IOHook::IsHookReady */
	bool IsHookReady() const override { return this->status == STATUS_OPEN; }
};

TLS::IOHook* TLS::GetHook(StreamSocket* sock)
{
	if (!sock)
		return nullptr; // No socket.

	auto* const lasthook = sock->GetLastHook();
	if (!lasthook || !lasthook->prov->service_name.starts_with("ssl/"))
		return nullptr; // Not our hook.

	return static_cast<TLS::IOHook*>(lasthook);
}