#pragma once #include #include #include #include "common/error.hpp" namespace smartbotic::certs { // What a stored certificate actually is, read out of the PEM rather than // typed in beside it. The API refuses a PEM it cannot parse, and the UI shows // these so an expiry is visible before it becomes a confusing TLS error at // three in the morning. struct CertificateInfo { std::string subject; std::string issuer; int64_t not_before_ms = 0; int64_t not_after_ms = 0; // Uppercase hex, colon separated - the form openssl x509 -fingerprint // prints, so it can be compared against a server by eye. std::string fingerprint_sha256; // A trust anchor is normally a CA. A leaf certificate can be pinned as an // anchor too and OpenSSL accepts it, so this is reported rather than // enforced. bool is_ca = false; nlohmann::json toJson() const; }; // Every certificate in a PEM bundle, in file order. A bundle is allowed: an // internal CA is often issued under a root that has to travel with it. // // Failure means nothing in the input parsed as a certificate. The caller // refuses the input rather than storing it - a PEM that is silently kept and // silently ignored at request time is the shape of bug this whole feature // exists to avoid. common::Result> parsePem(const std::string& pem); // Whether a request's host is one this certificate was assigned to. // // Exact match, or a single leading "*." wildcard that matches exactly one // label - "*.fsociety.hu" covers "api.fsociety.hu" but not "fsociety.hu" and // not "a.b.fsociety.hu", which is how certificate wildcards themselves work. // Comparison is case-insensitive; a trailing dot on either side is ignored. // // This lives here, next to the parsing, because the webserver validates // patterns with it and the runner decides with it. Two implementations of one // matching rule is exactly how a host quietly ends up trusted in one place and // not the other. bool hostMatches(const std::string& host, const std::vector& patterns); } // namespace smartbotic::certs